Compare commits
879 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f01c7e79a | ||
|
|
39eb512b27 | ||
|
|
ebb373ccad | ||
|
|
c7cccf4ba0 | ||
|
|
66b06ed84c | ||
|
|
b789e436b8 | ||
|
|
0cd73af691 | ||
|
|
63d23ca9df | ||
|
|
9af07d86d6 | ||
|
|
f561272f9a | ||
|
|
2b2473a6d8 | ||
|
|
0134c1fcfd | ||
|
|
c01f674234 | ||
|
|
482b622b1b | ||
|
|
1899e313fd | ||
|
|
d5c04318b2 | ||
|
|
87c6644751 | ||
|
|
b9d26ee268 | ||
|
|
9b9d7647a4 | ||
|
|
514138aad2 | ||
|
|
34815f5cf8 | ||
|
|
8a60855b88 | ||
|
|
e2cdb5c8cf | ||
|
|
0d94c17edc | ||
|
|
db7ccb0434 | ||
|
|
e1a03929e3 | ||
|
|
06bccfeb78 | ||
|
|
0236255a7e | ||
|
|
b6efe65891 | ||
|
|
3701eb121f | ||
|
|
69a58c9597 | ||
|
|
ea1ff1c1ea | ||
|
|
e28a01351b | ||
|
|
e3b8372b8b | ||
|
|
257753841b | ||
|
|
7fc53ae78a | ||
|
|
de3d2b1cb1 | ||
|
|
8c0bca90d3 | ||
|
|
2c03bc3410 | ||
|
|
513bb381d3 | ||
|
|
0a75c5a302 | ||
|
|
afd5d2c728 | ||
|
|
0e2a39da2a | ||
|
|
c9cd47b5b1 | ||
|
|
41dfa29648 | ||
|
|
8a193e2dc5 | ||
|
|
7a9f8fda72 | ||
|
|
2b84ea9dbe | ||
|
|
0ee7fac727 | ||
|
|
78f6ad14c2 | ||
|
|
3b957c5f2e | ||
|
|
8deb38e22d | ||
|
|
29fdd1acc4 | ||
|
|
2ab270dfac | ||
|
|
fb626ca5a8 | ||
|
|
9be26a8bfd | ||
|
|
333a37ffb2 | ||
|
|
9320214429 | ||
|
|
43cab4d978 | ||
|
|
4da8af0e1d | ||
|
|
a796d1f33f | ||
|
|
0299bd9764 | ||
|
|
17e2915876 | ||
|
|
1006f181e2 | ||
|
|
430d69f278 | ||
|
|
a1b73fc113 | ||
|
|
cdbff411d0 | ||
|
|
d1854eddaf | ||
|
|
52c280ec12 | ||
|
|
3275681afd | ||
|
|
67b4502fdb | ||
|
|
2f8686ec70 | ||
|
|
cc5da4d1fe | ||
|
|
b4a2352833 | ||
|
|
78623f4ec8 | ||
|
|
2a9d970641 | ||
|
|
274f2a9d19 | ||
|
|
e01db79ce9 | ||
|
|
4ef5db1bc4 | ||
|
|
47525f6a09 | ||
|
|
155a1901c0 | ||
|
|
88a5c8d29d | ||
|
|
e2f17c4be1 | ||
|
|
40c3295cd1 | ||
|
|
f2fadd7add | ||
|
|
0967b6abd2 | ||
|
|
910bbc8521 | ||
|
|
c9c0bc0bbd | ||
|
|
d8bfe23c0d | ||
|
|
58b7599152 | ||
|
|
f85e69ec77 | ||
|
|
2be7db29ed | ||
|
|
8da878c77c | ||
|
|
866c758660 | ||
|
|
68db8d04ad | ||
|
|
3649a36869 | ||
|
|
fb7c75a090 | ||
|
|
3c9e8ff9ab | ||
|
|
7f8508a367 | ||
|
|
da60d11b24 | ||
|
|
b3834835ed | ||
|
|
e306e2dadb | ||
|
|
d9a88e139c | ||
|
|
3a11a24be0 | ||
|
|
7cb781cc92 | ||
|
|
693178c8ee | ||
|
|
af6e5b1838 | ||
|
|
fac8d72d8c | ||
|
|
a57fc5c746 | ||
|
|
50d2ef3b90 | ||
|
|
4223e2f85d | ||
|
|
d28c323074 | ||
|
|
934d586286 | ||
|
|
65a05f334e | ||
|
|
4e505d52df | ||
|
|
0aa17bfa33 | ||
|
|
63e05e12ba | ||
|
|
e26eaaddc2 | ||
|
|
1cd86d79d9 | ||
|
|
b4d232badd | ||
|
|
2ca15d7667 | ||
|
|
8c87040cb6 | ||
|
|
0b6d2c2b0a | ||
|
|
601e56d2fa | ||
|
|
8f479407a0 | ||
|
|
f714be0ff7 | ||
|
|
19512e988b | ||
|
|
880da2d143 | ||
|
|
2ed480b40a | ||
|
|
fdc2458657 | ||
|
|
c3485821c7 | ||
|
|
77e0b8983c | ||
|
|
6d415b6653 | ||
|
|
0adda22d3c | ||
|
|
7dab8335e2 | ||
|
|
87a0c2a7a7 | ||
|
|
60e6d28eb1 | ||
|
|
919fe45813 | ||
|
|
fa1ac8d93c | ||
|
|
cba93954cd | ||
|
|
2307a0850f | ||
|
|
b91e2e3267 | ||
|
|
a7b74d8e83 | ||
|
|
33d66676c9 | ||
|
|
a76c0067e1 | ||
|
|
02c313eafd | ||
|
|
be7a21eb56 | ||
|
|
b0c25e1bed | ||
|
|
9721881261 | ||
|
|
5d68dc08d5 | ||
|
|
067be7aaa2 | ||
|
|
6fc560fc78 | ||
|
|
fb7fa0cb49 | ||
|
|
52129f2e4d | ||
|
|
d5e30fd728 | ||
|
|
a2b5bf0b73 | ||
|
|
2965f954ba | ||
|
|
257011a6d2 | ||
|
|
f33d659924 | ||
|
|
b14a37cf1f | ||
|
|
7fd0b52360 | ||
|
|
8ea6cf352b | ||
|
|
f3aaa1084a | ||
|
|
824825e67d | ||
|
|
889b03169a | ||
|
|
e6aa6b8235 | ||
|
|
5eb340d481 | ||
|
|
60f0175a36 | ||
|
|
5a5873d4ee | ||
|
|
49d297f7bf | ||
|
|
b85fe8f678 | ||
|
|
3a79f1293f | ||
|
|
244590f49d | ||
|
|
88b5007457 | ||
|
|
7a27469ecd | ||
|
|
8090d3e289 | ||
|
|
898f5c50c4 | ||
|
|
bd87b4eb10 | ||
|
|
ec3ad8a969 | ||
|
|
ba012c6ba8 | ||
|
|
b906d92053 | ||
|
|
72e30cc12c | ||
|
|
079fcc7eea | ||
|
|
c0e2550046 | ||
|
|
362f923f06 | ||
|
|
f18b5aa782 | ||
|
|
5d581d42f5 | ||
|
|
1ba61bbcbe | ||
|
|
911b2daebf | ||
|
|
5a03d31f6f | ||
|
|
eeeb763f8a | ||
|
|
9e27f2b3e7 | ||
|
|
44b4cb92be | ||
|
|
ac4fd7c563 | ||
|
|
1e8f72dfe6 | ||
|
|
f40f4082ba | ||
|
|
612387633d | ||
|
|
e8d5fb5cca | ||
|
|
214e750c69 | ||
|
|
b1f4b1eaba | ||
|
|
49f8bc3d63 | ||
|
|
fa29a0b686 | ||
|
|
1b0aea5e05 | ||
|
|
8416e97c6c | ||
|
|
93ff9006ad | ||
|
|
bc3e1b316d | ||
|
|
53fb5af99c | ||
|
|
01462008c9 | ||
|
|
d9ae3fd5aa | ||
|
|
de3b4adfd8 | ||
|
|
6fc391986f | ||
|
|
e9d58dae2a | ||
|
|
9f0e1a98a0 | ||
|
|
8bce2ba8e8 | ||
|
|
f1b20f6dc4 | ||
|
|
416712d2dc | ||
|
|
3dedd0d178 | ||
|
|
1444634abb | ||
|
|
bb647123b7 | ||
|
|
2698b8bb63 | ||
|
|
f15e2285ba | ||
|
|
0afaf2ce89 | ||
|
|
508a12a84c | ||
|
|
1609e0d445 | ||
|
|
16940db834 | ||
|
|
f92bb16408 | ||
|
|
9aa37febbe | ||
|
|
be4cd94207 | ||
|
|
d077f38ac4 | ||
|
|
79a337767a | ||
|
|
cdf6f52d15 | ||
|
|
6f9c62b24a | ||
|
|
353753c03c | ||
|
|
de0aa32c11 | ||
|
|
edd2f89d5d | ||
|
|
bd79d7e071 | ||
|
|
76bf35cbdd | ||
|
|
7a8fa5f46e | ||
|
|
e273f163a6 | ||
|
|
eeb565319f | ||
|
|
321d5c5d20 | ||
|
|
3df066c694 | ||
|
|
54713f57e5 | ||
|
|
a98466336e | ||
|
|
52b5492c6a | ||
|
|
ba88fc43e0 | ||
|
|
abf4d10d5b | ||
|
|
4c147b77c1 | ||
|
|
a32d74e983 | ||
|
|
d44ff5ba01 | ||
|
|
97e00ba4b5 | ||
|
|
eade305965 | ||
|
|
65a62b67a5 | ||
|
|
638e880604 | ||
|
|
bfb3a6594f | ||
|
|
08bc55995b | ||
|
|
4602afe770 | ||
|
|
820ffed07f | ||
|
|
f926e7b850 | ||
|
|
2c96446bb9 | ||
|
|
75b65e2f11 | ||
|
|
fc6a0dbe64 | ||
|
|
98b2b67b8b | ||
|
|
3956f81e73 | ||
|
|
49ead32f13 | ||
|
|
cb8d62866c | ||
|
|
7ecbae765d | ||
|
|
c16c625feb | ||
|
|
eb4c806ddb | ||
|
|
5c4277aac8 | ||
|
|
2ffb1604d3 | ||
|
|
ada15510a7 | ||
|
|
81c17627f7 | ||
|
|
d2e996a3b3 | ||
|
|
d2182edc7a | ||
|
|
ca26d97589 | ||
|
|
13bd4dd05d | ||
|
|
2fbc7b4e1d | ||
|
|
8b09003bd1 | ||
|
|
ecfd4a77a0 | ||
|
|
cfeeb7460b | ||
|
|
ea6a5de374 | ||
|
|
0d4e20c395 | ||
|
|
6c5876a494 | ||
|
|
5fbd488fdf | ||
|
|
18848315f5 | ||
|
|
17b235b523 | ||
|
|
46aeb25e7e | ||
|
|
439d51875f | ||
|
|
de38566c11 | ||
|
|
3ade923edb | ||
|
|
a3dfec20c1 | ||
|
|
f8b1e7739d | ||
|
|
9685445559 | ||
|
|
e482c76874 | ||
|
|
03114ccf51 | ||
|
|
b3d788fead | ||
|
|
96e8151cce | ||
|
|
9595aaf897 | ||
|
|
f2bba2e4fa | ||
|
|
ae73215724 | ||
|
|
b9bb515b3b | ||
|
|
9c42d87e7d | ||
|
|
05017c83d6 | ||
|
|
d511c5436d | ||
|
|
36f3e54e1d | ||
|
|
19c00ff92a | ||
|
|
63c77a4d76 | ||
|
|
685e3ffcfe | ||
|
|
145a21449b | ||
|
|
50c847562f | ||
|
|
ab02fcb116 | ||
|
|
316b4b5340 | ||
|
|
7fcb21573d | ||
|
|
5978b7b35f | ||
|
|
463320b599 | ||
|
|
1ada821092 | ||
|
|
cea664e396 | ||
|
|
fecebd6ced | ||
|
|
7d80825853 | ||
|
|
ab119975e7 | ||
|
|
8fead148e2 | ||
|
|
1409f4e564 | ||
|
|
5d63dfb24c | ||
|
|
19148a4593 | ||
|
|
615c6ffe5a | ||
|
|
0f907b1a77 | ||
|
|
75181e16fa | ||
|
|
19f7b92abb | ||
|
|
c89e804653 | ||
|
|
87e329aee3 | ||
|
|
5eeb223338 | ||
|
|
f43c7fa360 | ||
|
|
d3e8d46593 | ||
|
|
cb631d532a | ||
|
|
8b666d2d2e | ||
|
|
bd6e99158e | ||
|
|
93972ff3f1 | ||
|
|
ddb914dc65 | ||
|
|
8504ad6ff3 | ||
|
|
6abb42a066 | ||
|
|
4691753965 | ||
|
|
9c670e13ce | ||
|
|
a6b979539d | ||
|
|
14a69d9047 | ||
|
|
7c1fb1d215 | ||
|
|
2a7998847f | ||
|
|
74a7676111 | ||
|
|
d09afdf0ee | ||
|
|
8551288efb | ||
|
|
5e50824042 | ||
|
|
4ed07d6062 | ||
|
|
894da598d6 | ||
|
|
7033af816a | ||
|
|
eda15c53ad | ||
|
|
d6ea9b1e47 | ||
|
|
8a0be83e1e | ||
|
|
11579b3511 | ||
|
|
84907d5a2e | ||
|
|
c844023077 | ||
|
|
ab0034f9da | ||
|
|
bf72d81bd3 | ||
|
|
60a7e483af | ||
|
|
6219119476 | ||
|
|
3ee765869d | ||
|
|
174dd5dd9e | ||
|
|
5f6c8435a4 | ||
|
|
41b7ac27d7 | ||
|
|
5627a63265 | ||
|
|
e161458f91 | ||
|
|
33b7c4bdd0 | ||
|
|
45cc1aaeb0 | ||
|
|
a2836ba945 | ||
|
|
0a10a4f751 | ||
|
|
084d3de65b | ||
|
|
dae164abee | ||
|
|
28aadc4f96 | ||
|
|
867f2a8e2b | ||
|
|
8fd0690959 | ||
|
|
a8a9cdd81e | ||
|
|
978b90b5b1 | ||
|
|
2d43a1d2e7 | ||
|
|
5419d1caa1 | ||
|
|
9092c3a87f | ||
|
|
faeca30dfa | ||
|
|
b7c3c10b87 | ||
|
|
a8a976b324 | ||
|
|
2b274f8e0b | ||
|
|
3d6cbcf396 | ||
|
|
fa329c698e | ||
|
|
a2a95f5fee | ||
|
|
d266190518 | ||
|
|
c728d78bea | ||
|
|
32abb67d1f | ||
|
|
3c17bb97c0 | ||
|
|
1b8dfb6c36 | ||
|
|
76efba296f | ||
|
|
b93c0dad5a | ||
|
|
541abb2324 | ||
|
|
12d74c5b52 | ||
|
|
1893a33708 | ||
|
|
29ff4259d6 | ||
|
|
681bb058fa | ||
|
|
3d5bba9cff | ||
|
|
81acba4700 | ||
|
|
ca8d935cf4 | ||
|
|
cfa779ecb7 | ||
|
|
b959e885fc | ||
|
|
e349af7524 | ||
|
|
6eeacfe82b | ||
|
|
942dca3444 | ||
|
|
171392b582 | ||
|
|
9bf5ff1583 | ||
|
|
0e87c46849 | ||
|
|
22d7490126 | ||
|
|
0fae611021 | ||
|
|
47451aa495 | ||
|
|
9f163d90e1 | ||
|
|
ca199b0d3d | ||
|
|
7ae0d584e6 | ||
|
|
0b047e3e10 | ||
|
|
15fd552616 | ||
|
|
9a0c113f8a | ||
|
|
1913012c8a | ||
|
|
5f393ce312 | ||
|
|
54bc22dfd4 | ||
|
|
deb9ccb564 | ||
|
|
eca1fb7d3b | ||
|
|
80a72604c6 | ||
|
|
7fe9f53c97 | ||
|
|
301aaf5783 | ||
|
|
3aa59ea240 | ||
|
|
bd83ff2c64 | ||
|
|
e74995e81a | ||
|
|
75e65b9d4a | ||
|
|
a31f775d70 | ||
|
|
a506788f4f | ||
|
|
f64f873c11 | ||
|
|
572257921d | ||
|
|
417200fa70 | ||
|
|
d6912be223 | ||
|
|
ee57c30c53 | ||
|
|
0caa5d04d3 | ||
|
|
c736cdf87f | ||
|
|
e66588f818 | ||
|
|
9397cc74c1 | ||
|
|
561e5d17b9 | ||
|
|
71d33a47b3 | ||
|
|
ad50a7bfd2 | ||
|
|
5b718446b6 | ||
|
|
b59a7cdcc0 | ||
|
|
ce8b457bac | ||
|
|
68b12e6e9f | ||
|
|
0a9c0a1385 | ||
|
|
a0e028a851 | ||
|
|
164b2a3eef | ||
|
|
72103ec730 | ||
|
|
d0ca54a0cf | ||
|
|
4a8b23380c | ||
|
|
ddc41d2fa4 | ||
|
|
21a50cf961 | ||
|
|
b2e85a8b83 | ||
|
|
3170e35b31 | ||
|
|
7c0832daf2 | ||
|
|
c112290664 | ||
|
|
2f4910f1f2 | ||
|
|
e10940100d | ||
|
|
9c4564fd70 | ||
|
|
5510c5fda1 | ||
|
|
0743094c2f | ||
|
|
32ba5a5c95 | ||
|
|
ab768d6f6a | ||
|
|
b7f54a89db | ||
|
|
bba1eb0d76 | ||
|
|
d306f81130 | ||
|
|
007aa8ab8d | ||
|
|
e53cf08548 | ||
|
|
772f0025f1 | ||
|
|
b64eb8dfe6 | ||
|
|
4e77aa41d8 | ||
|
|
f28ab5285c | ||
|
|
f6eb8929c3 | ||
|
|
70868e1d99 | ||
|
|
219b75524b | ||
|
|
a472351423 | ||
|
|
11e04c79f4 | ||
|
|
52d7ff79fc | ||
|
|
f25e706e11 | ||
|
|
5f62c016cc | ||
|
|
cd1bd7d52a | ||
|
|
5b22209eef | ||
|
|
aa5da1b312 | ||
|
|
33d9d4fe90 | ||
|
|
63766c1711 | ||
|
|
80ee43beca | ||
|
|
c9bc72a539 | ||
|
|
a6f09b1f73 | ||
|
|
015889373e | ||
|
|
6d750aff8b | ||
|
|
ffddf9a15f | ||
|
|
e6749dcf9f | ||
|
|
e8cc74f499 | ||
|
|
6f9be72c5b | ||
|
|
715cc7b7dd | ||
|
|
5662407e01 | ||
|
|
4ac4833c93 | ||
|
|
c7133a662c | ||
|
|
11a0580e64 | ||
|
|
8d5c0b5fa2 | ||
|
|
4e4a1d01c4 | ||
|
|
995e563352 | ||
|
|
92bc61d08d | ||
|
|
c6ad23f921 | ||
|
|
504e29c004 | ||
|
|
d88a13598a | ||
|
|
b648345bf8 | ||
|
|
47ec052194 | ||
|
|
3779f59a23 | ||
|
|
30994b1154 | ||
|
|
207107082d | ||
|
|
c82be35639 | ||
|
|
16a3bece34 | ||
|
|
1ea1c1ac78 | ||
|
|
1ed427c342 | ||
|
|
7058a3be74 | ||
|
|
0b5ba828db | ||
|
|
3a5baa1956 | ||
|
|
74759fe04c | ||
|
|
9f49ac8e59 | ||
|
|
57fe674dc4 | ||
|
|
80650d4c96 | ||
|
|
44af6f3f1b | ||
|
|
9b7db8ee8a | ||
|
|
4e2e2606ef | ||
|
|
85dfe5c403 | ||
|
|
a3ce03e0bd | ||
|
|
fecb551c5e | ||
|
|
91b70fab70 | ||
|
|
f3b52aaf23 | ||
|
|
21fc848839 | ||
|
|
a7db197e45 | ||
|
|
eb7e114e5f | ||
|
|
9e524e4be8 | ||
|
|
5d8b48a2ed | ||
|
|
89bd723eaa | ||
|
|
7caab75fa4 | ||
|
|
8c3039abc9 | ||
|
|
3e38aab0f6 | ||
|
|
589605fe92 | ||
|
|
f92b52b20c | ||
|
|
1a81f7231b | ||
|
|
1a88b64c67 | ||
|
|
298dcb4c90 | ||
|
|
e0dd7970d8 | ||
|
|
ea80ded8d5 | ||
|
|
950e4227e3 | ||
|
|
15fecc962d | ||
|
|
53e7d13c2d | ||
|
|
c520130389 | ||
|
|
9d32807e33 | ||
|
|
1da58b6a4c | ||
|
|
b6f311f4b2 | ||
|
|
35b56f2659 | ||
|
|
f51ac8ab6e | ||
|
|
a9dfed948a | ||
|
|
37a9691e29 | ||
|
|
3306247ae5 | ||
|
|
cd97d5e6e2 | ||
|
|
01ccbc679d | ||
|
|
abd56a5abd | ||
|
|
0ee5302836 | ||
|
|
682c3462f1 | ||
|
|
9128afa01d | ||
|
|
a3d62c86df | ||
|
|
d0ec33730e | ||
|
|
86d3abc0c4 | ||
|
|
f45d572677 | ||
|
|
1d568a5cf4 | ||
|
|
7dbe9a59e8 | ||
|
|
aa3970c83e | ||
|
|
1c662ae94c | ||
|
|
0bdee1e292 | ||
|
|
e874db9ce3 | ||
|
|
5135e0f35d | ||
|
|
6d1e3fb2b7 | ||
|
|
e6a4237e1b | ||
|
|
e5771c785e | ||
|
|
29e8c01d17 | ||
|
|
054b92bbe8 | ||
|
|
7537acfd5d | ||
|
|
d9e095f7cf | ||
|
|
ec7583eb97 | ||
|
|
5fd3943ebc | ||
|
|
c987a94596 | ||
|
|
20d5b4e384 | ||
|
|
ce8d15d2b0 | ||
|
|
16375f20d5 | ||
|
|
5ceecc2b04 | ||
|
|
9c47128799 | ||
|
|
0893e3a038 | ||
|
|
26810e02c1 | ||
|
|
8c39e8f764 | ||
|
|
22c33ddfb8 | ||
|
|
8b29ce93ec | ||
|
|
bfeac178e2 | ||
|
|
9eefbf24a2 | ||
|
|
782ec573ab | ||
|
|
8ee3c743e4 | ||
|
|
fac666e607 | ||
|
|
67623930dd | ||
|
|
4a9cbf8930 | ||
|
|
08ea34174c | ||
|
|
8579616fea | ||
|
|
417a114a94 | ||
|
|
d3050d73ba | ||
|
|
46b85c11f1 | ||
|
|
cfcd1bba1e | ||
|
|
075dd8e0a4 | ||
|
|
8fc88a68fb | ||
|
|
6f028e9ad0 | ||
|
|
8dbf506916 | ||
|
|
c8de4675db | ||
|
|
0ebde6f2e9 | ||
|
|
a8733d7228 | ||
|
|
833df95485 | ||
|
|
989e60b01f | ||
|
|
d7a436568c | ||
|
|
ceab4a4c1f | ||
|
|
0aefffce4d | ||
|
|
eab9b70f28 | ||
|
|
a70feae98f | ||
|
|
132095c98c | ||
|
|
def2920a35 | ||
|
|
79935e048c | ||
|
|
5e96dc4374 | ||
|
|
532205aafa | ||
|
|
d8510e61aa | ||
|
|
fb5d0f7e14 | ||
|
|
b55ae02eda | ||
|
|
400e1bc7d7 | ||
|
|
e9023ce233 | ||
|
|
f16f425cb1 | ||
|
|
1a2ab0ffe7 | ||
|
|
d01a0b1d64 | ||
|
|
530a1859a3 | ||
|
|
7c33ff4046 | ||
|
|
2e5595b5c6 | ||
|
|
a940de3717 | ||
|
|
23eb6a6c53 | ||
|
|
8a9b98c2dc | ||
|
|
fe4dd579f9 | ||
|
|
cdaf3ac097 | ||
|
|
1dac05a7ac | ||
|
|
24cc54a574 | ||
|
|
4b9bba7505 | ||
|
|
677e188894 | ||
|
|
125b3c1de9 | ||
|
|
d2287b7a2e | ||
|
|
ddcc960aa5 | ||
|
|
88ff0c0425 | ||
|
|
520b473350 | ||
|
|
bb8bc7ea3c | ||
|
|
eb888cc8d7 | ||
|
|
b93bee18d0 | ||
|
|
40df5baa83 | ||
|
|
ab4c875792 | ||
|
|
54e9edfd60 | ||
|
|
3b1fb92b11 | ||
|
|
d77c9ae009 | ||
|
|
d7c51f7fc4 | ||
|
|
4f04c776c1 | ||
|
|
56f83ddde4 | ||
|
|
961eebaf71 | ||
|
|
3df469bc38 | ||
|
|
582236e42b | ||
|
|
c153ff5a97 | ||
|
|
e5b6ccd716 | ||
|
|
7690b9f8cd | ||
|
|
caca3614f8 | ||
|
|
b1c54f5706 | ||
|
|
2e912eeadf | ||
|
|
072d42347a | ||
|
|
e26e04985b | ||
|
|
c3deeaf283 | ||
|
|
a701426703 | ||
|
|
3652553a8f | ||
|
|
83515628a8 | ||
|
|
56e6864159 | ||
|
|
b0cc0c62cc | ||
|
|
0d12ddc3ea | ||
|
|
9f14ae184f | ||
|
|
6a40cbf160 | ||
|
|
dd0703eddf | ||
|
|
c5c01c5364 | ||
|
|
abd5da1fd1 | ||
|
|
036031a48b | ||
|
|
762205ef07 | ||
|
|
6edd096de4 | ||
|
|
02396cb455 | ||
|
|
f11d7ab489 | ||
|
|
ab9f17b053 | ||
|
|
1e9a70855d | ||
|
|
9d5beff937 | ||
|
|
d62cb58f7d | ||
|
|
9c3dcae793 | ||
|
|
84478cd81c | ||
|
|
748de85ba2 | ||
|
|
4b3a237e2a | ||
|
|
1f5cbf21a3 | ||
|
|
182bf1d688 | ||
|
|
aebc1a7d48 | ||
|
|
d5d22783ea | ||
|
|
971b413991 | ||
|
|
b2f95339ce | ||
|
|
968367b042 | ||
|
|
1e3f11ca13 | ||
|
|
22d77fadd7 | ||
|
|
2d2d71512f | ||
|
|
79766aa65b | ||
|
|
0e522d48ac | ||
|
|
6eb001fc34 | ||
|
|
859469c600 | ||
|
|
4fde8ff204 | ||
|
|
4a4a6549d0 | ||
|
|
9e258a490e | ||
|
|
bd7054fa2e | ||
|
|
aeb7bac886 | ||
|
|
a2ca59b822 | ||
|
|
cb2cd615e0 | ||
|
|
380021e818 | ||
|
|
12d729c3bc | ||
|
|
ffda82170d | ||
|
|
ef1de133ed | ||
|
|
743aa86dfb | ||
|
|
72c97ca846 | ||
|
|
c5e688f26c | ||
|
|
5db4493667 | ||
|
|
c86eaa17e2 | ||
|
|
b3ebcfd394 | ||
|
|
23bdee06b3 | ||
|
|
923785c781 | ||
|
|
06075a4974 | ||
|
|
a1798b3843 | ||
|
|
54a93dfcf8 | ||
|
|
4acb63ca49 | ||
|
|
d5f7ec4372 | ||
|
|
d94c0cf064 | ||
|
|
e25a33790f | ||
|
|
d06f07af80 | ||
|
|
9bb256b545 | ||
|
|
fb0a418d0a | ||
|
|
2a274f0d8b | ||
|
|
528b48dab6 | ||
|
|
9b473093b1 | ||
|
|
f6cfb0c529 | ||
|
|
7d620a4bfc | ||
|
|
05d076ba9f | ||
|
|
81b85994a1 | ||
|
|
8d8566a5ab | ||
|
|
a2cdb2e4db | ||
|
|
48b6c160f5 | ||
|
|
cd20c32973 | ||
|
|
141d020ede | ||
|
|
4848182204 | ||
|
|
1e8694f3cc | ||
|
|
3a7ac51a00 | ||
|
|
8940e05baf | ||
|
|
4ae33deebd | ||
|
|
5dbfff016e | ||
|
|
c87d8d0d8f | ||
|
|
f7a08dfd34 | ||
|
|
559059d244 | ||
|
|
699abef159 | ||
|
|
d9741d56c5 | ||
|
|
57e8b428c3 | ||
|
|
200b36bd36 | ||
|
|
2d7dd391a3 | ||
|
|
e654abf128 | ||
|
|
66d0591684 | ||
|
|
4932323d3b | ||
|
|
860b22c0e0 | ||
|
|
9fd2ad9909 | ||
|
|
1db4309eec | ||
|
|
2c4d2beea3 | ||
|
|
be5b8e2632 | ||
|
|
5003432a55 | ||
|
|
dea0aa9f7c | ||
|
|
5f3ee66775 | ||
|
|
957d5b3f02 | ||
|
|
ec08a19e7d | ||
|
|
64dac66259 | ||
|
|
81a9ea58d6 | ||
|
|
9b4da25578 | ||
|
|
9bb6ba0823 | ||
|
|
22725beede | ||
|
|
f64af6b64a | ||
|
|
8500509532 | ||
|
|
dc66ec5d8c | ||
|
|
555f43c854 | ||
|
|
aebd59247d | ||
|
|
c4e50369e7 | ||
|
|
dc8c919c30 | ||
|
|
5553e64a75 | ||
|
|
8268c1d7ff | ||
|
|
b6662fd243 | ||
|
|
3d508be9ff | ||
|
|
16fb9bc80e | ||
|
|
7aecebf028 | ||
|
|
e989d948ed | ||
|
|
695f734142 | ||
|
|
e22dc1b5c6 | ||
|
|
fe4eb19ecf | ||
|
|
6dc3108747 | ||
|
|
981f5fd09b | ||
|
|
25436e2544 | ||
|
|
e254ea2fa7 | ||
|
|
dcf89f7a28 | ||
|
|
a145497c65 | ||
|
|
81e9060239 | ||
|
|
caa05df16d | ||
|
|
7b3cb14e6e | ||
|
|
28b6b97f39 | ||
|
|
d04a087c2b | ||
|
|
737ff2cc69 | ||
|
|
f1fe26b0b7 | ||
|
|
7b17ab4b3f | ||
|
|
882beab3f2 | ||
|
|
d9c768ed86 | ||
|
|
db16a87e68 | ||
|
|
917f2a30de | ||
|
|
3b836d3483 | ||
|
|
cc747b00ce | ||
|
|
705767bcfb | ||
|
|
5cee39d315 | ||
|
|
c98eb5502d | ||
|
|
d961211188 | ||
|
|
21b3e05ed0 | ||
|
|
b1dde41b74 | ||
|
|
dcf0d21121 | ||
|
|
600919a23a | ||
|
|
a565b77bf0 | ||
|
|
8478a1ea3d | ||
|
|
26fdc129d3 | ||
|
|
55803afbd2 | ||
|
|
a8aaf01ff0 | ||
|
|
ddcb5445a2 | ||
|
|
7d181ee4b5 | ||
|
|
83f8d84012 | ||
|
|
734684636f | ||
|
|
5605d3cd8e | ||
|
|
63658d3a1e | ||
|
|
9728e90401 | ||
|
|
a01566ed15 | ||
|
|
012e63520f | ||
|
|
b87f0b6f65 | ||
|
|
344ebed6ad | ||
|
|
6433096611 | ||
|
|
7f9d4888fd | ||
|
|
2898c416aa | ||
|
|
5c5f992821 | ||
|
|
825939633a | ||
|
|
0fc99108bf | ||
|
|
052823b74c | ||
|
|
c737d7ab22 | ||
|
|
a8d79fedf3 | ||
|
|
5f1e0224a9 | ||
|
|
d4001a4a98 | ||
|
|
890e26b2b5 | ||
|
|
ed1d036077 | ||
|
|
4eda328a61 | ||
|
|
c7a5dd6abb | ||
|
|
f965c0daec | ||
|
|
70b58d1928 | ||
|
|
72040a3bbb | ||
|
|
8a64ce19c3 | ||
|
|
e7a66d92a8 | ||
|
|
8aca37e5d5 | ||
|
|
82b1bd10ec | ||
|
|
b184d2f94d |
@@ -7,7 +7,7 @@ environment:
|
||||
PYTHONUNBUFFERED: 1
|
||||
PYTHON: C:\Python36\python.exe
|
||||
matrix:
|
||||
- TESTENV: py36-pyqt59
|
||||
- TESTENV: py36-pyqt510
|
||||
- TESTENV: pylint
|
||||
|
||||
install:
|
||||
|
||||
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -8,3 +8,5 @@ tests/unit/completion/* @rcorre
|
||||
tests/unit/misc/test_sql.py @rcorre
|
||||
|
||||
qutebrowser/config/configdata.yml @mschilli87
|
||||
|
||||
qutebrowser/javascript/caret.js @artur-shaik
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -25,6 +25,7 @@ __pycache__
|
||||
/.tox
|
||||
/testresults.html
|
||||
/.cache
|
||||
/.pytest_cache
|
||||
/.testmondata
|
||||
/.hypothesis
|
||||
/.mypy_cache
|
||||
|
||||
15
.travis.yml
15
.travis.yml
@@ -14,13 +14,22 @@ matrix:
|
||||
services: docker
|
||||
- os: linux
|
||||
env: TESTENV=py36-pyqt571
|
||||
- os: linux
|
||||
env: TESTENV=py36-pyqt58
|
||||
- os: linux
|
||||
python: 3.5
|
||||
env: TESTENV=py35-pyqt59
|
||||
env: TESTENV=py35-pyqt571
|
||||
- os: linux
|
||||
env: TESTENV=py36-pyqt59-cov
|
||||
- os: linux
|
||||
env: TESTENV=py36-pyqt510
|
||||
# We need a newer Xvfb as a WORKAROUND for:
|
||||
# https://bugreports.qt.io/browse/QTBUG-64928
|
||||
sudo: required
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- sourceline: "deb http://us.archive.ubuntu.com/ubuntu/ xenial main universe"
|
||||
packages:
|
||||
- xvfb
|
||||
- os: osx
|
||||
env: TESTENV=py36 OSX=sierra
|
||||
osx_image: xcode9.2
|
||||
|
||||
@@ -8,7 +8,7 @@ graft icons
|
||||
graft doc/img
|
||||
graft misc/apparmor
|
||||
graft misc/userscripts
|
||||
recursive-include scripts *.py *.sh
|
||||
recursive-include scripts *.py *.sh *.js
|
||||
include qutebrowser/utils/testfile
|
||||
include qutebrowser/git-commit-id
|
||||
include LICENSE doc/* README.asciidoc
|
||||
|
||||
@@ -44,8 +44,8 @@ Documentation
|
||||
In addition to the topics mentioned in this README, the following documents are
|
||||
available:
|
||||
|
||||
* https://qutebrowser.org/img/cheatsheet-big.png[Key binding cheatsheet]: +
|
||||
image:https://qutebrowser.org/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="https://qutebrowser.org/img/cheatsheet-big.png"]
|
||||
* https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-big.png[Key binding cheatsheet]: +
|
||||
image:https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-big.png"]
|
||||
* link:doc/quickstart.asciidoc[Quick start guide]
|
||||
* https://www.shortcutfoo.com/app/dojos/qutebrowser[Free training course] to remember those key bindings
|
||||
* link:doc/faq.asciidoc[Frequently asked questions]
|
||||
@@ -91,7 +91,7 @@ https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at
|
||||
mailto:qutebrowser@lists.qutebrowser.org[].
|
||||
|
||||
For security bugs, please contact me directly at mail@qutebrowser.org, GPG ID
|
||||
https://www.the-compiler.org/pubkey.asc[0xFD55A072].
|
||||
https://www.the-compiler.org/pubkey.asc[0x916eb0c8fd55a072].
|
||||
|
||||
Requirements
|
||||
------------
|
||||
@@ -114,7 +114,7 @@ The following software and libraries are required to run qutebrowser:
|
||||
* http://fdik.org/pyPEG/[pyPEG2]
|
||||
* http://jinja.pocoo.org/[jinja2]
|
||||
* http://pygments.org/[pygments]
|
||||
* http://pyyaml.org/wiki/PyYAML[PyYAML]
|
||||
* https://github.com/yaml/pyyaml[PyYAML]
|
||||
* http://www.attrs.org/[attrs]
|
||||
|
||||
The following libraries are optional:
|
||||
|
||||
@@ -13,47 +13,75 @@ Thanks a lot to the following people who contributed to it:
|
||||
Gold sponsors
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
TODO
|
||||
- Iggy
|
||||
- zwitschi
|
||||
- 2x Anonymous
|
||||
|
||||
Silver sponsors
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
TODO
|
||||
- https://benary.org[benaryorg]
|
||||
- https://scratchbook.ch[Claude]
|
||||
- Martin Tournoij
|
||||
- http://supported.elsensohn.ch[Thomas Elsensohn]
|
||||
- Christian Helbling
|
||||
- Gavin Troy
|
||||
- Chris King-Parra
|
||||
- Tim Das Mool Wegener
|
||||
|
||||
Other sponsors
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
TODO: people with t-shirts or higher pledge levels
|
||||
|
||||
- 7scan
|
||||
- AMD1212
|
||||
- Alex
|
||||
- Alex Suykov
|
||||
- Alexey Zhikhartsev
|
||||
- Allan Nordhøy
|
||||
- Anirudh Sanjeev
|
||||
- Anssi Puustinen
|
||||
- Anton Grensjö
|
||||
- Aristaeus
|
||||
- Armin Fisslthaler
|
||||
- Ashley Hauck
|
||||
- Benedikt Steindorf
|
||||
- Bernardo Kuri
|
||||
- Blaise Duszynski
|
||||
- Bostan
|
||||
- Bruno Oliveira
|
||||
- BunnyApocalypse
|
||||
- Christian Kellermann
|
||||
- Colin Jacobs
|
||||
- Daniel Andersson
|
||||
- Daniel Nelson
|
||||
- Daniel P. Schmidt
|
||||
- Daniel Salby
|
||||
- Danilo
|
||||
- David Beley
|
||||
- David Hollings
|
||||
- David Keijser
|
||||
- David Parrish
|
||||
- Derin Yarsuvat
|
||||
- Dmytro Kostiuchenko
|
||||
- Eero Kari
|
||||
- Epictek
|
||||
- Eric
|
||||
- Faure Hu
|
||||
- Ferus
|
||||
- Frederik Thorøe
|
||||
- G4v4g4i
|
||||
- Granitosaurus
|
||||
- Gyula Teleki
|
||||
- H
|
||||
- Heinz Bruhin
|
||||
- Hosaka
|
||||
- Ihor Radchenko
|
||||
- Iordanis Grigoriou
|
||||
- Isaac Sandaljian
|
||||
- Jakub Podeszwik
|
||||
- Jamie Anderson
|
||||
- Jasper Woudenberg
|
||||
- Jay Kamat
|
||||
- Jens Højgaard
|
||||
- Johannes
|
||||
- John Baber-Lucero
|
||||
@@ -61,9 +89,11 @@ TODO: people with t-shirts or higher pledge levels
|
||||
- Kenichiro Ito
|
||||
- Kenny Low
|
||||
- Lars Ivar Igesund
|
||||
- Leulas
|
||||
- Lucas Aride Moulin
|
||||
- Ludovic Chabant
|
||||
- Lukas Gierth
|
||||
- Magnus Lindström
|
||||
- Marulkan
|
||||
- Matthew Chun-Lum
|
||||
- Matthew Cronen
|
||||
@@ -80,7 +110,10 @@ TODO: people with t-shirts or higher pledge levels
|
||||
- Peter Rice
|
||||
- Philipp Middendorf
|
||||
- Pkill9
|
||||
- PluMGMK
|
||||
- Prescott
|
||||
- ProXicT
|
||||
- Ram-Z
|
||||
- Robotichead
|
||||
- Roshless
|
||||
- Ryan Ellis
|
||||
@@ -90,35 +123,53 @@ TODO: people with t-shirts or higher pledge levels
|
||||
- Sean Herman
|
||||
- Sebastian Frysztak
|
||||
- Shelby Cruver
|
||||
- Simon Désaulniers
|
||||
- SirCmpwn
|
||||
- Soham Pal
|
||||
- Stephan Jauernick
|
||||
- Stewart Webb
|
||||
- Sven Reinecke
|
||||
- Timothée Floure
|
||||
- Tom Bass
|
||||
- Tom Kirchner
|
||||
- Tomas Slusny
|
||||
- Tomasz Kramkowski
|
||||
- Tommy Thomas
|
||||
- Tuscan
|
||||
- Ulrich Pötter
|
||||
- Vasilij Schneidermann
|
||||
- Vlaaaaaaad
|
||||
- XTaran
|
||||
- Z2h-A6n
|
||||
- ayekat
|
||||
- beanieuptop
|
||||
- cee
|
||||
- craftyguy
|
||||
- demure
|
||||
- dlangevi
|
||||
- epon
|
||||
- evenorbert
|
||||
- fishss
|
||||
- gsnewmark
|
||||
- guillermohs9
|
||||
- hernani
|
||||
- hubcaps
|
||||
- jnphilipp
|
||||
- lobachevsky
|
||||
- neodarz
|
||||
- nihlaeth
|
||||
- notbenh
|
||||
- nyctea
|
||||
- ongy
|
||||
- patrick suwanvithaya
|
||||
- pyratebeard
|
||||
- p≡p foundation
|
||||
- randm_dave
|
||||
- sabreman
|
||||
- toml
|
||||
- vimja
|
||||
- wiz
|
||||
- 44 Anonymous
|
||||
- 48 Anonymous
|
||||
|
||||
2016
|
||||
----
|
||||
|
||||
@@ -15,6 +15,162 @@ breaking changes (such as renamed commands) can happen in minor releases.
|
||||
// `Fixed` for any bug fixes.
|
||||
// `Security` to invite users to upgrade in case of vulnerabilities.
|
||||
|
||||
v1.2.0
|
||||
------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- Initial implementation of per-domain settings:
|
||||
* `:set` and `:config-cycle` now have a `-u`/`--pattern` argument taking a
|
||||
https://developer.chrome.com/extensions/match_patterns[URL match pattern]
|
||||
for supported settings.
|
||||
* `config.set` in `config.py` now takes a third argument which is the pattern.
|
||||
* New `with config.pattern('...') as p:` context manager for `config.py` to
|
||||
use the shorthand syntax with a pattern.
|
||||
* New `tsh` keybinding to toggle scripts for the current host. With a capital
|
||||
`S`, the toggle is saved. With a capital `H`, subdomains are included. With
|
||||
`u` instead of `h`, the exact current URL is used.
|
||||
* New `tph` keybinding to toggle plugins, with the same additional binding
|
||||
described above.
|
||||
- New QtWebEngine features:
|
||||
* Caret/visual mode
|
||||
* Authentication via ~/.netrc
|
||||
* Retrying downloads with Qt 5.10 or newer
|
||||
* Hinting and other features inside same-origin frames
|
||||
- New flags for existing commands:
|
||||
* `:session-load` has a new `--delete` flag which deletes the
|
||||
session after loading it.
|
||||
* New `--no-last` flag for `:tab-focus` to not focus the last tab when focusing
|
||||
the currently focused one.
|
||||
* New `--edit` flag for `:view-source` to open the source in an external editor.
|
||||
* New `--select` flag for `:follow-hint` which acts like the given string was entered but doesn't necessary follow the hint.
|
||||
- New special pages:
|
||||
* `qute://bindings` (opened via `:bind`) which shows all keybindings.
|
||||
* `qute://tabs` (opened via `:buffer`) which lists all tabs.
|
||||
- New settings:
|
||||
* `statusbar.widgets` to configure which widgets should be shown in which
|
||||
order in the statusbar.
|
||||
* `tabs.mode_on_change` which replaces `tabs.persist_mode_on_change`. It can
|
||||
now be set to `restore` which remembers input modes (input/passthrough)
|
||||
per tab.
|
||||
* `input.insert_mode.auto_enter` which makes it possible to disable entering
|
||||
insert mode automatically when an editable element was clicked. Together
|
||||
with `input.forward_unbound_keys`, this should allow for emacs-like
|
||||
"modeless" keybindings.
|
||||
- New `:prompt-yank` command (bound to `Alt-y` by default) to yank URLs
|
||||
referenced in prompts.
|
||||
- The `hostblock_blame` script which was removed in v1.0 was updated for the new
|
||||
config and re-added.
|
||||
- New `cycle-inputs.js` script in `scripts/` which can be used with `:jseval -f`
|
||||
to cycle through inputs.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- Complete refactoring of key input handling, with various effects:
|
||||
* emacs-like keychains such as `<Ctrl-X><Ctrl-C>` can now be bound.
|
||||
* Key chains can now be bound in any mode (this allows binding unused keys in
|
||||
hint mode).
|
||||
* Yes/no prompts don't use keybindings from the `prompt` section anymore, they
|
||||
have their own `yesno` section instead.
|
||||
* Trying to bind invalid keys now shows an error.
|
||||
* The `bindings.default` setting can now only be set in a `config.py`, and
|
||||
existing values in `autoconfig.yml` are ignored.
|
||||
- Improvements for GreaseMonkey support:
|
||||
* `@include` and `@exclude` now support regex matches. With QtWebEngine and Qt
|
||||
5.8 and newer, Qt handles the matching, but similar functionality will be
|
||||
added in Qt 5.11.
|
||||
* Support for `@requires`
|
||||
* Support for the GreaseMonkey 4.0 API
|
||||
- The sqlite history now uses write-ahead logging which should be
|
||||
a performance and stability improvement.
|
||||
- When an editor is spawned with `:open-editor` and `:config-edit`, the changes
|
||||
are now applied as soon as the file is saved in the editor.
|
||||
- The `hist_importer.py` script now only imports URL schemes qutebrowser can
|
||||
handle.
|
||||
- Deleting a prefix (`:`, `/` or `?`) via backspace now leaves command mode.
|
||||
- Angular 1 elements and `<summary>`/`<details>` now get hints assigned.
|
||||
- `:tab-only` with pinned tabs now still closes unpinned tabs.
|
||||
- The `url.incdec_segments` option now also can take `port` as possible segment.
|
||||
- QtWebEngine: `:view-source` now uses Chromium's `view-source:` scheme.
|
||||
- Tabs now show their full title as tooltip.
|
||||
- When there are multiple unknown keys in a autoconfig.yml, they now all get
|
||||
reported in one error.
|
||||
- More performance improvements when opening/closing many tabs.
|
||||
- The `:version` page now has a button to pastebin the information.
|
||||
- Replacements like `{url}` can now be escaped as `{{url}}`.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- QtWebEngine bugfixes:
|
||||
* Improved fullscreen handling with Qt 5.10.
|
||||
* Hinting and scrolling now works properly on special `view-source:` pages.
|
||||
* Scroll positions are now restored correctly from sessions.
|
||||
* `:follow-selected` should now work in more cases with Qt > 5.10.
|
||||
* Incremental search now flickers less and doesn't move to the second result
|
||||
when pressing Enter.
|
||||
* Keys like `Ctrl-V` or `Shift-Insert` are now correctly handled/filtered with
|
||||
Qt 5.10.
|
||||
* Fixed hangs/segfaults on exit with Qt 5.10.1.
|
||||
* Fixed favicons sometimes getting cleared with Qt 5.10.
|
||||
* Qt download objects are now cleaned up properly when a download is removed.
|
||||
* JavaScript messages are now not double-HTML escaped anymore on Qt < 5.11
|
||||
- QtWebKit bugfixes:
|
||||
* Fixed GreaseMonkey-related crashes.
|
||||
* `:view-source` now displays a valid URL.
|
||||
- URLs containing ampersands and other special chars are now shown correctly
|
||||
when filtering them in the completion.
|
||||
- `:bookmark-add "" foo` can now be used to save the current URL with a custom
|
||||
title.
|
||||
- `:spawn -o` now waits until the process has finished before trying to show the
|
||||
output. Previously, it incorrectly showed the previous output immediately.
|
||||
- Suspended pages now should always load the correct page when being un-suspended.
|
||||
- Exception types are now shown properly with `:config-source` and `:config-edit`.
|
||||
- When using `:bookmark-add --toggle`, bookmarks are now saved properly.
|
||||
- Crash when opening an invalid URL from an application on macOS.
|
||||
- Crash with an empty `completion.timestamp_format`.
|
||||
- Crash when `completion.min_chars` is set in some cases.
|
||||
- HTML/JS resource files are now read into RAM on start to avoid crashes when
|
||||
changing qutebrowser versions while it's open.
|
||||
- Setting `bindings.key_mappings` to an empty value is now allowed.
|
||||
- Bindings to an empty commands are now ignored rather than crashing.
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- `QUTE_SELECTED_HTML` is now not set for userscripts anymore except when called
|
||||
via hints.
|
||||
- The `qutebrowser_viewsource` userscript has been removed as
|
||||
`:view-source --edit` can now be used.
|
||||
- The `tabs.persist_mode_on_change` setting has been removed and replaced by
|
||||
`tabs.mode_on_change`.
|
||||
|
||||
v1.1.2
|
||||
------
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- Windows/macOS releases now bundle Qt 5.10.1 which includes security fixes from
|
||||
Chromium up to version 64.0.3282.140.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- QtWebEngine: Crash with Qt 5.10.1 when using :undo on some tabs.
|
||||
- Compatibility with Python 3.7
|
||||
|
||||
v1.1.1
|
||||
------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- The Makefile now actually works.
|
||||
- Fixed crashes with Qt 5.10 when closing a tab before it finished loading.
|
||||
|
||||
v1.1.0
|
||||
------
|
||||
|
||||
@@ -1094,7 +1250,7 @@ Added
|
||||
- New `:fake-key` command to send a fake keypress to a website or to
|
||||
qutebrowser.
|
||||
- New `--mhtml` argument for `:download` to download a page including all
|
||||
ressources as MHTML file.
|
||||
resources as MHTML file.
|
||||
- New option `tabs -> title-alignment` to change the alignment of tab titles.
|
||||
|
||||
Changed
|
||||
@@ -1294,7 +1450,7 @@ Added
|
||||
- New argument `--no-err-windows` to suppress all error windows.
|
||||
- New arguments `--top-navigate` and `--bottom-navigate` (`-t`/`-b`) for `:scroll-page` to specify a navigation action (e.g. automatically go to the next page when arriving at the bottom).
|
||||
- New flag `-d`/`--detach` for `:spawn` to detach the spawned process so it's not closed when qutebrowser is.
|
||||
- New flag `-v`/`--verbose` for `:spawn` to print informations when the process started/exited successfully.
|
||||
- New flag `-v`/`--verbose` for `:spawn` to print information when the process started/exited successfully.
|
||||
- Many new color settings (foreground setting for every background setting).
|
||||
- New setting `ui -> modal-js-dialog` to use the standard modal dialogs for javascript questions instead of using the statusbar.
|
||||
- New setting `colors -> webpage.bg` to set the background color to use for websites which don't set one.
|
||||
|
||||
@@ -44,8 +44,8 @@ be easy to solve]
|
||||
If you prefer C++ or Javascript to Python, see the relevant issues which involve
|
||||
work in those languages:
|
||||
|
||||
* https://github.com/qutebrowser/qutebrowser/issues?utf8=%E2%9C%93&q=is%3Aopen%20is%3Aissue%20label%3Ac%2B%2B[C++] (mostly work on Qt, the library behind qutebrowser)
|
||||
* https://github.com/qutebrowser/qutebrowser/issues?q=is%3Aopen+is%3Aissue+label%3Ajavascript[JavaScript]
|
||||
* https://github.com/qutebrowser/qutebrowser/issues?q=is%3Aopen+is%3Aissue+label%3A%22language%3A+c%2B%2B%22[C++] (mostly work on Qt, the library behind qutebrowser)
|
||||
* https://github.com/qutebrowser/qutebrowser/issues?q=is%3Aopen+is%3Aissue+label%3A%22language%3A+javascript%22[JavaScript]
|
||||
|
||||
There are also some things to do if you don't want to write code:
|
||||
|
||||
@@ -375,7 +375,7 @@ The following logging levels are available for every logger:
|
||||
|error |There was an issue and some kind of operation was abandoned.
|
||||
|warning |There was an issue but the operation can continue running.
|
||||
|info |General informational messages.
|
||||
|debug |Verbose debugging informations.
|
||||
|debug |Verbose debugging information.
|
||||
|=======================================================================
|
||||
|
||||
[[commands]]
|
||||
|
||||
@@ -32,7 +32,7 @@ When qutebrowser was created, the newer
|
||||
http://webkitgtk.org/reference/webkit2gtk/stable/index.html[WebKit2 API] lacked
|
||||
basic features like proxy support, and almost no projects have started porting
|
||||
to WebKit2. In the meantime, this situation has improved a bit, but there are
|
||||
stil only a few project which have some kind of WebKit2 support (see the
|
||||
still only a few projects which have some kind of WebKit2 support (see the
|
||||
https://github.com/qutebrowser/qutebrowser#similar-projects[list of
|
||||
alternatives]).
|
||||
+
|
||||
@@ -70,6 +70,31 @@ But isn't Python too slow for a browser?::
|
||||
and WebKit in C++, with the
|
||||
https://wiki.python.org/moin/GlobalInterpreterLock[GIL] released.
|
||||
|
||||
Is qutebrowser secure?::
|
||||
Most security issues are in the backend (which handles networking,
|
||||
rendering, JavaScript, etc.) and not qutebrowser itself.
|
||||
+
|
||||
qutebrowser uses http://wiki.qt.io/QtWebEngine[QtWebEngine] by default.
|
||||
QtWebEngine is based on Google's https://www.chromium.org/Home[Chromium]. While
|
||||
Qt only updates to a new Chromium release on every minor Qt release (all ~6
|
||||
months), every patch release backports security fixes from newer Chromium
|
||||
versions. In other words: As long as you're using an up-to-date Qt, you should
|
||||
be recieving security updates on a regular basis, without qutebrowser having to
|
||||
do anything. Chromium's process isolation and
|
||||
https://chromium.googlesource.com/chromium/src/+/master/docs/design/sandbox.md[sandboxing]
|
||||
features are also enabled as a second line of defense.
|
||||
+
|
||||
http://wiki.qt.io/QtWebKit[QtWebKit] is also supported as an alternative
|
||||
backend, but hasn't seen new releases
|
||||
https://github.com/annulen/webkit/releases[in a while]. It also doesn't have any
|
||||
process isolation or sandboxing.
|
||||
+
|
||||
Security issues in qutebrowser's code happen very rarely (as per March 2018,
|
||||
there has been one security issue caused by qutebrowser in over four years) and
|
||||
are fixed timely. To report security bugs, please contact me directly at
|
||||
mail@qutebrowser.org, GPG ID
|
||||
https://www.the-compiler.org/pubkey.asc[0x916eb0c8fd55a072].
|
||||
|
||||
Is there an adblocker?::
|
||||
There is a host-based adblocker which takes /etc/hosts-like lists. A "real"
|
||||
adblocker has a
|
||||
@@ -216,6 +241,29 @@ And then re-emerging qtwebengine with: +
|
||||
|
||||
emerge -1 qtwebengine
|
||||
|
||||
Unable to view DRM content (Netflix, Spotify, etc.).::
|
||||
You will need to install `widevine` and set `qt.args` to point to it.
|
||||
Qt 5.9 currently only supports widevine up to Chrome version 61.
|
||||
+
|
||||
On Arch, simply install `qt5-webengine-widevine` from the AUR and run:
|
||||
+
|
||||
----
|
||||
:set qt.args '["ppapi-widevine-path=/usr/lib/qt/plugins/ppapi/libwidevinecdmadapter.so"]'
|
||||
:restart
|
||||
----
|
||||
+
|
||||
For other distributions, download the chromium tarball and widevine-cdm zip from
|
||||
https://aur.archlinux.org/packages/qt5-webengine-widevine/[the AUR page],
|
||||
extract `libwidevinecdmadapter.so` and `libwidevinecdm.so` files, respectively,
|
||||
and move them to the `ppapi` plugin directory in your Qt library directory (create it if it does not exist).
|
||||
+
|
||||
Lastly, set your `qt.args` to point to that directory and restart qutebrowser:
|
||||
+
|
||||
----
|
||||
:set qt.args '["ppapi-widevine-path=/usr/lib64/qt5/plugins/ppapi/libwidevinecdmadapter.so"]'
|
||||
:restart
|
||||
----
|
||||
|
||||
My issue is not listed.::
|
||||
If you experience any segfaults or crashes, you can report the issue in
|
||||
https://github.com/qutebrowser/qutebrowser/issues[the issue tracker] or
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// DO NOT EDIT THIS FILE DIRECTLY!
|
||||
// It is autogenerated by running:
|
||||
// $ python3 scripts/dev/src2asciidoc.py
|
||||
// vim: readonly:
|
||||
|
||||
= Commands
|
||||
|
||||
@@ -13,6 +14,7 @@ For command arguments, there are also some variables you can use:
|
||||
|
||||
- `{url}` expands to the URL of the current page
|
||||
- `{url:pretty}` expands to the URL in decoded format
|
||||
- `{url:host}` expands to the host part of the URL
|
||||
- `{clipboard}` expands to the clipboard contents
|
||||
- `{primary}` expands to the primary selection contents
|
||||
|
||||
@@ -145,14 +147,16 @@ How many pages to go back.
|
||||
|
||||
[[bind]]
|
||||
=== bind
|
||||
Syntax: +:bind [*--mode* 'mode'] [*--default*] 'key' ['command']+
|
||||
Syntax: +:bind [*--mode* 'mode'] [*--default*] ['key'] ['command']+
|
||||
|
||||
Bind a key to a command.
|
||||
|
||||
==== positional arguments
|
||||
* +'key'+: The keychain or special key (inside `<...>`) to bind.
|
||||
* +'command'+: The command to execute, with optional args, or not given to print the current binding.
|
||||
If no command is given, show the current binding for the given key. Using :bind without any arguments opens a page showing all keybindings.
|
||||
|
||||
==== positional arguments
|
||||
* +'key'+: The keychain to bind. Examples of valid keychains are `gC`, `<Ctrl-X>` or `<Ctrl-C>a`.
|
||||
|
||||
* +'command'+: The command to execute, with optional args.
|
||||
|
||||
==== optional arguments
|
||||
* +*-m*+, +*--mode*+: A comma-separated list of modes to bind the key in (default: `normal`). See `:help bindings.commands` for the
|
||||
@@ -174,7 +178,8 @@ Save the current page as a bookmark, or a specific url.
|
||||
If no url and title are provided, then save the current page as a bookmark. If a url and title have been provided, then save the given url as a bookmark with the provided title. You can view all saved bookmarks on the link:qute://bookmarks[bookmarks page].
|
||||
|
||||
==== positional arguments
|
||||
* +'url'+: url to save as a bookmark. If None, use url of current page.
|
||||
* +'url'+: url to save as a bookmark. If not given, use url of current page.
|
||||
|
||||
* +'title'+: title of the new bookmark.
|
||||
|
||||
==== optional arguments
|
||||
@@ -218,7 +223,7 @@ Syntax: +:buffer ['index']+
|
||||
|
||||
Select tab by index or url/title best match.
|
||||
|
||||
Focuses window if necessary when index is given. If both index and count are given, use count.
|
||||
Focuses window if necessary when index is given. If both index and count are given, use count. With neither index nor count given, open the qute://tabs page.
|
||||
|
||||
==== positional arguments
|
||||
* +'index'+: The [win_id/]index of the tab to focus. Or a substring in which case the closest match will be focused.
|
||||
@@ -271,7 +276,8 @@ Set all settings back to their default.
|
||||
|
||||
[[config-cycle]]
|
||||
=== config-cycle
|
||||
Syntax: +:config-cycle [*--temp*] [*--print*] 'option' ['values' ['values' ...]]+
|
||||
Syntax: +:config-cycle [*--pattern* 'pattern'] [*--temp*] [*--print*]
|
||||
'option' ['values' ['values' ...]]+
|
||||
|
||||
Cycle an option between multiple values.
|
||||
|
||||
@@ -280,6 +286,7 @@ Cycle an option between multiple values.
|
||||
* +'values'+: The values to cycle through.
|
||||
|
||||
==== optional arguments
|
||||
* +*-u*+, +*--pattern*+: The URL pattern to use.
|
||||
* +*-t*+, +*--temp*+: Set value temporarily until qutebrowser is closed.
|
||||
* +*-p*+, +*--print*+: Print the value after setting.
|
||||
|
||||
@@ -492,10 +499,16 @@ Toggle fullscreen mode.
|
||||
|
||||
[[greasemonkey-reload]]
|
||||
=== greasemonkey-reload
|
||||
Syntax: +:greasemonkey-reload [*--force*]+
|
||||
|
||||
Re-read Greasemonkey scripts from disk.
|
||||
|
||||
The scripts are read from a 'greasemonkey' subdirectory in qutebrowser's data directory (see `:version`).
|
||||
|
||||
==== optional arguments
|
||||
* +*-f*+, +*--force*+: For any scripts that have required dependencies, re-download them.
|
||||
|
||||
|
||||
[[help]]
|
||||
=== help
|
||||
Syntax: +:help [*--tab*] [*--bg*] [*--window*] ['topic']+
|
||||
@@ -741,7 +754,13 @@ This tries to automatically click on typical _Previous Page_ or _Next Page_ link
|
||||
- `next`: Open a _next_ link.
|
||||
- `up`: Go up a level in the current URL.
|
||||
- `increment`: Increment the last number in the URL.
|
||||
Uses the
|
||||
link:settings.html#url.incdec_segments[url.incdec_segments]
|
||||
config option.
|
||||
- `decrement`: Decrement the last number in the URL.
|
||||
Uses the
|
||||
link:settings.html#url.incdec_segments[url.incdec_segments]
|
||||
config option.
|
||||
|
||||
|
||||
|
||||
@@ -1066,7 +1085,7 @@ Delete a session.
|
||||
|
||||
[[session-load]]
|
||||
=== session-load
|
||||
Syntax: +:session-load [*--clear*] [*--temp*] [*--force*] 'name'+
|
||||
Syntax: +:session-load [*--clear*] [*--temp*] [*--force*] [*--delete*] 'name'+
|
||||
|
||||
Load a session.
|
||||
|
||||
@@ -1078,6 +1097,7 @@ Load a session.
|
||||
* +*-t*+, +*--temp*+: Don't set the current session for :session-save.
|
||||
* +*-f*+, +*--force*+: Force loading internal sessions (starting with an underline).
|
||||
|
||||
* +*-d*+, +*--delete*+: Delete the saved session once it has loaded.
|
||||
|
||||
[[session-save]]
|
||||
=== session-save
|
||||
@@ -1100,11 +1120,11 @@ Save a session.
|
||||
|
||||
[[set]]
|
||||
=== set
|
||||
Syntax: +:set [*--temp*] [*--print*] ['option'] ['value']+
|
||||
Syntax: +:set [*--temp*] [*--print*] [*--pattern* 'pattern'] ['option'] ['value']+
|
||||
|
||||
Set an option.
|
||||
|
||||
If the option name ends with '?', the value of the option is shown instead.
|
||||
If the option name ends with '?', the value of the option is shown instead. Using :set without any arguments opens a page where settings can be changed interactively.
|
||||
|
||||
==== positional arguments
|
||||
* +'option'+: The name of the option.
|
||||
@@ -1113,6 +1133,7 @@ If the option name ends with '?', the value of the option is shown instead.
|
||||
==== optional arguments
|
||||
* +*-t*+, +*--temp*+: Set value temporarily until qutebrowser is closed.
|
||||
* +*-p*+, +*--print*+: Print the value after setting.
|
||||
* +*-u*+, +*--pattern*+: The URL pattern to use.
|
||||
|
||||
[[set-cmd-text]]
|
||||
=== set-cmd-text
|
||||
@@ -1202,7 +1223,7 @@ The tab index to close
|
||||
|
||||
[[tab-focus]]
|
||||
=== tab-focus
|
||||
Syntax: +:tab-focus ['index']+
|
||||
Syntax: +:tab-focus [*--no-last*] ['index']+
|
||||
|
||||
Select the tab given as argument/[count].
|
||||
|
||||
@@ -1214,6 +1235,9 @@ If neither count nor index are given, it behaves like tab-next. If both are give
|
||||
last tab.
|
||||
|
||||
|
||||
==== optional arguments
|
||||
* +*-n*+, +*--no-last*+: Whether to avoid focusing last tab if already focused.
|
||||
|
||||
==== count
|
||||
The tab index to focus, starting with 1.
|
||||
|
||||
@@ -1228,6 +1252,9 @@ If no win_id is given, the tab will get detached into a new window.
|
||||
==== positional arguments
|
||||
* +'win-id'+: The window ID of the window to give the current tab to.
|
||||
|
||||
==== count
|
||||
Overrides win_id (index starts at 1 for win_id=0).
|
||||
|
||||
[[tab-move]]
|
||||
=== tab-move
|
||||
Syntax: +:tab-move ['index']+
|
||||
@@ -1297,7 +1324,8 @@ Syntax: +:unbind [*--mode* 'mode'] 'key'+
|
||||
Unbind a keychain.
|
||||
|
||||
==== positional arguments
|
||||
* +'key'+: The keychain or special key (inside <...>) to unbind.
|
||||
* +'key'+: The keychain to unbind. See the help for `:bind` for the correct syntax for keychains.
|
||||
|
||||
|
||||
==== optional arguments
|
||||
* +*-m*+, +*--mode*+: A mode to unbind the key in (default: `normal`). See `:help bindings.commands` for the available modes.
|
||||
@@ -1309,12 +1337,22 @@ Re-open the last closed tab or tabs.
|
||||
|
||||
[[version]]
|
||||
=== version
|
||||
Syntax: +:version [*--paste*]+
|
||||
|
||||
Show version information.
|
||||
|
||||
==== optional arguments
|
||||
* +*-p*+, +*--paste*+: Paste to pastebin.
|
||||
|
||||
[[view-source]]
|
||||
=== view-source
|
||||
Syntax: +:view-source [*--edit*]+
|
||||
|
||||
Show the source of the current page in a new tab.
|
||||
|
||||
==== optional arguments
|
||||
* +*-e*+, +*--edit*+: Edit the source in the editor instead of opening a tab.
|
||||
|
||||
[[window-only]]
|
||||
=== window-only
|
||||
Close all windows except for the current one.
|
||||
@@ -1402,6 +1440,7 @@ How many steps to zoom out.
|
||||
|<<prompt-accept,prompt-accept>>|Accept the current prompt.
|
||||
|<<prompt-item-focus,prompt-item-focus>>|Shift the focus of the prompt file completion menu to another item.
|
||||
|<<prompt-open-download,prompt-open-download>>|Immediately open a download.
|
||||
|<<prompt-yank,prompt-yank>>|Yank URL to clipboard or primary selection.
|
||||
|<<rl-backward-char,rl-backward-char>>|Move back a character.
|
||||
|<<rl-backward-delete-char,rl-backward-delete-char>>|Delete the character before the cursor.
|
||||
|<<rl-backward-kill-word,rl-backward-kill-word>>|Remove chars from the cursor to the beginning of the word.
|
||||
@@ -1467,13 +1506,16 @@ Drop selection and keep selection mode enabled.
|
||||
|
||||
[[follow-hint]]
|
||||
=== follow-hint
|
||||
Syntax: +:follow-hint ['keystring']+
|
||||
Syntax: +:follow-hint [*--select*] ['keystring']+
|
||||
|
||||
Follow a hint.
|
||||
|
||||
==== positional arguments
|
||||
* +'keystring'+: The hint to follow.
|
||||
|
||||
==== optional arguments
|
||||
* +*-s*+, +*--select*+: Only select the given hint, don't necessarily follow it.
|
||||
|
||||
[[leave-mode]]
|
||||
=== leave-mode
|
||||
Leave the mode we're currently in.
|
||||
@@ -1607,6 +1649,15 @@ If no specific command is given, this will use the system's default application
|
||||
==== note
|
||||
* This command does not split arguments after the last argument and handles quotes literally.
|
||||
|
||||
[[prompt-yank]]
|
||||
=== prompt-yank
|
||||
Syntax: +:prompt-yank [*--sel*]+
|
||||
|
||||
Yank URL to clipboard or primary selection.
|
||||
|
||||
==== optional arguments
|
||||
* +*-s*+, +*--sel*+: Use the primary selection instead of the clipboard.
|
||||
|
||||
[[rl-backward-char]]
|
||||
=== rl-backward-char
|
||||
Move back a character.
|
||||
|
||||
@@ -63,6 +63,10 @@ customizable.
|
||||
Using the link:commands.html#set[`:set`] command and command completion, you
|
||||
can quickly set settings interactively, for example `:set tabs.position left`.
|
||||
|
||||
Some settings are also customizable for a given
|
||||
https://developer.chrome.com/apps/match_patterns[URL pattern] by doing e.g.
|
||||
`:set --pattern=*://example.com/ content.images false`.
|
||||
|
||||
To get more help about a setting, use e.g. `:help tabs.position`.
|
||||
|
||||
To bind and unbind keys, you can use the link:commands.html#bind[`:bind`] and
|
||||
@@ -147,7 +151,6 @@ prefix to preserve backslashes) or a Python regex object:
|
||||
If you want to read a setting, you can use the `c` object to do so as well:
|
||||
`c.colors.tabs.even.bg = c.colors.tabs.odd.bg`.
|
||||
|
||||
|
||||
Using strings for setting names
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -171,6 +174,26 @@ To read a setting, use the `config.get` method:
|
||||
color = config.get('colors.completion.fg')
|
||||
----
|
||||
|
||||
Per-domain settings
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Using `config.set`, some settings are also customizable for a given
|
||||
https://developer.chrome.com/apps/match_patterns[URL pattern]:
|
||||
|
||||
[source,python]
|
||||
----
|
||||
config.set('content.images', False, '*://example.com/')
|
||||
----
|
||||
|
||||
Alternatively, you can use `with config.pattern(...) as p:` to get a shortcut
|
||||
similar to `c.` which is scoped to the given domain:
|
||||
|
||||
[source,python]
|
||||
----
|
||||
with config.pattern('*://example.com/') as p:
|
||||
p.content.images = False
|
||||
----
|
||||
|
||||
Binding keys
|
||||
~~~~~~~~~~~~
|
||||
|
||||
@@ -254,7 +277,7 @@ Getting the config directory
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you need to get the qutebrowser config directory, you can do so by reading
|
||||
`config.configdir`. Similarily, you can get the qutebrowser data directory via
|
||||
`config.configdir`. Similarly, you can get the qutebrowser data directory via
|
||||
`config.datadir`.
|
||||
|
||||
This gives you a https://docs.python.org/3/library/pathlib.html[`pathlib.Path`
|
||||
@@ -366,6 +389,8 @@ You can use something like this to read colors from an `~/.Xresources` file:
|
||||
|
||||
[source,python]
|
||||
----
|
||||
import subprocess
|
||||
|
||||
def read_xresources(prefix):
|
||||
props = {}
|
||||
x = subprocess.run(['xrdb', '-query'], stdout=subprocess.PIPE)
|
||||
@@ -376,7 +401,7 @@ def read_xresources(prefix):
|
||||
return props
|
||||
|
||||
xresources = read_xresources('*')
|
||||
c.colors.statusbar.normal.bg = xresources['*background']
|
||||
c.colors.statusbar.normal.bg = xresources['*.background']
|
||||
----
|
||||
|
||||
Avoiding flake8 errors
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// DO NOT EDIT THIS FILE DIRECTLY!
|
||||
// It is autogenerated by running:
|
||||
// $ python3 scripts/dev/src2asciidoc.py
|
||||
// vim: readonly:
|
||||
|
||||
= Setting reference
|
||||
|
||||
@@ -200,6 +201,7 @@
|
||||
|<<hints.uppercase,hints.uppercase>>|Make characters in hint strings uppercase.
|
||||
|<<history_gap_interval,history_gap_interval>>|Maximum time (in minutes) between two history items for them to be considered being from the same browsing session.
|
||||
|<<input.forward_unbound_keys,input.forward_unbound_keys>>|Which unbound keys to forward to the webview in normal mode.
|
||||
|<<input.insert_mode.auto_enter,input.insert_mode.auto_enter>>|Enter insert mode if an editable element is clicked.
|
||||
|<<input.insert_mode.auto_leave,input.insert_mode.auto_leave>>|Leave insert mode if a non-editable element is clicked.
|
||||
|<<input.insert_mode.auto_load,input.insert_mode.auto_load>>|Automatically enter insert mode if an editable element is focused after loading the page.
|
||||
|<<input.insert_mode.plugins,input.insert_mode.plugins>>|Switch to insert mode when clicking flash and other plugins.
|
||||
@@ -229,6 +231,7 @@
|
||||
|<<statusbar.hide,statusbar.hide>>|Hide the statusbar unless a message is shown.
|
||||
|<<statusbar.padding,statusbar.padding>>|Padding (in pixels) for the statusbar.
|
||||
|<<statusbar.position,statusbar.position>>|Position of the status bar.
|
||||
|<<statusbar.widgets,statusbar.widgets>>|List of widgets displayed in the statusbar.
|
||||
|<<tabs.background,tabs.background>>|Open new tabs (middleclick/ctrl+click) in the background.
|
||||
|<<tabs.close_mouse_button,tabs.close_mouse_button>>|Mouse button with which to close tabs.
|
||||
|<<tabs.close_mouse_button_on_bar,tabs.close_mouse_button_on_bar>>|How to behave when the close mouse button is pressed on the tab bar.
|
||||
@@ -237,11 +240,11 @@
|
||||
|<<tabs.indicator.padding,tabs.indicator.padding>>|Padding (in pixels) for tab indicators.
|
||||
|<<tabs.indicator.width,tabs.indicator.width>>|Width (in pixels) of the progress indicator (0 to disable).
|
||||
|<<tabs.last_close,tabs.last_close>>|How to behave when the last tab is closed.
|
||||
|<<tabs.mode_on_change,tabs.mode_on_change>>|When switching tabs, what input mode is applied.
|
||||
|<<tabs.mousewheel_switching,tabs.mousewheel_switching>>|Switch between tabs using the mouse wheel.
|
||||
|<<tabs.new_position.related,tabs.new_position.related>>|Position of new tabs opened from another tab.
|
||||
|<<tabs.new_position.unrelated,tabs.new_position.unrelated>>|Position of new tabs which aren't opened from another tab.
|
||||
|<<tabs.padding,tabs.padding>>|Padding (in pixels) around text for tabs.
|
||||
|<<tabs.persist_mode_on_change,tabs.persist_mode_on_change>>|Stay in insert/passthrough mode when switching tabs.
|
||||
|<<tabs.pinned.shrink,tabs.pinned.shrink>>|Shrink pinned tabs down to their contents.
|
||||
|<<tabs.position,tabs.position>>|Position of the tab bar.
|
||||
|<<tabs.select_on_remove,tabs.select_on_remove>>|Which tab to select when the focused tab is removed.
|
||||
@@ -320,7 +323,7 @@ While it's possible to add bindings with this setting, it's recommended to use `
|
||||
This setting is a dictionary containing mode names and dictionaries mapping keys to commands:
|
||||
`{mode: {key: command}}`
|
||||
If you want to map a key to another key, check the `bindings.key_mappings` setting instead.
|
||||
For special keys (can't be part of a keychain), enclose them in `<`...`>`. For modifiers, you can use either `-` or `+` as delimiters, and these names:
|
||||
For modifiers, you can use either `-` or `+` as delimiters, and these names:
|
||||
|
||||
* Control: `Control`, `Ctrl`
|
||||
|
||||
@@ -356,11 +359,8 @@ The following modes are available:
|
||||
|
||||
* prompt: Entered when there's a prompt to display, like for download
|
||||
locations or when invoked from JavaScript.
|
||||
+
|
||||
You can bind normal keys in this mode, but they will be only active when
|
||||
a yes/no-prompt is asked. For other prompt modes, you can only bind
|
||||
special keys.
|
||||
|
||||
* yesno: Entered when there's a yes/no prompt displayed.
|
||||
* caret: Entered when pressing the `v` mode, used to select text using the
|
||||
keyboard.
|
||||
|
||||
@@ -377,6 +377,8 @@ Default keybindings. If you want to add bindings, modify `bindings.commands` ins
|
||||
The main purpose of this setting is that you can set it to an empty dictionary if you want to load no default keybindings at all.
|
||||
If you want to preserve default bindings (and get new bindings when there is an update), use `config.bind()` in `config.py` or the `:bind` command, and leave this setting alone.
|
||||
|
||||
This setting can only be set in config.py.
|
||||
|
||||
Type: <<types,Dict>>
|
||||
|
||||
Default:
|
||||
@@ -580,8 +582,20 @@ Default:
|
||||
* +pass:[sk]+: +pass:[set-cmd-text -s :bind]+
|
||||
* +pass:[sl]+: +pass:[set-cmd-text -s :set -t]+
|
||||
* +pass:[ss]+: +pass:[set-cmd-text -s :set]+
|
||||
* +pass:[tPH]+: +pass:[config-cycle -p -u *://*.{url:host}/* content.plugins ;; reload]+
|
||||
* +pass:[tPh]+: +pass:[config-cycle -p -u *://{url:host}/* content.plugins ;; reload]+
|
||||
* +pass:[tPu]+: +pass:[config-cycle -p -u {url} content.plugins ;; reload]+
|
||||
* +pass:[tSH]+: +pass:[config-cycle -p -u *://*.{url:host}/* content.javascript.enabled ;; reload]+
|
||||
* +pass:[tSh]+: +pass:[config-cycle -p -u *://{url:host}/* content.javascript.enabled ;; reload]+
|
||||
* +pass:[tSu]+: +pass:[config-cycle -p -u {url} content.javascript.enabled ;; reload]+
|
||||
* +pass:[th]+: +pass:[back -t]+
|
||||
* +pass:[tl]+: +pass:[forward -t]+
|
||||
* +pass:[tpH]+: +pass:[config-cycle -p -t -u *://*.{url:host}/* content.plugins ;; reload]+
|
||||
* +pass:[tph]+: +pass:[config-cycle -p -t -u *://{url:host}/* content.plugins ;; reload]+
|
||||
* +pass:[tpu]+: +pass:[config-cycle -p -t -u {url} content.plugins ;; reload]+
|
||||
* +pass:[tsH]+: +pass:[config-cycle -p -t -u *://*.{url:host}/* content.javascript.enabled ;; reload]+
|
||||
* +pass:[tsh]+: +pass:[config-cycle -p -t -u *://{url:host}/* content.javascript.enabled ;; reload]+
|
||||
* +pass:[tsu]+: +pass:[config-cycle -p -t -u {url} content.javascript.enabled ;; reload]+
|
||||
* +pass:[u]+: +pass:[undo]+
|
||||
* +pass:[v]+: +pass:[enter-mode caret]+
|
||||
* +pass:[wB]+: +pass:[set-cmd-text -s :bookmark-load -w]+
|
||||
@@ -615,6 +629,8 @@ Default:
|
||||
* +pass:[<Alt-Backspace>]+: +pass:[rl-backward-kill-word]+
|
||||
* +pass:[<Alt-D>]+: +pass:[rl-kill-word]+
|
||||
* +pass:[<Alt-F>]+: +pass:[rl-forward-word]+
|
||||
* +pass:[<Alt-Shift-Y>]+: +pass:[prompt-yank --sel]+
|
||||
* +pass:[<Alt-Y>]+: +pass:[prompt-yank]+
|
||||
* +pass:[<Ctrl-?>]+: +pass:[rl-delete-char]+
|
||||
* +pass:[<Ctrl-A>]+: +pass:[rl-beginning-of-line]+
|
||||
* +pass:[<Ctrl-B>]+: +pass:[rl-backward-char]+
|
||||
@@ -632,11 +648,17 @@ Default:
|
||||
* +pass:[<Shift-Tab>]+: +pass:[prompt-item-focus prev]+
|
||||
* +pass:[<Tab>]+: +pass:[prompt-item-focus next]+
|
||||
* +pass:[<Up>]+: +pass:[prompt-item-focus prev]+
|
||||
* +pass:[n]+: +pass:[prompt-accept no]+
|
||||
* +pass:[y]+: +pass:[prompt-accept yes]+
|
||||
- +pass:[register]+:
|
||||
|
||||
* +pass:[<Escape>]+: +pass:[leave-mode]+
|
||||
- +pass:[yesno]+:
|
||||
|
||||
* +pass:[<Alt-Shift-Y>]+: +pass:[prompt-yank --sel]+
|
||||
* +pass:[<Alt-Y>]+: +pass:[prompt-yank]+
|
||||
* +pass:[<Escape>]+: +pass:[leave-mode]+
|
||||
* +pass:[<Return>]+: +pass:[prompt-accept]+
|
||||
* +pass:[n]+: +pass:[prompt-accept no]+
|
||||
* +pass:[y]+: +pass:[prompt-accept yes]+
|
||||
|
||||
[[bindings.key_mappings]]
|
||||
=== bindings.key_mappings
|
||||
@@ -1443,6 +1465,8 @@ Default:
|
||||
Enable support for the HTML 5 web application cache feature.
|
||||
An application cache acts like an HTTP cache in some sense. For documents that use the application cache via JavaScript, the loader engine will first ask the application cache for the contents, before hitting the network.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[true]+
|
||||
@@ -1520,6 +1544,8 @@ This setting is only available with the QtWebKit backend.
|
||||
=== content.dns_prefetch
|
||||
Try to pre-fetch DNS entries to speed up browsing.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[true]+
|
||||
@@ -1531,6 +1557,8 @@ This setting is only available with the QtWebKit backend.
|
||||
Expand each subframe to its contents.
|
||||
This will flatten all the frames to become one scrollable page.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
@@ -1579,7 +1607,7 @@ Default: +pass:[true]+
|
||||
[[content.headers.referer]]
|
||||
=== content.headers.referer
|
||||
When to send the Referer header.
|
||||
The Referer header tells websites from which website you were coming from when visting them.
|
||||
The Referer header tells websites from which website you were coming from when visiting them.
|
||||
|
||||
Type: <<types,String>>
|
||||
|
||||
@@ -1647,6 +1675,8 @@ Default:
|
||||
=== content.hyperlink_auditing
|
||||
Enable hyperlink auditing (`<a ping>`).
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
@@ -1655,6 +1685,8 @@ Default: +pass:[false]+
|
||||
=== content.images
|
||||
Load images automatically in web pages.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[true]+
|
||||
@@ -1672,6 +1704,8 @@ Default: +pass:[true]+
|
||||
Allow JavaScript to read from or write to the clipboard.
|
||||
With QtWebEngine, writing the clipboard as response to a user interaction is always allowed.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
@@ -1680,6 +1714,8 @@ Default: +pass:[false]+
|
||||
=== content.javascript.can_close_tabs
|
||||
Allow JavaScript to close tabs.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
@@ -1690,6 +1726,8 @@ This setting is only available with the QtWebKit backend.
|
||||
=== content.javascript.can_open_tabs_automatically
|
||||
Allow JavaScript to open new tabs without user interaction.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
@@ -1698,6 +1736,8 @@ Default: +pass:[false]+
|
||||
=== content.javascript.enabled
|
||||
Enable JavaScript.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[true]+
|
||||
@@ -1737,6 +1777,8 @@ Default: +pass:[true]+
|
||||
=== content.local_content_can_access_file_urls
|
||||
Allow locally loaded documents to access other local URLs.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[true]+
|
||||
@@ -1745,6 +1787,8 @@ Default: +pass:[true]+
|
||||
=== content.local_content_can_access_remote_urls
|
||||
Allow locally loaded documents to access remote URLs.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
@@ -1753,6 +1797,8 @@ Default: +pass:[false]+
|
||||
=== content.local_storage
|
||||
Enable support for HTML 5 local storage and Web SQL.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[true]+
|
||||
@@ -1813,6 +1859,8 @@ This setting is only available with the QtWebKit backend.
|
||||
=== content.plugins
|
||||
Enable plugins in Web pages.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
@@ -1821,6 +1869,8 @@ Default: +pass:[false]+
|
||||
=== content.print_element_backgrounds
|
||||
Draw the background color and images also when the page is printed.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[true]+
|
||||
@@ -1885,6 +1935,8 @@ Default: empty
|
||||
=== content.webgl
|
||||
Enable WebGL.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[true]+
|
||||
@@ -1902,6 +1954,8 @@ Default: +pass:[false]+
|
||||
Monitor load requests for cross-site scripting attempts.
|
||||
Suspicious scripts will be blocked and reported in the inspector's JavaScript console. Enabling this feature might have an impact on performance.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
@@ -2347,6 +2401,14 @@ Valid values:
|
||||
|
||||
Default: +pass:[auto]+
|
||||
|
||||
[[input.insert_mode.auto_enter]]
|
||||
=== input.insert_mode.auto_enter
|
||||
Enter insert mode if an editable element is clicked.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[true]+
|
||||
|
||||
[[input.insert_mode.auto_leave]]
|
||||
=== input.insert_mode.auto_leave
|
||||
Leave insert mode if a non-editable element is clicked.
|
||||
@@ -2375,6 +2437,8 @@ Default: +pass:[false]+
|
||||
=== input.links_included_in_focus_chain
|
||||
Include hyperlinks in the keyboard focus chain when tabbing.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[true]+
|
||||
@@ -2402,6 +2466,8 @@ Default: +pass:[false]+
|
||||
Enable spatial navigation.
|
||||
Spatial navigation consists in the ability to navigate between focusable elements in a Web page, such as hyperlinks and form controls, by using Left, Right, Up and Down arrow keys. For example, if the user presses the Right key, heuristics determine whether there is an element he might be trying to reach towards the right and which element he probably wants.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
@@ -2546,6 +2612,8 @@ Default: +pass:[false]+
|
||||
Enable smooth scrolling for web pages.
|
||||
Note smooth scrolling does not work with the `:scroll-px` command.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
@@ -2682,6 +2750,31 @@ Valid values:
|
||||
|
||||
Default: +pass:[bottom]+
|
||||
|
||||
[[statusbar.widgets]]
|
||||
=== statusbar.widgets
|
||||
List of widgets displayed in the statusbar.
|
||||
|
||||
Type: <<types,List of String>>
|
||||
|
||||
Valid values:
|
||||
|
||||
* +url+: Current page URL.
|
||||
* +scroll+: Percentage of the current page position like `10%`.
|
||||
* +scroll_raw+: Raw percentage of the current page position like `10`.
|
||||
* +history+: Display an arrow when possible to go back/forward in history.
|
||||
* +tabs+: Current active tab, e.g. `2`.
|
||||
* +keypress+: Display pressed keys when composing a vi command.
|
||||
* +progress+: Progress bar for the current page loading.
|
||||
|
||||
Default:
|
||||
|
||||
- +pass:[keypress]+
|
||||
- +pass:[url]+
|
||||
- +pass:[scroll]+
|
||||
- +pass:[history]+
|
||||
- +pass:[tabs]+
|
||||
- +pass:[progress]+
|
||||
|
||||
[[tabs.background]]
|
||||
=== tabs.background
|
||||
Open new tabs (middleclick/ctrl+click) in the background.
|
||||
@@ -2773,6 +2866,20 @@ Valid values:
|
||||
|
||||
Default: +pass:[ignore]+
|
||||
|
||||
[[tabs.mode_on_change]]
|
||||
=== tabs.mode_on_change
|
||||
When switching tabs, what input mode is applied.
|
||||
|
||||
Type: <<types,String>>
|
||||
|
||||
Valid values:
|
||||
|
||||
* +persist+: Retain the current mode.
|
||||
* +restore+: Restore previously saved mode.
|
||||
* +normal+: Always revert to normal mode.
|
||||
|
||||
Default: +pass:[normal]+
|
||||
|
||||
[[tabs.mousewheel_switching]]
|
||||
=== tabs.mousewheel_switching
|
||||
Switch between tabs using the mouse wheel.
|
||||
@@ -2824,14 +2931,6 @@ Default:
|
||||
- +pass:[right]+: +pass:[5]+
|
||||
- +pass:[top]+: +pass:[0]+
|
||||
|
||||
[[tabs.persist_mode_on_change]]
|
||||
=== tabs.persist_mode_on_change
|
||||
Stay in insert/passthrough mode when switching tabs.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
|
||||
[[tabs.pinned.shrink]]
|
||||
=== tabs.pinned.shrink
|
||||
Shrink pinned tabs down to their contents.
|
||||
@@ -2993,6 +3092,7 @@ Type: <<types,FlagList>>
|
||||
Valid values:
|
||||
|
||||
* +host+
|
||||
* +port+
|
||||
* +path+
|
||||
* +query+
|
||||
* +anchor+
|
||||
@@ -3101,6 +3201,8 @@ Default: +pass:[512]+
|
||||
=== zoom.text_only
|
||||
Apply the zoom factor on a frame only to the text or to all content.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1024 KiB After Width: | Height: | Size: 1.0 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 46 KiB |
@@ -35,17 +35,21 @@ Ubuntu 16.04 doesn't come with an up-to-date engine (a new enough QtWebKit, or
|
||||
QtWebEngine). However, it comes with Python 3.5, so you can
|
||||
<<tox,install qutebrowser via tox>>.
|
||||
|
||||
You'll need some basic libraries to use the tox-installed PyQt:
|
||||
|
||||
----
|
||||
# apt install libglib2.0-0 libgl1 libfontconfig1 libx11-xcb1 libxi6 libxrender1 libdbus-1-3
|
||||
----
|
||||
|
||||
Debian Stretch / Ubuntu 17.04 and 17.10
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Those versions come with QtWebEngine in the repositories. This makes it possible
|
||||
to install qutebrowser via the Debian package.
|
||||
|
||||
Get the qutebrowser package from the
|
||||
https://github.com/qutebrowser/qutebrowser/releases[release page] and download
|
||||
the https://qutebrowser.org/python3-pypeg2_2.15.2-1_all.deb[PyPEG2 package].
|
||||
|
||||
(If you are using debian testing you can just use the python3-pypeg2 package from the repos)
|
||||
Download the https://packages.debian.org/sid/all/qutebrowser/download[qutebrowser] and
|
||||
https://packages.debian.org/sid/all/python3-pypeg2/download[PyPEG2]
|
||||
package from the Debian repositories.
|
||||
|
||||
Install the packages:
|
||||
|
||||
@@ -277,6 +281,11 @@ PS C:\> Install-Package qutebrowser
|
||||
----
|
||||
C:\> choco install qutebrowser
|
||||
----
|
||||
* Scoop's client
|
||||
----
|
||||
C:\> scoop bucket add extras
|
||||
C:\> scoop install qutebrowser
|
||||
----
|
||||
|
||||
Manual install
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
@@ -37,7 +37,7 @@ is available in the repositories:
|
||||
Archlinux
|
||||
^^^^^^^^^
|
||||
|
||||
For Archlinux, no debug informations are provided. You can either compile Qt
|
||||
For Archlinux, no debug information is provided. You can either compile Qt
|
||||
yourself (which will take a few hours even on a modern machine) or use
|
||||
debugging symbols compiled/packaged by me (x86_64 only).
|
||||
|
||||
|
||||
@@ -45,8 +45,6 @@ In `command` mode:
|
||||
- `QUTE_URL`: The current URL.
|
||||
- `QUTE_TITLE`: The title of the current page.
|
||||
- `QUTE_SELECTED_TEXT`: The text currently selected on the page.
|
||||
- `QUTE_SELECTED_HTML` The HTML currently selected on the page (not supported
|
||||
with QtWebEngine).
|
||||
|
||||
In `hints` mode:
|
||||
|
||||
|
||||
@@ -21,5 +21,5 @@ install: doc/qutebrowser.1.html
|
||||
$(wildcard misc/userscripts/*)
|
||||
install -Dm755 -t "$(DESTDIR)/usr/share/qutebrowser/scripts/" \
|
||||
$(filter-out scripts/__init__.py scripts/__pycache__ scripts/dev \
|
||||
scripts/testbrowser_cpp scripts/asciidoc2html.py scripts/setupcommon.py \
|
||||
scripts/testbrowser scripts/asciidoc2html.py scripts/setupcommon.py \
|
||||
scripts/link_pyqt.py,$(wildcard scripts/*))
|
||||
|
||||
@@ -32,22 +32,24 @@
|
||||
objecttolerance="10"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.24"
|
||||
inkscape:cx="305.29152"
|
||||
inkscape:cy="465.48793"
|
||||
inkscape:zoom="1.7536248"
|
||||
inkscape:cx="376.55567"
|
||||
inkscape:cy="268.64059"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
width="1024px"
|
||||
height="640px"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1024"
|
||||
inkscape:window-height="723"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1440"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:snap-text-baseline="true">
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:snap-text-baseline="true"
|
||||
inkscape:measure-start="0,0"
|
||||
inkscape:measure-end="0,0">
|
||||
<inkscape:grid
|
||||
id="GridFromPre046Settings"
|
||||
type="xygrid"
|
||||
@@ -2688,7 +2690,8 @@
|
||||
id="flowPara5711"> </flowPara></flowRoot> <flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot5691-0"
|
||||
style="font-style:normal;font-weight:normal;font-size:12.80000019px;line-height:0.01%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"><flowRegion
|
||||
style="font-style:normal;font-weight:normal;font-size:12.80000019px;line-height:0.01%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
transform="translate(0,-10)"><flowRegion
|
||||
id="flowRegion5693-7"
|
||||
style="font-family:sans-serif;stroke-width:1.06666672"><rect
|
||||
id="rect5695-0"
|
||||
@@ -3660,5 +3663,64 @@
|
||||
sodipodi:role="line"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672"
|
||||
id="tspan6220">items</tspan></text>
|
||||
</g>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12.80000019px;line-height:0%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
x="417.29486"
|
||||
y="205.18887"
|
||||
id="text7245-1-3"><tspan
|
||||
sodipodi:role="line"
|
||||
x="417.29486"
|
||||
y="205.18887"
|
||||
id="tspan7366-3-6"
|
||||
style="font-size:9.60000038px;line-height:0.89999998;stroke-width:1.06666672"> </tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="417.29486"
|
||||
y="213.07179"
|
||||
id="tspan5293-53"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">toggle</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="417.29486"
|
||||
y="220.75179"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672;fill:#ff0000"
|
||||
id="tspan6091">(12)</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="417.29486"
|
||||
y="225.70012"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672"
|
||||
id="tspan6087" /><tspan
|
||||
sodipodi:role="line"
|
||||
x="417.29486"
|
||||
y="225.70012"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672"
|
||||
id="tspan6089" /></text>
|
||||
<flowRoot
|
||||
transform="translate(-1.2953814,90.2721)"
|
||||
xml:space="preserve"
|
||||
id="flowRoot5691-0-5"
|
||||
style="font-style:normal;font-weight:normal;font-size:12.80000019px;line-height:0.01%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"><flowRegion
|
||||
id="flowRegion5693-7-6"
|
||||
style="font-family:sans-serif;stroke-width:1.06666672"><rect
|
||||
id="rect5695-0-2"
|
||||
width="344"
|
||||
height="173.33333"
|
||||
x="19.42783"
|
||||
y="520.07886"
|
||||
style="font-family:sans-serif;fill:#000000;stroke-width:1.13777781" /></flowRegion><flowPara
|
||||
style="font-weight:bold;font-size:10.66666698px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'Sans Bold';fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara5701-9-2"><flowSpan
|
||||
style="font-weight:bold;font-family:sans-serif;-inkscape-font-specification:'Sans Bold';fill:#ff0000;stroke-width:1.06666672"
|
||||
id="flowSpan5705-5-1">(10)</flowSpan> toggling settings:</flowPara><flowPara
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara6196">tsh - toggle scripts for the current host (temporarily)</flowPara><flowPara
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara6200">tSh - like <flowSpan
|
||||
style="font-style:italic"
|
||||
id="flowSpan6202">tsh</flowSpan>, but permanently</flowPara><flowPara
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara6206">tsH/tsu - like <flowSpan
|
||||
style="font-style:italic"
|
||||
id="flowSpan6210">tsh</flowSpan>, but including subdomains / with exact URL</flowPara><flowPara
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara6208">tph - toggle plugins</flowPara></flowRoot> </g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 178 KiB After Width: | Height: | Size: 181 KiB |
@@ -1,9 +1,9 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
certifi==2017.11.5
|
||||
certifi==2018.1.18
|
||||
chardet==3.0.4
|
||||
codecov==2.0.13
|
||||
coverage==4.4.2
|
||||
codecov==2.0.15
|
||||
coverage==4.5.1
|
||||
idna==2.6
|
||||
requests==2.18.4
|
||||
urllib3==1.22
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
attrs==17.4.0
|
||||
flake8==3.5.0
|
||||
flake8-bugbear==17.12.0
|
||||
flake8-bugbear==18.2.0
|
||||
flake8-builtins==1.0.post0
|
||||
flake8-comprehensions==1.4.1
|
||||
flake8-copyright==0.2.0
|
||||
flake8-debugger==3.0.0
|
||||
flake8-debugger==3.1.0
|
||||
flake8-deprecated==1.3
|
||||
flake8-docstrings==1.3.0
|
||||
flake8-future-import==0.4.4
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
appdirs==1.4.3
|
||||
packaging==16.8
|
||||
packaging==17.1
|
||||
pyparsing==2.2.0
|
||||
setuptools==38.4.0
|
||||
setuptools==38.5.1
|
||||
six==1.11.0
|
||||
wheel==0.30.0
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
-e git+https://github.com/PyCQA/astroid.git#egg=astroid
|
||||
certifi==2017.11.5
|
||||
certifi==2018.1.18
|
||||
chardet==3.0.4
|
||||
github3.py==0.9.6
|
||||
idna==2.6
|
||||
isort==4.2.15
|
||||
isort==4.3.4
|
||||
lazy-object-proxy==1.3.1
|
||||
mccabe==0.6.1
|
||||
-e git+https://github.com/PyCQA/pylint.git#egg=pylint
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
astroid==1.6.0
|
||||
certifi==2017.11.5
|
||||
astroid==1.6.1
|
||||
certifi==2018.1.18
|
||||
chardet==3.0.4
|
||||
github3.py==0.9.6
|
||||
idna==2.6
|
||||
isort==4.2.15
|
||||
isort==4.3.4
|
||||
lazy-object-proxy==1.3.1
|
||||
mccabe==0.6.1
|
||||
pylint==1.8.1
|
||||
pylint==1.8.2
|
||||
./scripts/dev/pylint_checkers
|
||||
requests==2.18.4
|
||||
six==1.11.0
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.9.2
|
||||
sip==4.19.6
|
||||
PyQt5==5.10.1
|
||||
sip==4.19.8
|
||||
|
||||
@@ -5,34 +5,35 @@ beautifulsoup4==4.6.0
|
||||
cheroot==6.0.0
|
||||
click==6.7
|
||||
# colorama==0.3.9
|
||||
coverage==4.4.2
|
||||
coverage==4.5.1
|
||||
EasyProcess==0.2.3
|
||||
fields==5.0.0
|
||||
Flask==0.12.2
|
||||
glob2==0.6
|
||||
hunter==2.0.2
|
||||
hypothesis==3.44.16
|
||||
hypothesis==3.48.0
|
||||
itsdangerous==0.24
|
||||
# Jinja2==2.10
|
||||
Mako==1.0.7
|
||||
# MarkupSafe==1.0
|
||||
more-itertools==4.1.0
|
||||
parse==1.8.2
|
||||
parse-type==0.4.2
|
||||
pluggy==0.6.0
|
||||
py==1.5.2
|
||||
py-cpuinfo==3.3.0
|
||||
pytest==3.3.1 # rq.filter: != 3.3.2
|
||||
pytest-bdd==2.19.0
|
||||
pytest==3.4.1
|
||||
pytest-bdd==2.20.0
|
||||
pytest-benchmark==3.1.1
|
||||
pytest-cov==2.5.1
|
||||
pytest-faulthandler==1.3.1
|
||||
pytest-faulthandler==1.4.1
|
||||
pytest-instafail==0.3.0
|
||||
pytest-mock==1.6.3
|
||||
pytest-mock==1.7.1
|
||||
pytest-qt==2.3.1
|
||||
pytest-repeat==0.4.1
|
||||
pytest-rerunfailures==4.0
|
||||
pytest-travis-fold==1.3.0
|
||||
pytest-xvfb==1.0.0
|
||||
pytest-xvfb==1.1.0
|
||||
PyVirtualDisplay==0.2.1
|
||||
six==1.11.0
|
||||
vulture==0.26
|
||||
|
||||
@@ -4,7 +4,7 @@ coverage
|
||||
Flask
|
||||
hunter
|
||||
hypothesis
|
||||
pytest==3.3.1
|
||||
pytest
|
||||
pytest-bdd
|
||||
pytest-benchmark
|
||||
pytest-cov
|
||||
@@ -19,4 +19,3 @@ pytest-xvfb
|
||||
vulture
|
||||
|
||||
#@ ignore: Jinja2, MarkupSafe, colorama
|
||||
#@ filter: pytest != 3.3.2
|
||||
|
||||
@@ -52,7 +52,7 @@ die() {
|
||||
if ! [ -d "$DOWNLOAD_DIR" ] ; then
|
||||
die "Download directory »$DOWNLOAD_DIR« not found!"
|
||||
fi
|
||||
if ! which "${ROFI_CMD}" > /dev/null ; then
|
||||
if ! command -v "${ROFI_CMD}" > /dev/null ; then
|
||||
die "Rofi command »${ROFI_CMD}« not found in PATH!"
|
||||
fi
|
||||
|
||||
|
||||
@@ -220,7 +220,7 @@ user_pattern='^(user|username|login): '
|
||||
GPG_OPTS=( "--quiet" "--yes" "--compress-algo=none" "--no-encrypt-to" )
|
||||
GPG="gpg"
|
||||
export GPG_TTY="${GPG_TTY:-$(tty 2>/dev/null)}"
|
||||
which gpg2 &>/dev/null && GPG="gpg2"
|
||||
command -v gpg2 &>/dev/null && GPG="gpg2"
|
||||
[[ -n $GPG_AGENT_INFO || $GPG == "gpg2" ]] && GPG_OPTS+=( "--batch" "--use-agent" )
|
||||
|
||||
pass_backend() {
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2015 Zach-Button <zachrey.button@gmail.com>
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#
|
||||
# This script fetches the unprocessed HTML source for a page and opens it in vim.
|
||||
# :bind gf spawn --userscript qutebrowser_viewsource
|
||||
#
|
||||
# Caveat: Does not use authentication of any kind. Add it in if you want it to.
|
||||
#
|
||||
|
||||
path=$(mktemp --tmpdir qutebrowser_XXXXXXXX.html)
|
||||
|
||||
curl "$QUTE_URL" > "$path"
|
||||
urxvt -e vim "$path"
|
||||
|
||||
rm "$path"
|
||||
@@ -13,7 +13,11 @@
|
||||
from __future__ import absolute_import
|
||||
import codecs, os
|
||||
|
||||
tmpfile=os.path.expanduser('~/.local/share/qutebrowser/userscripts/readability.html')
|
||||
tmpfile = os.path.join(
|
||||
os.environ.get('QUTE_DATA_DIR',
|
||||
os.path.expanduser('~/.local/share/qutebrowser')),
|
||||
'userscripts/readability.html')
|
||||
|
||||
if not os.path.exists(os.path.dirname(tmpfile)):
|
||||
os.makedirs(os.path.dirname(tmpfile))
|
||||
|
||||
|
||||
52
misc/userscripts/tor_identity
Executable file
52
misc/userscripts/tor_identity
Executable file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2018 jnphilipp <mail@jnphilipp.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# Change your tor identity.
|
||||
#
|
||||
# Set a hotkey to launch this script, then:
|
||||
# :bind ti spawn --userscript tor_identity PASSWORD
|
||||
#
|
||||
# Use the hotkey to change your tor identity, press 'ti' to change it.
|
||||
# https://stem.torproject.org/faq.html#how-do-i-request-a-new-identity-from-tor
|
||||
#
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
from stem import Signal
|
||||
from stem.control import Controller
|
||||
except ImportError:
|
||||
if os.getenv('QUTE_FIFO'):
|
||||
with open(os.environ['QUTE_FIFO'], 'w') as f:
|
||||
f.write('message-error "Failed to import stem."')
|
||||
else:
|
||||
print('Failed to import stem.')
|
||||
|
||||
|
||||
password = sys.argv[1]
|
||||
with Controller.from_port(port=9051) as controller:
|
||||
controller.authenticate(password)
|
||||
controller.signal(Signal.NEWNYM)
|
||||
if os.getenv('QUTE_FIFO'):
|
||||
with open(os.environ['QUTE_FIFO'], 'w') as f:
|
||||
f.write('message-info "Tor identity changed."')
|
||||
else:
|
||||
print('Tor identity changed.')
|
||||
@@ -1,4 +1,5 @@
|
||||
[pytest]
|
||||
log_level = NOTSET
|
||||
addopts = --strict -rfEw --faulthandler-timeout=90 --instafail --pythonwarnings error --benchmark-columns=Min,Max,Median
|
||||
testpaths = tests
|
||||
markers =
|
||||
@@ -25,6 +26,8 @@ markers =
|
||||
this: Used to mark tests during development
|
||||
no_invalid_lines: Don't fail on unparseable lines in end2end tests
|
||||
issue2478: Tests which are broken on Windows with QtWebEngine, https://github.com/qutebrowser/qutebrowser/issues/2478
|
||||
issue3572: Tests which are broken with QtWebEngine and Qt 5.10, https://github.com/qutebrowser/qutebrowser/issues/3572
|
||||
qtbug60673: Tests which are broken if the conversion from orange selection to real selection is flaky
|
||||
fake_os: Fake utils.is_* to a fake operating system
|
||||
unicode_locale: Tests which need an unicode locale to work
|
||||
qt_log_level_fail = WARNING
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -22,11 +22,11 @@
|
||||
import os.path
|
||||
|
||||
__author__ = "Florian Bruhin"
|
||||
__copyright__ = "Copyright 2014-2017 Florian Bruhin (The Compiler)"
|
||||
__copyright__ = "Copyright 2014-2018 Florian Bruhin (The Compiler)"
|
||||
__license__ = "GPL"
|
||||
__maintainer__ = __author__
|
||||
__email__ = "mail@qutebrowser.org"
|
||||
__version_info__ = (1, 1, 0)
|
||||
__version_info__ = (1, 2, 0)
|
||||
__version__ = '.'.join(str(e) for e in __version_info__)
|
||||
__description__ = "A keyboard-driven, vim-like browser based on PyQt5."
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -64,7 +64,7 @@ from qutebrowser.completion.models import miscmodels
|
||||
from qutebrowser.commands import cmdutils, runners, cmdexc
|
||||
from qutebrowser.config import config, websettings, configfiles, configinit
|
||||
from qutebrowser.browser import (urlmarks, adblock, history, browsertab,
|
||||
downloads, greasemonkey)
|
||||
qtnetworkdownloads, downloads, greasemonkey)
|
||||
from qutebrowser.browser.network import proxy
|
||||
from qutebrowser.browser.webkit import cookies, cache
|
||||
from qutebrowser.browser.webkit.network import networkmanager
|
||||
@@ -95,6 +95,7 @@ def run(args):
|
||||
|
||||
log.init.debug("Initializing directories...")
|
||||
standarddir.init(args)
|
||||
utils.preload_resources()
|
||||
|
||||
log.init.debug("Initializing config...")
|
||||
configinit.early_init(args)
|
||||
@@ -491,6 +492,10 @@ def _init_modules(args, crash_handler):
|
||||
diskcache = cache.DiskCache(standarddir.cache(), parent=qApp)
|
||||
objreg.register('cache', diskcache)
|
||||
|
||||
log.init.debug("Initializing downloads...")
|
||||
download_manager = qtnetworkdownloads.DownloadManager(parent=qApp)
|
||||
objreg.register('qtnetwork-download-manager', download_manager)
|
||||
|
||||
log.init.debug("Initializing Greasemonkey...")
|
||||
greasemonkey.init()
|
||||
|
||||
@@ -768,6 +773,8 @@ class Quitter:
|
||||
pre_text="Error while saving {}".format(key))
|
||||
# Disable storage so removing tempdir will work
|
||||
websettings.shutdown()
|
||||
# Disable application proxy factory to fix segfaults with Qt 5.10.1
|
||||
proxy.shutdown()
|
||||
# Re-enable faulthandler to stdout, then remove crash log
|
||||
log.destroy.debug("Deactivating crash log...")
|
||||
objreg.get('crash-handler').destroy_crashlogfile()
|
||||
@@ -836,7 +843,11 @@ class Application(QApplication):
|
||||
def event(self, e):
|
||||
"""Handle macOS FileOpen events."""
|
||||
if e.type() == QEvent.FileOpen:
|
||||
open_url(e.url(), no_raise=True)
|
||||
url = e.url()
|
||||
if url.isValid():
|
||||
open_url(url, no_raise=True)
|
||||
else:
|
||||
message.error("Invalid URL: {}".format(url.errorString()))
|
||||
else:
|
||||
return super().event(e)
|
||||
|
||||
@@ -874,6 +885,7 @@ class EventFilter(QObject):
|
||||
self._handlers = {
|
||||
QEvent.KeyPress: self._handle_key_event,
|
||||
QEvent.KeyRelease: self._handle_key_event,
|
||||
QEvent.ShortcutOverride: self._handle_key_event,
|
||||
}
|
||||
|
||||
def _handle_key_event(self, event):
|
||||
@@ -891,7 +903,7 @@ class EventFilter(QObject):
|
||||
return False
|
||||
try:
|
||||
man = objreg.get('mode-manager', scope='window', window='current')
|
||||
return man.eventFilter(event)
|
||||
return man.handle_event(event)
|
||||
except objreg.RegistryUnavailableError:
|
||||
# No window available yet, or not a MainWindow
|
||||
return False
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -176,8 +176,7 @@ class HostBlocker:
|
||||
self._config_blocked_hosts)
|
||||
self._blocked_hosts = set()
|
||||
self._done_count = 0
|
||||
download_manager = objreg.get('qtnetwork-download-manager',
|
||||
scope='window', window='last-focused')
|
||||
download_manager = objreg.get('qtnetwork-download-manager')
|
||||
for url in config.val.content.host_blocking.lists:
|
||||
if url.scheme() == 'file':
|
||||
filename = url.toLocalFile()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -30,7 +30,8 @@ from PyQt5.QtWidgets import QWidget, QApplication
|
||||
|
||||
from qutebrowser.keyinput import modeman
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import utils, objreg, usertypes, log, qtutils
|
||||
from qutebrowser.utils import (utils, objreg, usertypes, log, qtutils,
|
||||
urlutils, message)
|
||||
from qutebrowser.misc import miscwidgets, objects
|
||||
from qutebrowser.browser import mouse, hints
|
||||
|
||||
@@ -94,19 +95,24 @@ class TabData:
|
||||
keep_icon: Whether the (e.g. cloned) icon should not be cleared on page
|
||||
load.
|
||||
inspector: The QWebInspector used for this webview.
|
||||
viewing_source: Set if we're currently showing a source view.
|
||||
open_target: Where to open the next link.
|
||||
Only used for QtWebKit.
|
||||
override_target: Override for open_target for fake clicks (like hints).
|
||||
Only used for QtWebKit.
|
||||
pinned: Flag to pin the tab.
|
||||
fullscreen: Whether the tab has a video shown fullscreen currently.
|
||||
netrc_used: Whether netrc authentication was performed.
|
||||
input_mode: current input mode for the tab.
|
||||
"""
|
||||
|
||||
keep_icon = attr.ib(False)
|
||||
viewing_source = attr.ib(False)
|
||||
inspector = attr.ib(None)
|
||||
open_target = attr.ib(usertypes.ClickTarget.normal)
|
||||
override_target = attr.ib(None)
|
||||
pinned = attr.ib(False)
|
||||
fullscreen = attr.ib(False)
|
||||
netrc_used = attr.ib(False)
|
||||
input_mode = attr.ib(usertypes.KeyMode.normal)
|
||||
|
||||
|
||||
class AbstractAction:
|
||||
@@ -121,8 +127,9 @@ class AbstractAction:
|
||||
action_class = None
|
||||
action_base = None
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, tab):
|
||||
self._widget = None
|
||||
self._tab = tab
|
||||
|
||||
def exit_fullscreen(self):
|
||||
"""Exit the fullscreen mode."""
|
||||
@@ -139,6 +146,10 @@ class AbstractAction:
|
||||
raise WebTabError("{} is not a valid web action!".format(name))
|
||||
self._widget.triggerPageAction(member)
|
||||
|
||||
def show_source(self):
|
||||
"""Show the source of the current page in a new tab."""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class AbstractPrinting:
|
||||
|
||||
@@ -245,10 +256,10 @@ class AbstractZoom(QObject):
|
||||
_default_zoom_changed: Whether the zoom was changed from the default.
|
||||
"""
|
||||
|
||||
def __init__(self, win_id, parent=None):
|
||||
def __init__(self, tab, parent=None):
|
||||
super().__init__(parent)
|
||||
self._tab = tab
|
||||
self._widget = None
|
||||
self._win_id = win_id
|
||||
self._default_zoom_changed = False
|
||||
self._init_neighborlist()
|
||||
config.instance.changed.connect(self._on_config_changed)
|
||||
@@ -324,10 +335,9 @@ class AbstractCaret(QObject):
|
||||
|
||||
"""Attribute of AbstractTab for caret browsing."""
|
||||
|
||||
def __init__(self, win_id, tab, mode_manager, parent=None):
|
||||
def __init__(self, tab, mode_manager, parent=None):
|
||||
super().__init__(parent)
|
||||
self._tab = tab
|
||||
self._win_id = win_id
|
||||
self._widget = None
|
||||
self.selection_enabled = False
|
||||
mode_manager.entered.connect(self._on_mode_entered)
|
||||
@@ -336,7 +346,7 @@ class AbstractCaret(QObject):
|
||||
def _on_mode_entered(self, mode):
|
||||
raise NotImplementedError
|
||||
|
||||
def _on_mode_left(self):
|
||||
def _on_mode_left(self, mode):
|
||||
raise NotImplementedError
|
||||
|
||||
def move_to_next_line(self, count=1):
|
||||
@@ -390,10 +400,7 @@ class AbstractCaret(QObject):
|
||||
def drop_selection(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def has_selection(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def selection(self, html=False):
|
||||
def selection(self, callback):
|
||||
raise NotImplementedError
|
||||
|
||||
def follow_selected(self, *, tab=False):
|
||||
@@ -609,6 +616,7 @@ class AbstractTab(QWidget):
|
||||
process terminated.
|
||||
arg 0: A TerminationStatus member.
|
||||
arg 1: The exit code.
|
||||
predicted_navigation: Emitted before we tell Qt to open a URL.
|
||||
"""
|
||||
|
||||
window_close_requested = pyqtSignal()
|
||||
@@ -626,6 +634,7 @@ class AbstractTab(QWidget):
|
||||
add_history_item = pyqtSignal(QUrl, QUrl, str) # url, requested url, title
|
||||
fullscreen_requested = pyqtSignal(bool)
|
||||
renderer_process_terminated = pyqtSignal(TerminationStatus, int)
|
||||
predicted_navigation = pyqtSignal(QUrl)
|
||||
|
||||
def __init__(self, *, win_id, mode_manager, private, parent=None):
|
||||
self.private = private
|
||||
@@ -639,16 +648,6 @@ class AbstractTab(QWidget):
|
||||
tab_registry[self.tab_id] = self
|
||||
objreg.register('tab', self, registry=self.registry)
|
||||
|
||||
# self.history = AbstractHistory(self)
|
||||
# self.scroller = AbstractScroller(self, parent=self)
|
||||
# self.caret = AbstractCaret(win_id=win_id, tab=self,
|
||||
# mode_manager=mode_manager, parent=self)
|
||||
# self.zoom = AbstractZoom(win_id=win_id)
|
||||
# self.search = AbstractSearch(parent=self)
|
||||
# self.printing = AbstractPrinting()
|
||||
# self.elements = AbstractElements(self)
|
||||
# self.action = AbstractAction()
|
||||
|
||||
self.data = TabData()
|
||||
self._layout = miscwidgets.WrapperLayout(self)
|
||||
self._widget = None
|
||||
@@ -666,6 +665,9 @@ class AbstractTab(QWidget):
|
||||
objreg.register('hintmanager', hintmanager, scope='tab',
|
||||
window=self.win_id, tab=self.tab_id)
|
||||
|
||||
self.predicted_navigation.connect(
|
||||
lambda url: self.title_changed.emit(url.toDisplayString()))
|
||||
|
||||
def _set_widget(self, widget):
|
||||
# pylint: disable=protected-access
|
||||
self._widget = widget
|
||||
@@ -678,6 +680,7 @@ class AbstractTab(QWidget):
|
||||
self.printing._widget = widget
|
||||
self.action._widget = widget
|
||||
self.elements._widget = widget
|
||||
self.settings._settings = widget.settings()
|
||||
|
||||
self._install_event_filter()
|
||||
self.zoom.set_default()
|
||||
@@ -723,10 +726,26 @@ class AbstractTab(QWidget):
|
||||
def _on_load_started(self):
|
||||
self._progress = 0
|
||||
self._has_ssl_errors = False
|
||||
self.data.viewing_source = False
|
||||
self._set_load_status(usertypes.LoadStatus.loading)
|
||||
self.load_started.emit()
|
||||
|
||||
@pyqtSlot(usertypes.NavigationRequest)
|
||||
def _on_navigation_request(self, navigation):
|
||||
"""Handle common acceptNavigationRequest code."""
|
||||
url = utils.elide(navigation.url.toDisplayString(), 100)
|
||||
log.webview.debug("navigation request: url {}, type {}, is_main_frame "
|
||||
"{}".format(url,
|
||||
navigation.navigation_type,
|
||||
navigation.is_main_frame))
|
||||
|
||||
if (navigation.navigation_type == navigation.Type.link_clicked and
|
||||
not navigation.url.isValid()):
|
||||
msg = urlutils.get_errstring(navigation.url,
|
||||
"Invalid link clicked")
|
||||
message.error(msg)
|
||||
self.data.open_target = usertypes.ClickTarget.normal
|
||||
navigation.accepted = False
|
||||
|
||||
def handle_auto_insert_mode(self, ok):
|
||||
"""Handle `input.insert_mode.auto_load` after loading finished."""
|
||||
if not config.val.input.insert_mode.auto_load or not ok:
|
||||
@@ -749,6 +768,10 @@ class AbstractTab(QWidget):
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _on_load_finished(self, ok):
|
||||
if sip.isdeleted(self._widget):
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/3498
|
||||
return
|
||||
|
||||
sess_manager = objreg.get('session-manager')
|
||||
sess_manager.save_autosave()
|
||||
|
||||
@@ -761,7 +784,9 @@ class AbstractTab(QWidget):
|
||||
self._set_load_status(usertypes.LoadStatus.warn)
|
||||
else:
|
||||
self._set_load_status(usertypes.LoadStatus.error)
|
||||
|
||||
self.load_finished.emit(ok)
|
||||
|
||||
if not self.title():
|
||||
self.title_changed.emit(self.url().toDisplayString())
|
||||
|
||||
@@ -792,7 +817,7 @@ class AbstractTab(QWidget):
|
||||
|
||||
def _openurl_prepare(self, url):
|
||||
qtutils.ensure_valid(url)
|
||||
self.title_changed.emit(url.toDisplayString())
|
||||
self.predicted_navigation.emit(url)
|
||||
|
||||
def openurl(self, url):
|
||||
raise NotImplementedError
|
||||
@@ -811,7 +836,7 @@ class AbstractTab(QWidget):
|
||||
raise NotImplementedError
|
||||
|
||||
def dump_async(self, callback, *, plain=False):
|
||||
"""Dump the current page to a file ascync.
|
||||
"""Dump the current page's html asynchronously.
|
||||
|
||||
The given callback will be called with the result when dumping is
|
||||
complete.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -26,20 +26,16 @@ import functools
|
||||
import typing
|
||||
|
||||
from PyQt5.QtWidgets import QApplication, QTabBar, QDialog
|
||||
from PyQt5.QtCore import Qt, QUrl, QEvent, QUrlQuery
|
||||
from PyQt5.QtGui import QKeyEvent
|
||||
from PyQt5.QtCore import pyqtSlot, Qt, QUrl, QEvent, QUrlQuery
|
||||
from PyQt5.QtPrintSupport import QPrintDialog, QPrintPreviewDialog
|
||||
import pygments
|
||||
import pygments.lexers
|
||||
import pygments.formatters
|
||||
|
||||
from qutebrowser.commands import userscripts, cmdexc, cmdutils, runners
|
||||
from qutebrowser.config import config, configdata
|
||||
from qutebrowser.browser import (urlmarks, browsertab, inspector, navigate,
|
||||
webelem, downloads)
|
||||
from qutebrowser.keyinput import modeman
|
||||
from qutebrowser.keyinput import modeman, keyutils
|
||||
from qutebrowser.utils import (message, usertypes, log, qtutils, urlutils,
|
||||
objreg, utils, debug, standarddir)
|
||||
objreg, utils, standarddir)
|
||||
from qutebrowser.utils.usertypes import KeyMode
|
||||
from qutebrowser.misc import editor, guiprocess
|
||||
from qutebrowser.completion.models import urlmodel, miscmodels
|
||||
@@ -536,14 +532,19 @@ class CommandDispatcher:
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('win_id', completion=miscmodels.window)
|
||||
def tab_give(self, win_id: int = None):
|
||||
@cmdutils.argument('count', count=True)
|
||||
def tab_give(self, win_id: int = None, count=None):
|
||||
"""Give the current tab to a new or existing window if win_id given.
|
||||
|
||||
If no win_id is given, the tab will get detached into a new window.
|
||||
|
||||
Args:
|
||||
win_id: The window ID of the window to give the current tab to.
|
||||
count: Overrides win_id (index starts at 1 for win_id=0).
|
||||
"""
|
||||
if count is not None:
|
||||
win_id = count - 1
|
||||
|
||||
if win_id == self._win_id:
|
||||
raise cmdexc.CommandError("Can't give a tab to the same window")
|
||||
|
||||
@@ -638,7 +639,13 @@ class CommandDispatcher:
|
||||
- `next`: Open a _next_ link.
|
||||
- `up`: Go up a level in the current URL.
|
||||
- `increment`: Increment the last number in the URL.
|
||||
Uses the
|
||||
link:settings.html#url.incdec_segments[url.incdec_segments]
|
||||
config option.
|
||||
- `decrement`: Decrement the last number in the URL.
|
||||
Uses the
|
||||
link:settings.html#url.incdec_segments[url.incdec_segments]
|
||||
config option.
|
||||
|
||||
tab: Open in a new tab.
|
||||
bg: Open in a background tab.
|
||||
@@ -849,14 +856,21 @@ class CommandDispatcher:
|
||||
s = self._yank_url(what)
|
||||
what = 'URL' # For printing
|
||||
elif what == 'selection':
|
||||
def _selection_callback(s):
|
||||
if not s:
|
||||
message.info("Nothing to yank")
|
||||
return
|
||||
self._yank_to_target(s, sel, what, keep)
|
||||
|
||||
caret = self._current_widget().caret
|
||||
s = caret.selection()
|
||||
if not caret.has_selection() or not s:
|
||||
message.info("Nothing to yank")
|
||||
return
|
||||
caret.selection(callback=_selection_callback)
|
||||
return
|
||||
else: # pragma: no cover
|
||||
raise ValueError("Invalid value {!r} for `what'.".format(what))
|
||||
|
||||
self._yank_to_target(s, sel, what, keep)
|
||||
|
||||
def _yank_to_target(self, s, sel, what, keep):
|
||||
if sel and utils.supports_selection():
|
||||
target = "primary selection"
|
||||
else:
|
||||
@@ -953,22 +967,25 @@ class CommandDispatcher:
|
||||
(prev and i < cur_idx) or
|
||||
(next_ and i > cur_idx))
|
||||
|
||||
# Check to see if we are closing any pinned tabs
|
||||
if not force:
|
||||
for i, tab in enumerate(self._tabbed_browser.widgets()):
|
||||
if _to_close(i) and tab.data.pinned:
|
||||
self._tabbed_browser.tab_close_prompt_if_pinned(
|
||||
tab,
|
||||
force,
|
||||
lambda: self.tab_only(
|
||||
prev=prev, next_=next_, force=True))
|
||||
return
|
||||
|
||||
# close as many tabs as we can
|
||||
first_tab = True
|
||||
pinned_tabs_cleanup = False
|
||||
for i, tab in enumerate(self._tabbed_browser.widgets()):
|
||||
if _to_close(i):
|
||||
self._tabbed_browser.close_tab(tab, new_undo=first_tab)
|
||||
first_tab = False
|
||||
if force or not tab.data.pinned:
|
||||
self._tabbed_browser.close_tab(tab, new_undo=first_tab)
|
||||
first_tab = False
|
||||
else:
|
||||
pinned_tabs_cleanup = tab
|
||||
|
||||
# Check to see if we would like to close any pinned tabs
|
||||
if pinned_tabs_cleanup:
|
||||
self._tabbed_browser.tab_close_prompt_if_pinned(
|
||||
pinned_tabs_cleanup,
|
||||
force,
|
||||
lambda: self.tab_only(
|
||||
prev=prev, next_=next_, force=True),
|
||||
text="Are you sure you want to close pinned tabs?")
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def undo(self):
|
||||
@@ -1074,14 +1091,16 @@ class CommandDispatcher:
|
||||
Focuses window if necessary when index is given. If both index and
|
||||
count are given, use count.
|
||||
|
||||
With neither index nor count given, open the qute://tabs page.
|
||||
|
||||
Args:
|
||||
index: The [win_id/]index of the tab to focus. Or a substring
|
||||
in which case the closest match will be focused.
|
||||
count: The tab index to focus, starting with 1.
|
||||
"""
|
||||
if count is None and index is None:
|
||||
raise cmdexc.CommandError("buffer: Either a count or the argument "
|
||||
"index must be specified.")
|
||||
self.openurl('qute://tabs/', tab=True)
|
||||
return
|
||||
|
||||
if count is not None:
|
||||
index = str(count)
|
||||
@@ -1096,7 +1115,8 @@ class CommandDispatcher:
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('index', choices=['last'])
|
||||
@cmdutils.argument('count', count=True)
|
||||
def tab_focus(self, index: typing.Union[str, int] = None, count=None):
|
||||
def tab_focus(self, index: typing.Union[str, int] = None,
|
||||
count=None, no_last=False):
|
||||
"""Select the tab given as argument/[count].
|
||||
|
||||
If neither count nor index are given, it behaves like tab-next.
|
||||
@@ -1108,13 +1128,14 @@ class CommandDispatcher:
|
||||
Negative indices count from the end, such that -1 is the
|
||||
last tab.
|
||||
count: The tab index to focus, starting with 1.
|
||||
no_last: Whether to avoid focusing last tab if already focused.
|
||||
"""
|
||||
index = count if count is not None else index
|
||||
|
||||
if index == 'last':
|
||||
self._tab_focus_last()
|
||||
return
|
||||
elif index == self._current_index() + 1:
|
||||
elif not no_last and index == self._current_index() + 1:
|
||||
self._tab_focus_last(show_error=False)
|
||||
return
|
||||
elif index is None:
|
||||
@@ -1204,9 +1225,29 @@ class CommandDispatcher:
|
||||
|
||||
log.procs.debug("Executing {} with args {}, userscript={}".format(
|
||||
cmd, args, userscript))
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_proc_finished():
|
||||
if output:
|
||||
tb = objreg.get('tabbed-browser', scope='window',
|
||||
window='last-focused')
|
||||
tb.openurl(QUrl('qute://spawn-output'), newtab=True)
|
||||
|
||||
if userscript:
|
||||
def _selection_callback(s):
|
||||
try:
|
||||
runner = self._run_userscript(s, cmd, args, verbose)
|
||||
runner.finished.connect(_on_proc_finished)
|
||||
except cmdexc.CommandError as e:
|
||||
message.error(str(e))
|
||||
|
||||
# ~ expansion is handled by the userscript module.
|
||||
self._run_userscript(cmd, *args, verbose=verbose)
|
||||
# dirty hack for async call because of:
|
||||
# https://bugreports.qt.io/browse/QTBUG-53134
|
||||
# until it fixed or blocked async call implemented:
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/3327
|
||||
caret = self._current_widget().caret
|
||||
caret.selection(callback=_selection_callback)
|
||||
else:
|
||||
cmd = os.path.expanduser(cmd)
|
||||
proc = guiprocess.GUIProcess(what='command', verbose=verbose,
|
||||
@@ -1215,18 +1256,14 @@ class CommandDispatcher:
|
||||
proc.start_detached(cmd, args)
|
||||
else:
|
||||
proc.start(cmd, args)
|
||||
|
||||
if output:
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window='last-focused')
|
||||
tabbed_browser.openurl(QUrl('qute://spawn-output'), newtab=True)
|
||||
proc.finished.connect(_on_proc_finished)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def home(self):
|
||||
"""Open main startpage in current tab."""
|
||||
self.openurl(config.val.url.start_pages[0])
|
||||
|
||||
def _run_userscript(self, cmd, *args, verbose=False):
|
||||
def _run_userscript(self, selection, cmd, args, verbose):
|
||||
"""Run a userscript given as argument.
|
||||
|
||||
Args:
|
||||
@@ -1236,21 +1273,15 @@ class CommandDispatcher:
|
||||
"""
|
||||
env = {
|
||||
'QUTE_MODE': 'command',
|
||||
'QUTE_SELECTED_TEXT': selection,
|
||||
}
|
||||
|
||||
idx = self._current_index()
|
||||
if idx != -1:
|
||||
env['QUTE_TITLE'] = self._tabbed_browser.page_title(idx)
|
||||
|
||||
tab = self._tabbed_browser.currentWidget()
|
||||
if tab is not None and tab.caret.has_selection():
|
||||
env['QUTE_SELECTED_TEXT'] = tab.caret.selection()
|
||||
try:
|
||||
env['QUTE_SELECTED_HTML'] = tab.caret.selection(html=True)
|
||||
except browsertab.UnsupportedOperationError:
|
||||
pass
|
||||
|
||||
# FIXME:qtwebengine: If tab is None, run_async will fail!
|
||||
tab = self._tabbed_browser.currentWidget()
|
||||
|
||||
try:
|
||||
url = self._tabbed_browser.current_url()
|
||||
@@ -1260,10 +1291,11 @@ class CommandDispatcher:
|
||||
env['QUTE_URL'] = url.toString(QUrl.FullyEncoded)
|
||||
|
||||
try:
|
||||
userscripts.run_async(tab, cmd, *args, win_id=self._win_id,
|
||||
env=env, verbose=verbose)
|
||||
runner = userscripts.run_async(
|
||||
tab, cmd, *args, win_id=self._win_id, env=env, verbose=verbose)
|
||||
except userscripts.Error as e:
|
||||
raise cmdexc.CommandError(e)
|
||||
return runner
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def quickmark_save(self):
|
||||
@@ -1325,7 +1357,8 @@ class CommandDispatcher:
|
||||
link:qute://bookmarks[bookmarks page].
|
||||
|
||||
Args:
|
||||
url: url to save as a bookmark. If None, use url of current page.
|
||||
url: url to save as a bookmark. If not given, use url of current
|
||||
page.
|
||||
title: title of the new bookmark.
|
||||
toggle: remove the bookmark instead of raising an error if it
|
||||
already exists.
|
||||
@@ -1334,7 +1367,7 @@ class CommandDispatcher:
|
||||
raise cmdexc.CommandError('Title must be provided if url has '
|
||||
'been provided')
|
||||
bookmark_manager = objreg.get('bookmark-manager')
|
||||
if url is None:
|
||||
if not url:
|
||||
url = self._current_url()
|
||||
else:
|
||||
try:
|
||||
@@ -1434,8 +1467,7 @@ class CommandDispatcher:
|
||||
mhtml_: Download the current page and all assets as mhtml file.
|
||||
"""
|
||||
# FIXME:qtwebengine do this with the QtWebEngine download manager?
|
||||
download_manager = objreg.get('qtnetwork-download-manager',
|
||||
scope='window', window=self._win_id)
|
||||
download_manager = objreg.get('qtnetwork-download-manager')
|
||||
target = None
|
||||
if dest is not None:
|
||||
dest = downloads.transform_path(dest)
|
||||
@@ -1480,34 +1512,26 @@ class CommandDispatcher:
|
||||
)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def view_source(self):
|
||||
"""Show the source of the current page in a new tab."""
|
||||
tab = self._current_widget()
|
||||
if tab.data.viewing_source:
|
||||
raise cmdexc.CommandError("Already viewing source!")
|
||||
def view_source(self, edit=False):
|
||||
"""Show the source of the current page in a new tab.
|
||||
|
||||
Args:
|
||||
edit: Edit the source in the editor instead of opening a tab.
|
||||
"""
|
||||
tab = self._current_widget()
|
||||
try:
|
||||
current_url = self._current_url()
|
||||
except cmdexc.CommandError as e:
|
||||
message.error(str(e))
|
||||
return
|
||||
if current_url.scheme() == 'view-source':
|
||||
raise cmdexc.CommandError("Already viewing source!")
|
||||
|
||||
def show_source_cb(source):
|
||||
"""Show source as soon as it's ready."""
|
||||
# WORKAROUND for https://github.com/PyCQA/pylint/issues/491
|
||||
# pylint: disable=no-member
|
||||
lexer = pygments.lexers.HtmlLexer()
|
||||
formatter = pygments.formatters.HtmlFormatter(
|
||||
full=True, linenos='table',
|
||||
title='Source for {}'.format(current_url.toDisplayString()))
|
||||
# pylint: enable=no-member
|
||||
highlighted = pygments.highlight(source, lexer, formatter)
|
||||
|
||||
new_tab = self._tabbed_browser.tabopen()
|
||||
new_tab.set_html(highlighted)
|
||||
new_tab.data.viewing_source = True
|
||||
|
||||
tab.dump_async(show_source_cb)
|
||||
if edit:
|
||||
ed = editor.ExternalEditor(self._tabbed_browser)
|
||||
tab.dump_async(ed.edit)
|
||||
else:
|
||||
tab.action.show_source()
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
debug=True)
|
||||
@@ -1613,9 +1637,11 @@ class CommandDispatcher:
|
||||
|
||||
caret_position = elem.caret_position()
|
||||
|
||||
ed = editor.ExternalEditor(self._tabbed_browser)
|
||||
ed.editing_finished.connect(functools.partial(
|
||||
self.on_editing_finished, elem))
|
||||
ed = editor.ExternalEditor(watch=True, parent=self._tabbed_browser)
|
||||
ed.file_updated.connect(functools.partial(
|
||||
self.on_file_updated, elem))
|
||||
ed.editing_finished.connect(lambda: mainwindow.raise_window(
|
||||
objreg.last_focused_window(), alert=False))
|
||||
ed.edit(text, caret_position)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@@ -1628,10 +1654,10 @@ class CommandDispatcher:
|
||||
tab = self._current_widget()
|
||||
tab.elements.find_focused(self._open_editor_cb)
|
||||
|
||||
def on_editing_finished(self, elem, text):
|
||||
def on_file_updated(self, elem, text):
|
||||
"""Write the editor text into the form field and clean up tempfile.
|
||||
|
||||
Callback for GUIProcess when the editor was closed.
|
||||
Callback for GUIProcess when the edited text was updated.
|
||||
|
||||
Args:
|
||||
elem: The WebElementWrapper which was modified.
|
||||
@@ -1644,8 +1670,6 @@ class CommandDispatcher:
|
||||
except webelem.Error as e:
|
||||
raise cmdexc.CommandError(str(e))
|
||||
|
||||
mainwindow.raise_window(objreg.last_focused_window(), alert=False)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', maxsplit=0,
|
||||
scope='window')
|
||||
def insert_text(self, text):
|
||||
@@ -1753,10 +1777,10 @@ class CommandDispatcher:
|
||||
"""
|
||||
self.set_mark("'")
|
||||
tab = self._current_widget()
|
||||
if tab.search.search_displayed:
|
||||
tab.search.clear()
|
||||
|
||||
if not text:
|
||||
if tab.search.search_displayed:
|
||||
tab.search.clear()
|
||||
return
|
||||
|
||||
options = {
|
||||
@@ -2087,15 +2111,13 @@ class CommandDispatcher:
|
||||
global_: If given, the keys are sent to the qutebrowser UI.
|
||||
"""
|
||||
try:
|
||||
keyinfos = utils.parse_keystring(keystring)
|
||||
except utils.KeyParseError as e:
|
||||
sequence = keyutils.KeySequence.parse(keystring)
|
||||
except keyutils.KeyParseError as e:
|
||||
raise cmdexc.CommandError(str(e))
|
||||
|
||||
for keyinfo in keyinfos:
|
||||
press_event = QKeyEvent(QEvent.KeyPress, keyinfo.key,
|
||||
keyinfo.modifiers, keyinfo.text)
|
||||
release_event = QKeyEvent(QEvent.KeyRelease, keyinfo.key,
|
||||
keyinfo.modifiers, keyinfo.text)
|
||||
for keyinfo in sequence:
|
||||
press_event = keyinfo.to_event(QEvent.KeyPress)
|
||||
release_event = keyinfo.to_event(QEvent.KeyRelease)
|
||||
|
||||
if global_:
|
||||
window = QApplication.focusWindow()
|
||||
@@ -2138,7 +2160,7 @@ class CommandDispatcher:
|
||||
ed = editor.ExternalEditor(self._tabbed_browser)
|
||||
|
||||
# Passthrough for openurl args (e.g. -t, -b, -w)
|
||||
ed.editing_finished.connect(functools.partial(
|
||||
ed.file_updated.connect(functools.partial(
|
||||
self._open_if_changed, old_url=old_url, bg=bg, tab=tab,
|
||||
window=window, private=private, related=related))
|
||||
|
||||
@@ -2196,11 +2218,4 @@ class CommandDispatcher:
|
||||
return
|
||||
|
||||
window = self._tabbed_browser.window()
|
||||
if window.isFullScreen():
|
||||
window.setWindowState(
|
||||
window.state_before_fullscreen & ~Qt.WindowFullScreen)
|
||||
else:
|
||||
window.state_before_fullscreen = window.windowState()
|
||||
window.showFullScreen()
|
||||
log.misc.debug('state before fullscreen: {}'.format(
|
||||
debug.qflags_key(Qt, window.state_before_fullscreen)))
|
||||
window.setWindowState(window.windowState() ^ Qt.WindowFullScreen)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -31,7 +31,7 @@ import enum
|
||||
|
||||
import sip
|
||||
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex,
|
||||
QTimer, QAbstractListModel)
|
||||
QTimer, QAbstractListModel, QUrl)
|
||||
|
||||
from qutebrowser.commands import cmdexc, cmdutils
|
||||
from qutebrowser.config import config
|
||||
@@ -166,6 +166,7 @@ def get_filename_question(*, suggested_filename, url, parent=None):
|
||||
q.title = "Save file to:"
|
||||
q.text = "Please enter a location for <b>{}</b>".format(
|
||||
html.escape(url.toDisplayString()))
|
||||
q.url = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
q.mode = usertypes.PromptMode.download
|
||||
q.completed.connect(q.deleteLater)
|
||||
q.default = _path_suggestion(suggested_filename)
|
||||
@@ -237,11 +238,14 @@ class FileDownloadTarget(_DownloadTarget):
|
||||
|
||||
Attributes:
|
||||
filename: Filename where the download should be saved.
|
||||
force_overwrite: Whether to overwrite the target without
|
||||
prompting the user.
|
||||
"""
|
||||
|
||||
def __init__(self, filename):
|
||||
def __init__(self, filename, force_overwrite=False):
|
||||
# pylint: disable=super-init-not-called
|
||||
self.filename = filename
|
||||
self.force_overwrite = force_overwrite
|
||||
|
||||
def suggested_filename(self):
|
||||
return os.path.basename(self.filename)
|
||||
@@ -737,7 +741,8 @@ class AbstractDownloadItem(QObject):
|
||||
if isinstance(target, FileObjDownloadTarget):
|
||||
self._set_fileobj(target.fileobj, autoclose=False)
|
||||
elif isinstance(target, FileDownloadTarget):
|
||||
self._set_filename(target.filename)
|
||||
self._set_filename(
|
||||
target.filename, force_overwrite=target.force_overwrite)
|
||||
elif isinstance(target, OpenFileDownloadTarget):
|
||||
try:
|
||||
fobj = temp_download_manager.get_tmpfile(self.basename)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2017-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -25,12 +25,14 @@ import json
|
||||
import fnmatch
|
||||
import functools
|
||||
import glob
|
||||
import textwrap
|
||||
|
||||
import attr
|
||||
from PyQt5.QtCore import pyqtSignal, QObject, QUrl
|
||||
|
||||
from qutebrowser.utils import log, standarddir, jinja, objreg
|
||||
from qutebrowser.utils import log, standarddir, jinja, objreg, utils
|
||||
from qutebrowser.commands import cmdutils
|
||||
from qutebrowser.browser import downloads
|
||||
|
||||
|
||||
def _scripts_dir():
|
||||
@@ -46,6 +48,7 @@ class GreasemonkeyScript:
|
||||
self._code = code
|
||||
self.includes = []
|
||||
self.excludes = []
|
||||
self.requires = []
|
||||
self.description = None
|
||||
self.name = None
|
||||
self.namespace = None
|
||||
@@ -67,6 +70,8 @@ class GreasemonkeyScript:
|
||||
self.run_at = value
|
||||
elif name == 'noframes':
|
||||
self.runs_on_sub_frames = False
|
||||
elif name == 'require':
|
||||
self.requires.append(value)
|
||||
|
||||
HEADER_REGEX = r'// ==UserScript==|\n+// ==/UserScript==\n'
|
||||
PROPS_REGEX = r'// @(?P<prop>[^\s]+)\s*(?P<val>.*)'
|
||||
@@ -94,7 +99,7 @@ class GreasemonkeyScript:
|
||||
"""Return the processed JavaScript code of this script.
|
||||
|
||||
Adorns the source code with GM_* methods for Greasemonkey
|
||||
compatibility and wraps it in an IFFE to hide it within a
|
||||
compatibility and wraps it in an IIFE to hide it within a
|
||||
lexical scope. Note that this means line numbers in your
|
||||
browser's debugger/inspector will not match up to the line
|
||||
numbers in the source script directly.
|
||||
@@ -116,6 +121,14 @@ class GreasemonkeyScript:
|
||||
'run-at': self.run_at,
|
||||
})
|
||||
|
||||
def add_required_script(self, source):
|
||||
"""Add the source of a required script to this script."""
|
||||
# The additional source is indented in case it also contains a
|
||||
# metadata block. Because we pass everything at once to
|
||||
# QWebEngineScript and that would parse the first metadata block
|
||||
# found as the valid one.
|
||||
self._code = "\n".join([textwrap.indent(source, " "), self._code])
|
||||
|
||||
|
||||
@attr.s
|
||||
class MatchingScripts(object):
|
||||
@@ -135,7 +148,7 @@ class GreasemonkeyManager(QObject):
|
||||
Signals:
|
||||
scripts_reloaded: Emitted when scripts are reloaded from disk.
|
||||
Any cached or already-injected scripts should be
|
||||
considered obselete.
|
||||
considered obsolete.
|
||||
"""
|
||||
|
||||
scripts_reloaded = pyqtSignal()
|
||||
@@ -146,15 +159,24 @@ class GreasemonkeyManager(QObject):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._run_start = []
|
||||
self._run_end = []
|
||||
self._run_idle = []
|
||||
self._in_progress_dls = []
|
||||
|
||||
self.load_scripts()
|
||||
|
||||
@cmdutils.register(name='greasemonkey-reload',
|
||||
instance='greasemonkey')
|
||||
def load_scripts(self):
|
||||
def load_scripts(self, force=False):
|
||||
"""Re-read Greasemonkey scripts from disk.
|
||||
|
||||
The scripts are read from a 'greasemonkey' subdirectory in
|
||||
qutebrowser's data directory (see `:version`).
|
||||
|
||||
Args:
|
||||
force: For any scripts that have required dependencies,
|
||||
re-download them.
|
||||
"""
|
||||
self._run_start = []
|
||||
self._run_end = []
|
||||
@@ -170,24 +192,115 @@ class GreasemonkeyManager(QObject):
|
||||
script = GreasemonkeyScript.parse(script_file.read())
|
||||
if not script.name:
|
||||
script.name = script_filename
|
||||
|
||||
if script.run_at == 'document-start':
|
||||
self._run_start.append(script)
|
||||
elif script.run_at == 'document-end':
|
||||
self._run_end.append(script)
|
||||
elif script.run_at == 'document-idle':
|
||||
self._run_idle.append(script)
|
||||
else:
|
||||
log.greasemonkey.warning("Script {} has invalid run-at "
|
||||
"defined, defaulting to "
|
||||
"document-end"
|
||||
.format(script_path))
|
||||
# Default as per
|
||||
# https://wiki.greasespot.net/Metadata_Block#.40run-at
|
||||
self._run_end.append(script)
|
||||
log.greasemonkey.debug("Loaded script: {}".format(script.name))
|
||||
self.add_script(script, force)
|
||||
self.scripts_reloaded.emit()
|
||||
|
||||
def add_script(self, script, force=False):
|
||||
"""Add a GreasemonkeyScript to this manager.
|
||||
|
||||
Args:
|
||||
force: Fetch and overwrite any dependancies which are
|
||||
already locally cached.
|
||||
"""
|
||||
if script.requires:
|
||||
log.greasemonkey.debug(
|
||||
"Deferring script until requirements are "
|
||||
"fulfilled: {}".format(script.name))
|
||||
self._get_required_scripts(script, force)
|
||||
else:
|
||||
self._add_script(script)
|
||||
|
||||
def _add_script(self, script):
|
||||
if script.run_at == 'document-start':
|
||||
self._run_start.append(script)
|
||||
elif script.run_at == 'document-end':
|
||||
self._run_end.append(script)
|
||||
elif script.run_at == 'document-idle':
|
||||
self._run_idle.append(script)
|
||||
else:
|
||||
if script.run_at:
|
||||
log.greasemonkey.warning("Script {} has invalid run-at "
|
||||
"defined, defaulting to "
|
||||
"document-end"
|
||||
.format(script.name))
|
||||
# Default as per
|
||||
# https://wiki.greasespot.net/Metadata_Block#.40run-at
|
||||
self._run_end.append(script)
|
||||
log.greasemonkey.debug("Loaded script: {}".format(script.name))
|
||||
|
||||
def _required_url_to_file_path(self, url):
|
||||
requires_dir = os.path.join(_scripts_dir(), 'requires')
|
||||
if not os.path.exists(requires_dir):
|
||||
os.mkdir(requires_dir)
|
||||
return os.path.join(requires_dir, utils.sanitize_filename(url))
|
||||
|
||||
def _on_required_download_finished(self, script, download):
|
||||
self._in_progress_dls.remove(download)
|
||||
if not self._add_script_with_requires(script):
|
||||
log.greasemonkey.debug(
|
||||
"Finished download {} for script {} "
|
||||
"but some requirements are still pending"
|
||||
.format(download.basename, script.name))
|
||||
|
||||
def _add_script_with_requires(self, script, quiet=False):
|
||||
"""Add a script with pending downloads to this GreasemonkeyManager.
|
||||
|
||||
Specifically a script that has dependancies specified via an
|
||||
`@require` rule.
|
||||
|
||||
Args:
|
||||
script: The GreasemonkeyScript to add.
|
||||
quiet: True to suppress the scripts_reloaded signal after
|
||||
adding `script`.
|
||||
Returns: True if the script was added, False if there are still
|
||||
dependancies being downloaded.
|
||||
"""
|
||||
# See if we are still waiting on any required scripts for this one
|
||||
for dl in self._in_progress_dls:
|
||||
if dl.requested_url in script.requires:
|
||||
return False
|
||||
|
||||
# Need to add the required scripts to the IIFE now
|
||||
for url in reversed(script.requires):
|
||||
target_path = self._required_url_to_file_path(url)
|
||||
log.greasemonkey.debug(
|
||||
"Adding required script for {} to IIFE: {}"
|
||||
.format(script.name, url))
|
||||
with open(target_path, encoding='utf8') as f:
|
||||
script.add_required_script(f.read())
|
||||
|
||||
self._add_script(script)
|
||||
if not quiet:
|
||||
self.scripts_reloaded.emit()
|
||||
return True
|
||||
|
||||
def _get_required_scripts(self, script, force=False):
|
||||
required_dls = [(url, self._required_url_to_file_path(url))
|
||||
for url in script.requires]
|
||||
if not force:
|
||||
required_dls = [(url, path) for (url, path) in required_dls
|
||||
if not os.path.exists(path)]
|
||||
if not required_dls:
|
||||
# All the required files exist already
|
||||
self._add_script_with_requires(script, quiet=True)
|
||||
return
|
||||
|
||||
download_manager = objreg.get('qtnetwork-download-manager')
|
||||
|
||||
for url, target_path in required_dls:
|
||||
target = downloads.FileDownloadTarget(target_path,
|
||||
force_overwrite=True)
|
||||
download = download_manager.get(QUrl(url), target=target,
|
||||
auto_remove=True)
|
||||
download.requested_url = url
|
||||
self._in_progress_dls.append(download)
|
||||
if download.successful:
|
||||
self._on_required_download_finished(script, download)
|
||||
else:
|
||||
download.finished.connect(
|
||||
functools.partial(self._on_required_download_finished,
|
||||
script, download))
|
||||
|
||||
def scripts_for(self, url):
|
||||
"""Fetch scripts that are registered to run for url.
|
||||
|
||||
@@ -196,11 +309,23 @@ class GreasemonkeyManager(QObject):
|
||||
"""
|
||||
if url.scheme() not in self.greaseable_schemes:
|
||||
return MatchingScripts(url, [], [], [])
|
||||
match = functools.partial(fnmatch.fnmatch,
|
||||
url.toString(QUrl.FullyEncoded))
|
||||
|
||||
string_url = url.toString(QUrl.FullyEncoded)
|
||||
|
||||
def _match(pattern):
|
||||
# For include and exclude rules if they start and end with '/' they
|
||||
# should be treated as a (ecma syntax) regular expression.
|
||||
if pattern.startswith('/') and pattern.endswith('/'):
|
||||
matches = re.search(pattern[1:-1], string_url, flags=re.I)
|
||||
return matches is not None
|
||||
|
||||
# Otherwise they are glob expressions.
|
||||
return fnmatch.fnmatch(string_url, pattern)
|
||||
|
||||
tester = (lambda script:
|
||||
any(match(pat) for pat in script.includes) and
|
||||
not any(match(pat) for pat in script.excludes))
|
||||
any(_match(pat) for pat in script.includes) and
|
||||
not any(_match(pat) for pat in script.excludes))
|
||||
|
||||
return MatchingScripts(
|
||||
url,
|
||||
[script for script in self._run_start if tester(script)],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -291,8 +291,7 @@ class HintActions:
|
||||
user_agent = context.tab.user_agent()
|
||||
|
||||
# FIXME:qtwebengine do this with QtWebEngine downloads?
|
||||
download_manager = objreg.get('qtnetwork-download-manager',
|
||||
scope='window', window=self._win_id)
|
||||
download_manager = objreg.get('qtnetwork-download-manager')
|
||||
download_manager.get(url, qnam=qnam, user_agent=user_agent,
|
||||
prompt_download_directory=prompt)
|
||||
|
||||
@@ -910,20 +909,27 @@ class HintManager(QObject):
|
||||
|
||||
@cmdutils.register(instance='hintmanager', scope='tab',
|
||||
modes=[usertypes.KeyMode.hint])
|
||||
def follow_hint(self, keystring=None):
|
||||
def follow_hint(self, select=False, keystring=None):
|
||||
"""Follow a hint.
|
||||
|
||||
Args:
|
||||
select: Only select the given hint, don't necessarily follow it.
|
||||
keystring: The hint to follow, or None.
|
||||
"""
|
||||
if keystring is None:
|
||||
if self._context.to_follow is None:
|
||||
raise cmdexc.CommandError("No hint to follow")
|
||||
elif select:
|
||||
raise cmdexc.CommandError("Can't use --select without hint.")
|
||||
else:
|
||||
keystring = self._context.to_follow
|
||||
elif keystring not in self._context.labels:
|
||||
raise cmdexc.CommandError("No hint {}!".format(keystring))
|
||||
self._fire(keystring)
|
||||
|
||||
if select:
|
||||
self.handle_partial_key(keystring)
|
||||
else:
|
||||
self._fire(keystring)
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def on_mode_left(self, mode):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2015-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2015-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -172,7 +172,7 @@ class WebHistory(sql.SqlTable):
|
||||
@pyqtSlot(QUrl, QUrl, str)
|
||||
def add_from_tab(self, url, requested_url, title):
|
||||
"""Add a new history entry as slot, called from a BrowserTab."""
|
||||
if any(url.scheme() == 'data' or
|
||||
if any(url.scheme() in ('data', 'view-source') or
|
||||
(url.scheme(), url.host()) == ('qute', 'back')
|
||||
for url in (url, requested_url)):
|
||||
return
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2015-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2015-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -151,8 +151,9 @@ class MouseEventFilter(QObject):
|
||||
|
||||
if elem.is_editable():
|
||||
log.mouse.debug("Clicked editable element!")
|
||||
modeman.enter(self._tab.win_id, usertypes.KeyMode.insert,
|
||||
'click', only_if_normal=True)
|
||||
if config.val.input.insert_mode.auto_enter:
|
||||
modeman.enter(self._tab.win_id, usertypes.KeyMode.insert,
|
||||
'click', only_if_normal=True)
|
||||
else:
|
||||
log.mouse.debug("Clicked non-editable element!")
|
||||
if config.val.input.insert_mode.auto_leave:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -34,6 +34,10 @@ def init():
|
||||
QNetworkProxyFactory.setApplicationProxyFactory(proxy_factory)
|
||||
|
||||
|
||||
def shutdown():
|
||||
QNetworkProxyFactory.setApplicationProxyFactory(None)
|
||||
|
||||
|
||||
class ProxyFactory(QNetworkProxyFactory):
|
||||
|
||||
"""Factory for proxies to be used by qutebrowser."""
|
||||
@@ -61,6 +65,9 @@ class ProxyFactory(QNetworkProxyFactory):
|
||||
"""
|
||||
proxy = config.val.content.proxy
|
||||
if proxy is configtypes.SYSTEM_PROXY:
|
||||
# On Linux, use "export http_proxy=socks5://host:port" to manually
|
||||
# set system proxy.
|
||||
# ref. http://doc.qt.io/qt-5/qnetworkproxyfactory.html#systemProxyForQuery
|
||||
proxies = QNetworkProxyFactory.systemProxyForQuery(query)
|
||||
elif isinstance(proxy, pac.PACFetcher):
|
||||
proxies = proxy.resolve(query)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2015 Daniel Schadt
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -20,6 +20,7 @@
|
||||
"""Download manager."""
|
||||
|
||||
import io
|
||||
import os.path
|
||||
import shutil
|
||||
import functools
|
||||
|
||||
@@ -198,21 +199,23 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
||||
|
||||
def _ask_confirm_question(self, title, msg):
|
||||
no_action = functools.partial(self.cancel, remove_data=False)
|
||||
url = 'file://{}'.format(self._filename)
|
||||
message.confirm_async(title=title, text=msg,
|
||||
yes_action=self._after_set_filename,
|
||||
no_action=no_action, cancel_action=no_action,
|
||||
abort_on=[self.cancelled, self.error])
|
||||
abort_on=[self.cancelled, self.error], url=url)
|
||||
|
||||
def _ask_create_parent_question(self, title, msg,
|
||||
force_overwrite, remember_directory):
|
||||
no_action = functools.partial(self.cancel, remove_data=False)
|
||||
url = 'file://{}'.format(os.path.dirname(self._filename))
|
||||
message.confirm_async(title=title, text=msg,
|
||||
yes_action=(lambda:
|
||||
self._after_create_parent_question(
|
||||
force_overwrite,
|
||||
remember_directory)),
|
||||
no_action=no_action, cancel_action=no_action,
|
||||
abort_on=[self.cancelled, self.error])
|
||||
abort_on=[self.cancelled, self.error], url=url)
|
||||
|
||||
def _set_fileobj(self, fileobj, *, autoclose=True):
|
||||
"""Set the file object to write the download to.
|
||||
@@ -378,10 +381,10 @@ class DownloadManager(downloads.AbstractDownloadManager):
|
||||
_networkmanager: A NetworkManager for generic downloads.
|
||||
"""
|
||||
|
||||
def __init__(self, win_id, parent=None):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._networkmanager = networkmanager.NetworkManager(
|
||||
win_id=win_id, tab_id=None,
|
||||
win_id=None, tab_id=None,
|
||||
private=config.val.content.private_browsing, parent=self)
|
||||
|
||||
@pyqtSlot('QUrl')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -30,8 +30,10 @@ import time
|
||||
import textwrap
|
||||
import mimetypes
|
||||
import urllib
|
||||
import collections
|
||||
|
||||
import pkg_resources
|
||||
import sip
|
||||
from PyQt5.QtCore import QUrlQuery, QUrl
|
||||
|
||||
import qutebrowser
|
||||
@@ -201,6 +203,27 @@ def qute_bookmarks(_url):
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
@add_handler('tabs')
|
||||
def qute_tabs(_url):
|
||||
"""Handler for qute://tabs. Display information about all open tabs."""
|
||||
tabs = collections.defaultdict(list)
|
||||
for win_id, window in objreg.window_registry.items():
|
||||
if sip.isdeleted(window):
|
||||
continue
|
||||
tabbed_browser = objreg.get('tabbed-browser',
|
||||
scope='window',
|
||||
window=win_id)
|
||||
for tab in tabbed_browser.widgets():
|
||||
if tab.url() not in [QUrl("qute://tabs/"), QUrl("qute://tabs")]:
|
||||
urlstr = tab.url().toDisplayString()
|
||||
tabs[str(win_id)].append((tab.title(), urlstr))
|
||||
|
||||
html = jinja.render('tabs.html',
|
||||
title='Tabs',
|
||||
tab_list_by_window=tabs)
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
def history_data(start_time, offset=None):
|
||||
"""Return history data.
|
||||
|
||||
@@ -240,8 +263,6 @@ def qute_history(url):
|
||||
|
||||
return 'text/html', json.dumps(history_data(start_time, offset))
|
||||
else:
|
||||
if not config.val.content.javascript.enabled:
|
||||
return 'text/plain', b'JavaScript is required for qute://history'
|
||||
return 'text/html', jinja.render(
|
||||
'history.html',
|
||||
title='History',
|
||||
@@ -435,6 +456,22 @@ def qute_settings(url):
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
@add_handler('bindings')
|
||||
def qute_bindings(_url):
|
||||
"""Handler for qute://bindings. View keybindings."""
|
||||
bindings = {}
|
||||
defaults = config.val.bindings.default
|
||||
modes = set(defaults.keys()).union(config.val.bindings.commands)
|
||||
modes.remove('normal')
|
||||
modes = ['normal'] + sorted(list(modes))
|
||||
for mode in modes:
|
||||
bindings[mode] = config.key_instance.get_bindings_for(mode)
|
||||
|
||||
html = jinja.render('bindings.html', title='Bindings',
|
||||
bindings=bindings)
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
@add_handler('back')
|
||||
def qute_back(url):
|
||||
"""Handler for qute://back.
|
||||
@@ -460,3 +497,10 @@ def qute_configdiff(url):
|
||||
else:
|
||||
data = config.instance.dump_userconfig().encode('utf-8')
|
||||
return 'text/plain', data
|
||||
|
||||
|
||||
@add_handler('pastebin-version')
|
||||
def qute_pastebin_version(_url):
|
||||
"""Handler that pastebins the version string."""
|
||||
version.pastebin_version()
|
||||
return 'text/plain', b'Paste called.'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -19,7 +19,11 @@
|
||||
|
||||
"""Various utilities shared between webpage/webview subclasses."""
|
||||
|
||||
import os
|
||||
import html
|
||||
import netrc
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import usertypes, message, log, objreg, jinja, utils
|
||||
@@ -27,7 +31,6 @@ from qutebrowser.mainwindow import mainwindow
|
||||
|
||||
|
||||
class CallSuper(Exception):
|
||||
|
||||
"""Raised when the caller should call the superclass instead."""
|
||||
|
||||
|
||||
@@ -61,30 +64,33 @@ def authentication_required(url, authenticator, abort_on):
|
||||
else:
|
||||
msg = '<b>{}</b> needs authentication'.format(
|
||||
html.escape(url.toDisplayString()))
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
answer = message.ask(title="Authentication required", text=msg,
|
||||
mode=usertypes.PromptMode.user_pwd,
|
||||
abort_on=abort_on)
|
||||
abort_on=abort_on, url=urlstr)
|
||||
if answer is not None:
|
||||
authenticator.setUser(answer.user)
|
||||
authenticator.setPassword(answer.password)
|
||||
return answer
|
||||
|
||||
|
||||
def javascript_confirm(url, js_msg, abort_on):
|
||||
def javascript_confirm(url, js_msg, abort_on, *, escape_msg=True):
|
||||
"""Display a javascript confirm prompt."""
|
||||
log.js.debug("confirm: {}".format(js_msg))
|
||||
if config.val.content.javascript.modal_dialog:
|
||||
raise CallSuper
|
||||
|
||||
js_msg = html.escape(js_msg) if escape_msg else js_msg
|
||||
msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
|
||||
html.escape(js_msg))
|
||||
js_msg)
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
ans = message.ask('Javascript confirm', msg,
|
||||
mode=usertypes.PromptMode.yesno,
|
||||
abort_on=abort_on)
|
||||
abort_on=abort_on, url=urlstr)
|
||||
return bool(ans)
|
||||
|
||||
|
||||
def javascript_prompt(url, js_msg, default, abort_on):
|
||||
def javascript_prompt(url, js_msg, default, abort_on, *, escape_msg=True):
|
||||
"""Display a javascript prompt."""
|
||||
log.js.debug("prompt: {}".format(js_msg))
|
||||
if config.val.content.javascript.modal_dialog:
|
||||
@@ -92,12 +98,14 @@ def javascript_prompt(url, js_msg, default, abort_on):
|
||||
if not config.val.content.javascript.prompt:
|
||||
return (False, "")
|
||||
|
||||
js_msg = html.escape(js_msg) if escape_msg else js_msg
|
||||
msg = '<b>{}</b> asks:<br/>{}'.format(html.escape(url.toDisplayString()),
|
||||
html.escape(js_msg))
|
||||
js_msg)
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
answer = message.ask('Javascript prompt', msg,
|
||||
mode=usertypes.PromptMode.text,
|
||||
default=default,
|
||||
abort_on=abort_on)
|
||||
abort_on=abort_on, url=urlstr)
|
||||
|
||||
if answer is None:
|
||||
return (False, "")
|
||||
@@ -105,7 +113,7 @@ def javascript_prompt(url, js_msg, default, abort_on):
|
||||
return (True, answer)
|
||||
|
||||
|
||||
def javascript_alert(url, js_msg, abort_on):
|
||||
def javascript_alert(url, js_msg, abort_on, *, escape_msg=True):
|
||||
"""Display a javascript alert."""
|
||||
log.js.debug("alert: {}".format(js_msg))
|
||||
if config.val.content.javascript.modal_dialog:
|
||||
@@ -114,10 +122,12 @@ def javascript_alert(url, js_msg, abort_on):
|
||||
if not config.val.content.javascript.alert:
|
||||
return
|
||||
|
||||
js_msg = html.escape(js_msg) if escape_msg else js_msg
|
||||
msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
|
||||
html.escape(js_msg))
|
||||
js_msg)
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
message.ask('Javascript alert', msg, mode=usertypes.PromptMode.alert,
|
||||
abort_on=abort_on)
|
||||
abort_on=abort_on, url=urlstr)
|
||||
|
||||
|
||||
def javascript_log_message(level, source, line, msg):
|
||||
@@ -164,9 +174,10 @@ def ignore_certificate_errors(url, errors, abort_on):
|
||||
""".strip())
|
||||
msg = err_template.render(url=url, errors=errors)
|
||||
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
ignore = message.ask(title="Certificate errors - continue?", text=msg,
|
||||
mode=usertypes.PromptMode.yesno, default=False,
|
||||
abort_on=abort_on)
|
||||
abort_on=abort_on, url=urlstr)
|
||||
if ignore is None:
|
||||
# prompt aborted
|
||||
ignore = False
|
||||
@@ -202,15 +213,17 @@ def feature_permission(url, option, msg, yes_action, no_action, abort_on):
|
||||
config_val = config.instance.get(option)
|
||||
if config_val == 'ask':
|
||||
if url.isValid():
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
text = "Allow the website at <b>{}</b> to {}?".format(
|
||||
html.escape(url.toDisplayString()), msg)
|
||||
else:
|
||||
urlstr = None
|
||||
text = "Allow the website to {}?".format(msg)
|
||||
|
||||
return message.confirm_async(
|
||||
yes_action=yes_action, no_action=no_action,
|
||||
cancel_action=no_action, abort_on=abort_on,
|
||||
title='Permission request', text=text)
|
||||
title='Permission request', text=text, url=urlstr)
|
||||
elif config_val:
|
||||
yes_action()
|
||||
return None
|
||||
@@ -260,3 +273,41 @@ def get_user_stylesheet():
|
||||
css += '\nhtml > ::-webkit-scrollbar { width: 0px; height: 0px; }'
|
||||
|
||||
return css
|
||||
|
||||
|
||||
def netrc_authentication(url, authenticator):
|
||||
"""Perform authorization using netrc.
|
||||
|
||||
Args:
|
||||
url: The URL the request was done for.
|
||||
authenticator: QAuthenticator object used to set credentials provided.
|
||||
|
||||
Return:
|
||||
True if netrc found credentials for the URL.
|
||||
False otherwise.
|
||||
"""
|
||||
if 'HOME' not in os.environ:
|
||||
# We'll get an OSError by netrc if 'HOME' isn't available in
|
||||
# os.environ. We don't want to log that, so we prevent it
|
||||
# altogether.
|
||||
return False
|
||||
user, password = None, None
|
||||
try:
|
||||
net = netrc.netrc(config.val.content.netrc_file)
|
||||
authenticators = net.authenticators(url.host())
|
||||
if authenticators is not None:
|
||||
(user, _account, password) = authenticators
|
||||
except FileNotFoundError:
|
||||
log.misc.debug("No .netrc file found")
|
||||
except OSError:
|
||||
log.misc.exception("Unable to read the netrc file")
|
||||
except netrc.NetrcParseError:
|
||||
log.misc.exception("Error when parsing the netrc file")
|
||||
|
||||
if user is None:
|
||||
return False
|
||||
|
||||
authenticator.setUser(user)
|
||||
authenticator.setPassword(password)
|
||||
|
||||
return True
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2015-2017 Antoni Boucher <bouanto@zoho.com>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2015-2018 Antoni Boucher <bouanto@zoho.com>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -161,7 +161,7 @@ class QuickmarkManager(UrlMarkManager):
|
||||
"Add quickmark:", usertypes.PromptMode.text,
|
||||
functools.partial(self.quickmark_add, urlstr),
|
||||
text="Please enter a quickmark name for<br/><b>{}</b>".format(
|
||||
html.escape(url.toDisplayString())))
|
||||
html.escape(url.toDisplayString())), url=urlstr)
|
||||
|
||||
@cmdutils.register(instance='quickmark-manager')
|
||||
def quickmark_add(self, url, name):
|
||||
@@ -192,7 +192,7 @@ class QuickmarkManager(UrlMarkManager):
|
||||
if name in self.marks:
|
||||
message.confirm_async(
|
||||
title="Override existing quickmark?",
|
||||
yes_action=set_mark, default=True)
|
||||
yes_action=set_mark, default=True, url=url)
|
||||
else:
|
||||
set_mark()
|
||||
|
||||
@@ -280,7 +280,7 @@ class BookmarkManager(UrlMarkManager):
|
||||
|
||||
if urlstr in self.marks:
|
||||
if toggle:
|
||||
del self.marks[urlstr]
|
||||
self.delete(urlstr)
|
||||
return False
|
||||
else:
|
||||
raise AlreadyExistsError("Bookmark already exists!")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -41,8 +41,10 @@ Group = enum.Enum('Group', ['all', 'links', 'images', 'url', 'inputs'])
|
||||
|
||||
SELECTORS = {
|
||||
Group.all: ('a, area, textarea, select, input:not([type=hidden]), button, '
|
||||
'frame, iframe, link, [onclick], [onmousedown], [role=link], '
|
||||
'[role=option], [role=button], img'),
|
||||
'frame, iframe, link, summary, [onclick], [onmousedown], '
|
||||
'[role=link], [role=option], [role=button], img, '
|
||||
# Angular 1 selectors
|
||||
'[ng-click], [ngClick], [data-ng-click], [x-ng-click]'),
|
||||
Group.links: 'a[href], area[href], link[href], [role=link][href]',
|
||||
Group.images: 'img',
|
||||
Group.url: '[src], [href]',
|
||||
@@ -409,8 +411,9 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
||||
elif self.is_editable(strict=True):
|
||||
log.webelem.debug("Clicking via JS focus()")
|
||||
self._click_editable(click_target)
|
||||
modeman.enter(self._tab.win_id, usertypes.KeyMode.insert,
|
||||
'clicking input')
|
||||
if config.val.input.insert_mode.auto_enter:
|
||||
modeman.enter(self._tab.win_id, usertypes.KeyMode.insert,
|
||||
'clicking input')
|
||||
else:
|
||||
self._click_fake_event(click_target)
|
||||
elif click_target in [usertypes.ClickTarget.tab,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2017 Michal Siedlaczek <michal.siedlaczek@gmail.com>
|
||||
# Copyright 2017-2018 Michal Siedlaczek <michal.siedlaczek@gmail.com>
|
||||
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2015-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2015-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -45,6 +45,10 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
||||
qt_item.downloadProgress.connect(self.stats.on_download_progress)
|
||||
qt_item.stateChanged.connect(self._on_state_changed)
|
||||
|
||||
# Ensure wrapped qt_item is deleted manually when the wrapper object
|
||||
# is deleted. See https://github.com/qutebrowser/qutebrowser/issues/3373
|
||||
self.destroyed.connect(self._qt_item.deleteLater)
|
||||
|
||||
def _is_page_download(self):
|
||||
"""Check if this item is a page (i.e. mhtml) download."""
|
||||
return (self._qt_item.savePageFormat() !=
|
||||
@@ -96,9 +100,15 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
||||
self._qt_item.cancel()
|
||||
|
||||
def retry(self):
|
||||
# https://bugreports.qt.io/browse/QTBUG-56840
|
||||
raise downloads.UnsupportedOperationError(
|
||||
"Retrying downloads is unsupported with QtWebEngine")
|
||||
state = self._qt_item.state()
|
||||
assert state == QWebEngineDownloadItem.DownloadInterrupted, state
|
||||
|
||||
try:
|
||||
self._qt_item.resume()
|
||||
except AttributeError:
|
||||
raise downloads.UnsupportedOperationError(
|
||||
"Retrying downloads is unsupported with QtWebEngine on "
|
||||
"Qt/PyQt < 5.10")
|
||||
|
||||
def _get_open_filename(self):
|
||||
return self._filename
|
||||
@@ -125,6 +135,7 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
||||
question = usertypes.Question()
|
||||
question.title = title
|
||||
question.text = msg
|
||||
question.url = 'file://{}'.format(self._filename)
|
||||
question.mode = usertypes.PromptMode.yesno
|
||||
question.answered_yes.connect(self._after_set_filename)
|
||||
question.answered_no.connect(no_action)
|
||||
@@ -139,6 +150,7 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
||||
question = usertypes.Question()
|
||||
question.title = title
|
||||
question.text = msg
|
||||
question.url = 'file://{}'.format(os.path.dirname(self._filename))
|
||||
question.mode = usertypes.PromptMode.yesno
|
||||
question.answered_yes.connect(lambda:
|
||||
self._after_create_parent_question(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2015-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2015-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -43,8 +43,8 @@ class WebEngineInspector(inspector.AbstractWebInspector):
|
||||
port = int(os.environ['QTWEBENGINE_REMOTE_DEBUGGING'])
|
||||
except KeyError:
|
||||
raise inspector.WebInspectorError(
|
||||
"Debugging is not enabled. See 'qutebrowser --help' for "
|
||||
"details.")
|
||||
"QtWebEngine inspector is not enabled. See "
|
||||
"'qutebrowser --help' for details.")
|
||||
url = QUrl('http://localhost:{}/'.format(port))
|
||||
self._widget.load(url)
|
||||
self.show()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -17,9 +17,6 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# We get various "abstract but not overridden" warnings
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
"""Bridge from QWebEngineSettings to our own settings.
|
||||
|
||||
Module attributes:
|
||||
@@ -44,116 +41,132 @@ from qutebrowser.utils import (utils, standarddir, javascript, qtutils,
|
||||
default_profile = None
|
||||
# The QWebEngineProfile used for private (off-the-record) windows
|
||||
private_profile = None
|
||||
# The global WebEngineSettings object
|
||||
global_settings = None
|
||||
|
||||
|
||||
class Base(websettings.Base):
|
||||
class _SettingsWrapper:
|
||||
|
||||
"""Base settings class with appropriate _get_global_settings."""
|
||||
"""Expose a QWebEngineSettings interface which acts on all profiles.
|
||||
|
||||
def _get_global_settings(self):
|
||||
return [default_profile.settings(), private_profile.settings()]
|
||||
|
||||
|
||||
class Attribute(Base, websettings.Attribute):
|
||||
|
||||
"""A setting set via QWebEngineSettings::setAttribute."""
|
||||
|
||||
ENUM_BASE = QWebEngineSettings
|
||||
|
||||
|
||||
class Setter(Base, websettings.Setter):
|
||||
|
||||
"""A setting set via a QWebEngineSettings setter method."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class FontFamilySetter(Base, websettings.FontFamilySetter):
|
||||
|
||||
"""A setter for a font family.
|
||||
|
||||
Gets the default value from QFont.
|
||||
For read operations, the default profile value is always used.
|
||||
"""
|
||||
|
||||
def __init__(self, font):
|
||||
# Mapping from WebEngineSettings::initDefaults in
|
||||
# qtwebengine/src/core/web_engine_settings.cpp
|
||||
font_to_qfont = {
|
||||
QWebEngineSettings.StandardFont: QFont.Serif,
|
||||
QWebEngineSettings.FixedFont: QFont.Monospace,
|
||||
QWebEngineSettings.SerifFont: QFont.Serif,
|
||||
QWebEngineSettings.SansSerifFont: QFont.SansSerif,
|
||||
QWebEngineSettings.CursiveFont: QFont.Cursive,
|
||||
QWebEngineSettings.FantasyFont: QFont.Fantasy,
|
||||
def __init__(self):
|
||||
self._settings = [default_profile.settings(),
|
||||
private_profile.settings()]
|
||||
|
||||
def setAttribute(self, *args, **kwargs):
|
||||
for settings in self._settings:
|
||||
settings.setAttribute(*args, **kwargs)
|
||||
|
||||
def setFontFamily(self, *args, **kwargs):
|
||||
for settings in self._settings:
|
||||
settings.setFontFamily(*args, **kwargs)
|
||||
|
||||
def setFontSize(self, *args, **kwargs):
|
||||
for settings in self._settings:
|
||||
settings.setFontSize(*args, **kwargs)
|
||||
|
||||
def setDefaultTextEncoding(self, *args, **kwargs):
|
||||
for settings in self._settings:
|
||||
settings.setDefaultTextEncoding(*args, **kwargs)
|
||||
|
||||
def testAttribute(self, *args, **kwargs):
|
||||
return self._settings[0].testAttribute(*args, **kwargs)
|
||||
|
||||
def fontSize(self, *args, **kwargs):
|
||||
return self._settings[0].fontSize(*args, **kwargs)
|
||||
|
||||
def fontFamily(self, *args, **kwargs):
|
||||
return self._settings[0].fontFamily(*args, **kwargs)
|
||||
|
||||
def defaultTextEncoding(self, *args, **kwargs):
|
||||
return self._settings[0].defaultTextEncoding(*args, **kwargs)
|
||||
|
||||
|
||||
class WebEngineSettings(websettings.AbstractSettings):
|
||||
|
||||
"""A wrapper for the config for QWebEngineSettings."""
|
||||
|
||||
_ATTRIBUTES = {
|
||||
'content.xss_auditing':
|
||||
[QWebEngineSettings.XSSAuditingEnabled],
|
||||
'content.images':
|
||||
[QWebEngineSettings.AutoLoadImages],
|
||||
'content.javascript.enabled':
|
||||
[QWebEngineSettings.JavascriptEnabled],
|
||||
'content.javascript.can_open_tabs_automatically':
|
||||
[QWebEngineSettings.JavascriptCanOpenWindows],
|
||||
'content.javascript.can_access_clipboard':
|
||||
[QWebEngineSettings.JavascriptCanAccessClipboard],
|
||||
'content.plugins':
|
||||
[QWebEngineSettings.PluginsEnabled],
|
||||
'content.hyperlink_auditing':
|
||||
[QWebEngineSettings.HyperlinkAuditingEnabled],
|
||||
'content.local_content_can_access_remote_urls':
|
||||
[QWebEngineSettings.LocalContentCanAccessRemoteUrls],
|
||||
'content.local_content_can_access_file_urls':
|
||||
[QWebEngineSettings.LocalContentCanAccessFileUrls],
|
||||
'content.webgl':
|
||||
[QWebEngineSettings.WebGLEnabled],
|
||||
'content.local_storage':
|
||||
[QWebEngineSettings.LocalStorageEnabled],
|
||||
|
||||
'input.spatial_navigation':
|
||||
[QWebEngineSettings.SpatialNavigationEnabled],
|
||||
'input.links_included_in_focus_chain':
|
||||
[QWebEngineSettings.LinksIncludedInFocusChain],
|
||||
|
||||
'scrolling.smooth':
|
||||
[QWebEngineSettings.ScrollAnimatorEnabled],
|
||||
}
|
||||
|
||||
_FONT_SIZES = {
|
||||
'fonts.web.size.minimum':
|
||||
QWebEngineSettings.MinimumFontSize,
|
||||
'fonts.web.size.minimum_logical':
|
||||
QWebEngineSettings.MinimumLogicalFontSize,
|
||||
'fonts.web.size.default':
|
||||
QWebEngineSettings.DefaultFontSize,
|
||||
'fonts.web.size.default_fixed':
|
||||
QWebEngineSettings.DefaultFixedFontSize,
|
||||
}
|
||||
|
||||
_FONT_FAMILIES = {
|
||||
'fonts.web.family.standard': QWebEngineSettings.StandardFont,
|
||||
'fonts.web.family.fixed': QWebEngineSettings.FixedFont,
|
||||
'fonts.web.family.serif': QWebEngineSettings.SerifFont,
|
||||
'fonts.web.family.sans_serif': QWebEngineSettings.SansSerifFont,
|
||||
'fonts.web.family.cursive': QWebEngineSettings.CursiveFont,
|
||||
'fonts.web.family.fantasy': QWebEngineSettings.FantasyFont,
|
||||
}
|
||||
|
||||
# Mapping from WebEngineSettings::initDefaults in
|
||||
# qtwebengine/src/core/web_engine_settings.cpp
|
||||
_FONT_TO_QFONT = {
|
||||
QWebEngineSettings.StandardFont: QFont.Serif,
|
||||
QWebEngineSettings.FixedFont: QFont.Monospace,
|
||||
QWebEngineSettings.SerifFont: QFont.Serif,
|
||||
QWebEngineSettings.SansSerifFont: QFont.SansSerif,
|
||||
QWebEngineSettings.CursiveFont: QFont.Cursive,
|
||||
QWebEngineSettings.FantasyFont: QFont.Fantasy,
|
||||
}
|
||||
|
||||
def __init__(self, settings):
|
||||
super().__init__(settings)
|
||||
# Attributes which don't exist in all Qt versions.
|
||||
new_attributes = {
|
||||
# Qt 5.8
|
||||
'content.print_element_backgrounds': 'PrintElementBackgrounds',
|
||||
}
|
||||
super().__init__(setter=QWebEngineSettings.setFontFamily, font=font,
|
||||
qfont=font_to_qfont[font])
|
||||
for name, attribute in new_attributes.items():
|
||||
try:
|
||||
value = getattr(QWebEngineSettings, attribute)
|
||||
except AttributeError:
|
||||
continue
|
||||
|
||||
|
||||
class DefaultProfileSetter(websettings.Base):
|
||||
|
||||
"""A setting set on the QWebEngineProfile."""
|
||||
|
||||
def __init__(self, setter, converter=None, default=websettings.UNSET):
|
||||
super().__init__(default)
|
||||
self._setter = setter
|
||||
self._converter = converter
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, setter=self._setter, constructor=True)
|
||||
|
||||
def _set(self, value, settings=None):
|
||||
if settings is not None:
|
||||
raise ValueError("'settings' may not be set with "
|
||||
"DefaultProfileSetters!")
|
||||
|
||||
setter = getattr(default_profile, self._setter)
|
||||
if self._converter is not None:
|
||||
value = self._converter(value)
|
||||
|
||||
setter(value)
|
||||
|
||||
|
||||
class PersistentCookiePolicy(DefaultProfileSetter):
|
||||
|
||||
"""The content.cookies.store setting is different from other settings."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__('setPersistentCookiesPolicy')
|
||||
|
||||
def _set(self, value, settings=None):
|
||||
if settings is not None:
|
||||
raise ValueError("'settings' may not be set with "
|
||||
"PersistentCookiePolicy!")
|
||||
setter = getattr(QWebEngineProfile.defaultProfile(), self._setter)
|
||||
setter(
|
||||
QWebEngineProfile.AllowPersistentCookies if value else
|
||||
QWebEngineProfile.NoPersistentCookies
|
||||
)
|
||||
|
||||
|
||||
class DictionaryLanguageSetter(DefaultProfileSetter):
|
||||
|
||||
"""Sets paths to dictionary files based on language codes."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__('setSpellCheckLanguages', default=[])
|
||||
|
||||
def _find_installed(self, code):
|
||||
local_filename = spell.local_filename(code)
|
||||
if not local_filename:
|
||||
message.warning(
|
||||
"Language {} is not installed - see scripts/dictcli.py "
|
||||
"in qutebrowser's sources".format(code))
|
||||
return local_filename
|
||||
|
||||
def _set(self, value, settings=None):
|
||||
if settings is not None:
|
||||
raise ValueError("'settings' may not be set with "
|
||||
"DictionaryLanguageSetter!")
|
||||
filenames = [self._find_installed(code) for code in value]
|
||||
log.config.debug("Found dicts: {}".format(filenames))
|
||||
super()._set([f for f in filenames if f], settings)
|
||||
self._ATTRIBUTES[name] = [value]
|
||||
|
||||
|
||||
def _init_stylesheet(profile):
|
||||
@@ -210,9 +223,48 @@ def _set_http_headers(profile):
|
||||
profile.setHttpAcceptLanguage(accept_language)
|
||||
|
||||
|
||||
def _set_http_cache_size(profile):
|
||||
"""Initialize the HTTP cache size for the given profile."""
|
||||
size = config.val.content.cache.size
|
||||
if size is None:
|
||||
size = 0
|
||||
else:
|
||||
size = qtutils.check_overflow(size, 'int', fatal=False)
|
||||
|
||||
# 0: automatically managed by QtWebEngine
|
||||
profile.setHttpCacheMaximumSize(size)
|
||||
|
||||
|
||||
def _set_persistent_cookie_policy(profile):
|
||||
"""Set the HTTP Cookie size for the given profile."""
|
||||
if config.val.content.cookies.store:
|
||||
value = QWebEngineProfile.AllowPersistentCookies
|
||||
else:
|
||||
value = QWebEngineProfile.NoPersistentCookies
|
||||
profile.setPersistentCookiesPolicy(value)
|
||||
|
||||
|
||||
def _set_dictionary_language(profile, warn=True):
|
||||
filenames = []
|
||||
for code in config.val.spellcheck.languages or []:
|
||||
local_filename = spell.local_filename(code)
|
||||
if not local_filename:
|
||||
if warn:
|
||||
message.warning(
|
||||
"Language {} is not installed - see scripts/dictcli.py "
|
||||
"in qutebrowser's sources".format(code))
|
||||
continue
|
||||
|
||||
filenames.append(local_filename)
|
||||
|
||||
log.config.debug("Found dicts: {}".format(filenames))
|
||||
profile.setSpellCheckLanguages(filenames)
|
||||
|
||||
|
||||
def _update_settings(option):
|
||||
"""Update global settings when qwebsettings changed."""
|
||||
websettings.update_mappings(MAPPINGS, option)
|
||||
global_settings.update_setting(option)
|
||||
|
||||
if option in ['scrolling.bar', 'content.user_stylesheets']:
|
||||
_init_stylesheet(default_profile)
|
||||
_init_stylesheet(private_profile)
|
||||
@@ -221,27 +273,46 @@ def _update_settings(option):
|
||||
'content.headers.accept_language']:
|
||||
_set_http_headers(default_profile)
|
||||
_set_http_headers(private_profile)
|
||||
elif option == 'content.cache.size':
|
||||
_set_http_cache_size(default_profile)
|
||||
_set_http_cache_size(private_profile)
|
||||
elif (option == 'content.cookies.store' and
|
||||
# https://bugreports.qt.io/browse/QTBUG-58650
|
||||
qtutils.version_check('5.9', compiled=False)):
|
||||
_set_persistent_cookie_policy(default_profile)
|
||||
# We're not touching the private profile's cookie policy.
|
||||
elif option == 'spellcheck.languages':
|
||||
_set_dictionary_language(default_profile)
|
||||
_set_dictionary_language(private_profile, warn=False)
|
||||
|
||||
|
||||
def _init_profile(profile):
|
||||
"""Init the given profile."""
|
||||
_init_stylesheet(profile)
|
||||
_set_http_headers(profile)
|
||||
_set_http_cache_size(profile)
|
||||
profile.settings().setAttribute(
|
||||
QWebEngineSettings.FullScreenSupportEnabled, True)
|
||||
if qtutils.version_check('5.8'):
|
||||
profile.setSpellCheckEnabled(True)
|
||||
_set_dictionary_language(profile)
|
||||
|
||||
|
||||
def _init_profiles():
|
||||
"""Init the two used QWebEngineProfiles."""
|
||||
global default_profile, private_profile
|
||||
|
||||
default_profile = QWebEngineProfile.defaultProfile()
|
||||
default_profile.setCachePath(
|
||||
os.path.join(standarddir.cache(), 'webengine'))
|
||||
default_profile.setPersistentStoragePath(
|
||||
os.path.join(standarddir.data(), 'webengine'))
|
||||
_init_stylesheet(default_profile)
|
||||
_set_http_headers(default_profile)
|
||||
_init_profile(default_profile)
|
||||
_set_persistent_cookie_policy(default_profile)
|
||||
|
||||
private_profile = QWebEngineProfile()
|
||||
assert private_profile.isOffTheRecord()
|
||||
_init_stylesheet(private_profile)
|
||||
_set_http_headers(private_profile)
|
||||
|
||||
if qtutils.version_check('5.8'):
|
||||
default_profile.setSpellCheckEnabled(True)
|
||||
private_profile.setSpellCheckEnabled(True)
|
||||
_init_profile(private_profile)
|
||||
|
||||
|
||||
def inject_userscripts():
|
||||
@@ -287,111 +358,12 @@ def init(args):
|
||||
os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = str(utils.random_port())
|
||||
|
||||
_init_profiles()
|
||||
|
||||
# We need to do this here as a WORKAROUND for
|
||||
# https://bugreports.qt.io/browse/QTBUG-58650
|
||||
if not qtutils.version_check('5.9', compiled=False):
|
||||
PersistentCookiePolicy().set(config.val.content.cookies.store)
|
||||
Attribute(QWebEngineSettings.FullScreenSupportEnabled).set(True)
|
||||
|
||||
websettings.init_mappings(MAPPINGS)
|
||||
config.instance.changed.connect(_update_settings)
|
||||
|
||||
global global_settings
|
||||
global_settings = WebEngineSettings(_SettingsWrapper())
|
||||
global_settings.init_settings()
|
||||
|
||||
|
||||
def shutdown():
|
||||
# FIXME:qtwebengine do we need to do something for a clean shutdown here?
|
||||
pass
|
||||
|
||||
|
||||
# Missing QtWebEngine attributes:
|
||||
# - ScreenCaptureEnabled
|
||||
# - Accelerated2dCanvasEnabled
|
||||
# - AutoLoadIconsForPage
|
||||
# - TouchIconsEnabled
|
||||
# - FocusOnNavigationEnabled (5.8)
|
||||
# - AllowRunningInsecureContent (5.8)
|
||||
#
|
||||
# Missing QtWebEngine fonts:
|
||||
# - PictographFont
|
||||
|
||||
|
||||
MAPPINGS = {
|
||||
'content.images':
|
||||
Attribute(QWebEngineSettings.AutoLoadImages),
|
||||
'content.javascript.enabled':
|
||||
Attribute(QWebEngineSettings.JavascriptEnabled),
|
||||
'content.javascript.can_open_tabs_automatically':
|
||||
Attribute(QWebEngineSettings.JavascriptCanOpenWindows),
|
||||
'content.javascript.can_access_clipboard':
|
||||
Attribute(QWebEngineSettings.JavascriptCanAccessClipboard),
|
||||
'content.plugins':
|
||||
Attribute(QWebEngineSettings.PluginsEnabled),
|
||||
'content.hyperlink_auditing':
|
||||
Attribute(QWebEngineSettings.HyperlinkAuditingEnabled),
|
||||
'content.local_content_can_access_remote_urls':
|
||||
Attribute(QWebEngineSettings.LocalContentCanAccessRemoteUrls),
|
||||
'content.local_content_can_access_file_urls':
|
||||
Attribute(QWebEngineSettings.LocalContentCanAccessFileUrls),
|
||||
'content.webgl':
|
||||
Attribute(QWebEngineSettings.WebGLEnabled),
|
||||
'content.local_storage':
|
||||
Attribute(QWebEngineSettings.LocalStorageEnabled),
|
||||
'content.cache.size':
|
||||
# 0: automatically managed by QtWebEngine
|
||||
DefaultProfileSetter('setHttpCacheMaximumSize', default=0,
|
||||
converter=lambda val:
|
||||
qtutils.check_overflow(val, 'int', fatal=False)),
|
||||
'content.xss_auditing':
|
||||
Attribute(QWebEngineSettings.XSSAuditingEnabled),
|
||||
'content.default_encoding':
|
||||
Setter(QWebEngineSettings.setDefaultTextEncoding),
|
||||
|
||||
'input.spatial_navigation':
|
||||
Attribute(QWebEngineSettings.SpatialNavigationEnabled),
|
||||
'input.links_included_in_focus_chain':
|
||||
Attribute(QWebEngineSettings.LinksIncludedInFocusChain),
|
||||
|
||||
'fonts.web.family.standard':
|
||||
FontFamilySetter(QWebEngineSettings.StandardFont),
|
||||
'fonts.web.family.fixed':
|
||||
FontFamilySetter(QWebEngineSettings.FixedFont),
|
||||
'fonts.web.family.serif':
|
||||
FontFamilySetter(QWebEngineSettings.SerifFont),
|
||||
'fonts.web.family.sans_serif':
|
||||
FontFamilySetter(QWebEngineSettings.SansSerifFont),
|
||||
'fonts.web.family.cursive':
|
||||
FontFamilySetter(QWebEngineSettings.CursiveFont),
|
||||
'fonts.web.family.fantasy':
|
||||
FontFamilySetter(QWebEngineSettings.FantasyFont),
|
||||
'fonts.web.size.minimum':
|
||||
Setter(QWebEngineSettings.setFontSize,
|
||||
args=[QWebEngineSettings.MinimumFontSize]),
|
||||
'fonts.web.size.minimum_logical':
|
||||
Setter(QWebEngineSettings.setFontSize,
|
||||
args=[QWebEngineSettings.MinimumLogicalFontSize]),
|
||||
'fonts.web.size.default':
|
||||
Setter(QWebEngineSettings.setFontSize,
|
||||
args=[QWebEngineSettings.DefaultFontSize]),
|
||||
'fonts.web.size.default_fixed':
|
||||
Setter(QWebEngineSettings.setFontSize,
|
||||
args=[QWebEngineSettings.DefaultFixedFontSize]),
|
||||
|
||||
'scrolling.smooth':
|
||||
Attribute(QWebEngineSettings.ScrollAnimatorEnabled),
|
||||
}
|
||||
|
||||
try:
|
||||
MAPPINGS['content.print_element_backgrounds'] = Attribute(
|
||||
QWebEngineSettings.PrintElementBackgrounds)
|
||||
except AttributeError:
|
||||
# Added in Qt 5.8
|
||||
pass
|
||||
|
||||
|
||||
if qtutils.version_check('5.8'):
|
||||
MAPPINGS['spellcheck.languages'] = DictionaryLanguageSetter()
|
||||
|
||||
|
||||
if qtutils.version_check('5.9', compiled=False):
|
||||
# https://bugreports.qt.io/browse/QTBUG-58650
|
||||
MAPPINGS['content.cookies.store'] = PersistentCookiePolicy()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -21,16 +21,19 @@
|
||||
|
||||
import math
|
||||
import functools
|
||||
import sys
|
||||
import re
|
||||
import html as html_utils
|
||||
|
||||
import sip
|
||||
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QEvent, QPoint, QPointF,
|
||||
QUrl, QTimer)
|
||||
from PyQt5.QtGui import QKeyEvent
|
||||
from PyQt5.QtGui import QKeyEvent, QIcon
|
||||
from PyQt5.QtNetwork import QAuthenticator
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript
|
||||
|
||||
from qutebrowser.config import configdata
|
||||
from qutebrowser.browser import browsertab, mouse, shared
|
||||
from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory,
|
||||
interceptor, webenginequtescheme,
|
||||
@@ -98,6 +101,19 @@ class WebEngineAction(browsertab.AbstractAction):
|
||||
"""Save the current page."""
|
||||
self._widget.triggerPageAction(QWebEnginePage.SavePage)
|
||||
|
||||
def show_source(self):
|
||||
try:
|
||||
self._widget.triggerPageAction(QWebEnginePage.ViewSource)
|
||||
except AttributeError:
|
||||
# Qt < 5.8
|
||||
tb = objreg.get('tabbed-browser', scope='window',
|
||||
window=self._tab.win_id)
|
||||
urlstr = self._tab.url().toString(QUrl.RemoveUserInfo)
|
||||
# The original URL becomes the path of a view-source: URL
|
||||
# (without a host), but query/fragment should stay.
|
||||
url = QUrl('view-source:' + urlstr)
|
||||
tb.tabopen(url, background=False, related=True)
|
||||
|
||||
|
||||
class WebEnginePrinting(browsertab.AbstractPrinting):
|
||||
|
||||
@@ -169,6 +185,12 @@ class WebEngineSearch(browsertab.AbstractSearch):
|
||||
|
||||
def search(self, text, *, ignore_case='never', reverse=False,
|
||||
result_cb=None):
|
||||
# Don't go to next entry on duplicate search
|
||||
if self.text == text and self.search_displayed:
|
||||
log.webview.debug("Ignoring duplicate search request"
|
||||
" for {}".format(text))
|
||||
return
|
||||
|
||||
self.text = text
|
||||
self._flags = QWebEnginePage.FindFlags(0)
|
||||
if self._is_case_sensitive(ignore_case):
|
||||
@@ -201,70 +223,96 @@ class WebEngineCaret(browsertab.AbstractCaret):
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def _on_mode_entered(self, mode):
|
||||
pass
|
||||
if mode != usertypes.KeyMode.caret:
|
||||
return
|
||||
|
||||
if self._tab.search.search_displayed:
|
||||
# We are currently in search mode.
|
||||
# convert the search to a blue selection so we can operate on it
|
||||
# https://bugreports.qt.io/browse/QTBUG-60673
|
||||
self._tab.search.clear()
|
||||
|
||||
self._tab.run_js_async(
|
||||
javascript.assemble('caret', 'setPlatform', sys.platform))
|
||||
self._js_call('setInitialCursor')
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def _on_mode_left(self):
|
||||
pass
|
||||
def _on_mode_left(self, mode):
|
||||
if mode != usertypes.KeyMode.caret:
|
||||
return
|
||||
|
||||
self.drop_selection()
|
||||
self._js_call('disableCaret')
|
||||
|
||||
def move_to_next_line(self, count=1):
|
||||
log.stub()
|
||||
for _ in range(count):
|
||||
self._js_call('moveDown')
|
||||
|
||||
def move_to_prev_line(self, count=1):
|
||||
log.stub()
|
||||
for _ in range(count):
|
||||
self._js_call('moveUp')
|
||||
|
||||
def move_to_next_char(self, count=1):
|
||||
log.stub()
|
||||
for _ in range(count):
|
||||
self._js_call('moveRight')
|
||||
|
||||
def move_to_prev_char(self, count=1):
|
||||
log.stub()
|
||||
for _ in range(count):
|
||||
self._js_call('moveLeft')
|
||||
|
||||
def move_to_end_of_word(self, count=1):
|
||||
log.stub()
|
||||
for _ in range(count):
|
||||
self._js_call('moveToEndOfWord')
|
||||
|
||||
def move_to_next_word(self, count=1):
|
||||
log.stub()
|
||||
for _ in range(count):
|
||||
self._js_call('moveToNextWord')
|
||||
|
||||
def move_to_prev_word(self, count=1):
|
||||
log.stub()
|
||||
for _ in range(count):
|
||||
self._js_call('moveToPreviousWord')
|
||||
|
||||
def move_to_start_of_line(self):
|
||||
log.stub()
|
||||
self._js_call('moveToStartOfLine')
|
||||
|
||||
def move_to_end_of_line(self):
|
||||
log.stub()
|
||||
self._js_call('moveToEndOfLine')
|
||||
|
||||
def move_to_start_of_next_block(self, count=1):
|
||||
log.stub()
|
||||
for _ in range(count):
|
||||
self._js_call('moveToStartOfNextBlock')
|
||||
|
||||
def move_to_start_of_prev_block(self, count=1):
|
||||
log.stub()
|
||||
for _ in range(count):
|
||||
self._js_call('moveToStartOfPrevBlock')
|
||||
|
||||
def move_to_end_of_next_block(self, count=1):
|
||||
log.stub()
|
||||
for _ in range(count):
|
||||
self._js_call('moveToEndOfNextBlock')
|
||||
|
||||
def move_to_end_of_prev_block(self, count=1):
|
||||
log.stub()
|
||||
for _ in range(count):
|
||||
self._js_call('moveToEndOfPrevBlock')
|
||||
|
||||
def move_to_start_of_document(self):
|
||||
log.stub()
|
||||
self._js_call('moveToStartOfDocument')
|
||||
|
||||
def move_to_end_of_document(self):
|
||||
log.stub()
|
||||
self._js_call('moveToEndOfDocument')
|
||||
|
||||
def toggle_selection(self):
|
||||
log.stub()
|
||||
self._js_call('toggleSelection')
|
||||
|
||||
def drop_selection(self):
|
||||
log.stub()
|
||||
self._js_call('dropSelection')
|
||||
|
||||
def has_selection(self):
|
||||
return self._widget.hasSelection()
|
||||
|
||||
def selection(self, html=False):
|
||||
if html:
|
||||
raise browsertab.UnsupportedOperationError
|
||||
return self._widget.selectedText()
|
||||
def selection(self, callback):
|
||||
# Not using selectedText() as WORKAROUND for
|
||||
# https://bugreports.qt.io/browse/QTBUG-53134
|
||||
# Even on Qt 5.10 selectedText() seems to work poorly, see
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/3523
|
||||
self._tab.run_js_async(javascript.assemble('caret', 'getSelection'),
|
||||
callback)
|
||||
|
||||
def _follow_selected_cb(self, js_elem, tab=False):
|
||||
"""Callback for javascript which clicks the selected element.
|
||||
@@ -308,6 +356,10 @@ class WebEngineCaret(browsertab.AbstractCaret):
|
||||
self._tab.run_js_async(js_code, lambda jsret:
|
||||
self._follow_selected_cb(jsret, tab))
|
||||
|
||||
def _js_call(self, command):
|
||||
self._tab.run_js_async(
|
||||
javascript.assemble('caret', command))
|
||||
|
||||
|
||||
class WebEngineScroller(browsertab.AbstractScroller):
|
||||
|
||||
@@ -435,7 +487,8 @@ class WebEngineHistory(browsertab.AbstractHistory):
|
||||
return self._history.itemAt(i)
|
||||
|
||||
def _go_to_item(self, item):
|
||||
return self._history.goToItem(item)
|
||||
self._tab.predicted_navigation.emit(item.url())
|
||||
self._history.goToItem(item)
|
||||
|
||||
def serialize(self):
|
||||
if not qtutils.version_check('5.9', compiled=False):
|
||||
@@ -455,13 +508,18 @@ class WebEngineHistory(browsertab.AbstractHistory):
|
||||
def load_items(self, items):
|
||||
stream, _data, cur_data = tabhistory.serialize(items)
|
||||
qtutils.deserialize_stream(stream, self._history)
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_load_finished():
|
||||
self._tab.scroller.to_point(cur_data['scroll-pos'])
|
||||
self._tab.load_finished.disconnect(_on_load_finished)
|
||||
|
||||
if cur_data is not None:
|
||||
if 'zoom' in cur_data:
|
||||
self._tab.zoom.set_factor(cur_data['zoom'])
|
||||
if ('scroll-pos' in cur_data and
|
||||
self._tab.scroller.pos_px() == QPoint(0, 0)):
|
||||
QTimer.singleShot(0, functools.partial(
|
||||
self._tab.scroller.to_point, cur_data['scroll-pos']))
|
||||
self._tab.load_finished.connect(_on_load_finished)
|
||||
|
||||
|
||||
class WebEngineZoom(browsertab.AbstractZoom):
|
||||
@@ -557,19 +615,22 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
private=private)
|
||||
self.history = WebEngineHistory(self)
|
||||
self.scroller = WebEngineScroller(self, parent=self)
|
||||
self.caret = WebEngineCaret(win_id=win_id, mode_manager=mode_manager,
|
||||
self.caret = WebEngineCaret(mode_manager=mode_manager,
|
||||
tab=self, parent=self)
|
||||
self.zoom = WebEngineZoom(win_id=win_id, parent=self)
|
||||
self.zoom = WebEngineZoom(tab=self, parent=self)
|
||||
self.search = WebEngineSearch(parent=self)
|
||||
self.printing = WebEnginePrinting()
|
||||
self.elements = WebEngineElements(self)
|
||||
self.action = WebEngineAction()
|
||||
self.elements = WebEngineElements(tab=self)
|
||||
self.action = WebEngineAction(tab=self)
|
||||
# We're assigning settings in _set_widget
|
||||
self.settings = webenginesettings.WebEngineSettings(settings=None)
|
||||
self._set_widget(widget)
|
||||
self._connect_signals()
|
||||
self.backend = usertypes.Backend.QtWebEngine
|
||||
self._init_js()
|
||||
self._child_event_filter = None
|
||||
self._saved_zoom = None
|
||||
self._reload_url = None
|
||||
|
||||
def _init_js(self):
|
||||
js_code = '\n'.join([
|
||||
@@ -577,9 +638,12 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
'window._qutebrowser = window._qutebrowser || {};',
|
||||
utils.read_file('javascript/scroll.js'),
|
||||
utils.read_file('javascript/webelem.js'),
|
||||
utils.read_file('javascript/caret.js'),
|
||||
])
|
||||
script = QWebEngineScript()
|
||||
script.setInjectionPoint(QWebEngineScript.DocumentCreation)
|
||||
# We can't use DocumentCreation here as WORKAROUND for
|
||||
# https://bugreports.qt.io/browse/QTBUG-66011
|
||||
script.setInjectionPoint(QWebEngineScript.DocumentReady)
|
||||
script.setSourceCode(js_code)
|
||||
|
||||
page = self._widget.page()
|
||||
@@ -597,6 +661,9 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
|
||||
@pyqtSlot()
|
||||
def _restore_zoom(self):
|
||||
if sip.isdeleted(self._widget):
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/3498
|
||||
return
|
||||
if self._saved_zoom is None:
|
||||
return
|
||||
self.zoom.set_factor(self._saved_zoom)
|
||||
@@ -636,13 +703,10 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
def shutdown(self):
|
||||
self.shutting_down.emit()
|
||||
self.action.exit_fullscreen()
|
||||
if qtutils.version_check('5.8', exact=True, compiled=False):
|
||||
# WORKAROUND for
|
||||
# https://bugreports.qt.io/browse/QTBUG-58563
|
||||
self.search.clear()
|
||||
self._widget.shutdown()
|
||||
|
||||
def reload(self, *, force=False):
|
||||
self.predicted_navigation.emit(self.url())
|
||||
if force:
|
||||
action = QWebEnginePage.ReloadAndBypassCache
|
||||
else:
|
||||
@@ -682,6 +746,16 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
self.send_event(press_evt)
|
||||
self.send_event(release_evt)
|
||||
|
||||
def _show_error_page(self, url, error):
|
||||
"""Show an error page in the tab."""
|
||||
log.misc.debug("Showing error page for {}".format(error))
|
||||
url_string = url.toDisplayString()
|
||||
error_page = jinja.render(
|
||||
'error.html',
|
||||
title="Error loading page: {}".format(url_string),
|
||||
url=url_string, error=error)
|
||||
self.set_html(error_page)
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_history_trigger(self):
|
||||
try:
|
||||
@@ -716,10 +790,11 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
"""Called when a proxy needs authentication."""
|
||||
msg = "<b>{}</b> requires a username and password.".format(
|
||||
html_utils.escape(proxy_host))
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
answer = message.ask(
|
||||
title="Proxy authentication required", text=msg,
|
||||
mode=usertypes.PromptMode.user_pwd,
|
||||
abort_on=[self.shutting_down, self.load_started])
|
||||
abort_on=[self.shutting_down, self.load_started], url=urlstr)
|
||||
if answer is not None:
|
||||
authenticator.setUser(answer.user)
|
||||
authenticator.setPassword(answer.password)
|
||||
@@ -729,21 +804,19 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
sip.assign(authenticator, QAuthenticator())
|
||||
# pylint: enable=no-member, useless-suppression
|
||||
except AttributeError:
|
||||
url_string = url.toDisplayString()
|
||||
error_page = jinja.render(
|
||||
'error.html',
|
||||
title="Error loading page: {}".format(url_string),
|
||||
url=url_string, error="Proxy authentication required",
|
||||
icon='')
|
||||
self.set_html(error_page)
|
||||
self._show_error_page(url, "Proxy authentication required")
|
||||
|
||||
@pyqtSlot(QUrl, 'QAuthenticator*')
|
||||
def _on_authentication_required(self, url, authenticator):
|
||||
# FIXME:qtwebengine support .netrc
|
||||
answer = shared.authentication_required(
|
||||
url, authenticator, abort_on=[self.shutting_down,
|
||||
self.load_started])
|
||||
if answer is None:
|
||||
netrc_success = False
|
||||
if not self.data.netrc_used:
|
||||
self.data.netrc_used = True
|
||||
netrc_success = shared.netrc_authentication(url, authenticator)
|
||||
if not netrc_success:
|
||||
abort_on = [self.shutting_down, self.load_started]
|
||||
answer = shared.authentication_required(url, authenticator,
|
||||
abort_on)
|
||||
if not netrc_success and answer is None:
|
||||
try:
|
||||
# pylint: disable=no-member, useless-suppression
|
||||
sip.assign(authenticator, QAuthenticator())
|
||||
@@ -751,12 +824,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
except AttributeError:
|
||||
# WORKAROUND for
|
||||
# https://www.riverbankcomputing.com/pipermail/pyqt/2016-December/038400.html
|
||||
url_string = url.toDisplayString()
|
||||
error_page = jinja.render(
|
||||
'error.html',
|
||||
title="Error loading page: {}".format(url_string),
|
||||
url=url_string, error="Authentication required")
|
||||
self.set_html(error_page)
|
||||
self._show_error_page(url, "Authentication required")
|
||||
|
||||
@pyqtSlot('QWebEngineFullScreenRequest')
|
||||
def _on_fullscreen_requested(self, request):
|
||||
@@ -779,6 +847,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
# https://bugreports.qt.io/browse/QTBUG-61506
|
||||
self.search.clear()
|
||||
super()._on_load_started()
|
||||
self.data.netrc_used = False
|
||||
|
||||
@pyqtSlot(QWebEnginePage.RenderProcessTerminationStatus, int)
|
||||
def _on_render_process_terminated(self, status, exitcode):
|
||||
@@ -820,6 +889,73 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
if not ok:
|
||||
self._load_finished_fake.emit(False)
|
||||
|
||||
def _error_page_workaround(self, html):
|
||||
"""Check if we're displaying a Chromium error page.
|
||||
|
||||
This gets only called if we got loadFinished(False) without JavaScript,
|
||||
so we can display at least some error page.
|
||||
|
||||
WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66643
|
||||
Needs to check the page content as a WORKAROUND for
|
||||
https://bugreports.qt.io/browse/QTBUG-66661
|
||||
"""
|
||||
match = re.search(r'"errorCode":"([^"]*)"', html)
|
||||
if match is None:
|
||||
return
|
||||
self._show_error_page(self.url(), error=match.group(1))
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _on_load_finished(self, ok):
|
||||
"""Display a static error page if JavaScript is disabled."""
|
||||
super()._on_load_finished(ok)
|
||||
js_enabled = self.settings.test_attribute('content.javascript.enabled')
|
||||
if not ok and not js_enabled:
|
||||
self.dump_async(self._error_page_workaround)
|
||||
|
||||
if ok and self._reload_url is not None:
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66656
|
||||
log.config.debug(
|
||||
"Reloading {} because of config change".format(
|
||||
self._reload_url.toDisplayString()))
|
||||
QTimer.singleShot(100, lambda url=self._reload_url:
|
||||
self.openurl(url))
|
||||
self._reload_url = None
|
||||
|
||||
if not qtutils.version_check('5.10', compiled=False):
|
||||
# We can't do this when we have the loadFinished workaround as that
|
||||
# sometimes clears icons without loading a new page.
|
||||
# In general, this is handled by Qt, but when loading takes long,
|
||||
# the old icon is still displayed.
|
||||
self.icon_changed.emit(QIcon())
|
||||
|
||||
@pyqtSlot(QUrl)
|
||||
def _on_predicted_navigation(self, url):
|
||||
"""If we know we're going to visit an URL soon, change the settings."""
|
||||
self.settings.update_for_url(url)
|
||||
|
||||
@pyqtSlot(usertypes.NavigationRequest)
|
||||
def _on_navigation_request(self, navigation):
|
||||
super()._on_navigation_request(navigation)
|
||||
if not navigation.accepted or not navigation.is_main_frame:
|
||||
return
|
||||
|
||||
needs_reload = {
|
||||
'content.plugins',
|
||||
'content.javascript.enabled',
|
||||
'content.javascript.can_access_clipboard',
|
||||
'content.javascript.can_access_clipboard',
|
||||
'content.print_element_backgrounds',
|
||||
'input.spatial_navigation',
|
||||
'input.spatial_navigation',
|
||||
}
|
||||
assert needs_reload.issubset(configdata.DATA)
|
||||
|
||||
changed = self.settings.update_for_url(navigation.url)
|
||||
if (changed & needs_reload and navigation.navigation_type !=
|
||||
navigation.Type.link_clicked):
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66656
|
||||
self._reload_url = navigation.url
|
||||
|
||||
def _connect_signals(self):
|
||||
view = self._widget
|
||||
page = view.page()
|
||||
@@ -834,6 +970,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
self._on_proxy_authentication_required)
|
||||
page.fullScreenRequested.connect(self._on_fullscreen_requested)
|
||||
page.contentsSizeChanged.connect(self.contents_size_changed)
|
||||
page.navigation_request.connect(self._on_navigation_request)
|
||||
|
||||
view.titleChanged.connect(self.title_changed)
|
||||
view.urlChanged.connect(self._on_url_changed)
|
||||
@@ -854,5 +991,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
page.loadFinished.connect(self._restore_zoom)
|
||||
page.loadFinished.connect(self._on_load_finished)
|
||||
|
||||
self.predicted_navigation.connect(self._on_predicted_navigation)
|
||||
|
||||
def event_target(self):
|
||||
return self._widget.focusProxy()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -29,8 +29,7 @@ from PyQt5.QtWebEngineWidgets import (QWebEngineView, QWebEnginePage,
|
||||
from qutebrowser.browser import shared
|
||||
from qutebrowser.browser.webengine import certificateerror, webenginesettings
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import (log, debug, usertypes, jinja, urlutils, message,
|
||||
objreg, qtutils)
|
||||
from qutebrowser.utils import log, debug, usertypes, jinja, objreg, qtutils
|
||||
|
||||
|
||||
class WebEngineView(QWebEngineView):
|
||||
@@ -124,10 +123,12 @@ class WebEnginePage(QWebEnginePage):
|
||||
Signals:
|
||||
certificate_error: Emitted on certificate errors.
|
||||
shutting_down: Emitted when the page is shutting down.
|
||||
navigation_request: Emitted on acceptNavigationRequest.
|
||||
"""
|
||||
|
||||
certificate_error = pyqtSignal()
|
||||
shutting_down = pyqtSignal()
|
||||
navigation_request = pyqtSignal(usertypes.NavigationRequest)
|
||||
|
||||
def __init__(self, *, theme_color, profile, parent=None):
|
||||
super().__init__(profile, parent)
|
||||
@@ -242,10 +243,12 @@ class WebEnginePage(QWebEnginePage):
|
||||
"""Override javaScriptConfirm to use qutebrowser prompts."""
|
||||
if self._is_shutting_down:
|
||||
return False
|
||||
escape_msg = qtutils.version_check('5.11', compiled=False)
|
||||
try:
|
||||
return shared.javascript_confirm(url, js_msg,
|
||||
abort_on=[self.loadStarted,
|
||||
self.shutting_down])
|
||||
self.shutting_down],
|
||||
escape_msg=escape_msg)
|
||||
except shared.CallSuper:
|
||||
return super().javaScriptConfirm(url, js_msg)
|
||||
|
||||
@@ -255,12 +258,14 @@ class WebEnginePage(QWebEnginePage):
|
||||
# https://www.riverbankcomputing.com/pipermail/pyqt/2016-November/038293.html
|
||||
def javaScriptPrompt(self, url, js_msg, default):
|
||||
"""Override javaScriptPrompt to use qutebrowser prompts."""
|
||||
escape_msg = qtutils.version_check('5.11', compiled=False)
|
||||
if self._is_shutting_down:
|
||||
return (False, "")
|
||||
try:
|
||||
return shared.javascript_prompt(url, js_msg, default,
|
||||
abort_on=[self.loadStarted,
|
||||
self.shutting_down])
|
||||
self.shutting_down],
|
||||
escape_msg=escape_msg)
|
||||
except shared.CallSuper:
|
||||
return super().javaScriptPrompt(url, js_msg, default)
|
||||
|
||||
@@ -268,10 +273,12 @@ class WebEnginePage(QWebEnginePage):
|
||||
"""Override javaScriptAlert to use qutebrowser prompts."""
|
||||
if self._is_shutting_down:
|
||||
return
|
||||
escape_msg = qtutils.version_check('5.11', compiled=False)
|
||||
try:
|
||||
shared.javascript_alert(url, js_msg,
|
||||
abort_on=[self.loadStarted,
|
||||
self.shutting_down])
|
||||
self.shutting_down],
|
||||
escape_msg=escape_msg)
|
||||
except shared.CallSuper:
|
||||
super().javaScriptAlert(url, js_msg)
|
||||
|
||||
@@ -288,21 +295,26 @@ class WebEnginePage(QWebEnginePage):
|
||||
url: QUrl,
|
||||
typ: QWebEnginePage.NavigationType,
|
||||
is_main_frame: bool):
|
||||
"""Override acceptNavigationRequest to handle clicked links.
|
||||
|
||||
This only show an error on invalid links - everything else is handled
|
||||
in createWindow.
|
||||
"""
|
||||
log.webview.debug("navigation request: url {}, type {}, is_main_frame "
|
||||
"{}".format(url.toDisplayString(),
|
||||
debug.qenum_key(QWebEnginePage, typ),
|
||||
is_main_frame))
|
||||
if (typ == QWebEnginePage.NavigationTypeLinkClicked and
|
||||
not url.isValid()):
|
||||
msg = urlutils.get_errstring(url, "Invalid link clicked")
|
||||
message.error(msg)
|
||||
return False
|
||||
return True
|
||||
"""Override acceptNavigationRequest to forward it to the tab API."""
|
||||
type_map = {
|
||||
QWebEnginePage.NavigationTypeLinkClicked:
|
||||
usertypes.NavigationRequest.Type.link_clicked,
|
||||
QWebEnginePage.NavigationTypeTyped:
|
||||
usertypes.NavigationRequest.Type.typed,
|
||||
QWebEnginePage.NavigationTypeFormSubmitted:
|
||||
usertypes.NavigationRequest.Type.form_submitted,
|
||||
QWebEnginePage.NavigationTypeBackForward:
|
||||
usertypes.NavigationRequest.Type.back_forward,
|
||||
QWebEnginePage.NavigationTypeReload:
|
||||
usertypes.NavigationRequest.Type.reloaded,
|
||||
QWebEnginePage.NavigationTypeOther:
|
||||
usertypes.NavigationRequest.Type.other,
|
||||
}
|
||||
navigation = usertypes.NavigationRequest(url=url,
|
||||
navigation_type=type_map[typ],
|
||||
is_main_frame=is_main_frame)
|
||||
self.navigation_request.emit(navigation)
|
||||
return navigation.accepted
|
||||
|
||||
@pyqtSlot('QUrl')
|
||||
def _inject_userjs(self, url):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2015-2017 Daniel Schadt
|
||||
# Copyright 2015-2018 Daniel Schadt
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -254,7 +254,6 @@ class _Downloader:
|
||||
_finished_file: A flag indicating if the file has already been
|
||||
written.
|
||||
_used: A flag indicating if the downloader has already been used.
|
||||
_win_id: The window this downloader belongs to.
|
||||
"""
|
||||
|
||||
def __init__(self, tab, target):
|
||||
@@ -265,7 +264,6 @@ class _Downloader:
|
||||
self.pending_downloads = set()
|
||||
self._finished_file = False
|
||||
self._used = False
|
||||
self._win_id = tab.win_id
|
||||
|
||||
def run(self):
|
||||
"""Download and save the page.
|
||||
@@ -365,8 +363,7 @@ class _Downloader:
|
||||
self.writer.add_file(urlutils.encoded_url(url), b'')
|
||||
return
|
||||
|
||||
download_manager = objreg.get('qtnetwork-download-manager',
|
||||
scope='window', window=self._win_id)
|
||||
download_manager = objreg.get('qtnetwork-download-manager')
|
||||
target = downloads.FileObjDownloadTarget(_NoCloseBytesIO())
|
||||
item = download_manager.get(url, target=target,
|
||||
auto_remove=True)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2015-2017 Antoni Boucher (antoyo) <bouanto@zoho.com>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2015-2018 Antoni Boucher (antoyo) <bouanto@zoho.com>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
import os
|
||||
|
||||
from qutebrowser.browser.webkit.network import schemehandler, networkreply
|
||||
from qutebrowser.browser.webkit.network import networkreply
|
||||
from qutebrowser.utils import jinja
|
||||
|
||||
|
||||
@@ -111,27 +111,21 @@ def dirbrowser_html(path):
|
||||
return html.encode('UTF-8', errors='xmlcharrefreplace')
|
||||
|
||||
|
||||
class FileSchemeHandler(schemehandler.SchemeHandler):
|
||||
def handler(request):
|
||||
"""Handler for a file:// URL.
|
||||
|
||||
"""Scheme handler for file: URLs."""
|
||||
Args:
|
||||
request: QNetworkRequest to answer to.
|
||||
|
||||
def createRequest(self, _op, request, _outgoing_data):
|
||||
"""Create a new request.
|
||||
|
||||
Args:
|
||||
request: const QNetworkRequest & req
|
||||
_op: Operation op
|
||||
_outgoing_data: QIODevice * outgoingData
|
||||
|
||||
Return:
|
||||
A QNetworkReply for directories, None for files.
|
||||
"""
|
||||
path = request.url().toLocalFile()
|
||||
try:
|
||||
if os.path.isdir(path):
|
||||
data = dirbrowser_html(path)
|
||||
return networkreply.FixedDataNetworkReply(
|
||||
request, data, 'text/html', self.parent())
|
||||
return None
|
||||
except UnicodeEncodeError:
|
||||
return None
|
||||
Return:
|
||||
A QNetworkReply for directories, None for files.
|
||||
"""
|
||||
path = request.url().toLocalFile()
|
||||
try:
|
||||
if os.path.isdir(path):
|
||||
data = dirbrowser_html(path)
|
||||
return networkreply.FixedDataNetworkReply(
|
||||
request, data, 'text/html')
|
||||
return None
|
||||
except UnicodeEncodeError:
|
||||
return None
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -19,9 +19,7 @@
|
||||
|
||||
"""Our own QNetworkAccessManager."""
|
||||
|
||||
import os
|
||||
import collections
|
||||
import netrc
|
||||
import html
|
||||
|
||||
import attr
|
||||
@@ -127,10 +125,13 @@ class NetworkManager(QNetworkAccessManager):
|
||||
_scheme_handlers: A dictionary (scheme -> handler) of supported custom
|
||||
schemes.
|
||||
_win_id: The window ID this NetworkManager is associated with.
|
||||
(or None for generic network managers)
|
||||
_tab_id: The tab ID this NetworkManager is associated with.
|
||||
(or None for generic network managers)
|
||||
_rejected_ssl_errors: A {QUrl: [SslError]} dict of rejected errors.
|
||||
_accepted_ssl_errors: A {QUrl: [SslError]} dict of accepted errors.
|
||||
_private: Whether we're in private browsing mode.
|
||||
netrc_used: Whether netrc authentication was performed.
|
||||
|
||||
Signals:
|
||||
shutting_down: Emitted when the QNAM is shutting down.
|
||||
@@ -150,8 +151,8 @@ class NetworkManager(QNetworkAccessManager):
|
||||
self._tab_id = tab_id
|
||||
self._private = private
|
||||
self._scheme_handlers = {
|
||||
'qute': webkitqutescheme.QuteSchemeHandler(win_id),
|
||||
'file': filescheme.FileSchemeHandler(win_id),
|
||||
'qute': webkitqutescheme.handler,
|
||||
'file': filescheme.handler,
|
||||
}
|
||||
self._set_cookiejar()
|
||||
self._set_cache()
|
||||
@@ -161,6 +162,7 @@ class NetworkManager(QNetworkAccessManager):
|
||||
self.authenticationRequired.connect(self.on_authentication_required)
|
||||
self.proxyAuthenticationRequired.connect(
|
||||
self.on_proxy_authentication_required)
|
||||
self.netrc_used = False
|
||||
|
||||
def _set_cookiejar(self):
|
||||
"""Set the cookie jar of the NetworkManager correctly."""
|
||||
@@ -194,6 +196,7 @@ class NetworkManager(QNetworkAccessManager):
|
||||
# This might be a generic network manager, e.g. one belonging to a
|
||||
# DownloadManager. In this case, just skip the webview thing.
|
||||
if self._tab_id is not None:
|
||||
assert self._win_id is not None
|
||||
tab = objreg.get('tab', scope='tab', window=self._win_id,
|
||||
tab=self._tab_id)
|
||||
abort_on.append(tab.load_started)
|
||||
@@ -270,28 +273,12 @@ class NetworkManager(QNetworkAccessManager):
|
||||
@pyqtSlot('QNetworkReply*', 'QAuthenticator*')
|
||||
def on_authentication_required(self, reply, authenticator):
|
||||
"""Called when a website needs authentication."""
|
||||
user, password = None, None
|
||||
if not hasattr(reply, "netrc_used") and 'HOME' in os.environ:
|
||||
# We'll get an OSError by netrc if 'HOME' isn't available in
|
||||
# os.environ. We don't want to log that, so we prevent it
|
||||
# altogether.
|
||||
reply.netrc_used = True
|
||||
try:
|
||||
net = netrc.netrc(config.val.content.netrc_file)
|
||||
authenticators = net.authenticators(reply.url().host())
|
||||
if authenticators is not None:
|
||||
(user, _account, password) = authenticators
|
||||
except FileNotFoundError:
|
||||
log.misc.debug("No .netrc file found")
|
||||
except OSError:
|
||||
log.misc.exception("Unable to read the netrc file")
|
||||
except netrc.NetrcParseError:
|
||||
log.misc.exception("Error when parsing the netrc file")
|
||||
|
||||
if user is not None:
|
||||
authenticator.setUser(user)
|
||||
authenticator.setPassword(password)
|
||||
else:
|
||||
netrc_success = False
|
||||
if not self.netrc_used:
|
||||
self.netrc_used = True
|
||||
netrc_success = shared.netrc_authentication(reply.url(),
|
||||
authenticator)
|
||||
if not netrc_success:
|
||||
abort_on = self._get_abort_signals(reply)
|
||||
shared.authentication_required(reply.url(), authenticator,
|
||||
abort_on=abort_on)
|
||||
@@ -386,9 +373,9 @@ class NetworkManager(QNetworkAccessManager):
|
||||
|
||||
scheme = req.url().scheme()
|
||||
if scheme in self._scheme_handlers:
|
||||
result = self._scheme_handlers[scheme].createRequest(
|
||||
op, req, outgoing_data)
|
||||
result = self._scheme_handlers[scheme](req)
|
||||
if result is not None:
|
||||
result.setParent(self)
|
||||
return result
|
||||
|
||||
for header, value in shared.custom_headers():
|
||||
@@ -408,6 +395,7 @@ class NetworkManager(QNetworkAccessManager):
|
||||
current_url = QUrl()
|
||||
|
||||
if self._tab_id is not None:
|
||||
assert self._win_id is not None
|
||||
try:
|
||||
tab = objreg.get('tab', scope='tab', window=self._win_id,
|
||||
tab=self._tab_id)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# Based on the Eric5 helpviewer,
|
||||
# Copyright (c) 2009 - 2014 Detlev Offenbach <detlev@die-offenbachs.de>
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# Based on the Eric5 helpviewer,
|
||||
# Copyright (c) 2009 - 2014 Detlev Offenbach <detlev@die-offenbachs.de>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Base class for custom scheme handlers."""
|
||||
|
||||
from PyQt5.QtCore import QObject
|
||||
|
||||
|
||||
class SchemeHandler(QObject):
|
||||
|
||||
"""Abstract base class for custom scheme handlers.
|
||||
|
||||
Attributes:
|
||||
_win_id: The window ID this scheme handler is associated with.
|
||||
"""
|
||||
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
self._win_id = win_id
|
||||
|
||||
def createRequest(self, op, request, outgoing_data):
|
||||
"""Create a new request.
|
||||
|
||||
Args:
|
||||
op: Operation op
|
||||
req: const QNetworkRequest & req
|
||||
outgoing_data: QIODevice * outgoingData
|
||||
|
||||
Return:
|
||||
A QNetworkReply.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -24,46 +24,36 @@ import mimetypes
|
||||
from PyQt5.QtNetwork import QNetworkReply
|
||||
|
||||
from qutebrowser.browser import pdfjs, qutescheme
|
||||
from qutebrowser.browser.webkit.network import schemehandler, networkreply
|
||||
from qutebrowser.browser.webkit.network import networkreply
|
||||
from qutebrowser.utils import log, usertypes, qtutils
|
||||
|
||||
|
||||
class QuteSchemeHandler(schemehandler.SchemeHandler):
|
||||
def handler(request):
|
||||
"""Scheme handler for qute:// URLs.
|
||||
|
||||
"""Scheme handler for qute:// URLs."""
|
||||
Args:
|
||||
request: QNetworkRequest to answer to.
|
||||
|
||||
def createRequest(self, _op, request, _outgoing_data):
|
||||
"""Create a new request.
|
||||
Return:
|
||||
A QNetworkReply.
|
||||
"""
|
||||
try:
|
||||
mimetype, data = qutescheme.data_for_url(request.url())
|
||||
except qutescheme.NoHandlerFound:
|
||||
errorstr = "No handler found for {}!".format(
|
||||
request.url().toDisplayString())
|
||||
return networkreply.ErrorNetworkReply(
|
||||
request, errorstr, QNetworkReply.ContentNotFoundError)
|
||||
except qutescheme.QuteSchemeOSError as e:
|
||||
return networkreply.ErrorNetworkReply(
|
||||
request, str(e), QNetworkReply.ContentNotFoundError)
|
||||
except qutescheme.QuteSchemeError as e:
|
||||
return networkreply.ErrorNetworkReply(request, e.errorstring, e.error)
|
||||
except qutescheme.Redirect as e:
|
||||
qtutils.ensure_valid(e.url)
|
||||
return networkreply.RedirectNetworkReply(e.url)
|
||||
|
||||
Args:
|
||||
request: const QNetworkRequest & req
|
||||
_op: Operation op
|
||||
_outgoing_data: QIODevice * outgoingData
|
||||
|
||||
Return:
|
||||
A QNetworkReply.
|
||||
"""
|
||||
try:
|
||||
mimetype, data = qutescheme.data_for_url(request.url())
|
||||
except qutescheme.NoHandlerFound:
|
||||
errorstr = "No handler found for {}!".format(
|
||||
request.url().toDisplayString())
|
||||
return networkreply.ErrorNetworkReply(
|
||||
request, errorstr, QNetworkReply.ContentNotFoundError,
|
||||
self.parent())
|
||||
except qutescheme.QuteSchemeOSError as e:
|
||||
return networkreply.ErrorNetworkReply(
|
||||
request, str(e), QNetworkReply.ContentNotFoundError,
|
||||
self.parent())
|
||||
except qutescheme.QuteSchemeError as e:
|
||||
return networkreply.ErrorNetworkReply(request, e.errorstring,
|
||||
e.error, self.parent())
|
||||
except qutescheme.Redirect as e:
|
||||
qtutils.ensure_valid(e.url)
|
||||
return networkreply.RedirectNetworkReply(e.url, self.parent())
|
||||
|
||||
return networkreply.FixedDataNetworkReply(request, data, mimetype,
|
||||
self.parent())
|
||||
return networkreply.FixedDataNetworkReply(request, data, mimetype)
|
||||
|
||||
|
||||
@qutescheme.add_handler('pdfjs', backend=usertypes.Backend.QtWebKit)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2015-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2015-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2015-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2015-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2015-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2015-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -17,9 +17,6 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# We get various "abstract but not overridden" warnings
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
"""Bridge from QWebSettings to our own settings.
|
||||
|
||||
Module attributes:
|
||||
@@ -37,85 +34,130 @@ from qutebrowser.utils import standarddir, urlutils
|
||||
from qutebrowser.browser import shared
|
||||
|
||||
|
||||
class Base(websettings.Base):
|
||||
|
||||
"""Base settings class with appropriate _get_global_settings."""
|
||||
|
||||
def _get_global_settings(self):
|
||||
return [QWebSettings.globalSettings()]
|
||||
# The global WebKitSettings object
|
||||
global_settings = None
|
||||
|
||||
|
||||
class Attribute(Base, websettings.Attribute):
|
||||
class WebKitSettings(websettings.AbstractSettings):
|
||||
|
||||
"""A setting set via QWebSettings::setAttribute."""
|
||||
"""A wrapper for the config for QWebSettings."""
|
||||
|
||||
ENUM_BASE = QWebSettings
|
||||
_ATTRIBUTES = {
|
||||
'content.images':
|
||||
[QWebSettings.AutoLoadImages],
|
||||
'content.javascript.enabled':
|
||||
[QWebSettings.JavascriptEnabled],
|
||||
'content.javascript.can_open_tabs_automatically':
|
||||
[QWebSettings.JavascriptCanOpenWindows],
|
||||
'content.javascript.can_close_tabs':
|
||||
[QWebSettings.JavascriptCanCloseWindows],
|
||||
'content.javascript.can_access_clipboard':
|
||||
[QWebSettings.JavascriptCanAccessClipboard],
|
||||
'content.plugins':
|
||||
[QWebSettings.PluginsEnabled],
|
||||
'content.webgl':
|
||||
[QWebSettings.WebGLEnabled],
|
||||
'content.hyperlink_auditing':
|
||||
[QWebSettings.HyperlinkAuditingEnabled],
|
||||
'content.local_content_can_access_remote_urls':
|
||||
[QWebSettings.LocalContentCanAccessRemoteUrls],
|
||||
'content.local_content_can_access_file_urls':
|
||||
[QWebSettings.LocalContentCanAccessFileUrls],
|
||||
'content.dns_prefetch':
|
||||
[QWebSettings.DnsPrefetchEnabled],
|
||||
'content.frame_flattening':
|
||||
[QWebSettings.FrameFlatteningEnabled],
|
||||
'content.cache.appcache':
|
||||
[QWebSettings.OfflineWebApplicationCacheEnabled],
|
||||
'content.local_storage':
|
||||
[QWebSettings.LocalStorageEnabled,
|
||||
QWebSettings.OfflineStorageDatabaseEnabled],
|
||||
'content.developer_extras':
|
||||
[QWebSettings.DeveloperExtrasEnabled],
|
||||
'content.print_element_backgrounds':
|
||||
[QWebSettings.PrintElementBackgrounds],
|
||||
'content.xss_auditing':
|
||||
[QWebSettings.XSSAuditingEnabled],
|
||||
|
||||
'input.spatial_navigation':
|
||||
[QWebSettings.SpatialNavigationEnabled],
|
||||
'input.links_included_in_focus_chain':
|
||||
[QWebSettings.LinksIncludedInFocusChain],
|
||||
|
||||
'zoom.text_only':
|
||||
[QWebSettings.ZoomTextOnly],
|
||||
'scrolling.smooth':
|
||||
[QWebSettings.ScrollAnimatorEnabled],
|
||||
}
|
||||
|
||||
_FONT_SIZES = {
|
||||
'fonts.web.size.minimum':
|
||||
QWebSettings.MinimumFontSize,
|
||||
'fonts.web.size.minimum_logical':
|
||||
QWebSettings.MinimumLogicalFontSize,
|
||||
'fonts.web.size.default':
|
||||
QWebSettings.DefaultFontSize,
|
||||
'fonts.web.size.default_fixed':
|
||||
QWebSettings.DefaultFixedFontSize,
|
||||
}
|
||||
|
||||
_FONT_FAMILIES = {
|
||||
'fonts.web.family.standard': QWebSettings.StandardFont,
|
||||
'fonts.web.family.fixed': QWebSettings.FixedFont,
|
||||
'fonts.web.family.serif': QWebSettings.SerifFont,
|
||||
'fonts.web.family.sans_serif': QWebSettings.SansSerifFont,
|
||||
'fonts.web.family.cursive': QWebSettings.CursiveFont,
|
||||
'fonts.web.family.fantasy': QWebSettings.FantasyFont,
|
||||
}
|
||||
|
||||
# Mapping from QWebSettings::QWebSettings() in
|
||||
# qtwebkit/Source/WebKit/qt/Api/qwebsettings.cpp
|
||||
_FONT_TO_QFONT = {
|
||||
QWebSettings.StandardFont: QFont.Serif,
|
||||
QWebSettings.FixedFont: QFont.Monospace,
|
||||
QWebSettings.SerifFont: QFont.Serif,
|
||||
QWebSettings.SansSerifFont: QFont.SansSerif,
|
||||
QWebSettings.CursiveFont: QFont.Cursive,
|
||||
QWebSettings.FantasyFont: QFont.Fantasy,
|
||||
}
|
||||
|
||||
|
||||
class Setter(Base, websettings.Setter):
|
||||
|
||||
"""A setting set via a QWebSettings setter method."""
|
||||
|
||||
pass
|
||||
def _set_user_stylesheet(settings):
|
||||
"""Set the generated user-stylesheet."""
|
||||
stylesheet = shared.get_user_stylesheet().encode('utf-8')
|
||||
url = urlutils.data_url('text/css;charset=utf-8', stylesheet)
|
||||
settings.setUserStyleSheetUrl(url)
|
||||
|
||||
|
||||
class StaticSetter(Base, websettings.StaticSetter):
|
||||
|
||||
"""A setting set via a static QWebSettings setter method."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class FontFamilySetter(Base, websettings.FontFamilySetter):
|
||||
|
||||
"""A setter for a font family.
|
||||
|
||||
Gets the default value from QFont.
|
||||
"""
|
||||
|
||||
def __init__(self, font):
|
||||
# Mapping from QWebSettings::QWebSettings() in
|
||||
# qtwebkit/Source/WebKit/qt/Api/qwebsettings.cpp
|
||||
font_to_qfont = {
|
||||
QWebSettings.StandardFont: QFont.Serif,
|
||||
QWebSettings.FixedFont: QFont.Monospace,
|
||||
QWebSettings.SerifFont: QFont.Serif,
|
||||
QWebSettings.SansSerifFont: QFont.SansSerif,
|
||||
QWebSettings.CursiveFont: QFont.Cursive,
|
||||
QWebSettings.FantasyFont: QFont.Fantasy,
|
||||
}
|
||||
super().__init__(setter=QWebSettings.setFontFamily, font=font,
|
||||
qfont=font_to_qfont[font])
|
||||
|
||||
|
||||
class CookiePolicy(Base):
|
||||
|
||||
"""The ThirdPartyCookiePolicy setting is different from other settings."""
|
||||
|
||||
MAPPING = {
|
||||
def _set_cookie_accept_policy(settings):
|
||||
"""Update the content.cookies.accept setting."""
|
||||
mapping = {
|
||||
'all': QWebSettings.AlwaysAllowThirdPartyCookies,
|
||||
'no-3rdparty': QWebSettings.AlwaysBlockThirdPartyCookies,
|
||||
'never': QWebSettings.AlwaysBlockThirdPartyCookies,
|
||||
'no-unknown-3rdparty': QWebSettings.AllowThirdPartyWithExistingCookies,
|
||||
}
|
||||
|
||||
def _set(self, value, settings=None):
|
||||
for obj in self._get_settings(settings):
|
||||
obj.setThirdPartyCookiePolicy(self.MAPPING[value])
|
||||
value = config.val.content.cookies.accept
|
||||
settings.setThirdPartyCookiePolicy(mapping[value])
|
||||
|
||||
|
||||
def _set_user_stylesheet():
|
||||
"""Set the generated user-stylesheet."""
|
||||
stylesheet = shared.get_user_stylesheet().encode('utf-8')
|
||||
url = urlutils.data_url('text/css;charset=utf-8', stylesheet)
|
||||
QWebSettings.globalSettings().setUserStyleSheetUrl(url)
|
||||
def _set_cache_maximum_pages(settings):
|
||||
"""Update the content.cache.maximum_pages setting."""
|
||||
value = config.val.content.cache.maximum_pages
|
||||
settings.setMaximumPagesInCache(value)
|
||||
|
||||
|
||||
def _update_settings(option):
|
||||
"""Update global settings when qwebsettings changed."""
|
||||
global_settings.update_setting(option)
|
||||
|
||||
settings = QWebSettings.globalSettings()
|
||||
if option in ['scrollbar.hide', 'content.user_stylesheets']:
|
||||
_set_user_stylesheet()
|
||||
websettings.update_mappings(MAPPINGS, option)
|
||||
_set_user_stylesheet(settings)
|
||||
elif option == 'content.cookies.accept':
|
||||
_set_cookie_accept_policy(settings)
|
||||
elif option == 'content.cache.maximum_pages':
|
||||
_set_cache_maximum_pages(settings)
|
||||
|
||||
|
||||
def init(_args):
|
||||
@@ -131,92 +173,20 @@ def init(_args):
|
||||
QWebSettings.setOfflineStoragePath(
|
||||
os.path.join(data_path, 'offline-storage'))
|
||||
|
||||
websettings.init_mappings(MAPPINGS)
|
||||
_set_user_stylesheet()
|
||||
settings = QWebSettings.globalSettings()
|
||||
_set_user_stylesheet(settings)
|
||||
_set_cookie_accept_policy(settings)
|
||||
_set_cache_maximum_pages(settings)
|
||||
|
||||
config.instance.changed.connect(_update_settings)
|
||||
|
||||
global global_settings
|
||||
global_settings = WebKitSettings(QWebSettings.globalSettings())
|
||||
global_settings.init_settings()
|
||||
|
||||
|
||||
def shutdown():
|
||||
"""Disable storage so removing tmpdir will work."""
|
||||
QWebSettings.setIconDatabasePath('')
|
||||
QWebSettings.setOfflineWebApplicationCachePath('')
|
||||
QWebSettings.globalSettings().setLocalStoragePath('')
|
||||
|
||||
|
||||
MAPPINGS = {
|
||||
'content.images':
|
||||
Attribute(QWebSettings.AutoLoadImages),
|
||||
'content.javascript.enabled':
|
||||
Attribute(QWebSettings.JavascriptEnabled),
|
||||
'content.javascript.can_open_tabs_automatically':
|
||||
Attribute(QWebSettings.JavascriptCanOpenWindows),
|
||||
'content.javascript.can_close_tabs':
|
||||
Attribute(QWebSettings.JavascriptCanCloseWindows),
|
||||
'content.javascript.can_access_clipboard':
|
||||
Attribute(QWebSettings.JavascriptCanAccessClipboard),
|
||||
'content.plugins':
|
||||
Attribute(QWebSettings.PluginsEnabled),
|
||||
'content.webgl':
|
||||
Attribute(QWebSettings.WebGLEnabled),
|
||||
'content.hyperlink_auditing':
|
||||
Attribute(QWebSettings.HyperlinkAuditingEnabled),
|
||||
'content.local_content_can_access_remote_urls':
|
||||
Attribute(QWebSettings.LocalContentCanAccessRemoteUrls),
|
||||
'content.local_content_can_access_file_urls':
|
||||
Attribute(QWebSettings.LocalContentCanAccessFileUrls),
|
||||
'content.cookies.accept':
|
||||
CookiePolicy(),
|
||||
'content.dns_prefetch':
|
||||
Attribute(QWebSettings.DnsPrefetchEnabled),
|
||||
'content.frame_flattening':
|
||||
Attribute(QWebSettings.FrameFlatteningEnabled),
|
||||
'content.cache.appcache':
|
||||
Attribute(QWebSettings.OfflineWebApplicationCacheEnabled),
|
||||
'content.local_storage':
|
||||
Attribute(QWebSettings.LocalStorageEnabled,
|
||||
QWebSettings.OfflineStorageDatabaseEnabled),
|
||||
'content.cache.maximum_pages':
|
||||
StaticSetter(QWebSettings.setMaximumPagesInCache),
|
||||
'content.developer_extras':
|
||||
Attribute(QWebSettings.DeveloperExtrasEnabled),
|
||||
'content.print_element_backgrounds':
|
||||
Attribute(QWebSettings.PrintElementBackgrounds),
|
||||
'content.xss_auditing':
|
||||
Attribute(QWebSettings.XSSAuditingEnabled),
|
||||
'content.default_encoding':
|
||||
Setter(QWebSettings.setDefaultTextEncoding),
|
||||
# content.user_stylesheets is handled separately
|
||||
|
||||
'input.spatial_navigation':
|
||||
Attribute(QWebSettings.SpatialNavigationEnabled),
|
||||
'input.links_included_in_focus_chain':
|
||||
Attribute(QWebSettings.LinksIncludedInFocusChain),
|
||||
|
||||
'fonts.web.family.standard':
|
||||
FontFamilySetter(QWebSettings.StandardFont),
|
||||
'fonts.web.family.fixed':
|
||||
FontFamilySetter(QWebSettings.FixedFont),
|
||||
'fonts.web.family.serif':
|
||||
FontFamilySetter(QWebSettings.SerifFont),
|
||||
'fonts.web.family.sans_serif':
|
||||
FontFamilySetter(QWebSettings.SansSerifFont),
|
||||
'fonts.web.family.cursive':
|
||||
FontFamilySetter(QWebSettings.CursiveFont),
|
||||
'fonts.web.family.fantasy':
|
||||
FontFamilySetter(QWebSettings.FantasyFont),
|
||||
'fonts.web.size.minimum':
|
||||
Setter(QWebSettings.setFontSize, args=[QWebSettings.MinimumFontSize]),
|
||||
'fonts.web.size.minimum_logical':
|
||||
Setter(QWebSettings.setFontSize,
|
||||
args=[QWebSettings.MinimumLogicalFontSize]),
|
||||
'fonts.web.size.default':
|
||||
Setter(QWebSettings.setFontSize, args=[QWebSettings.DefaultFontSize]),
|
||||
'fonts.web.size.default_fixed':
|
||||
Setter(QWebSettings.setFontSize,
|
||||
args=[QWebSettings.DefaultFixedFontSize]),
|
||||
|
||||
'zoom.text_only':
|
||||
Attribute(QWebSettings.ZoomTextOnly),
|
||||
'scrolling.smooth':
|
||||
Attribute(QWebSettings.ScrollAnimatorEnabled),
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -23,16 +23,21 @@ import re
|
||||
import functools
|
||||
import xml.etree.ElementTree
|
||||
|
||||
import pygments
|
||||
import pygments.lexers
|
||||
import pygments.formatters
|
||||
|
||||
import sip
|
||||
from PyQt5.QtCore import (pyqtSlot, Qt, QEvent, QUrl, QPoint, QTimer, QSizeF,
|
||||
QSize)
|
||||
from PyQt5.QtGui import QKeyEvent
|
||||
from PyQt5.QtGui import QKeyEvent, QIcon
|
||||
from PyQt5.QtWebKitWidgets import QWebPage, QWebFrame
|
||||
from PyQt5.QtWebKit import QWebSettings
|
||||
from PyQt5.QtPrintSupport import QPrinter
|
||||
|
||||
from qutebrowser.browser import browsertab
|
||||
from qutebrowser.browser.webkit import webview, tabhistory, webkitelem
|
||||
from qutebrowser.browser import browsertab, shared
|
||||
from qutebrowser.browser.webkit import (webview, tabhistory, webkitelem,
|
||||
webkitsettings)
|
||||
from qutebrowser.utils import qtutils, objreg, usertypes, utils, log, debug
|
||||
|
||||
|
||||
@@ -50,6 +55,29 @@ class WebKitAction(browsertab.AbstractAction):
|
||||
"""Save the current page."""
|
||||
raise browsertab.UnsupportedOperationError
|
||||
|
||||
def show_source(self):
|
||||
|
||||
def show_source_cb(source):
|
||||
"""Show source as soon as it's ready."""
|
||||
# WORKAROUND for https://github.com/PyCQA/pylint/issues/491
|
||||
# pylint: disable=no-member
|
||||
lexer = pygments.lexers.HtmlLexer()
|
||||
formatter = pygments.formatters.HtmlFormatter(
|
||||
full=True, linenos='table')
|
||||
# pylint: enable=no-member
|
||||
highlighted = pygments.highlight(source, lexer, formatter)
|
||||
|
||||
tb = objreg.get('tabbed-browser', scope='window',
|
||||
window=self._tab.win_id)
|
||||
new_tab = tb.tabopen(background=False, related=True)
|
||||
# The original URL becomes the path of a view-source: URL
|
||||
# (without a host), but query/fragment should stay.
|
||||
url = QUrl('view-source:' + urlstr)
|
||||
new_tab.set_html(highlighted, url)
|
||||
|
||||
urlstr = self._tab.url().toString(QUrl.RemoveUserInfo)
|
||||
self._tab.dump_async(show_source_cb)
|
||||
|
||||
|
||||
class WebKitPrinting(browsertab.AbstractPrinting):
|
||||
|
||||
@@ -119,8 +147,17 @@ class WebKitSearch(browsertab.AbstractSearch):
|
||||
|
||||
def search(self, text, *, ignore_case='never', reverse=False,
|
||||
result_cb=None):
|
||||
self.search_displayed = True
|
||||
# Don't go to next entry on duplicate search
|
||||
if self.text == text and self.search_displayed:
|
||||
log.webview.debug("Ignoring duplicate search request"
|
||||
" for {}".format(text))
|
||||
return
|
||||
|
||||
# Clear old search results, this is done automatically on QtWebEngine.
|
||||
self.clear()
|
||||
|
||||
self.text = text
|
||||
self.search_displayed = True
|
||||
self._flags = QWebPage.FindWrapsAroundDocument
|
||||
if self._is_case_sensitive(ignore_case):
|
||||
self._flags |= QWebPage.FindCaseSensitively
|
||||
@@ -161,7 +198,7 @@ class WebKitCaret(browsertab.AbstractCaret):
|
||||
|
||||
settings = self._widget.settings()
|
||||
settings.setAttribute(QWebSettings.CaretBrowsingEnabled, True)
|
||||
self.selection_enabled = bool(self.selection())
|
||||
self.selection_enabled = self._widget.hasSelection()
|
||||
|
||||
if self._widget.isVisible():
|
||||
# Sometimes the caret isn't immediately visible, but unfocusing
|
||||
@@ -174,12 +211,12 @@ class WebKitCaret(browsertab.AbstractCaret):
|
||||
#
|
||||
# Note: We can't use hasSelection() here, as that's always
|
||||
# true in caret mode.
|
||||
if not self.selection():
|
||||
if not self.selection_enabled:
|
||||
self._widget.page().currentFrame().evaluateJavaScript(
|
||||
utils.read_file('javascript/position_caret.js'))
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_mode_left(self):
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def _on_mode_left(self, _mode):
|
||||
settings = self._widget.settings()
|
||||
if settings.testAttribute(QWebSettings.CaretBrowsingEnabled):
|
||||
if self.selection_enabled and self._widget.hasSelection():
|
||||
@@ -327,23 +364,16 @@ class WebKitCaret(browsertab.AbstractCaret):
|
||||
def toggle_selection(self):
|
||||
self.selection_enabled = not self.selection_enabled
|
||||
mainwindow = objreg.get('main-window', scope='window',
|
||||
window=self._win_id)
|
||||
window=self._tab.win_id)
|
||||
mainwindow.status.set_mode_active(usertypes.KeyMode.caret, True)
|
||||
|
||||
def drop_selection(self):
|
||||
self._widget.triggerPageAction(QWebPage.MoveToNextChar)
|
||||
|
||||
def has_selection(self):
|
||||
return self._widget.hasSelection()
|
||||
|
||||
def selection(self, html=False):
|
||||
if html:
|
||||
return self._widget.selectedHtml()
|
||||
return self._widget.selectedText()
|
||||
def selection(self, callback):
|
||||
callback(self._widget.selectedText())
|
||||
|
||||
def follow_selected(self, *, tab=False):
|
||||
if not self.has_selection():
|
||||
return
|
||||
if QWebSettings.globalSettings().testAttribute(
|
||||
QWebSettings.JavascriptEnabled):
|
||||
if tab:
|
||||
@@ -351,7 +381,9 @@ class WebKitCaret(browsertab.AbstractCaret):
|
||||
self._tab.run_js_async(
|
||||
'window.getSelection().anchorNode.parentNode.click()')
|
||||
else:
|
||||
selection = self.selection(html=True)
|
||||
selection = self._widget.selectedHtml()
|
||||
if not selection:
|
||||
return
|
||||
try:
|
||||
selected_element = xml.etree.ElementTree.fromstring(
|
||||
'<html>{}</html>'.format(selection)).find('a')
|
||||
@@ -495,7 +527,8 @@ class WebKitHistory(browsertab.AbstractHistory):
|
||||
return self._history.itemAt(i)
|
||||
|
||||
def _go_to_item(self, item):
|
||||
return self._history.goToItem(item)
|
||||
self._tab.predicted_navigation.emit(item.url())
|
||||
self._history.goToItem(item)
|
||||
|
||||
def serialize(self):
|
||||
return qtutils.serialize(self._history)
|
||||
@@ -615,13 +648,15 @@ class WebKitTab(browsertab.AbstractTab):
|
||||
self._make_private(widget)
|
||||
self.history = WebKitHistory(self)
|
||||
self.scroller = WebKitScroller(self, parent=self)
|
||||
self.caret = WebKitCaret(win_id=win_id, mode_manager=mode_manager,
|
||||
self.caret = WebKitCaret(mode_manager=mode_manager,
|
||||
tab=self, parent=self)
|
||||
self.zoom = WebKitZoom(win_id=win_id, parent=self)
|
||||
self.zoom = WebKitZoom(tab=self, parent=self)
|
||||
self.search = WebKitSearch(parent=self)
|
||||
self.printing = WebKitPrinting()
|
||||
self.elements = WebKitElements(self)
|
||||
self.action = WebKitAction()
|
||||
self.elements = WebKitElements(tab=self)
|
||||
self.action = WebKitAction(tab=self)
|
||||
# We're assigning settings in _set_widget
|
||||
self.settings = webkitsettings.WebKitSettings(settings=None)
|
||||
self._set_widget(widget)
|
||||
self._connect_signals()
|
||||
self.backend = usertypes.Backend.QtWebKit
|
||||
@@ -666,6 +701,7 @@ class WebKitTab(browsertab.AbstractTab):
|
||||
self._widget.shutdown()
|
||||
|
||||
def reload(self, *, force=False):
|
||||
self.predicted_navigation.emit(self.url())
|
||||
if force:
|
||||
action = QWebPage.ReloadAndBypassCache
|
||||
else:
|
||||
@@ -704,6 +740,13 @@ class WebKitTab(browsertab.AbstractTab):
|
||||
page = self._widget.page()
|
||||
return page.userAgentForUrl(self.url())
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_load_started(self):
|
||||
super()._on_load_started()
|
||||
self.networkaccessmanager().netrc_used = False
|
||||
# Make sure the icon is cleared when navigating to a page without one.
|
||||
self.icon_changed.emit(QIcon())
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_frame_load_finished(self):
|
||||
"""Make sure we emit an appropriate status when loading finished.
|
||||
@@ -734,6 +777,31 @@ class WebKitTab(browsertab.AbstractTab):
|
||||
def _on_contents_size_changed(self, size):
|
||||
self.contents_size_changed.emit(QSizeF(size))
|
||||
|
||||
@pyqtSlot(usertypes.NavigationRequest)
|
||||
def _on_navigation_request(self, navigation):
|
||||
super()._on_navigation_request(navigation)
|
||||
if not navigation.accepted:
|
||||
return
|
||||
|
||||
log.webview.debug("target {} override {}".format(
|
||||
self.data.open_target, self.data.override_target))
|
||||
|
||||
if self.data.override_target is not None:
|
||||
target = self.data.override_target
|
||||
self.data.override_target = None
|
||||
else:
|
||||
target = self.data.open_target
|
||||
|
||||
if (navigation.navigation_type == navigation.Type.link_clicked and
|
||||
target != usertypes.ClickTarget.normal):
|
||||
tab = shared.get_tab(self.win_id, target)
|
||||
tab.openurl(navigation.url)
|
||||
self.data.open_target = usertypes.ClickTarget.normal
|
||||
navigation.accepted = False
|
||||
|
||||
if navigation.is_main_frame:
|
||||
self.settings.update_for_url(navigation.url)
|
||||
|
||||
def _connect_signals(self):
|
||||
view = self._widget
|
||||
page = view.page()
|
||||
@@ -752,6 +820,7 @@ class WebKitTab(browsertab.AbstractTab):
|
||||
page.frameCreated.connect(self._on_frame_created)
|
||||
frame.contentsSizeChanged.connect(self._on_contents_size_changed)
|
||||
frame.initialLayoutCompleted.connect(self._on_history_trigger)
|
||||
page.navigation_request.connect(self._on_navigation_request)
|
||||
|
||||
def event_target(self):
|
||||
return self._widget
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -22,6 +22,7 @@
|
||||
import html
|
||||
import functools
|
||||
|
||||
import sip
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QUrl, QPoint
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
|
||||
@@ -33,8 +34,7 @@ from qutebrowser.config import config
|
||||
from qutebrowser.browser import pdfjs, shared
|
||||
from qutebrowser.browser.webkit import http
|
||||
from qutebrowser.browser.webkit.network import networkmanager
|
||||
from qutebrowser.utils import (message, usertypes, log, jinja, objreg, debug,
|
||||
urlutils)
|
||||
from qutebrowser.utils import message, usertypes, log, jinja, objreg
|
||||
|
||||
|
||||
class BrowserPage(QWebPage):
|
||||
@@ -54,10 +54,12 @@ class BrowserPage(QWebPage):
|
||||
shutting_down: Emitted when the page is currently shutting down.
|
||||
reloading: Emitted before a web page reloads.
|
||||
arg: The URL which gets reloaded.
|
||||
navigation_request: Emitted on acceptNavigationRequest.
|
||||
"""
|
||||
|
||||
shutting_down = pyqtSignal()
|
||||
reloading = pyqtSignal(QUrl)
|
||||
navigation_request = pyqtSignal(usertypes.NavigationRequest)
|
||||
|
||||
def __init__(self, win_id, tab_id, tabdata, private, parent=None):
|
||||
super().__init__(parent)
|
||||
@@ -70,7 +72,6 @@ class BrowserPage(QWebPage):
|
||||
}
|
||||
self._ignore_load_started = False
|
||||
self.error_occurred = False
|
||||
self.open_target = usertypes.ClickTarget.normal
|
||||
self._networkmanager = networkmanager.NetworkManager(
|
||||
win_id=win_id, tab_id=tab_id, private=private, parent=self)
|
||||
self.setNetworkAccessManager(self._networkmanager)
|
||||
@@ -147,7 +148,8 @@ class BrowserPage(QWebPage):
|
||||
title="Open external application for {}-link?".format(scheme),
|
||||
text="URL: <b>{}</b>".format(
|
||||
html.escape(url.toDisplayString())),
|
||||
yes_action=functools.partial(QDesktopServices.openUrl, url))
|
||||
yes_action=functools.partial(QDesktopServices.openUrl, url),
|
||||
url=info.url.toString(QUrl.RemovePassword | QUrl.FullyEncoded))
|
||||
return True
|
||||
elif (info.domain, info.error) in ignored_errors:
|
||||
log.webview.debug("Ignored error on {}: {} (error domain: {}, "
|
||||
@@ -219,8 +221,7 @@ class BrowserPage(QWebPage):
|
||||
"""Prepare the web page for being deleted."""
|
||||
self._is_shutting_down = True
|
||||
self.shutting_down.emit()
|
||||
download_manager = objreg.get('qtnetwork-download-manager',
|
||||
scope='window', window=self._win_id)
|
||||
download_manager = objreg.get('qtnetwork-download-manager')
|
||||
nam = self.networkAccessManager()
|
||||
if download_manager.has_downloads_with_nam(nam):
|
||||
nam.setParent(download_manager)
|
||||
@@ -248,8 +249,7 @@ class BrowserPage(QWebPage):
|
||||
after this slot returns.
|
||||
"""
|
||||
req = QNetworkRequest(request)
|
||||
download_manager = objreg.get('qtnetwork-download-manager',
|
||||
scope='window', window=self._win_id)
|
||||
download_manager = objreg.get('qtnetwork-download-manager')
|
||||
download_manager.get_request(req, qnam=self.networkAccessManager())
|
||||
|
||||
@pyqtSlot('QNetworkReply*')
|
||||
@@ -263,8 +263,7 @@ class BrowserPage(QWebPage):
|
||||
here: http://mimesniff.spec.whatwg.org/
|
||||
"""
|
||||
inline, suggested_filename = http.parse_content_disposition(reply)
|
||||
download_manager = objreg.get('qtnetwork-download-manager',
|
||||
scope='window', window=self._win_id)
|
||||
download_manager = objreg.get('qtnetwork-download-manager')
|
||||
if not inline:
|
||||
# Content-Disposition: attachment -> force download
|
||||
download_manager.fetch(reply,
|
||||
@@ -304,6 +303,10 @@ class BrowserPage(QWebPage):
|
||||
Args:
|
||||
frame: The QWebFrame to inject the user scripts into.
|
||||
"""
|
||||
if sip.isdeleted(frame):
|
||||
log.greasemonkey.debug("_inject_userjs called for deleted frame!")
|
||||
return
|
||||
|
||||
url = frame.url()
|
||||
if url.isEmpty():
|
||||
url = frame.requestedUrl()
|
||||
@@ -476,7 +479,7 @@ class BrowserPage(QWebPage):
|
||||
source, line, msg)
|
||||
|
||||
def acceptNavigationRequest(self,
|
||||
_frame: QWebFrame,
|
||||
frame: QWebFrame,
|
||||
request: QNetworkRequest,
|
||||
typ: QWebPage.NavigationType):
|
||||
"""Override acceptNavigationRequest to handle clicked links.
|
||||
@@ -488,36 +491,27 @@ class BrowserPage(QWebPage):
|
||||
Checks if it should open it in a tab (middle-click or control) or not,
|
||||
and then conditionally opens the URL here or in another tab/window.
|
||||
"""
|
||||
url = request.url()
|
||||
log.webview.debug("navigation request: url {}, type {}, "
|
||||
"target {} override {}".format(
|
||||
url.toDisplayString(),
|
||||
debug.qenum_key(QWebPage, typ),
|
||||
self.open_target,
|
||||
self._tabdata.override_target))
|
||||
type_map = {
|
||||
QWebPage.NavigationTypeLinkClicked:
|
||||
usertypes.NavigationRequest.Type.link_clicked,
|
||||
QWebPage.NavigationTypeFormSubmitted:
|
||||
usertypes.NavigationRequest.Type.form_submitted,
|
||||
QWebPage.NavigationTypeFormResubmitted:
|
||||
usertypes.NavigationRequest.Type.form_resubmitted,
|
||||
QWebPage.NavigationTypeBackOrForward:
|
||||
usertypes.NavigationRequest.Type.back_forward,
|
||||
QWebPage.NavigationTypeReload:
|
||||
usertypes.NavigationRequest.Type.reloaded,
|
||||
QWebPage.NavigationTypeOther:
|
||||
usertypes.NavigationRequest.Type.other,
|
||||
}
|
||||
is_main_frame = frame is self.mainFrame()
|
||||
navigation = usertypes.NavigationRequest(url=request.url(),
|
||||
navigation_type=type_map[typ],
|
||||
is_main_frame=is_main_frame)
|
||||
|
||||
if self._tabdata.override_target is not None:
|
||||
target = self._tabdata.override_target
|
||||
self._tabdata.override_target = None
|
||||
else:
|
||||
target = self.open_target
|
||||
if navigation.navigation_type == navigation.Type.reloaded:
|
||||
self.reloading.emit(navigation.url)
|
||||
|
||||
if typ == QWebPage.NavigationTypeReload:
|
||||
self.reloading.emit(url)
|
||||
return True
|
||||
elif typ != QWebPage.NavigationTypeLinkClicked:
|
||||
return True
|
||||
|
||||
if not url.isValid():
|
||||
msg = urlutils.get_errstring(url, "Invalid link clicked")
|
||||
message.error(msg)
|
||||
self.open_target = usertypes.ClickTarget.normal
|
||||
return False
|
||||
|
||||
if target == usertypes.ClickTarget.normal:
|
||||
return True
|
||||
|
||||
tab = shared.get_tab(self._win_id, target)
|
||||
tab.openurl(url)
|
||||
self.open_target = usertypes.ClickTarget.normal
|
||||
return False
|
||||
self.navigation_request.emit(navigation)
|
||||
return navigation.accepted
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -262,10 +262,10 @@ class WebView(QWebView):
|
||||
target = usertypes.ClickTarget.tab_bg
|
||||
else:
|
||||
target = usertypes.ClickTarget.tab
|
||||
self.page().open_target = target
|
||||
self._tabdata.open_target = target
|
||||
log.mouse.debug("Ctrl/Middle click, setting target: {}".format(
|
||||
target))
|
||||
else:
|
||||
self.page().open_target = usertypes.ClickTarget.normal
|
||||
self._tabdata.open_target = usertypes.ClickTarget.normal
|
||||
log.mouse.debug("Normal click, setting normal target")
|
||||
super().mousePressEvent(e)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -26,6 +26,7 @@ For command arguments, there are also some variables you can use:
|
||||
|
||||
- `{url}` expands to the URL of the current page
|
||||
- `{url:pretty}` expands to the URL in decoded format
|
||||
- `{url:host}` expands to the host part of the URL
|
||||
- `{clipboard}` expands to the clipboard contents
|
||||
- `{primary}` expands to the primary selection contents
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -105,7 +105,8 @@ class register: # noqa: N801,N806 pylint: disable=invalid-name
|
||||
else:
|
||||
assert isinstance(self._name, str), self._name
|
||||
name = self._name
|
||||
log.commands.vdebug("Registering command {}".format(name))
|
||||
log.commands.vdebug("Registering command {} (from {}:{})".format(
|
||||
name, func.__module__, func.__qualname__))
|
||||
if name in cmd_dict:
|
||||
raise ValueError("{} is already registered!".format(name))
|
||||
cmd = command.Command(name=name, instance=self._instance,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -193,10 +193,10 @@ class Command:
|
||||
return False
|
||||
|
||||
def _inspect_func(self):
|
||||
"""Inspect the function to get useful informations from it.
|
||||
"""Inspect the function to get useful information from it.
|
||||
|
||||
Sets instance attributes (desc, type_conv, name_conv) based on the
|
||||
informations.
|
||||
information.
|
||||
|
||||
Return:
|
||||
How many user-visible arguments the command has.
|
||||
@@ -394,11 +394,12 @@ class Command:
|
||||
if isinstance(typ, tuple):
|
||||
raise TypeError("{}: Legacy tuple type annotation!".format(
|
||||
self.name))
|
||||
elif type(typ) is type(typing.Union): # noqa: E721
|
||||
elif getattr(typ, '__origin__', None) is typing.Union or (
|
||||
# Older Python 3.5 patch versions
|
||||
# pylint: disable=no-member,useless-suppression
|
||||
hasattr(typing, 'UnionMeta') and
|
||||
isinstance(typ, typing.UnionMeta)):
|
||||
# this is... slightly evil, I know
|
||||
# We also can't use isinstance here because typing.Union doesn't
|
||||
# support that.
|
||||
# pylint: disable=no-member,useless-suppression
|
||||
try:
|
||||
types = list(typ.__args__)
|
||||
except AttributeError:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -63,9 +63,13 @@ def replace_variables(win_id, arglist):
|
||||
QUrl.FullyEncoded | QUrl.RemovePassword),
|
||||
'url:pretty': lambda: _current_url(tabbed_browser).toString(
|
||||
QUrl.DecodeReserved | QUrl.RemovePassword),
|
||||
'url:host': lambda: _current_url(tabbed_browser).host(),
|
||||
'clipboard': utils.get_clipboard,
|
||||
'primary': lambda: utils.get_clipboard(selection=True),
|
||||
}
|
||||
for key in list(variables):
|
||||
modified_key = '{' + key + '}'
|
||||
variables[modified_key] = lambda x=modified_key: x
|
||||
values = {}
|
||||
args = []
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
@@ -162,7 +166,7 @@ class CommandParser:
|
||||
yield self.parse(sub, *args, **kwargs)
|
||||
|
||||
def parse_all(self, *args, **kwargs):
|
||||
"""Wrapper over parse_all."""
|
||||
"""Wrapper over _parse_all_gen."""
|
||||
return list(self._parse_all_gen(*args, **kwargs))
|
||||
|
||||
def parse(self, text, *, fallback=False, keep=False):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -446,3 +446,4 @@ def run_async(tab, cmd, *args, win_id, env, verbose=False):
|
||||
runner.prepare_run(cmd_path, *args, env=env, verbose=verbose)
|
||||
tab.dump_async(runner.store_html)
|
||||
tab.dump_async(runner.store_text, plain=True)
|
||||
return runner
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -60,7 +60,7 @@ class Completer(QObject):
|
||||
self._timer.setSingleShot(True)
|
||||
self._timer.setInterval(0)
|
||||
self._timer.timeout.connect(self._update_completion)
|
||||
self._last_cursor_pos = None
|
||||
self._last_cursor_pos = -1
|
||||
self._last_text = None
|
||||
self._last_completion_func = None
|
||||
self._cmd.update_completion.connect(self.schedule_completion_update)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user