Compare commits
1243 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8bd4d3d24a | ||
|
|
8acbdda00e | ||
|
|
2ccb4342a4 | ||
|
|
0d6d722732 | ||
|
|
d75ee78845 | ||
|
|
88d28e690e | ||
|
|
06f1d00083 | ||
|
|
e0557ac29e | ||
|
|
e603470260 | ||
|
|
5b68092277 | ||
|
|
e05bc20885 | ||
|
|
a97ba9aa09 | ||
|
|
814e6f5959 | ||
|
|
eefc94fb4d | ||
|
|
129990857a | ||
|
|
5ff37c482f | ||
|
|
7001f068b3 | ||
|
|
8a3969e4ac | ||
|
|
51a371ae7c | ||
|
|
8ab0b5b4ac | ||
|
|
8d63f2bf93 | ||
|
|
705d77abfb | ||
|
|
20faecc7a0 | ||
|
|
ea182934f3 | ||
|
|
6eeb3fa32c | ||
|
|
a5f2ac5f03 | ||
|
|
c1d3a94936 | ||
|
|
d8ac32fd0a | ||
|
|
8b76eb54a3 | ||
|
|
a7d699eeb4 | ||
|
|
c2e77cbaa9 | ||
|
|
abfe894027 | ||
|
|
4bae668294 | ||
|
|
5f248abd7d | ||
|
|
2f51dae2b3 | ||
|
|
b7998838d3 | ||
|
|
e9bf5fc219 | ||
|
|
81e1c72588 | ||
|
|
168f65b1a4 | ||
|
|
9101046fe5 | ||
|
|
95e67bba7b | ||
|
|
9f64dfb3b6 | ||
|
|
12f44d0a5e | ||
|
|
4b770f4f35 | ||
|
|
8fdbd94d71 | ||
|
|
59c8e5c3d1 | ||
|
|
7944217da4 | ||
|
|
2b1622b34d | ||
|
|
be0eb0b1cd | ||
|
|
b8727d338f | ||
|
|
6d02ef68ec | ||
|
|
4fdf318fda | ||
|
|
f2ed14a24a | ||
|
|
1279e418ee | ||
|
|
c0b3160676 | ||
|
|
49ed04715e | ||
|
|
a072cf0175 | ||
|
|
384a9ad923 | ||
|
|
3a13e46c25 | ||
|
|
cab1d3c666 | ||
|
|
2f34bdf9b9 | ||
|
|
8e4ca1fc23 | ||
|
|
c3f2cb02c8 | ||
|
|
d7d577b1fa | ||
|
|
738fad50da | ||
|
|
1c13a3cd68 | ||
|
|
623b6fba7e | ||
|
|
0095b72e03 | ||
|
|
b243acf172 | ||
|
|
9eb72bf08a | ||
|
|
ef85d1af4c | ||
|
|
ccb8e31e78 | ||
|
|
58cc1e9202 | ||
|
|
60b9d7e6c3 | ||
|
|
0811418746 | ||
|
|
d89b1f4125 | ||
|
|
dc9cd47afc | ||
|
|
e6548dd9e7 | ||
|
|
3dfaab6194 | ||
|
|
8b141037ef | ||
|
|
1882fb6006 | ||
|
|
2887250641 | ||
|
|
a4f808f56b | ||
|
|
fcfc09f541 | ||
|
|
ea4d9e884e | ||
|
|
5e0090d5b8 | ||
|
|
0ee13392e1 | ||
|
|
fc3a3ea8c6 | ||
|
|
fcf94fd527 | ||
|
|
ffc76b2a5c | ||
|
|
84ed602b8d | ||
|
|
3687011e09 | ||
|
|
6b89eb43a2 | ||
|
|
5c769d8000 | ||
|
|
d3dc90cb2a | ||
|
|
f787f004af | ||
|
|
1e43b882ae | ||
|
|
b3e4ac8043 | ||
|
|
45d6dc6aa3 | ||
|
|
eed4a992ab | ||
|
|
172157ed0d | ||
|
|
a7dee6c053 | ||
|
|
fc84e58043 | ||
|
|
8786e979a6 | ||
|
|
f19de5b908 | ||
|
|
e78d2a8399 | ||
|
|
c1951a8f15 | ||
|
|
e9a0dffe3b | ||
|
|
a1f4914271 | ||
|
|
982e78249a | ||
|
|
a157f822d6 | ||
|
|
e6284ed5d4 | ||
|
|
bba6589e19 | ||
|
|
7f83c6c5c8 | ||
|
|
4a58e10e09 | ||
|
|
a387e9669c | ||
|
|
7b5f51b145 | ||
|
|
90c782a891 | ||
|
|
a2423b0b6c | ||
|
|
fd96685b02 | ||
|
|
c74bcf95aa | ||
|
|
ee845590b7 | ||
|
|
0f97fad87d | ||
|
|
879f99bddf | ||
|
|
06298022a3 | ||
|
|
430b133c41 | ||
|
|
494d48aac1 | ||
|
|
3a2d031479 | ||
|
|
49be07eb18 | ||
|
|
7245e300e6 | ||
|
|
d267cbd44f | ||
|
|
24607fda8b | ||
|
|
202bf59edc | ||
|
|
ec06247327 | ||
|
|
10dd1b50b9 | ||
|
|
b3b2fb51fc | ||
|
|
9b88167ae1 | ||
|
|
bed37defde | ||
|
|
dc4600008b | ||
|
|
fd63b21261 | ||
|
|
8d7e43e4c4 | ||
|
|
73b4704d6c | ||
|
|
057cd70c9e | ||
|
|
14c083f915 | ||
|
|
6058c8aab2 | ||
|
|
3506947add | ||
|
|
1bc8d10ac7 | ||
|
|
dddd1e87a5 | ||
|
|
9814c24d9e | ||
|
|
491a02e4a5 | ||
|
|
c319b524bf | ||
|
|
2a030107b9 | ||
|
|
0a6c82d071 | ||
|
|
d8d873f044 | ||
|
|
5acc982b44 | ||
|
|
1941af8add | ||
|
|
e68965fea2 | ||
|
|
4170e3c9af | ||
|
|
4e888e8e19 | ||
|
|
f342f129c6 | ||
|
|
7df01440a3 | ||
|
|
cab6bf87b4 | ||
|
|
4e73aa9f0f | ||
|
|
e64f64e456 | ||
|
|
f23af85161 | ||
|
|
d418f7f9fa | ||
|
|
71d1aed041 | ||
|
|
91fcc91092 | ||
|
|
eeadeb400c | ||
|
|
6e8ac374dd | ||
|
|
de3460da3e | ||
|
|
eff0e4c7cc | ||
|
|
cf54fa1ef1 | ||
|
|
e1f53302be | ||
|
|
b3515f5e82 | ||
|
|
5817b47f75 | ||
|
|
109984c96e | ||
|
|
803398c49b | ||
|
|
ad72b26b1a | ||
|
|
c7fdcc92b8 | ||
|
|
788a096150 | ||
|
|
150a83d8f4 | ||
|
|
a156d51844 | ||
|
|
31265b80b6 | ||
|
|
b59951cc98 | ||
|
|
c9d47ae92a | ||
|
|
25545617a0 | ||
|
|
3a5ce22eac | ||
|
|
d53c4f2702 | ||
|
|
81f522336f | ||
|
|
8be04e4f6c | ||
|
|
9cce8fe825 | ||
|
|
7e8f16dd22 | ||
|
|
6f9b02741a | ||
|
|
251e657bd1 | ||
|
|
009e29789a | ||
|
|
101f81844c | ||
|
|
a42d99a8b7 | ||
|
|
9755a9b00f | ||
|
|
5b5003d599 | ||
|
|
99258dac46 | ||
|
|
5dc891f207 | ||
|
|
41b9fd7853 | ||
|
|
d00f999dcf | ||
|
|
455e5f6523 | ||
|
|
6ad1d37eb5 | ||
|
|
5f13fd2ece | ||
|
|
7d9ef320d2 | ||
|
|
f93785a0be | ||
|
|
a987c08b69 | ||
|
|
2f955470db | ||
|
|
48252258dd | ||
|
|
6e392f0f09 | ||
|
|
0004bebc09 | ||
|
|
a9de97968d | ||
|
|
f7a13fa1f9 | ||
|
|
2ba55a0e77 | ||
|
|
27d0c004e2 | ||
|
|
288bf1524e | ||
|
|
e227712e21 | ||
|
|
7e6be517e3 | ||
|
|
307fe6b109 | ||
|
|
a4f47150b4 | ||
|
|
a008b488b1 | ||
|
|
a8b808819a | ||
|
|
b9aa40ea74 | ||
|
|
98a454a428 | ||
|
|
d286f637d6 | ||
|
|
f7a3a9e015 | ||
|
|
736fd7d45e | ||
|
|
6a46aea934 | ||
|
|
a67644589e | ||
|
|
f9d7f20aac | ||
|
|
f5febc4807 | ||
|
|
e26c499bd6 | ||
|
|
8d5b6b2dad | ||
|
|
052e758f38 | ||
|
|
429f520997 | ||
|
|
1d4c51b78a | ||
|
|
c236e70920 | ||
|
|
83b94a8be5 | ||
|
|
03b6459d2f | ||
|
|
8cf6ace0e7 | ||
|
|
913aa209c6 | ||
|
|
dbc6f63fc0 | ||
|
|
03ba38578e | ||
|
|
fdde05569c | ||
|
|
c14b52f916 | ||
|
|
c322130dc0 | ||
|
|
6f65397dfe | ||
|
|
9b453aaad5 | ||
|
|
9aaf5c18c1 | ||
|
|
55edd9cda7 | ||
|
|
2867df4c21 | ||
|
|
6431542eba | ||
|
|
2a705e2eb6 | ||
|
|
75f9f2af8d | ||
|
|
ff6d3e05a6 | ||
|
|
73f999da31 | ||
|
|
f847ddf3cb | ||
|
|
9d92baa996 | ||
|
|
bf3bd3bb9d | ||
|
|
962ba36cda | ||
|
|
d6acb3c3f4 | ||
|
|
b79049261e | ||
|
|
d5b634301f | ||
|
|
f577da9a6b | ||
|
|
e9ca1ba5c1 | ||
|
|
be4cf19bb1 | ||
|
|
f3c378858b | ||
|
|
edf5463007 | ||
|
|
84d6359990 | ||
|
|
b344f92a90 | ||
|
|
a43c206893 | ||
|
|
e4947b03be | ||
|
|
b4a630563f | ||
|
|
dbdedf74c0 | ||
|
|
23068e9679 | ||
|
|
3d291482a3 | ||
|
|
bf8a82db12 | ||
|
|
8d1a60528d | ||
|
|
8e7a76280b | ||
|
|
26bbf11ed2 | ||
|
|
7f5e07d7cc | ||
|
|
3bcc80048d | ||
|
|
6f1474be71 | ||
|
|
f56e7b1220 | ||
|
|
987ee59f58 | ||
|
|
5a95cfdc7a | ||
|
|
bc28a168cc | ||
|
|
102db181c5 | ||
|
|
7eb6f658eb | ||
|
|
b8467b8fef | ||
|
|
7baed5f80f | ||
|
|
fac76576d7 | ||
|
|
c861cf54e6 | ||
|
|
fc328b275a | ||
|
|
9328e1703c | ||
|
|
d082c248a8 | ||
|
|
951c2e8eb6 | ||
|
|
c755a78518 | ||
|
|
a6045ab551 | ||
|
|
67f726c9d4 | ||
|
|
73e9fd1118 | ||
|
|
816d62bca5 | ||
|
|
eed2584e3f | ||
|
|
0a7b552111 | ||
|
|
21a1f9ca65 | ||
|
|
9670ffb78e | ||
|
|
388cc7ae29 | ||
|
|
9fe02d55c9 | ||
|
|
7cfea665ff | ||
|
|
24a71e5c2e | ||
|
|
ba4471bb2f | ||
|
|
eaec930605 | ||
|
|
f48bebb7ff | ||
|
|
027a10c04b | ||
|
|
8f2b29a1f9 | ||
|
|
93ef85ff16 | ||
|
|
f8668ff710 | ||
|
|
c22e1df400 | ||
|
|
28b739b9b5 | ||
|
|
287b2ba258 | ||
|
|
925a5ba809 | ||
|
|
8faf4717e5 | ||
|
|
2ef2e3088f | ||
|
|
d58a5d4d15 | ||
|
|
b7323914b8 | ||
|
|
35de87f973 | ||
|
|
b37dac7bfa | ||
|
|
e405492667 | ||
|
|
7b1e2ab471 | ||
|
|
3905884a84 | ||
|
|
43d7b139e8 | ||
|
|
d13ae4c666 | ||
|
|
922fdc0526 | ||
|
|
4c1eff625f | ||
|
|
542d13b70d | ||
|
|
75178b0cdc | ||
|
|
14f4689998 | ||
|
|
0becee05e1 | ||
|
|
c73de6ea4d | ||
|
|
b0d4ebe844 | ||
|
|
9479a50784 | ||
|
|
196d1e95be | ||
|
|
607ca0f478 | ||
|
|
d4c24c935c | ||
|
|
79680f505e | ||
|
|
f3b89dba9b | ||
|
|
e2da9aa3f8 | ||
|
|
d324dd5f70 | ||
|
|
39f29e2531 | ||
|
|
17b5ccde0e | ||
|
|
162c8b30e6 | ||
|
|
7a82bb55e4 | ||
|
|
1e79aae231 | ||
|
|
76143574ef | ||
|
|
31892b437e | ||
|
|
52aca30342 | ||
|
|
e6eaa5f140 | ||
|
|
b7b4ee1b27 | ||
|
|
ae00dac0be | ||
|
|
0d60ec43ed | ||
|
|
16e1a65448 | ||
|
|
89c9b5959e | ||
|
|
d68798a15b | ||
|
|
efa745b53f | ||
|
|
049102c644 | ||
|
|
f4cccdf13f | ||
|
|
b45b8d57bb | ||
|
|
135dc64de7 | ||
|
|
ee0627128a | ||
|
|
ea06c64ed4 | ||
|
|
f3f0ef9841 | ||
|
|
4fa32bd0cd | ||
|
|
706a4e3d39 | ||
|
|
86eda2843d | ||
|
|
39def173ca | ||
|
|
eb232734b4 | ||
|
|
4f165a7669 | ||
|
|
7d6327873a | ||
|
|
59c9c6ccb0 | ||
|
|
8607d527bf | ||
|
|
11c9155961 | ||
|
|
8b8ce6d9f0 | ||
|
|
8c88b8168f | ||
|
|
c54df5f142 | ||
|
|
c7294781f5 | ||
|
|
c12011c84d | ||
|
|
2727f3c2a8 | ||
|
|
401f71236d | ||
|
|
b9240924d2 | ||
|
|
d9f1699a3b | ||
|
|
b776aeac84 | ||
|
|
af875f4b8f | ||
|
|
0a9a75c337 | ||
|
|
0c2a285fef | ||
|
|
d5bc962046 | ||
|
|
7f8c118991 | ||
|
|
b38a3caa4c | ||
|
|
cd78086d10 | ||
|
|
729d29b8a7 | ||
|
|
09e310277d | ||
|
|
fb8f200291 | ||
|
|
613faf84ef | ||
|
|
fe1c8ee4e8 | ||
|
|
097a14d5f3 | ||
|
|
cb40b1020c | ||
|
|
a991a8c43c | ||
|
|
3466ee03f6 | ||
|
|
452c6f5310 | ||
|
|
1ec03462c8 | ||
|
|
9a310dd1fb | ||
|
|
22a8f8def6 | ||
|
|
1e37e09e29 | ||
|
|
2dc7fc3b5a | ||
|
|
b9163c5079 | ||
|
|
bb807cfa07 | ||
|
|
d2baced354 | ||
|
|
cdb6c6b3a3 | ||
|
|
a164cd4da2 | ||
|
|
271cb47f87 | ||
|
|
c7f386cec0 | ||
|
|
4d1f37f296 | ||
|
|
55992337b8 | ||
|
|
4060fd5e90 | ||
|
|
3438a45b19 | ||
|
|
0d6d276592 | ||
|
|
90c1240ad4 | ||
|
|
dd2ec30b97 | ||
|
|
9f89033b8e | ||
|
|
202fc8a5bd | ||
|
|
0298fcc841 | ||
|
|
a845bf22ff | ||
|
|
3251bd8419 | ||
|
|
dcfa73cf91 | ||
|
|
c46015b906 | ||
|
|
89e3c2a703 | ||
|
|
3ec38539c5 | ||
|
|
6f2407ac26 | ||
|
|
b735d079d5 | ||
|
|
7239f7bb68 | ||
|
|
98fbdde846 | ||
|
|
244d86c85a | ||
|
|
cb624ea6ee | ||
|
|
ad5988513b | ||
|
|
0ccd19f980 | ||
|
|
fbe5eb4671 | ||
|
|
be19c76369 | ||
|
|
d37b311dcb | ||
|
|
ea5484c3a2 | ||
|
|
f20ffca540 | ||
|
|
5a10ca96c5 | ||
|
|
4b531d142b | ||
|
|
e010602791 | ||
|
|
54fe9407c4 | ||
|
|
7822a33975 | ||
|
|
09999654e2 | ||
|
|
773982ad23 | ||
|
|
516a81c3cc | ||
|
|
726525b26a | ||
|
|
40e2258ef3 | ||
|
|
71134f97e3 | ||
|
|
779df4c08e | ||
|
|
7fe9be432a | ||
|
|
bc96da47ef | ||
|
|
bbaab24ce8 | ||
|
|
b68adf1245 | ||
|
|
a4d15b550e | ||
|
|
eef760359c | ||
|
|
6a4ea944cf | ||
|
|
fe08cb24f8 | ||
|
|
eeab4d41ba | ||
|
|
29a1620e81 | ||
|
|
7de206e350 | ||
|
|
3d44d619fc | ||
|
|
1af30772b1 | ||
|
|
4c75422c05 | ||
|
|
145772476b | ||
|
|
cfea69494a | ||
|
|
9f1c6e0139 | ||
|
|
384c753094 | ||
|
|
46d335cdee | ||
|
|
d745819715 | ||
|
|
e9ece3d114 | ||
|
|
cd912b7582 | ||
|
|
9607fae935 | ||
|
|
b61462ccb8 | ||
|
|
1da7996c3b | ||
|
|
04c2e45bee | ||
|
|
3290048458 | ||
|
|
205af3737f | ||
|
|
0085421ec6 | ||
|
|
ce66d731f2 | ||
|
|
c29643b98d | ||
|
|
3695e05691 | ||
|
|
55b83409d9 | ||
|
|
40c7990d3a | ||
|
|
85c82b32fb | ||
|
|
a740ace115 | ||
|
|
1dd5bb1596 | ||
|
|
4210d7e15d | ||
|
|
00ccc236bb | ||
|
|
25921792ef | ||
|
|
354259777a | ||
|
|
69a92af097 | ||
|
|
200467c7f3 | ||
|
|
ef79b87597 | ||
|
|
ba902f1d92 | ||
|
|
aef26e7779 | ||
|
|
9c5ce8a688 | ||
|
|
30e2afb59d | ||
|
|
2a9d56790f | ||
|
|
f22ccae6fd | ||
|
|
c759bf7a2f | ||
|
|
256352024b | ||
|
|
1f3df64fe4 | ||
|
|
ad8c9988f0 | ||
|
|
18da73227b | ||
|
|
f0083adfb5 | ||
|
|
15b77bf2e9 | ||
|
|
e25ae49c0e | ||
|
|
62b0c4d178 | ||
|
|
3680f01576 | ||
|
|
4e3cd8b9e8 | ||
|
|
0f8ec73c55 | ||
|
|
43c3a38938 | ||
|
|
416cfaf002 | ||
|
|
a5efbe7412 | ||
|
|
6bd45bbf24 | ||
|
|
7cddd52b2d | ||
|
|
d99f9a3a20 | ||
|
|
54e2cea460 | ||
|
|
a26e99f004 | ||
|
|
128465f12b | ||
|
|
7701bf602a | ||
|
|
cd25a25c96 | ||
|
|
788ea2720b | ||
|
|
2ca23d8037 | ||
|
|
250f0e7410 | ||
|
|
374b448e51 | ||
|
|
a5ee39c35e | ||
|
|
d71618031d | ||
|
|
ada4b669bc | ||
|
|
e1c6cd6c6d | ||
|
|
dc3bfb5eb4 | ||
|
|
6b4dbad15b | ||
|
|
596ed5f545 | ||
|
|
f440953ada | ||
|
|
6e7d6fb00e | ||
|
|
3fac74656e | ||
|
|
aaf62fc6d0 | ||
|
|
a2a1b77857 | ||
|
|
fe8ddd79c0 | ||
|
|
c0535727ef | ||
|
|
2fc1612bd4 | ||
|
|
566f94111c | ||
|
|
5e38861649 | ||
|
|
0162583444 | ||
|
|
41f7c11ab5 | ||
|
|
6579866abe | ||
|
|
d288c9598d | ||
|
|
4f6415631f | ||
|
|
9a2125fc18 | ||
|
|
65648da1ad | ||
|
|
dce44f2dc5 | ||
|
|
f3d76b5af6 | ||
|
|
1aebefca18 | ||
|
|
35c36725f2 | ||
|
|
f79722975f | ||
|
|
a1bc020fec | ||
|
|
4f01382c64 | ||
|
|
71533b3456 | ||
|
|
5601c8e7c1 | ||
|
|
9694b3b548 | ||
|
|
252dc9a8bd | ||
|
|
c2218f51cd | ||
|
|
f34161423c | ||
|
|
a780325a3a | ||
|
|
ae8a9b8798 | ||
|
|
8bb887ddab | ||
|
|
a1e0ccb787 | ||
|
|
f2f9529af7 | ||
|
|
ed8a6a4c7b | ||
|
|
3a2bb2d348 | ||
|
|
12a9deb9bc | ||
|
|
d1f8d29c20 | ||
|
|
8cf0af004f | ||
|
|
a898fd21d1 | ||
|
|
b17d74452f | ||
|
|
dd8ff860f4 | ||
|
|
b027e6af1b | ||
|
|
dab0db30a5 | ||
|
|
919365dfa1 | ||
|
|
1902e4858f | ||
|
|
957d68c477 | ||
|
|
ce1a99cc7c | ||
|
|
706b8c6600 | ||
|
|
6601df14a3 | ||
|
|
420c087373 | ||
|
|
749b1c02cc | ||
|
|
b05a0d191d | ||
|
|
2eeace1c2c | ||
|
|
a092ef1fe6 | ||
|
|
9bf9124324 | ||
|
|
366916a8bf | ||
|
|
bf90c8c06b | ||
|
|
5fcbc839bb | ||
|
|
afa2f339e6 | ||
|
|
cb477a2623 | ||
|
|
a63aed5965 | ||
|
|
ba81332d45 | ||
|
|
d3a21927f2 | ||
|
|
e5bfb9884b | ||
|
|
a3cc71e317 | ||
|
|
83aee4fad5 | ||
|
|
8593144fa7 | ||
|
|
f58f6f24ee | ||
|
|
64c74bde90 | ||
|
|
05cc4b9650 | ||
|
|
8eafa1a105 | ||
|
|
02c1fa1232 | ||
|
|
991b6d4fc9 | ||
|
|
5c6b715720 | ||
|
|
11ed60620a | ||
|
|
6b086d159d | ||
|
|
679ab65b5f | ||
|
|
fd7820ea16 | ||
|
|
111feebf89 | ||
|
|
49a32f0041 | ||
|
|
024ae52366 | ||
|
|
930871be01 | ||
|
|
fbe5386e56 | ||
|
|
99e090db78 | ||
|
|
30db09bbda | ||
|
|
0daf5885be | ||
|
|
b59a56921e | ||
|
|
89c0ff0d9b | ||
|
|
021ea444a1 | ||
|
|
8b016df023 | ||
|
|
62f11273c5 | ||
|
|
64730f566f | ||
|
|
8aa29a2ba2 | ||
|
|
a05da2a956 | ||
|
|
ae0e391c04 | ||
|
|
56bdb74ed9 | ||
|
|
9dfe84c197 | ||
|
|
9cfd96fcef | ||
|
|
c1c5b0f2b4 | ||
|
|
95483f73d8 | ||
|
|
612174ada0 | ||
|
|
19a9985f0d | ||
|
|
44e60ccc3e | ||
|
|
4c4515cba9 | ||
|
|
4c2e92c998 | ||
|
|
25ecd9068c | ||
|
|
f5f74b7ddc | ||
|
|
da88908815 | ||
|
|
3fcc27636a | ||
|
|
cbb9fd203a | ||
|
|
5541e3ed32 | ||
|
|
b0430ca3e7 | ||
|
|
547fc9f40e | ||
|
|
10e52c6e9f | ||
|
|
bf74fda5b2 | ||
|
|
76f5f4fefb | ||
|
|
beab639d7a | ||
|
|
924b0052c6 | ||
|
|
6c718bc839 | ||
|
|
8ccadda0e9 | ||
|
|
5fc26bc477 | ||
|
|
59bc72cfbb | ||
|
|
58c5b52ff3 | ||
|
|
ac10fbc095 | ||
|
|
939e95b344 | ||
|
|
3525659b90 | ||
|
|
4e8cf70c10 | ||
|
|
8354894838 | ||
|
|
af3318e72a | ||
|
|
c383b42af3 | ||
|
|
38756fc466 | ||
|
|
6c20190473 | ||
|
|
702842c977 | ||
|
|
782f09488a | ||
|
|
dc06787f83 | ||
|
|
896ac0a7e9 | ||
|
|
84c498b638 | ||
|
|
0233423d9a | ||
|
|
a923572341 | ||
|
|
2c7b0d2fb4 | ||
|
|
49137150ad | ||
|
|
2427bf5cb6 | ||
|
|
85608a8b8d | ||
|
|
7dadd97f01 | ||
|
|
17396e1030 | ||
|
|
d992390cbe | ||
|
|
e31240e6c9 | ||
|
|
c78bbd9fd0 | ||
|
|
535bcab310 | ||
|
|
f5eb755ef3 | ||
|
|
7e0e770d53 | ||
|
|
e13de98790 | ||
|
|
7da0d2b6d5 | ||
|
|
3155e20999 | ||
|
|
ce6ba605e4 | ||
|
|
bb4152d705 | ||
|
|
144acc9f91 | ||
|
|
4da2bdfaa7 | ||
|
|
d9af27670b | ||
|
|
0fb2778e4b | ||
|
|
16ba597173 | ||
|
|
1906a8c66e | ||
|
|
066c7959e8 | ||
|
|
1563693037 | ||
|
|
a1b394d373 | ||
|
|
7431d7cf89 | ||
|
|
8884de71d8 | ||
|
|
362ef3f74e | ||
|
|
611cf7eba7 | ||
|
|
672d11e25a | ||
|
|
0d67cff5cb | ||
|
|
56758c8cea | ||
|
|
6dc3b5de36 | ||
|
|
8e09fd929b | ||
|
|
4451165a27 | ||
|
|
b79ccb5e79 | ||
|
|
dac2898585 | ||
|
|
6431997a5a | ||
|
|
239bc3bdea | ||
|
|
dfc1782bbf | ||
|
|
70decdc2c8 | ||
|
|
e88e9a66da | ||
|
|
4b683cdd8f | ||
|
|
1c7fd7d80e | ||
|
|
18ead66f04 | ||
|
|
5ed419c7f5 | ||
|
|
2be5c4cd27 | ||
|
|
86d32f6e19 | ||
|
|
1a562594fa | ||
|
|
3a948be490 | ||
|
|
ac148c11ec | ||
|
|
b878b139dd | ||
|
|
be20001594 | ||
|
|
ee8d538964 | ||
|
|
8664e45558 | ||
|
|
6e786ff9b7 | ||
|
|
4bf6359205 | ||
|
|
c052c8a107 | ||
|
|
af000a8ac6 | ||
|
|
ffc465e863 | ||
|
|
7d17957e90 | ||
|
|
670a4d274b | ||
|
|
76fcec4e4c | ||
|
|
baa3bd18a0 | ||
|
|
37d37148b7 | ||
|
|
867f509bcc | ||
|
|
3a522fb551 | ||
|
|
79e80afac8 | ||
|
|
998d78c5b9 | ||
|
|
e86795f644 | ||
|
|
70a6fe1561 | ||
|
|
d127469d78 | ||
|
|
b94f7c7681 | ||
|
|
404da750c6 | ||
|
|
571d7a680b | ||
|
|
35762955cf | ||
|
|
3c2bc670ff | ||
|
|
e1446c3448 | ||
|
|
39e37b043e | ||
|
|
11e5774f46 | ||
|
|
b2dceb078f | ||
|
|
367e501e56 | ||
|
|
468b2c4ade | ||
|
|
d5fe1d3635 | ||
|
|
c09ae4675c | ||
|
|
4c03197984 | ||
|
|
5bd07d23a3 | ||
|
|
e5b98a9762 | ||
|
|
6d6ef1e386 | ||
|
|
d2672bce86 | ||
|
|
27ec9e1c43 | ||
|
|
b5af1c8730 | ||
|
|
b3fa19eb96 | ||
|
|
05994ad90e | ||
|
|
88ba4831a8 | ||
|
|
6125e51de3 | ||
|
|
af70e783b6 | ||
|
|
5a34fdfd0c | ||
|
|
7cb462ff82 | ||
|
|
1488f59d8f | ||
|
|
f807842a52 | ||
|
|
8600acddb1 | ||
|
|
a4aacde88f | ||
|
|
9a8032fa91 | ||
|
|
a6526a1be2 | ||
|
|
2cab750a54 | ||
|
|
e4c79a68d1 | ||
|
|
618d9ceabf | ||
|
|
ebe9835e5a | ||
|
|
6f07eb562f | ||
|
|
497fba5667 | ||
|
|
8de3f8d487 | ||
|
|
11098b8b82 | ||
|
|
776a89820f | ||
|
|
fe32b349a5 | ||
|
|
8004508b3c | ||
|
|
9b104b0af7 | ||
|
|
a0c2c37a40 | ||
|
|
02dda23be6 | ||
|
|
daaa15396c | ||
|
|
381d857f2c | ||
|
|
1d2683993e | ||
|
|
66b337c2b1 | ||
|
|
021a379dd2 | ||
|
|
fa671a7b5e | ||
|
|
7656d1320a | ||
|
|
4fcb2feced | ||
|
|
c85aa40073 | ||
|
|
c335aff900 | ||
|
|
0cdc744afd | ||
|
|
6f646a9da5 | ||
|
|
fc573963f4 | ||
|
|
4e48e3d725 | ||
|
|
66afdbaf16 | ||
|
|
cf623f0d8d | ||
|
|
8919a152fe | ||
|
|
8dc5c95cef | ||
|
|
8fee25491b | ||
|
|
a1ade58557 | ||
|
|
68024ba6bd | ||
|
|
883febe243 | ||
|
|
34cc2870f4 | ||
|
|
589e8e9d05 | ||
|
|
9b3987febb | ||
|
|
bb5e5137cd | ||
|
|
2d4adf4476 | ||
|
|
eb1a1bbdd8 | ||
|
|
08c4bfefe0 | ||
|
|
f614e5b98a | ||
|
|
857a70ded7 | ||
|
|
343e0b89c0 | ||
|
|
cdb9c0998f | ||
|
|
9722d4ba03 | ||
|
|
279d0926ee | ||
|
|
0851999b89 | ||
|
|
7a413ad6d5 | ||
|
|
8d88dd9d75 | ||
|
|
bb75cb23b9 | ||
|
|
3d93413022 | ||
|
|
14334dce21 | ||
|
|
023d80fe40 | ||
|
|
67a0a6b944 | ||
|
|
87e94930b5 | ||
|
|
24887a6564 | ||
|
|
de0542929a | ||
|
|
9482662d7e | ||
|
|
4a8dec5eb8 | ||
|
|
82c608038d | ||
|
|
0b78fb65c9 | ||
|
|
aa62a547d5 | ||
|
|
da800e3fa7 | ||
|
|
a99d3f6525 | ||
|
|
d9d2366f27 | ||
|
|
81556430c9 | ||
|
|
fabe53564f | ||
|
|
aef4f4ed00 | ||
|
|
ef8a681fcc | ||
|
|
daf81f5fcd | ||
|
|
f6fffee9d3 | ||
|
|
406b7a7034 | ||
|
|
4fb374e764 | ||
|
|
aba31babca | ||
|
|
b12cfa9d05 | ||
|
|
db350719d5 | ||
|
|
4dd3483aca | ||
|
|
4f4dfb1e31 | ||
|
|
764914a8b2 | ||
|
|
0187dd6ac6 | ||
|
|
45f9e61815 | ||
|
|
2f36789ff4 | ||
|
|
7d026efbfb | ||
|
|
e03068ed84 | ||
|
|
08bbb6b7c7 | ||
|
|
7703fa217b | ||
|
|
71a150af22 | ||
|
|
332df99a77 | ||
|
|
6ebb37aa17 | ||
|
|
0a40dfced6 | ||
|
|
8e0389fd37 | ||
|
|
bd189392a8 | ||
|
|
5fce514168 | ||
|
|
7519694e22 | ||
|
|
5499e686e2 | ||
|
|
66e0812ab6 | ||
|
|
de4ee92b56 | ||
|
|
9f11990efc | ||
|
|
4f10e553a8 | ||
|
|
a88962fa98 | ||
|
|
d326cc050e | ||
|
|
ec3cafc293 | ||
|
|
9c68a928ea | ||
|
|
86ee53a7e6 | ||
|
|
82dfec6a18 | ||
|
|
e92a7495c4 | ||
|
|
9b5c0075b9 | ||
|
|
f1b9a3408f | ||
|
|
d862821552 | ||
|
|
c29562d769 | ||
|
|
b22fbe3993 | ||
|
|
c8346a11fc | ||
|
|
78c57ad416 | ||
|
|
2398762e85 | ||
|
|
ff4c002096 | ||
|
|
6391da4f6f | ||
|
|
b18549bbed | ||
|
|
0400945ac4 | ||
|
|
869e2d9127 | ||
|
|
9b8b2130ef | ||
|
|
09b18fbc68 | ||
|
|
2a11adc8ac | ||
|
|
27db1ad891 | ||
|
|
743c3b1e26 | ||
|
|
24dc166e1f | ||
|
|
71e6c38065 | ||
|
|
4bb1a37cf1 | ||
|
|
309be9b057 | ||
|
|
619f47ee1d | ||
|
|
57d8ebfb83 | ||
|
|
19c27a04e5 | ||
|
|
0e186487f5 | ||
|
|
ebd576d98f | ||
|
|
5c85dd22b4 | ||
|
|
91ce3ed672 | ||
|
|
326757917c | ||
|
|
5f8ea6dc16 | ||
|
|
e6ee0c08cf | ||
|
|
f858af666f | ||
|
|
6843e9c413 | ||
|
|
4876bdf7ce | ||
|
|
e78b00cce2 | ||
|
|
d7d4c232d0 | ||
|
|
26596316c6 | ||
|
|
ab099ea6ea | ||
|
|
36b7db0672 | ||
|
|
62fa2811e3 | ||
|
|
d3925e91a3 | ||
|
|
67ecd93326 | ||
|
|
65418307fd | ||
|
|
28a28763c7 | ||
|
|
f8ad011a32 | ||
|
|
294eb19e61 | ||
|
|
3e9088083f | ||
|
|
904b7b202e | ||
|
|
18e52922c1 | ||
|
|
893df967df | ||
|
|
2f075c382b | ||
|
|
1acd32f697 | ||
|
|
9c0ef87a62 | ||
|
|
4f2dbb3a72 | ||
|
|
d0be9256b5 | ||
|
|
b5dc9d485d | ||
|
|
749d7bfc3c | ||
|
|
2c8aa26c93 | ||
|
|
93a64a4ae5 | ||
|
|
bfccb91e42 | ||
|
|
20bd1cc5fd | ||
|
|
ffab11c871 | ||
|
|
661f7cde92 | ||
|
|
10f4798559 | ||
|
|
6be5c65c36 | ||
|
|
224ab3237d | ||
|
|
0960f229f0 | ||
|
|
fb9e3639d0 | ||
|
|
0845671165 | ||
|
|
94d88e280b | ||
|
|
c2ea2aa6d7 | ||
|
|
129df05932 | ||
|
|
a5df7675eb | ||
|
|
7db1f65425 | ||
|
|
2e46efccdc | ||
|
|
2918f33569 | ||
|
|
52ced6c652 | ||
|
|
b0671ef530 | ||
|
|
3153f69945 | ||
|
|
5ecd935ee3 | ||
|
|
6d117eac6a | ||
|
|
2f6d0083d6 | ||
|
|
7e25d30814 | ||
|
|
424809b120 | ||
|
|
1e8170d98b | ||
|
|
afc166a13e | ||
|
|
7e2ae9f39f | ||
|
|
bdff26a024 | ||
|
|
439c00f32a | ||
|
|
d094a495cc | ||
|
|
98e451c90e | ||
|
|
d9018fed14 | ||
|
|
f79db832e4 | ||
|
|
aa9498bb41 | ||
|
|
d7ca469d08 | ||
|
|
4b2e0470c8 | ||
|
|
5e958faf29 | ||
|
|
bc26592bb7 | ||
|
|
04619e0f81 | ||
|
|
0a3844a3be | ||
|
|
fda477397e | ||
|
|
45c9768c16 | ||
|
|
228b4104b0 | ||
|
|
cabbe406e3 | ||
|
|
54557fee20 | ||
|
|
17778f1457 | ||
|
|
6293842c18 | ||
|
|
b6f3b1951e | ||
|
|
bff93ff00a | ||
|
|
03754b360a | ||
|
|
005753e83e | ||
|
|
f4fdcbdd71 | ||
|
|
54ae1582af | ||
|
|
55efd1358b | ||
|
|
12a11a5931 | ||
|
|
f607659ece | ||
|
|
252abc8954 | ||
|
|
83a75bd263 | ||
|
|
4600832eb6 | ||
|
|
de49e5f0a5 | ||
|
|
7ff881c3e3 | ||
|
|
6a4785ec3a | ||
|
|
a2c94e448e | ||
|
|
c6c34c4ac5 | ||
|
|
801d2ae8e6 | ||
|
|
fbf6696e2a | ||
|
|
89e0b96bb6 | ||
|
|
d229e90724 | ||
|
|
0b4dc12869 | ||
|
|
de5308cbbf | ||
|
|
739d2cfffd | ||
|
|
73ba3ddaee | ||
|
|
f12fbe875e | ||
|
|
7761dd5af4 | ||
|
|
c78f83e692 | ||
|
|
b863c9807b | ||
|
|
8cdbd2a15f | ||
|
|
2c719006cf | ||
|
|
240e271b0d | ||
|
|
18bd20e109 | ||
|
|
b88a22b139 | ||
|
|
9f7836131d | ||
|
|
b42b12b7a5 | ||
|
|
454b2348a8 | ||
|
|
ea663f9975 | ||
|
|
264f9c4919 | ||
|
|
afa7a1a9a0 | ||
|
|
5c52d4d04e | ||
|
|
93ed853c36 | ||
|
|
d6301beb2a | ||
|
|
16ac3baf2e | ||
|
|
e4a216e7cf | ||
|
|
8e52e5f2fc | ||
|
|
65891c6f0d | ||
|
|
c9a959043b | ||
|
|
8228a96180 | ||
|
|
f552f433f8 | ||
|
|
0b4cee420f | ||
|
|
f16cc5986c | ||
|
|
37e5a46808 | ||
|
|
d849cd42e5 | ||
|
|
f7d49f29b7 | ||
|
|
c989351b14 | ||
|
|
293c9f1022 | ||
|
|
044c0a0c4f | ||
|
|
54d4f4f48b | ||
|
|
ce9aafdc1b | ||
|
|
be332fe723 | ||
|
|
4881d81444 | ||
|
|
ab6390b44e | ||
|
|
7c72929af4 | ||
|
|
57abd53e56 | ||
|
|
ae3497a3a1 | ||
|
|
bc631d7d8b | ||
|
|
7fcbbc98f6 | ||
|
|
bed90d1319 | ||
|
|
1bdb012b2c | ||
|
|
eb662a2468 | ||
|
|
f972d043c5 | ||
|
|
07c02041fc | ||
|
|
edc74657ad | ||
|
|
985c067f90 | ||
|
|
b82772ca4c | ||
|
|
14ca66f20e | ||
|
|
637133f39f | ||
|
|
a164eee1ba | ||
|
|
2e62d24062 | ||
|
|
ddeabc6643 | ||
|
|
b5ec476ca6 | ||
|
|
75d53e2879 | ||
|
|
a98878dd8a | ||
|
|
11961db72c | ||
|
|
5db4ed0ed1 | ||
|
|
bb9bd40f6b | ||
|
|
9846011ca7 | ||
|
|
866017f9c1 | ||
|
|
590caa53f5 | ||
|
|
2cfc1361b5 | ||
|
|
1e5028a7f3 | ||
|
|
2500b1f759 | ||
|
|
4854ca42fd | ||
|
|
1861b0a5e4 | ||
|
|
7cd7b43e7a | ||
|
|
1b6860b748 | ||
|
|
88caa1a8c8 | ||
|
|
3789a37f5b | ||
|
|
3e2985d776 | ||
|
|
05bcddb6f9 | ||
|
|
43a42def2b | ||
|
|
4fa2f34af4 | ||
|
|
d7dd1b3507 | ||
|
|
fc7488645f | ||
|
|
717298e423 | ||
|
|
8bcc0e4b92 | ||
|
|
70597d574f | ||
|
|
a6ceab5dbc | ||
|
|
2940a4267b | ||
|
|
0de1e40f20 | ||
|
|
a9144cdd21 | ||
|
|
a76d9a5914 | ||
|
|
311ae78bc3 | ||
|
|
cbc0721906 | ||
|
|
6a8c1e62b7 | ||
|
|
977bfb4c73 | ||
|
|
5ba7b5cf0f | ||
|
|
64a6b518dc | ||
|
|
f96de5a598 | ||
|
|
957116658d | ||
|
|
7d6c39d64b | ||
|
|
ae07e00038 | ||
|
|
a8a2fd2e7d | ||
|
|
34f8e9ef18 | ||
|
|
fd2855f800 | ||
|
|
8dbc1cd1ed | ||
|
|
54ff8d2c0e | ||
|
|
841e8fbbd1 | ||
|
|
f62bf099a0 | ||
|
|
72cddb290b | ||
|
|
a80c61e78a | ||
|
|
a4569b11ad | ||
|
|
2cb1f9226a | ||
|
|
fbf9c74752 | ||
|
|
20e5c4cbe9 | ||
|
|
d9bad853e7 | ||
|
|
4adce826b0 | ||
|
|
7e84a1a5b8 | ||
|
|
7eeabb2467 | ||
|
|
0f69487f22 | ||
|
|
a6f113375c | ||
|
|
01f424fdb0 | ||
|
|
c195ee225d | ||
|
|
929957c4fb | ||
|
|
c530312aca | ||
|
|
fd264631c4 | ||
|
|
2bfb7609ac | ||
|
|
48b599a774 | ||
|
|
db513aa956 | ||
|
|
855f8402c6 | ||
|
|
9e25e3b96b | ||
|
|
b8f200b370 | ||
|
|
bb224a9d39 | ||
|
|
ee2b9adce4 | ||
|
|
accbf157e0 | ||
|
|
ac27f46170 | ||
|
|
f9645d608b | ||
|
|
a589ddab4a | ||
|
|
731a86ce33 | ||
|
|
7f573616c8 | ||
|
|
57242e925c | ||
|
|
375b7c8ab2 | ||
|
|
d5baed5e83 | ||
|
|
0745de647b | ||
|
|
545d82a04d | ||
|
|
80d3bb712d | ||
|
|
ef17f50298 | ||
|
|
503e3ce48d | ||
|
|
8a01dc9194 | ||
|
|
c451fc3883 | ||
|
|
28f34b76af | ||
|
|
8c66f3c45a | ||
|
|
98e470cc22 | ||
|
|
fe16b95f00 | ||
|
|
a01c172bc4 | ||
|
|
1a1862a10d | ||
|
|
88167ce4ce | ||
|
|
7eca4acb6a | ||
|
|
55cf8516e5 | ||
|
|
b47fd21b5a | ||
|
|
dd679c6c14 | ||
|
|
fc806525a2 | ||
|
|
523e071a97 | ||
|
|
ccdb59cce1 | ||
|
|
3904b7de58 | ||
|
|
9561b7b02c | ||
|
|
f83f4a6a1a | ||
|
|
08e1d9a304 | ||
|
|
0fa9da56bf | ||
|
|
9ab14c0384 | ||
|
|
443a2e1657 | ||
|
|
7ffff72368 | ||
|
|
dd2a70e01f | ||
|
|
73cb981ebb | ||
|
|
ff24ba5437 | ||
|
|
47b75e1cdc | ||
|
|
91b4ba2a48 | ||
|
|
d887bbeb24 | ||
|
|
89b4adf158 | ||
|
|
0bbd410016 | ||
|
|
00d6970c66 | ||
|
|
cc052a539f | ||
|
|
df095daf24 | ||
|
|
04ec9c2624 | ||
|
|
3bdfa3001c | ||
|
|
748496e88d | ||
|
|
f5221ceb4b | ||
|
|
f1ce6d0ed6 | ||
|
|
8e925cc5a2 | ||
|
|
d054fda4ac | ||
|
|
5130be4495 | ||
|
|
ec11a61ed8 | ||
|
|
f0a2128499 | ||
|
|
2aa7e5bb35 | ||
|
|
c08beda1aa | ||
|
|
e075e6c9df | ||
|
|
09b0877eab | ||
|
|
330f3f8f13 | ||
|
|
b16a482b4c | ||
|
|
7a32bbd955 | ||
|
|
851bf4cd31 | ||
|
|
6541a360b1 | ||
|
|
d36c701b07 | ||
|
|
418328e61b | ||
|
|
bbdec1779a | ||
|
|
96e3a0b1f1 | ||
|
|
de5cdf6f0f | ||
|
|
e5b7fdb565 | ||
|
|
197feade32 | ||
|
|
fe6a6c33ae |
@@ -4,15 +4,14 @@ cache:
|
||||
- C:\projects\qutebrowser\.cache
|
||||
build: off
|
||||
environment:
|
||||
PYTHON: 'C:\Python34'
|
||||
PYTHONUNBUFFERED: 1
|
||||
matrix:
|
||||
- TESTENV: py34
|
||||
- TESTENV: unittests-frozen
|
||||
- TESTENV: pylint
|
||||
|
||||
install:
|
||||
- C:\Python27\python -u scripts\dev\ci_install.py
|
||||
|
||||
test_script:
|
||||
- C:\Python34\Scripts\tox -e py34
|
||||
- C:\Python34\Scripts\tox -e py34-integration
|
||||
- C:\Python34\Scripts\tox -e unittests-frozen
|
||||
- C:\Python34\Scripts\tox -e smoke-frozen
|
||||
- C:\Python34\Scripts\tox -e pylint
|
||||
- C:\Python34\Scripts\tox -e %TESTENV% -- -p "no:sugar" -v --junitxml=junit.xml
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
[run]
|
||||
source = qutebrowser
|
||||
branch = true
|
||||
omit =
|
||||
qutebrowser/__main__.py
|
||||
|
||||
1
.eslintignore
Normal file
1
.eslintignore
Normal file
@@ -0,0 +1 @@
|
||||
qutebrowser/3rdparty/pdfjs/*
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -21,7 +21,8 @@ __pycache__
|
||||
/.venv
|
||||
/.coverage
|
||||
/htmlcov
|
||||
/.coverage.xml
|
||||
/coverage.xml
|
||||
/.coverage.*
|
||||
/.tox
|
||||
/testresults.html
|
||||
/.cache
|
||||
|
||||
27
.pylintrc
27
.pylintrc
@@ -9,8 +9,8 @@ load-plugins=pylint_checkers.config,
|
||||
pylint_checkers.settrace
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
enable=all
|
||||
disable=no-self-use,
|
||||
bad-builtin,
|
||||
fixme,
|
||||
global-statement,
|
||||
locally-disabled,
|
||||
@@ -23,28 +23,29 @@ disable=no-self-use,
|
||||
blacklisted-name,
|
||||
too-many-lines,
|
||||
logging-format-interpolation,
|
||||
interface-not-implemented,
|
||||
broad-except,
|
||||
bare-except,
|
||||
eval-used,
|
||||
exec-used,
|
||||
file-ignored
|
||||
file-ignored,
|
||||
wrong-import-order,
|
||||
ungrouped-imports,
|
||||
redefined-variable-type,
|
||||
suppressed-message
|
||||
|
||||
[BASIC]
|
||||
module-rgx=(__)?[a-z][a-z0-9_]*(__)?$
|
||||
function-rgx=([a-z_][a-z0-9_]{2,50}|setUpModule|tearDownModule)$
|
||||
function-rgx=[a-z_][a-z0-9_]{2,50}$
|
||||
const-rgx=[A-Za-z_][A-Za-z0-9_]{0,30}$
|
||||
method-rgx=[a-z_][A-Za-z0-9_]{2,50}$
|
||||
attr-rgx=[a-z_][a-z0-9_]{0,30}$
|
||||
argument-rgx=[a-z_][a-z0-9_]{0,30}$
|
||||
variable-rgx=[a-z_][a-z0-9_]{0,30}$
|
||||
class-attribute-rgx=[A-Za-z_][A-Za-z0-9_]{1,30}$
|
||||
inlinevar-rgx=[a-z_][a-z0-9_]*$
|
||||
docstring-min-length=3
|
||||
|
||||
[FORMAT]
|
||||
max-line-length=79
|
||||
ignore-long-lines=<?https?://
|
||||
ignore-long-lines=(<?https?://|^# Copyright 201\d)
|
||||
expected-line-ending-format=LF
|
||||
|
||||
[SIMILARITIES]
|
||||
min-similarity-lines=8
|
||||
@@ -52,11 +53,13 @@ min-similarity-lines=8
|
||||
[VARIABLES]
|
||||
dummy-variables-rgx=_.*
|
||||
|
||||
[CLASSES]
|
||||
defining-attr-methods=__init__,__new__,setUp
|
||||
|
||||
[DESIGN]
|
||||
max-args=10
|
||||
|
||||
[TYPECHECK]
|
||||
ignored-classes=WebElementWrapper,AnsiCodes,UnsetObject
|
||||
# MsgType added as WORKAROUND for
|
||||
# https://bitbucket.org/logilab/pylint/issues/690/
|
||||
# UnsetObject because pylint infers any objreg.get(...) as UnsetObject.
|
||||
ignored-classes=qutebrowser.utils.objreg.UnsetObject,
|
||||
qutebrowser.browser.webelem.WebElementWrapper,
|
||||
scripts.dev.check_coverage.MsgType
|
||||
|
||||
67
.travis.yml
67
.travis.yml
@@ -1,9 +1,30 @@
|
||||
# So we get Ubuntu Trusty - using "dist: trusty" breaks OS X.
|
||||
sudo: required
|
||||
dist: trusty
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
|
||||
env:
|
||||
global:
|
||||
- PATH=/home/travis/bin:/home/travis/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
matrix:
|
||||
- TESTENV=py35
|
||||
- TESTENV=py34
|
||||
- TESTENV=unittests-nodisp
|
||||
- TESTENV=misc
|
||||
- TESTENV=vulture
|
||||
- TESTENV=pep257
|
||||
- TESTENV=pyflakes
|
||||
- TESTENV=pep8
|
||||
- TESTENV=mccabe
|
||||
- TESTENV=pyroma
|
||||
- TESTENV=check-manifest
|
||||
- TESTENV=pylint
|
||||
- TESTENV=eslint
|
||||
|
||||
# Not really, but this is here so we can do stuff by hand.
|
||||
language: c
|
||||
|
||||
@@ -16,18 +37,40 @@ install:
|
||||
- python scripts/dev/ci_install.py
|
||||
|
||||
script:
|
||||
- xvfb-run -s "-screen 0 640x480x16" tox -e py34,py34-integration
|
||||
- tox -e misc
|
||||
- tox -e pep257
|
||||
- tox -e pyflakes
|
||||
- tox -e pep8
|
||||
- tox -e mccabe
|
||||
- tox -e pylint
|
||||
- tox -e pyroma
|
||||
- tox -e check-manifest
|
||||
- tox -e $TESTENV -- -p no:sugar -v --cov-report term tests
|
||||
|
||||
after_success:
|
||||
- '[[ ($TESTENV == py34 || $TESTENV == py35) && $TRAVIS_OX == linux ]] && codecov -e TESTENV -X gcov'
|
||||
|
||||
# Travis bug - OS X builds get routed to Ubuntu Trusty if "dist: trusty" is
|
||||
# given.
|
||||
matrix:
|
||||
allow_failures:
|
||||
exclude:
|
||||
- os: linux
|
||||
env: TESTENV=py35
|
||||
- os: osx
|
||||
env: TESTENV=py34
|
||||
- os: osx
|
||||
env: TESTENV=unittests-nodisp
|
||||
- os: osx
|
||||
env: TESTENV=misc
|
||||
- os: osx
|
||||
env: TESTENV=vulture
|
||||
- os: osx
|
||||
env: TESTENV=pep257
|
||||
- os: osx
|
||||
env: TESTENV=pyflakes
|
||||
- os: osx
|
||||
env: TESTENV=pep8
|
||||
- os: osx
|
||||
env: TESTENV=mccabe
|
||||
- os: osx
|
||||
env: TESTENV=pyroma
|
||||
- os: osx
|
||||
env: TESTENV=check-manifest
|
||||
- os: osx
|
||||
env: TESTENV=pylint
|
||||
- os: osx
|
||||
env: TESTENV=eslint
|
||||
|
||||
notifications:
|
||||
webhooks:
|
||||
- https://buildtimetrend.herokuapp.com/travis
|
||||
|
||||
@@ -14,6 +14,144 @@ This project adheres to http://semver.org/[Semantic Versioning].
|
||||
// `Fixed` for any bug fixes.
|
||||
// `Security` to invite users to upgrade in case of vulnerabilities.
|
||||
|
||||
v0.5.0
|
||||
------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- Ability to preview PDFs using pdf.js in the browser if it's installed. This
|
||||
is disabled by default and can be enabled using the
|
||||
`content -> pdfjs-enabled` setting.
|
||||
- New setting `ui -> hide-wayland-decoration` to hide the window decoration
|
||||
when using wayland.
|
||||
- New userscripts in `misc/userscripts`:
|
||||
- `open_download` to easily open a file in your downloads folder.
|
||||
- `view_in_mpv` to open a video in mpv and remove it from the page.
|
||||
- `qutedmenu` and `dmenu_qutebrowser` to select URLs via dmenu
|
||||
- New setting `content -> host-blocking-whitelist` to whitelist certain domains
|
||||
from the adblocker.
|
||||
- `{scroll_pos}` can now be used in `ui -> window-title-format` and
|
||||
`tabs -> title-format`.
|
||||
- New setting `general -> url-incdec-segments` to configure which segments of
|
||||
the URL should be affected by `:navigate increment/decrement`.
|
||||
- New `--target` argument to specify how URLs should be opened in an existing
|
||||
instance.
|
||||
- New setting `statusbar.url.fg.success.https` to set the foreground color for
|
||||
the URL when a page was loaded via HTTPS.
|
||||
- The scrollbar in the completion is now styled, and the following new options
|
||||
got added:
|
||||
* `completion -> scrollbar-width`
|
||||
* `completion -> scrollbar-padding`
|
||||
* `colors -> completion.scrollbar.fg`
|
||||
* `colors -> completion.scrollbar.bg`
|
||||
- New value `none` for options taking a color system so they don't display a
|
||||
gradient:
|
||||
* `colors -> tabs.indicator.system`
|
||||
* `colors -> downloads.fg.system`
|
||||
* `colors -> downloads.bg.system`
|
||||
- New command `:download-retry` to retry a failed download.
|
||||
- New command `:download-clear` which replaces `:download-remove --all`.
|
||||
- `:set-cmd-text` has a new `--append` argument to append to the current
|
||||
statusbar text.
|
||||
- qutebrowser now uses `~/.netrc` if available to authenticate via HTTP.
|
||||
- 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.
|
||||
- New option `tabs -> title-alignment` to change the alignment of tab titles.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- The `colors -> tabs.bg/fg.selected` option got split into
|
||||
`tabs.bg/fg.selected.odd/even`.
|
||||
- `:spawn --userscript` and `:hint` with the `userscript` target now look up
|
||||
relative paths in `~/.local/share/qutebrowser/userscripts` or
|
||||
`$XDG_DATA_DIR`. Using a binary in `$PATH` won't work anymore with
|
||||
`--userscript`.
|
||||
- New design for error pages
|
||||
- Link filtering for hints now checks if the text is contained anywhere in
|
||||
the link, and matches case-insensitively.
|
||||
- The `ui -> remove-finished-downloads` option got changed to an integer and
|
||||
now takes a time (in milliseconds) to keep the download around after it's
|
||||
finished. When set to `-1`, downloads are never removed.
|
||||
- The `:follow-hint` command now optionally takes the keystring of a hint to
|
||||
follow.
|
||||
- `:scroll-px` now doesn't take floats anymore, which made little sense.
|
||||
- Updated the user agent list for the `:set network user-agent` completion.
|
||||
- Starting with `--debug` doesn't log `VDEBUG` messages anymore (add
|
||||
`--loglevel VDEBUG` to get them).
|
||||
- `:debug-console` now hides the console if it's already shown.
|
||||
- `:yank-selected` now doesn't log the selected text anymore.
|
||||
- `general -> log-javascript-console` got changed from a boolean to an option
|
||||
taking a loglevel (`none`, `info`, `debug`).
|
||||
- `:tab-move +/-` now wraps around if `tabs -> wrap` is `true`.
|
||||
- When a subprocess (like launched by `:spawn`) fails, its stdout/stderr is now
|
||||
logged to the console.
|
||||
- A search engine name can now contain any non-space character, like dashes.
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- `:download-remove --all` is now deprecated and `:download-clear` should be
|
||||
used instead.
|
||||
- `:download <url> <destination>` is now deprecated and
|
||||
`:download --dest <destination> <url>` should be used instead.
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- `:scroll` with two pixel-arguments (deprecated in v0.3.0)
|
||||
- The `:run-userscript` command (deprecated in v0.2.0)
|
||||
- The `rapid` and `rapid-win` targets for `:hint` (deprecated in v0.2.0)
|
||||
- The `:cancel-download` command (deprecated in v0.2.0)
|
||||
- The `:download-page` command (deprecated in v0.2.0)
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Fixed retrying of downloads which were started in a now closed tab.
|
||||
- Fixed displaying of web history if `web-history-max-items` is set to -1.
|
||||
- Cloned tabs now don't display favicons anymore if show-favicons is False.
|
||||
- Fixed a crash when clicking a bookmark name and pressing `Ctrl-D`.
|
||||
- Fixed a crash when a website presents a very small favicon.
|
||||
- Fixed prompting for download directory when
|
||||
`storage -> prompt-download-directory` was unset.
|
||||
- Fixed crash when using `:follow-hint` outside of hint mode.
|
||||
- Fixed crash when using `:set foo bar?` with invalid section/option.
|
||||
- Fixed scrolling to the very left/right with `:scroll-perc`.
|
||||
- Using an external editor should now work correctly with some funny chars
|
||||
(U+2028/U+2029/BOM).
|
||||
- Movements in caret mode now should work correctly on OS X and Windows.
|
||||
- Fixed upgrade from earlier config versions.
|
||||
- Fixed crash when killing a running userscript.
|
||||
- Fixed characters being passed through when shifted with
|
||||
`forward-unbound-keys` set to `auto`.
|
||||
- Fixed restarting after a crash is reported.
|
||||
- Removed `.pyc` files accidentally contained in source releases.
|
||||
|
||||
v0.4.1
|
||||
------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Adjusted AppArmor config for the IPC changes in v0.4.0.
|
||||
- Fixed atime update frequency for IPC file.
|
||||
- Worked around a Qt issue where middle-clicking caused scrolling with a
|
||||
touchpad to restart at the beginning of the page.
|
||||
- The `completion -> web-history-max-items` setting is now also respected for
|
||||
items added after starting qutebrowser.
|
||||
- Search terms are now shared between different tabs again
|
||||
- Tests (a reduced subset of them) now run correctly again when DISPLAY is not
|
||||
set.
|
||||
- Fixed an issue causing qutebrowser to crash with Python 3.5 as soon as an ad
|
||||
was blocked.
|
||||
- Fixed an issue causing qutebrowser to not start with more recent Python 3.4
|
||||
versions (e.g. on Debian experimental).
|
||||
- Fixed various `PendingDeprecationWarnings` shown with Python 3.5.
|
||||
|
||||
v0.4.0
|
||||
------
|
||||
|
||||
@@ -423,7 +561,7 @@ Removed
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
* Fix rare exception when a key is pressed shorly after opening a window
|
||||
* Fix rare exception when a key is pressed shortly after opening a window
|
||||
* Fix exception with certain invalid URLs like `http:foo:0`
|
||||
* Work around Qt bug which renders checkboxes on OS X unusable
|
||||
* Fix exception when a local files can't be read in `:adblock-update`
|
||||
|
||||
@@ -227,7 +227,7 @@ Hints
|
||||
Python and Qt objects
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
For many tasks, there are solutions in both Qt and the Python standard libary
|
||||
For many tasks, there are solutions in both Qt and the Python standard library
|
||||
available.
|
||||
|
||||
In qutebrowser, the policy is usually using the Python libraries, as they
|
||||
@@ -548,27 +548,40 @@ workaround.
|
||||
https://github.com/The-Compiler/qutebrowser/issues?q=is%3Aopen+is%3Aissue+label%3Aqt[qutebrowser
|
||||
bugs] and check if they're fixed.
|
||||
|
||||
New PyQt release
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
* See above
|
||||
* Install new PyQt in Windows VM (32- and 64-bit)
|
||||
* Download new installer and update PyQt installer path in `ci_install.py`.
|
||||
|
||||
qutebrowser release
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Make sure there are no unstaged changes.
|
||||
* Run `src2asciidoc.py` and commit changes if necessary.
|
||||
* Run `asciidoc2html.py`.
|
||||
* Adjust `__version_info__` in `qutebrowser/__init__.py`.
|
||||
* Make sure there are no unstaged changes and the tests are green.
|
||||
|
||||
* Run all tests on all supported systems.
|
||||
* Test an upgrade from the previous version (no manual intervention).
|
||||
* Test an upgrade from the first version (no manual intervention).
|
||||
* Adjust `__version_info__` in `qutebrowser/__init__.py`.
|
||||
* Remove *(unreleased)* from changelog.
|
||||
* Run tests again
|
||||
* Run `asciidoc2html.py`.
|
||||
* Commit
|
||||
|
||||
* Create annotated git tag (`git tag -s "v0.X.Y" -m "Release v0.X.Y"`)
|
||||
* If it's a new minor, create git branch `v0.X.x`
|
||||
* `git push`; `git push "v0.X.Y"`
|
||||
* If committing on minor branch, cherry-pick release commit to master.
|
||||
* `git push origin`; `git push origin v0.X.Y`
|
||||
* Create release on github
|
||||
* Mark the milestone at https://github.com/The-Compiler/qutebrowser/milestones
|
||||
as closed.
|
||||
|
||||
* Create standalone Windows package (32/64bit) in Windows VM
|
||||
* Upload to PyPI: `python setup.py register sdist upload --sign`
|
||||
* Upload to qutebrowser.org with checksum/GPG
|
||||
* Run `scripts/dev/build_release.py` on Linux to build an sdist
|
||||
* Upload to PyPI: `twine upload dist/foo{,.asc}`
|
||||
* Create Windows packages via `scripts/dev/build_release.py` and upload.
|
||||
|
||||
* Upload to qutebrowser.org with checksum/GPG
|
||||
- On server: `sudo mkdir -p /srv/http/qutebrowser/releases/v0.X.Y/windows`
|
||||
- `rsync -avPh dist/ tonks:`
|
||||
- On server: `sudo mv qutebrowser-0.X.Y.tar.gz* /srv/http/qutebrowser/releases/v0.X.Y`
|
||||
|
||||
* Update AUR package
|
||||
* Announce to qutebrowser mailinglist
|
||||
|
||||
17
FAQ.asciidoc
17
FAQ.asciidoc
@@ -1,5 +1,6 @@
|
||||
Frequently asked questions
|
||||
==========================
|
||||
:title: Frequently asked questions
|
||||
The Compiler <mail@qutebrowser.org>
|
||||
|
||||
[qanda]
|
||||
@@ -60,7 +61,7 @@ Why Python?::
|
||||
|
||||
But isn't Python too slow for a browser?::
|
||||
http://www.infoworld.com/d/application-development/van-rossum-python-not-too-slow-188715[No.]
|
||||
I believe efficency while coding is a lot more important than efficency
|
||||
I believe efficiency while coding is a lot more important than efficiency
|
||||
while running. Also, most of the heavy lifting of qutebrowser is done by Qt
|
||||
and WebKit in C++, with the
|
||||
https://wiki.python.org/moin/GlobalInterpreterLock[GIL] released.
|
||||
@@ -87,6 +88,16 @@ Note that you might need an additional package (e.g.
|
||||
https://www.archlinux.org/packages/community/any/youtube-dl/[youtube-dl] on
|
||||
Archlinux) to play web videos with mpv.
|
||||
|
||||
How do I use qutebrowser with mutt?::
|
||||
Due to a Qt limitation, local files without `.html` extensions are
|
||||
"downloaded" instead of displayed, see
|
||||
https://github.com/The-Compiler/qutebrowser/issues/566[#566]. You can work
|
||||
around this by using this in your `mailcap`:
|
||||
+
|
||||
----
|
||||
text/html; mv %s %s.html && qutebrowser %s.html >/dev/null 2>/dev/null; needsterminal;
|
||||
----
|
||||
|
||||
== Troubleshooting
|
||||
|
||||
Configuration not saved after modifying config.::
|
||||
@@ -103,7 +114,7 @@ Unable to view flash content.::
|
||||
Experiencing freezing on sites like duckduckgo and youtube.::
|
||||
This issue could be caused by stale plugin files installed by `mozplugger`
|
||||
if mozplugger was subsequently removed.
|
||||
Try exiting qutebroser and removing `~/.mozilla/plugins/mozplugger*.so`.
|
||||
Try exiting qutebrowser and removing `~/.mozilla/plugins/mozplugger*.so`.
|
||||
See https://github.com/The-Compiler/qutebrowser/issues/357[Issue #357]
|
||||
for more details.
|
||||
|
||||
@@ -115,7 +126,7 @@ Experiencing segfaults (crashes) on Debian systems.::
|
||||
|
||||
Segfaults on Facebook, Medium, Amazon, ...::
|
||||
If you are on a Debian or Ubuntu based system, you might experience some crashes
|
||||
visting these sites. This is caused by various bugs in Qt which have been
|
||||
visiting these sites. This is caused by various bugs in Qt which have been
|
||||
fixed in Qt 5.4. However Debian and Ubuntu are slow to adopt or upgrade
|
||||
some packages. On Debian Jessie, it's recommended to use the experimental
|
||||
repos as described in https://github.com/The-Compiler/qutebrowser/blob/master/INSTALL.asciidoc#on-debian--ubuntu[INSTALL].
|
||||
|
||||
@@ -8,7 +8,7 @@ qutebrowser should run on these systems:
|
||||
|
||||
* Debian jessie or newer
|
||||
* Ubuntu Trusty (14.04 LTS) or newer
|
||||
* Any other distribution based on these (e.g. Linux Mint)
|
||||
* Any other distribution based on these (e.g. Linux Mint 17+)
|
||||
|
||||
Unfortunately there is no Debian package yet, but installing qutebrowser is
|
||||
still relatively easy! If you want to help packaging it for Debian, please
|
||||
@@ -32,7 +32,7 @@ Then install the packages like this:
|
||||
|
||||
----
|
||||
# apt-get update
|
||||
# apt-get install -t experimental python3-pyqt5 python3-pyqt5.qtwebkit python3-sip
|
||||
# apt-get install -t experimental python3-pyqt5 python3-pyqt5.qtwebkit python3-sip python3-dev
|
||||
# apt-get install python-tox
|
||||
----
|
||||
|
||||
@@ -50,14 +50,14 @@ For distributions other than Debian or if you prefer to not use the
|
||||
experimental repo:
|
||||
|
||||
----
|
||||
# apt-get install python3-pyqt5 python3-pyqt5.qtwebkit python-tox python3-sip
|
||||
# apt-get install python3-pyqt5 python3-pyqt5.qtwebkit python-tox python3-sip python3-dev
|
||||
----
|
||||
|
||||
To generate the documentation for the `:help` command, when using the git
|
||||
repository (rather than a release):
|
||||
|
||||
----
|
||||
# apt-get install asciidoc
|
||||
# apt-get install asciidoc source-highlight
|
||||
$ python3 scripts/asciidoc2html.py
|
||||
----
|
||||
|
||||
@@ -83,7 +83,7 @@ To generate the documentation for the `:help` command, when using the git
|
||||
repository (rather than a release):
|
||||
|
||||
----
|
||||
# dnf install asciidoc
|
||||
# dnf install asciidoc source-highlight
|
||||
$ python3 scripts/asciidoc2html.py
|
||||
----
|
||||
|
||||
@@ -208,7 +208,7 @@ it as part of the packaging process.
|
||||
Installing qutebrowser with tox
|
||||
-------------------------------
|
||||
|
||||
Run tox like this to set up a
|
||||
Run tox inside the qutebrowser repository to set up a
|
||||
https://docs.python.org/3/library/venv.html[virtual environment]:
|
||||
|
||||
----
|
||||
|
||||
71
MANIFEST.in
71
MANIFEST.in
@@ -1,34 +1,37 @@
|
||||
global-exclude __pycache__ *.pyc *.pyo
|
||||
|
||||
recursive-include qutebrowser *.py
|
||||
recursive-include qutebrowser/html *.html
|
||||
recursive-include qutebrowser/img *.svg
|
||||
recursive-include qutebrowser/test *.py
|
||||
recursive-include qutebrowser/javascript *.js
|
||||
graft icons
|
||||
graft doc/img
|
||||
graft misc
|
||||
graft scripts
|
||||
include qutebrowser/utils/testfile
|
||||
include qutebrowser/git-commit-id
|
||||
include COPYING doc/* README.asciidoc CONTRIBUTING.asciidoc FAQ.asciidoc INSTALL.asciidoc CHANGELOG.asciidoc
|
||||
include qutebrowser.desktop
|
||||
include requirements.txt
|
||||
include tox.ini
|
||||
include qutebrowser.py
|
||||
|
||||
prune scripts/dev
|
||||
exclude scripts/asciidoc2html.py
|
||||
exclude doc/notes
|
||||
recursive-exclude doc *.asciidoc
|
||||
include doc/qutebrowser.1.asciidoc
|
||||
prune tests
|
||||
exclude pytest.ini
|
||||
exclude qutebrowser.rcc
|
||||
exclude .coveragerc
|
||||
exclude .pylintrc
|
||||
exclude .eslintrc
|
||||
exclude doc/help
|
||||
exclude .appveyor.yml
|
||||
exclude .travis.yml
|
||||
exclude misc/appveyor_install.py
|
||||
recursive-include qutebrowser *.py
|
||||
recursive-include qutebrowser/html *.html
|
||||
recursive-include qutebrowser/img *.svg *.png
|
||||
recursive-include qutebrowser/test *.py
|
||||
recursive-include qutebrowser/javascript *.js
|
||||
graft qutebrowser/3rdparty
|
||||
graft icons
|
||||
graft doc/img
|
||||
graft misc
|
||||
recursive-include scripts *.py
|
||||
include qutebrowser/utils/testfile
|
||||
include qutebrowser/git-commit-id
|
||||
include COPYING doc/* README.asciidoc CONTRIBUTING.asciidoc FAQ.asciidoc INSTALL.asciidoc CHANGELOG.asciidoc
|
||||
include qutebrowser.desktop
|
||||
include requirements.txt
|
||||
include tox.ini
|
||||
include qutebrowser.py
|
||||
|
||||
prune www
|
||||
prune scripts/dev
|
||||
exclude scripts/asciidoc2html.py
|
||||
exclude doc/notes
|
||||
recursive-exclude doc *.asciidoc
|
||||
include doc/qutebrowser.1.asciidoc
|
||||
prune tests
|
||||
exclude pytest.ini
|
||||
exclude qutebrowser.rcc
|
||||
exclude .coveragerc
|
||||
exclude .pylintrc
|
||||
exclude .eslintrc
|
||||
exclude .eslintignore
|
||||
exclude doc/help
|
||||
exclude .appveyor.yml
|
||||
exclude .travis.yml
|
||||
exclude misc/appveyor_install.py
|
||||
|
||||
global-exclude __pycache__ *.pyc *.pyo
|
||||
|
||||
@@ -6,14 +6,16 @@
|
||||
qutebrowser
|
||||
===========
|
||||
|
||||
// QUTE_WEB_HIDE
|
||||
image:icons/qutebrowser-64x64.png[qutebrowser logo] *A keyboard-driven, vim-like browser based on PyQt5 and QtWebKit.*
|
||||
|
||||
image:https://img.shields.io/pypi/l/qutebrowser.svg?style=flat["license badge",link="https://github.com/The-Compiler/qutebrowser/blob/master/COPYING"]
|
||||
image:https://img.shields.io/pypi/v/qutebrowser.svg?style=flat["version badge",link="https://pypi.python.org/pypi/qutebrowser/"]
|
||||
image:https://img.shields.io/github/issues/The-Compiler/qutebrowser.svg?style=flat["issues badge",link="https://github.com/The-Compiler/qutebrowser/issues"]
|
||||
image:https://requires.io/github/The-Compiler/qutebrowser/requirements.svg?branch=master["requirements badge",link="https://requires.io/github/The-Compiler/qutebrowser/requirements/?branch=master"]
|
||||
image:https://travis-ci.org/The-Compiler/qutebrowser.svg?branch=master["Build Status", link="https://travis-ci.org/The-Compiler/qutebrowser"]
|
||||
image:https://ci.appveyor.com/api/projects/status/9gmnuip6i1oq7046?svg=true["AppVeyor build status", link="https://ci.appveyor.com/project/The-Compiler/qutebrowser"]
|
||||
image:https://codecov.io/github/The-Compiler/qutebrowser/coverage.svg?branch=master["coverage badge",link="https://codecov.io/github/The-Compiler/qutebrowser?branch=master"]
|
||||
// QUTE_WEB_HIDE_END
|
||||
|
||||
qutebrowser is a keyboard-focused browser with a minimal GUI. It's based
|
||||
on Python, PyQt5 and QtWebKit and free software, licensed under the GPL.
|
||||
@@ -24,7 +26,7 @@ Screenshots
|
||||
-----------
|
||||
|
||||
image:doc/img/main.png["screenshot 1",width=300,link="doc/img/main.png"]
|
||||
image:doc/img/downloads.png["screenshot 2",width=300j,link="doc/img/downloads.png"]
|
||||
image:doc/img/downloads.png["screenshot 2",width=300,link="doc/img/downloads.png"]
|
||||
image:doc/img/completion.png["screenshot 3",width=300,link="doc/img/completion.png"]
|
||||
image:doc/img/hints.png["screenshot 4",width=300,link="doc/img/hints.png"]
|
||||
|
||||
@@ -89,17 +91,21 @@ Requirements
|
||||
|
||||
The following software and libraries are required to run qutebrowser:
|
||||
|
||||
* http://www.python.org/[Python] 3.4
|
||||
* http://qt.io/[Qt] 5.2.0 or newer (5.5.0 recommended)
|
||||
* http://www.python.org/[Python] 3.4 or newer
|
||||
* http://qt.io/[Qt] 5.2.0 or newer (5.5.1 recommended)
|
||||
* QtWebKit
|
||||
* http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.2.0 or newer
|
||||
(5.5.0 recommended) for Python 3
|
||||
(5.5.1 recommended) for Python 3
|
||||
* https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools]
|
||||
* http://fdik.org/pyPEG/[pyPEG2]
|
||||
* http://jinja.pocoo.org/[jinja2]
|
||||
* http://pygments.org/[pygments]
|
||||
* http://pyyaml.org/wiki/PyYAML[PyYAML]
|
||||
|
||||
The following libraries are optional and provide a better user experience:
|
||||
|
||||
* http://cthedot.de/cssutils/[cssutils]
|
||||
|
||||
To generate the documentation for the `:help` command, when using the git
|
||||
repository (rather than a release), http://asciidoc.org/[asciidoc] is needed.
|
||||
|
||||
@@ -135,49 +141,65 @@ Contributors, sorted by the number of commits in descending order:
|
||||
|
||||
// QUTE_AUTHORS_START
|
||||
* Florian Bruhin
|
||||
* Daniel Schadt
|
||||
* Antoni Boucher
|
||||
* Lamar Pavel
|
||||
* Bruno Oliveira
|
||||
* Martin Tournoij
|
||||
* Alexander Cogneau
|
||||
* Martin Tournoij
|
||||
* Raphael Pierzina
|
||||
* Joel Torstensson
|
||||
* Claude
|
||||
* Lamar Pavel
|
||||
* Austin Anderson
|
||||
* Artur Shaik
|
||||
* ZDarian
|
||||
* Peter Vilim
|
||||
* John ShaggyTwoDope Jenkins
|
||||
* Daniel
|
||||
* Jimmy
|
||||
* Zach-Button
|
||||
* rikn00
|
||||
* Thorsten Wißmann
|
||||
* meles5
|
||||
* Patric Schmitz
|
||||
* Artur Shaik
|
||||
* Nathan Isom
|
||||
* Austin Anderson
|
||||
* Thorsten Wißmann
|
||||
* Alexey "Averrin" Nabrodov
|
||||
* ZDarian
|
||||
* John ShaggyTwoDope Jenkins
|
||||
* Peter Vilim
|
||||
* Jonas Schürmann
|
||||
* Panagiotis Ktistakis
|
||||
* Jimmy
|
||||
* skinnay
|
||||
* error800
|
||||
* Zach-Button
|
||||
* Halfwit
|
||||
* Felix Van der Jeugt
|
||||
* rikn00
|
||||
* Martin Zimmermann
|
||||
* Error 800
|
||||
* Brian Jackson
|
||||
* sbinix
|
||||
* neeasade
|
||||
* jnphilipp
|
||||
* Tobias Patzl
|
||||
* Peter Michely
|
||||
* Larry Hynes
|
||||
* Johannes Altmanninger
|
||||
* Samir Benmendil
|
||||
* Regina Hug
|
||||
* Mathias Fussenegger
|
||||
* Larry Hynes
|
||||
* Fritz V155 Reichwald
|
||||
* Franz Fellner
|
||||
* Corentin Jule
|
||||
* zwarag
|
||||
* meles5
|
||||
* error800
|
||||
* xd1le
|
||||
* dylan araps
|
||||
* Tim Harder
|
||||
* Thiago Barroso Perrotta
|
||||
* Samuel Loury
|
||||
* Matthias Lisin
|
||||
* Marcel Schilling
|
||||
* Jean-Christophe Petkovich
|
||||
* Helen Sherwood-Taylor
|
||||
* HalosGhost
|
||||
* Gregor Pohl
|
||||
* Eivind Uggedal
|
||||
* Daniel Lu
|
||||
* Arseniy Seroka
|
||||
* Andy Balaam
|
||||
* Andreas Fischer
|
||||
// QUTE_AUTHORS_END
|
||||
|
||||
@@ -250,3 +272,14 @@ GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
pdf.js
|
||||
------
|
||||
|
||||
qutebrowser optionally uses https://github.com/mozilla/pdf.js/[pdf.js] to
|
||||
display PDF files in the browser. Windows releases come with a bundled pdf.js.
|
||||
|
||||
pdf.js is distributed under the terms of the Apache License. You can
|
||||
find a copy of the license in `qutebrowser/3rdparty/pdfjs/LICENSE` (in the
|
||||
Windows release or after running `scripts/dev/update_3rdparty.py`), or online
|
||||
http://www.apache.org/licenses/LICENSE-2.0.html[here].
|
||||
|
||||
@@ -14,9 +14,12 @@
|
||||
|<<close,close>>|Close the current window.
|
||||
|<<download,download>>|Download a given URL, or current page if no URL given.
|
||||
|<<download-cancel,download-cancel>>|Cancel the last/[count]th download.
|
||||
|<<download-clear,download-clear>>|Remove all finished downloads from the list.
|
||||
|<<download-delete,download-delete>>|Delete the last/[count]th download from disk.
|
||||
|<<download-open,download-open>>|Open the last/[count]th download.
|
||||
|<<download-remove,download-remove>>|Remove the last/[count]th download from the list.
|
||||
|<<download-retry,download-retry>>|Retry the first failed/[count]th download.
|
||||
|<<fake-key,fake-key>>|Send a fake keypress or key string to the website or qutebrowser.
|
||||
|<<forward,forward>>|Go forward in the history of the current tab.
|
||||
|<<fullscreen,fullscreen>>|Toggle fullscreen mode.
|
||||
|<<help,help>>|Show help about a command or setting.
|
||||
@@ -143,13 +146,18 @@ Close the current window.
|
||||
|
||||
[[download]]
|
||||
=== download
|
||||
Syntax: +:download ['url'] ['dest']+
|
||||
Syntax: +:download [*--mhtml*] [*--dest* 'DEST'] ['url'] ['dest-old']+
|
||||
|
||||
Download a given URL, or current page if no URL given.
|
||||
|
||||
The form `:download [url] [dest]` is deprecated, use `:download --dest [dest] [url]` instead.
|
||||
|
||||
==== positional arguments
|
||||
* +'url'+: The URL to download. If not given, download the current page.
|
||||
* +'dest'+: The file path to write the download to, or not given to ask.
|
||||
|
||||
==== optional arguments
|
||||
* +*-m*+, +*--mhtml*+: Download the current page and all assets as mhtml file.
|
||||
* +*-d*+, +*--dest*+: The file path to write the download to, or not given to ask.
|
||||
|
||||
[[download-cancel]]
|
||||
=== download-cancel
|
||||
@@ -158,6 +166,10 @@ Cancel the last/[count]th download.
|
||||
==== count
|
||||
The index of the download to cancel.
|
||||
|
||||
[[download-clear]]
|
||||
=== download-clear
|
||||
Remove all finished downloads from the list.
|
||||
|
||||
[[download-delete]]
|
||||
=== download-delete
|
||||
Delete the last/[count]th download from disk.
|
||||
@@ -179,11 +191,32 @@ Syntax: +:download-remove [*--all*]+
|
||||
Remove the last/[count]th download from the list.
|
||||
|
||||
==== optional arguments
|
||||
* +*-a*+, +*--all*+: If given removes all finished downloads.
|
||||
* +*-a*+, +*--all*+: Deprecated argument for removing all finished downloads.
|
||||
|
||||
==== count
|
||||
The index of the download to cancel.
|
||||
|
||||
[[download-retry]]
|
||||
=== download-retry
|
||||
Retry the first failed/[count]th download.
|
||||
|
||||
==== count
|
||||
The index of the download to cancel.
|
||||
|
||||
[[fake-key]]
|
||||
=== fake-key
|
||||
Syntax: +:fake-key [*--global*] 'keystring'+
|
||||
|
||||
Send a fake keypress or key string to the website or qutebrowser.
|
||||
|
||||
:fake-key xy - sends the keychain 'xy' :fake-key <Ctrl-x> - sends Ctrl-x :fake-key <Escape> - sends the escape key
|
||||
|
||||
==== positional arguments
|
||||
* +'keystring'+: The keystring to send.
|
||||
|
||||
==== optional arguments
|
||||
* +*-g*+, +*--global*+: If given, the keys are sent to the qutebrowser UI.
|
||||
|
||||
[[forward]]
|
||||
=== forward
|
||||
Syntax: +:forward [*--tab*] [*--bg*] [*--window*]+
|
||||
@@ -261,7 +294,11 @@ Start hinting.
|
||||
- With `spawn`: The executable and arguments to spawn.
|
||||
`{hint-url}` will get replaced by the selected
|
||||
URL.
|
||||
- With `userscript`: The userscript to execute.
|
||||
- With `userscript`: The userscript to execute. Either store
|
||||
the userscript in
|
||||
`~/.local/share/qutebrowser/userscripts`
|
||||
(or `$XDG_DATA_DIR`), or use an absolute
|
||||
path.
|
||||
- With `fill`: The command to fill the statusbar with.
|
||||
`{hint-url}` will get replaced by the selected
|
||||
URL.
|
||||
@@ -554,7 +591,7 @@ If the option name ends with '?', the value of the option is shown instead. If t
|
||||
|
||||
[[set-cmd-text]]
|
||||
=== set-cmd-text
|
||||
Syntax: +:set-cmd-text [*--space*] 'text'+
|
||||
Syntax: +:set-cmd-text [*--space*] [*--append*] 'text'+
|
||||
|
||||
Preset the statusbar to some text.
|
||||
|
||||
@@ -563,6 +600,7 @@ Preset the statusbar to some text.
|
||||
|
||||
==== optional arguments
|
||||
* +*-s*+, +*--space*+: If given, a space is added to the end.
|
||||
* +*-a*+, +*--append*+: If given, the text is appended to the current text.
|
||||
|
||||
==== note
|
||||
* This command does not split arguments after the last argument and handles quotes literally.
|
||||
@@ -580,7 +618,9 @@ Note the {url} variable which gets replaced by the current URL might be useful h
|
||||
* +'cmdline'+: The commandline to execute.
|
||||
|
||||
==== optional arguments
|
||||
* +*-u*+, +*--userscript*+: Run the command as a userscript.
|
||||
* +*-u*+, +*--userscript*+: Run the command as a userscript. Either store the userscript in `~/.local/share/qutebrowser/userscripts`
|
||||
(or `$XDG_DATA_DIR`), or use an absolute path.
|
||||
|
||||
* +*-v*+, +*--verbose*+: Show notifications when the command started/exited.
|
||||
* +*-d*+, +*--detach*+: Whether the command should be detached from qutebrowser.
|
||||
|
||||
@@ -769,7 +809,7 @@ How many steps to zoom out.
|
||||
|<<completion-item-prev,completion-item-prev>>|Select the previous completion item.
|
||||
|<<drop-selection,drop-selection>>|Drop selection and keep selection mode enabled.
|
||||
|<<enter-mode,enter-mode>>|Enter a key mode.
|
||||
|<<follow-hint,follow-hint>>|Follow the currently selected hint.
|
||||
|<<follow-hint,follow-hint>>|Follow a hint.
|
||||
|<<follow-selected,follow-selected>>|Follow the selected text.
|
||||
|<<leave-mode,leave-mode>>|Leave the mode we're currently in.
|
||||
|<<message-error,message-error>>|Show an error message in the statusbar.
|
||||
@@ -858,7 +898,12 @@ Enter a key mode.
|
||||
|
||||
[[follow-hint]]
|
||||
=== follow-hint
|
||||
Follow the currently selected hint.
|
||||
Syntax: +:follow-hint ['keystring']+
|
||||
|
||||
Follow a hint.
|
||||
|
||||
==== positional arguments
|
||||
* +'keystring'+: The hint to follow.
|
||||
|
||||
[[follow-selected]]
|
||||
=== follow-selected
|
||||
@@ -1091,7 +1136,7 @@ This acts like readline's yank.
|
||||
|
||||
[[scroll]]
|
||||
=== scroll
|
||||
Syntax: +:scroll 'direction' ['dy']+
|
||||
Syntax: +:scroll 'direction'+
|
||||
|
||||
Scroll the current tab in the given direction.
|
||||
|
||||
@@ -1181,6 +1226,7 @@ These commands are mainly intended for debugging. They are hidden if qutebrowser
|
||||
|<<debug-cache-stats,debug-cache-stats>>|Print LRU cache stats.
|
||||
|<<debug-console,debug-console>>|Show the debugging console.
|
||||
|<<debug-crash,debug-crash>>|Crash for debugging purposes.
|
||||
|<<debug-dump-page,debug-dump-page>>|Dump the current page's content to a file.
|
||||
|<<debug-pyeval,debug-pyeval>>|Evaluate a python string and display the results as a web page.
|
||||
|<<debug-trace,debug-trace>>|Trace executed code via hunter.
|
||||
|<<debug-webaction,debug-webaction>>|Execute a webaction.
|
||||
@@ -1206,6 +1252,18 @@ Crash for debugging purposes.
|
||||
==== positional arguments
|
||||
* +'typ'+: either 'exception' or 'segfault'.
|
||||
|
||||
[[debug-dump-page]]
|
||||
=== debug-dump-page
|
||||
Syntax: +:debug-dump-page [*--plain*] 'dest'+
|
||||
|
||||
Dump the current page's content to a file.
|
||||
|
||||
==== positional arguments
|
||||
* +'dest'+: Where to write the file to.
|
||||
|
||||
==== optional arguments
|
||||
* +*-p*+, +*--plain*+: Write plain text instead of HTML.
|
||||
|
||||
[[debug-pyeval]]
|
||||
=== debug-pyeval
|
||||
Syntax: +:debug-pyeval 's'+
|
||||
|
||||
@@ -20,9 +20,10 @@
|
||||
|<<general-site-specific-quirks,site-specific-quirks>>|Enable workarounds for broken sites.
|
||||
|<<general-default-encoding,default-encoding>>|Default encoding to use for websites.
|
||||
|<<general-new-instance-open-target,new-instance-open-target>>|How to open links in an existing instance if a new one is launched.
|
||||
|<<general-log-javascript-console,log-javascript-console>>|Whether to log javascript console messages.
|
||||
|<<general-log-javascript-console,log-javascript-console>>|How to log javascript console messages.
|
||||
|<<general-save-session,save-session>>|Whether to always save the open pages.
|
||||
|<<general-session-default-name,session-default-name>>|The name of the session to save by default, or empty for the last loaded session.
|
||||
|<<general-url-incdec-segments,url-incdec-segments>>|The URL segments where `:navigate increment/decrement` will search for a number.
|
||||
|==============
|
||||
|
||||
.Quick reference for section ``ui''
|
||||
@@ -41,12 +42,13 @@
|
||||
|<<ui-user-stylesheet,user-stylesheet>>|User stylesheet to use (absolute filename, filename relative to the config directory or CSS string). Will expand environment variables.
|
||||
|<<ui-css-media-type,css-media-type>>|Set the CSS media type.
|
||||
|<<ui-smooth-scrolling,smooth-scrolling>>|Whether to enable smooth scrolling for webpages.
|
||||
|<<ui-remove-finished-downloads,remove-finished-downloads>>|Whether to remove finished downloads automatically.
|
||||
|<<ui-remove-finished-downloads,remove-finished-downloads>>|Number of milliseconds to wait before removing finished downloads. Will not be removed if value is -1.
|
||||
|<<ui-hide-statusbar,hide-statusbar>>|Whether to hide the statusbar unless a message is shown.
|
||||
|<<ui-statusbar-padding,statusbar-padding>>|Padding for statusbar (top, bottom, left, right).
|
||||
|<<ui-window-title-format,window-title-format>>|The format to use for the window title. The following placeholders are defined:
|
||||
|<<ui-hide-mouse-cursor,hide-mouse-cursor>>|Whether to hide the mouse cursor.
|
||||
|<<ui-modal-js-dialog,modal-js-dialog>>|Use standard JavaScript modal dialog for alert() and confirm()
|
||||
|<<ui-hide-wayland-decoration,hide-wayland-decoration>>|Hide the window decoration when using wayland (requires restart)
|
||||
|==============
|
||||
|
||||
.Quick reference for section ``network''
|
||||
@@ -76,6 +78,8 @@
|
||||
|<<completion-web-history-max-items,web-history-max-items>>|How many URLs to show in the web history.
|
||||
|<<completion-quick-complete,quick-complete>>|Whether to move on to the next part when there's only one possible completion left.
|
||||
|<<completion-shrink,shrink>>|Whether to shrink the completion to be smaller than the configured size if there are no scrollbars.
|
||||
|<<completion-scrollbar-width,scrollbar-width>>|Width of the scrollbar in the completion window (in px).
|
||||
|<<completion-scrollbar-padding,scrollbar-padding>>|Padding of scrollbar handle in completion window (in px).
|
||||
|==============
|
||||
|
||||
.Quick reference for section ``input''
|
||||
@@ -114,6 +118,7 @@
|
||||
|<<tabs-indicator-width,indicator-width>>|Width of the progress indicator (0 to disable).
|
||||
|<<tabs-tabs-are-windows,tabs-are-windows>>|Whether to open windows instead of tabs.
|
||||
|<<tabs-title-format,title-format>>|The format to use for the tab title. The following placeholders are defined:
|
||||
|<<tabs-title-alignment,title-alignment>>|Alignment of the text inside of tabs
|
||||
|<<tabs-mousewheel-tab-switching,mousewheel-tab-switching>>|Switch between tabs using the mouse wheel.
|
||||
|<<tabs-padding,padding>>|Padding for tabs (top, bottom, left, right).
|
||||
|<<tabs-indicator-padding,indicator-padding>>|Padding for indicators (top, bottom, left, right).
|
||||
@@ -159,6 +164,8 @@
|
||||
|<<content-cookies-store,cookies-store>>|Whether to store cookies.
|
||||
|<<content-host-block-lists,host-block-lists>>|List of URLs of lists which contain hosts to block.
|
||||
|<<content-host-blocking-enabled,host-blocking-enabled>>|Whether host blocking is enabled.
|
||||
|<<content-host-blocking-whitelist,host-blocking-whitelist>>|List of domains that should always be loaded, despite being ad-blocked.
|
||||
|<<content-enable-pdfjs,enable-pdfjs>>|Enable pdf.js to view PDF files in the browser.
|
||||
|==============
|
||||
|
||||
.Quick reference for section ``hints''
|
||||
@@ -169,7 +176,7 @@
|
||||
|<<hints-opacity,opacity>>|Opacity for hints.
|
||||
|<<hints-mode,mode>>|Mode to use for hints.
|
||||
|<<hints-chars,chars>>|Chars used for hint strings.
|
||||
|<<hints-min-chars,min-chars>>|Mininum number of chars used for hint strings.
|
||||
|<<hints-min-chars,min-chars>>|Minimum number of chars used for hint strings.
|
||||
|<<hints-scatter,scatter>>|Whether to scatter hint key chains (like Vimium) or not (like dwb).
|
||||
|<<hints-uppercase,uppercase>>|Make chars in hint strings uppercase.
|
||||
|<<hints-auto-follow,auto-follow>>|Whether to auto-follow a hint if there's only one left.
|
||||
@@ -193,8 +200,10 @@
|
||||
|<<colors-completion.item.selected.border.top,completion.item.selected.border.top>>|Top border color of the completion widget category headers.
|
||||
|<<colors-completion.item.selected.border.bottom,completion.item.selected.border.bottom>>|Bottom border color of the selected completion item.
|
||||
|<<colors-completion.match.fg,completion.match.fg>>|Foreground color of the matched text in the completion.
|
||||
|<<colors-completion.scrollbar.fg,completion.scrollbar.fg>>|Color of the scrollbar handle in completion view.
|
||||
|<<colors-completion.scrollbar.bg,completion.scrollbar.bg>>|Color of the scrollbar in completion view
|
||||
|<<colors-statusbar.fg,statusbar.fg>>|Foreground color of the statusbar.
|
||||
|<<colors-statusbar.bg,statusbar.bg>>|Foreground color of the statusbar.
|
||||
|<<colors-statusbar.bg,statusbar.bg>>|Background color of the statusbar.
|
||||
|<<colors-statusbar.fg.error,statusbar.fg.error>>|Foreground color of the statusbar if there was an error.
|
||||
|<<colors-statusbar.bg.error,statusbar.bg.error>>|Background color of the statusbar if there was an error.
|
||||
|<<colors-statusbar.fg.warning,statusbar.fg.warning>>|Foreground color of the statusbar if there is a warning.
|
||||
@@ -211,7 +220,8 @@
|
||||
|<<colors-statusbar.bg.caret-selection,statusbar.bg.caret-selection>>|Background color of the statusbar in caret mode with a selection
|
||||
|<<colors-statusbar.progress.bg,statusbar.progress.bg>>|Background color of the progress bar.
|
||||
|<<colors-statusbar.url.fg,statusbar.url.fg>>|Default foreground color of the URL in the statusbar.
|
||||
|<<colors-statusbar.url.fg.success,statusbar.url.fg.success>>|Foreground color of the URL in the statusbar on successful load.
|
||||
|<<colors-statusbar.url.fg.success,statusbar.url.fg.success>>|Foreground color of the URL in the statusbar on successful load (http).
|
||||
|<<colors-statusbar.url.fg.success.https,statusbar.url.fg.success.https>>|Foreground color of the URL in the statusbar on successful load (https).
|
||||
|<<colors-statusbar.url.fg.error,statusbar.url.fg.error>>|Foreground color of the URL in the statusbar on error.
|
||||
|<<colors-statusbar.url.fg.warn,statusbar.url.fg.warn>>|Foreground color of the URL in the statusbar when there's a warning.
|
||||
|<<colors-statusbar.url.fg.hover,statusbar.url.fg.hover>>|Foreground color of the URL in the statusbar for hovered links.
|
||||
@@ -219,8 +229,10 @@
|
||||
|<<colors-tabs.bg.odd,tabs.bg.odd>>|Background color of unselected odd tabs.
|
||||
|<<colors-tabs.fg.even,tabs.fg.even>>|Foreground color of unselected even tabs.
|
||||
|<<colors-tabs.bg.even,tabs.bg.even>>|Background color of unselected even tabs.
|
||||
|<<colors-tabs.fg.selected,tabs.fg.selected>>|Foreground color of selected tabs.
|
||||
|<<colors-tabs.bg.selected,tabs.bg.selected>>|Background color of selected tabs.
|
||||
|<<colors-tabs.fg.selected.odd,tabs.fg.selected.odd>>|Foreground color of selected odd tabs.
|
||||
|<<colors-tabs.bg.selected.odd,tabs.bg.selected.odd>>|Background color of selected odd tabs.
|
||||
|<<colors-tabs.fg.selected.even,tabs.fg.selected.even>>|Foreground color of selected even tabs.
|
||||
|<<colors-tabs.bg.selected.even,tabs.bg.selected.even>>|Background color of selected even tabs.
|
||||
|<<colors-tabs.bg.bar,tabs.bg.bar>>|Background color of the tab bar.
|
||||
|<<colors-tabs.indicator.start,tabs.indicator.start>>|Color gradient start for the tab indicator.
|
||||
|<<colors-tabs.indicator.stop,tabs.indicator.stop>>|Color gradient end for the tab indicator.
|
||||
@@ -294,7 +306,7 @@ Default: +pass:[true]+
|
||||
=== startpage
|
||||
The default page(s) to open at the start, separated by commas.
|
||||
|
||||
Default: +pass:[https://www.duckduckgo.com]+
|
||||
Default: +pass:[https://duckduckgo.com]+
|
||||
|
||||
[[general-default-page]]
|
||||
=== default-page
|
||||
@@ -335,7 +347,8 @@ Default: +pass:[15000]+
|
||||
=== editor
|
||||
The editor (and arguments) to use for the `open-editor` command.
|
||||
|
||||
Use `{}` for the filename. The value gets split like in a shell, so you can use `"` or `'` to quote arguments.
|
||||
The arguments get split like in a shell, so you can use `"` or `'` to quote them.
|
||||
`{}` gets replaced by the filename of the file to be edited.
|
||||
|
||||
Default: +pass:[gvim -f "{}"]+
|
||||
|
||||
@@ -428,14 +441,15 @@ Default: +pass:[tab]+
|
||||
|
||||
[[general-log-javascript-console]]
|
||||
=== log-javascript-console
|
||||
Whether to log javascript console messages.
|
||||
How to log javascript console messages.
|
||||
|
||||
Valid values:
|
||||
|
||||
* +true+
|
||||
* +false+
|
||||
* +none+: Don't log messages.
|
||||
* +debug+: Log messages with debug level.
|
||||
* +info+: Log messages with info level.
|
||||
|
||||
Default: +pass:[false]+
|
||||
Default: +pass:[debug]+
|
||||
|
||||
[[general-save-session]]
|
||||
=== save-session
|
||||
@@ -454,6 +468,19 @@ The name of the session to save by default, or empty for the last loaded session
|
||||
|
||||
Default: empty
|
||||
|
||||
[[general-url-incdec-segments]]
|
||||
=== url-incdec-segments
|
||||
The URL segments where `:navigate increment/decrement` will search for a number.
|
||||
|
||||
Valid values:
|
||||
|
||||
* +host+
|
||||
* +path+
|
||||
* +query+
|
||||
* +anchor+
|
||||
|
||||
Default: +pass:[path,query]+
|
||||
|
||||
== ui
|
||||
General options related to the user interface.
|
||||
|
||||
@@ -570,14 +597,9 @@ Default: +pass:[false]+
|
||||
|
||||
[[ui-remove-finished-downloads]]
|
||||
=== remove-finished-downloads
|
||||
Whether to remove finished downloads automatically.
|
||||
Number of milliseconds to wait before removing finished downloads. Will not be removed if value is -1.
|
||||
|
||||
Valid values:
|
||||
|
||||
* +true+
|
||||
* +false+
|
||||
|
||||
Default: +pass:[false]+
|
||||
Default: +pass:[-1]+
|
||||
|
||||
[[ui-hide-statusbar]]
|
||||
=== hide-statusbar
|
||||
@@ -605,6 +627,7 @@ The format to use for the window title. The following placeholders are defined:
|
||||
* `{title}`: The title of the current web page
|
||||
* `{title_sep}`: The string ` - ` if a title is set, empty otherwise.
|
||||
* `{id}`: The internal window ID of this window.
|
||||
* `{scroll_pos}`: The page scroll position.
|
||||
|
||||
Default: +pass:[{perc}{title}{title_sep}qutebrowser]+
|
||||
|
||||
@@ -630,6 +653,17 @@ Valid values:
|
||||
|
||||
Default: +pass:[false]+
|
||||
|
||||
[[ui-hide-wayland-decoration]]
|
||||
=== hide-wayland-decoration
|
||||
Hide the window decoration when using wayland (requires restart)
|
||||
|
||||
Valid values:
|
||||
|
||||
* +true+
|
||||
* +false+
|
||||
|
||||
Default: +pass:[false]+
|
||||
|
||||
== network
|
||||
Settings related to the network.
|
||||
|
||||
@@ -802,6 +836,18 @@ Valid values:
|
||||
|
||||
Default: +pass:[false]+
|
||||
|
||||
[[completion-scrollbar-width]]
|
||||
=== scrollbar-width
|
||||
Width of the scrollbar in the completion window (in px).
|
||||
|
||||
Default: +pass:[12]+
|
||||
|
||||
[[completion-scrollbar-padding]]
|
||||
=== scrollbar-padding
|
||||
Padding of scrollbar handle in completion window (in px).
|
||||
|
||||
Default: +pass:[2]+
|
||||
|
||||
== input
|
||||
Options related to input modes.
|
||||
|
||||
@@ -1079,9 +1125,22 @@ The format to use for the tab title. The following placeholders are defined:
|
||||
* `{title_sep}`: The string ` - ` if a title is set, empty otherwise.
|
||||
* `{index}`: The index of this tab.
|
||||
* `{id}`: The internal tab ID of this tab.
|
||||
* `{scroll_pos}`: The page scroll position.
|
||||
|
||||
Default: +pass:[{index}: {title}]+
|
||||
|
||||
[[tabs-title-alignment]]
|
||||
=== title-alignment
|
||||
Alignment of the text inside of tabs
|
||||
|
||||
Valid values:
|
||||
|
||||
* +left+
|
||||
* +right+
|
||||
* +center+
|
||||
|
||||
Default: +pass:[left]+
|
||||
|
||||
[[tabs-mousewheel-tab-switching]]
|
||||
=== mousewheel-tab-switching
|
||||
Switch between tabs using the mouse wheel.
|
||||
@@ -1433,6 +1492,29 @@ Valid values:
|
||||
|
||||
Default: +pass:[true]+
|
||||
|
||||
[[content-host-blocking-whitelist]]
|
||||
=== host-blocking-whitelist
|
||||
List of domains that should always be loaded, despite being ad-blocked.
|
||||
|
||||
Domains may contain * and ? wildcards and are otherwise required to exactly match the requested domain.
|
||||
|
||||
Local domains are always exempt from hostblocking.
|
||||
|
||||
Default: +pass:[piwik.org]+
|
||||
|
||||
[[content-enable-pdfjs]]
|
||||
=== enable-pdfjs
|
||||
Enable pdf.js to view PDF files in the browser.
|
||||
|
||||
Note that the files can still be downloaded by clicking the download button in the pdf.js viewer.
|
||||
|
||||
Valid values:
|
||||
|
||||
* +true+
|
||||
* +false+
|
||||
|
||||
Default: +pass:[false]+
|
||||
|
||||
== hints
|
||||
Hinting settings.
|
||||
|
||||
@@ -1467,7 +1549,7 @@ Default: +pass:[asdfghjkl]+
|
||||
|
||||
[[hints-min-chars]]
|
||||
=== min-chars
|
||||
Mininum number of chars used for hint strings.
|
||||
Minimum number of chars used for hint strings.
|
||||
|
||||
Default: +pass:[1]+
|
||||
|
||||
@@ -1613,6 +1695,18 @@ Foreground color of the matched text in the completion.
|
||||
|
||||
Default: +pass:[#ff4444]+
|
||||
|
||||
[[colors-completion.scrollbar.fg]]
|
||||
=== completion.scrollbar.fg
|
||||
Color of the scrollbar handle in completion view.
|
||||
|
||||
Default: +pass:[${completion.fg}]+
|
||||
|
||||
[[colors-completion.scrollbar.bg]]
|
||||
=== completion.scrollbar.bg
|
||||
Color of the scrollbar in completion view
|
||||
|
||||
Default: +pass:[${completion.bg}]+
|
||||
|
||||
[[colors-statusbar.fg]]
|
||||
=== statusbar.fg
|
||||
Foreground color of the statusbar.
|
||||
@@ -1621,7 +1715,7 @@ Default: +pass:[white]+
|
||||
|
||||
[[colors-statusbar.bg]]
|
||||
=== statusbar.bg
|
||||
Foreground color of the statusbar.
|
||||
Background color of the statusbar.
|
||||
|
||||
Default: +pass:[black]+
|
||||
|
||||
@@ -1723,7 +1817,13 @@ Default: +pass:[${statusbar.fg}]+
|
||||
|
||||
[[colors-statusbar.url.fg.success]]
|
||||
=== statusbar.url.fg.success
|
||||
Foreground color of the URL in the statusbar on successful load.
|
||||
Foreground color of the URL in the statusbar on successful load (http).
|
||||
|
||||
Default: +pass:[white]+
|
||||
|
||||
[[colors-statusbar.url.fg.success.https]]
|
||||
=== statusbar.url.fg.success.https
|
||||
Foreground color of the URL in the statusbar on successful load (https).
|
||||
|
||||
Default: +pass:[lime]+
|
||||
|
||||
@@ -1769,18 +1869,30 @@ Background color of unselected even tabs.
|
||||
|
||||
Default: +pass:[darkgrey]+
|
||||
|
||||
[[colors-tabs.fg.selected]]
|
||||
=== tabs.fg.selected
|
||||
Foreground color of selected tabs.
|
||||
[[colors-tabs.fg.selected.odd]]
|
||||
=== tabs.fg.selected.odd
|
||||
Foreground color of selected odd tabs.
|
||||
|
||||
Default: +pass:[white]+
|
||||
|
||||
[[colors-tabs.bg.selected]]
|
||||
=== tabs.bg.selected
|
||||
Background color of selected tabs.
|
||||
[[colors-tabs.bg.selected.odd]]
|
||||
=== tabs.bg.selected.odd
|
||||
Background color of selected odd tabs.
|
||||
|
||||
Default: +pass:[black]+
|
||||
|
||||
[[colors-tabs.fg.selected.even]]
|
||||
=== tabs.fg.selected.even
|
||||
Foreground color of selected even tabs.
|
||||
|
||||
Default: +pass:[${tabs.fg.selected.odd}]+
|
||||
|
||||
[[colors-tabs.bg.selected.even]]
|
||||
=== tabs.bg.selected.even
|
||||
Background color of selected even tabs.
|
||||
|
||||
Default: +pass:[${tabs.bg.selected.odd}]+
|
||||
|
||||
[[colors-tabs.bg.bar]]
|
||||
=== tabs.bg.bar
|
||||
Background color of the tab bar.
|
||||
@@ -1814,6 +1926,7 @@ Valid values:
|
||||
* +rgb+: Interpolate in the RGB color system.
|
||||
* +hsv+: Interpolate in the HSV color system.
|
||||
* +hsl+: Interpolate in the HSL color system.
|
||||
* +none+: Don't show a gradient.
|
||||
|
||||
Default: +pass:[rgb]+
|
||||
|
||||
@@ -1874,6 +1987,7 @@ Valid values:
|
||||
* +rgb+: Interpolate in the RGB color system.
|
||||
* +hsv+: Interpolate in the HSV color system.
|
||||
* +hsl+: Interpolate in the HSL color system.
|
||||
* +none+: Don't show a gradient.
|
||||
|
||||
Default: +pass:[rgb]+
|
||||
|
||||
@@ -1886,6 +2000,7 @@ Valid values:
|
||||
* +rgb+: Interpolate in the RGB color system.
|
||||
* +hsv+: Interpolate in the HSV color system.
|
||||
* +hsl+: Interpolate in the HSL color system.
|
||||
* +none+: Don't show a gradient.
|
||||
|
||||
Default: +pass:[rgb]+
|
||||
|
||||
|
||||
@@ -62,6 +62,9 @@ show it.
|
||||
*-R*, *--override-restore*::
|
||||
Don't restore a session even if one would be restored.
|
||||
|
||||
*--target* '{auto,tab,tab-bg,tab-silent,tab-bg-silent,window}'::
|
||||
How URLs should be opened if there is already a qutebrowser instance running.
|
||||
|
||||
=== debug arguments
|
||||
*-l* 'LOGLEVEL', *--loglevel* 'LOGLEVEL'::
|
||||
Set loglevel
|
||||
|
||||
@@ -76,7 +76,7 @@ Server = http://qutebrowser.org/qt-debug/$arch
|
||||
Then install the packages:
|
||||
|
||||
----
|
||||
# pacman -Sy pyqt5-common-debug python-pyqt5-debug qt5-base-debug qt5-webkit-debug
|
||||
# pacman -Suy pyqt5-common-debug python-pyqt5-debug qt5-base-debug qt5-webkit-debug
|
||||
----
|
||||
|
||||
The `-debug` packages conflict with the non-debug variants - it's safe to
|
||||
|
||||
@@ -5,6 +5,9 @@ The Compiler <mail@qutebrowser.org>
|
||||
qutebrowser is extensible by writing userscripts which can be called via the
|
||||
`:spawn --userscript` command, or via a key binding.
|
||||
|
||||
You can also call a userscript via hints so they get the selected hint URL by
|
||||
calling `:hint links userscript ...`.
|
||||
|
||||
These userscripts are similar to the (non-javascript) dwb userscripts. They can
|
||||
be written in any language which can read environment variables and write to a
|
||||
FIFO. Note they are *not* related to Greasemonkey userscripts.
|
||||
@@ -15,6 +18,10 @@ mpv, a simple key binding to something like `:spawn mpv {url}` should suffice.
|
||||
Also note userscripts need to have the executable bit set (`chmod +x`) for
|
||||
qutebrowser to run them.
|
||||
|
||||
To call a userscript, it needs to be stored in your data directory under
|
||||
`userscripts` (for example: `~/.local/share/qutebrowser/userscripts/myscript`),
|
||||
or just use an absolute path.
|
||||
|
||||
Getting information
|
||||
-------------------
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ profile qutebrowser /usr/{local/,}bin/qutebrowser {
|
||||
|
||||
/proc/*/mounts r,
|
||||
owner /tmp/** rwkl,
|
||||
owner /run/user/*/ rw,
|
||||
owner /run/user/*/** krw,
|
||||
|
||||
@{HOME}/.config/qutebrowser/** krw,
|
||||
@{HOME}/.local/share/qutebrowser/** krw,
|
||||
|
||||
113
misc/userscripts/open_download
Executable file
113
misc/userscripts/open_download
Executable file
@@ -0,0 +1,113 @@
|
||||
#!/bin/bash -e
|
||||
# Both standalone script and qutebrowser userscript that opens a rofi menu with
|
||||
# all files from the download director and opens the selected file. It works
|
||||
# both as a userscript and a standalone script that is called from outside of
|
||||
# qutebrowser.
|
||||
#
|
||||
# Suggested keybinding (for "show downloads"):
|
||||
# spawn --userscript ~/.config/qutebrowser/open_download
|
||||
# sd
|
||||
#
|
||||
# Requirements:
|
||||
# - rofi (in a recent version)
|
||||
# - xdg-open and xdg-mime
|
||||
# - You should configure qutebrowser to download files to a single directory
|
||||
# - It comes in handy if you enable remove-finished-downloads. If you want to
|
||||
# see the recent downloads, just press "sd".
|
||||
#
|
||||
# Thorsten Wißmann, 2015 (thorsten` on freenode)
|
||||
# Any feedback is welcome!
|
||||
|
||||
# open a file from the download directory using rofi
|
||||
DOWNLOAD_DIR=${DOWNLOAD_DIR:-$HOME/Downloads}
|
||||
# the name of the rofi command
|
||||
ROFI_CMD=${ROFI_CMD:-rofi}
|
||||
ROFI_ARGS=${ROFI_ARGS:-}
|
||||
|
||||
msg() {
|
||||
local cmd="$1"
|
||||
shift
|
||||
local msg="$*"
|
||||
if [ -z "$QUTE_FIFO" ] ; then
|
||||
echo "$cmd: $msg" >&2
|
||||
else
|
||||
echo "message-$cmd '${msg//\'/\\\'}'" >> "$QUTE_FIFO"
|
||||
fi
|
||||
}
|
||||
die() {
|
||||
msg error "$*"
|
||||
if [ -n "$QUTE_FIFO" ] ; then
|
||||
# when run as a userscript, the above error message already informs the
|
||||
# user about the failure, and no additional "userscript exited with status
|
||||
# 1" is needed.
|
||||
exit 0;
|
||||
else
|
||||
exit 1;
|
||||
fi
|
||||
}
|
||||
|
||||
if ! [ -d "$DOWNLOAD_DIR" ] ; then
|
||||
die "Download directory »$DOWNLOAD_DIR« not found!"
|
||||
fi
|
||||
if ! $(which "${ROFI_CMD}" > /dev/null ) ; then
|
||||
die "Rofi command »${ROFI_CMD}« not found in PATH!"
|
||||
fi
|
||||
|
||||
rofi_default_args=(
|
||||
-monitor -2 # place above window
|
||||
-location 6 # aligned at the bottom
|
||||
-width 100 # use full window width
|
||||
-i
|
||||
-no-custom
|
||||
-format i # make rofi return the index
|
||||
-l 10
|
||||
-p 'Open download:' -dmenu
|
||||
)
|
||||
|
||||
crop-first-column() {
|
||||
local maxlength=${1:-40}
|
||||
local expression='s|^\([^\t]\{0,'"$maxlength"'\}\)[^\t]*\t|\1\t|'
|
||||
sed "$expression"
|
||||
}
|
||||
|
||||
ls-files() {
|
||||
# add the slash at the end of the download dir enforces to follow the
|
||||
# symlink, if the DOWNLOAD_DIR itself is a symlink
|
||||
ls -Q --quoting-style escape -h -o -1 -A -t "${DOWNLOAD_DIR}/" \
|
||||
| grep '^[-]' \
|
||||
| cut -d' ' -f3- \
|
||||
| sed 's,^\(.*[^\]\) \(.*\)$,\2\t\1,' \
|
||||
| sed 's,\\\(.\),\1,g'
|
||||
}
|
||||
|
||||
mapfile -t entries < <(ls-files)
|
||||
|
||||
# we need to manually check that there are items, because rofi doesn't show up
|
||||
# if there are no items and -no-custom is passed to rofi.
|
||||
if [ "${#entries[@]}" -eq 0 ] ; then
|
||||
die "Download directory »${DOWNLOAD_DIR}« empty"
|
||||
fi
|
||||
|
||||
line=$(printf "%s\n" "${entries[@]}" \
|
||||
| crop-first-column 55 \
|
||||
| column -s $'\t' -t \
|
||||
| $ROFI_CMD "${rofi_default_args[@]}" $ROFI_ARGS) || true
|
||||
if [ -z "$line" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
file="${entries[$line]}"
|
||||
file="${file%%$'\t'*}"
|
||||
path="$DOWNLOAD_DIR/$file"
|
||||
filetype=$(xdg-mime query filetype "$path")
|
||||
application=$(xdg-mime query default "$filetype")
|
||||
|
||||
if [ -z "$application" ] ; then
|
||||
die "Do not know how to open »$file« of type $filetype"
|
||||
fi
|
||||
|
||||
msg info "Opening »$file« (of type $filetype) with ${application%.desktop}"
|
||||
|
||||
xdg-open "$path" &
|
||||
|
||||
|
||||
60
misc/userscripts/qutedmenu
Executable file
60
misc/userscripts/qutedmenu
Executable file
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env bash
|
||||
# Handle open -s && open -t with bemenu
|
||||
|
||||
#:bind o spawn --userscript /path/to/userscripts/qutedmenu open
|
||||
#:bind O spawn --userscript /path/to/userscripts/qutedmenu tab
|
||||
|
||||
# If you would like to set a custom colorscheme/font use these dirs.
|
||||
# https://github.com/halfwit/dotfiles/blob/master/.config/dmenu/bemenucolors
|
||||
readonly confdir=${XDG_CONFIG_HOME:-$HOME/.config}
|
||||
readonly datadir=${XDG_DATA_HOME:-$HOME/.local/share}
|
||||
|
||||
readonly optsfile=$confdir/dmenu/bemenucolors
|
||||
|
||||
create_menu() {
|
||||
# Check quickmarks
|
||||
while read -r url; do
|
||||
printf -- '%s\n' "$url"
|
||||
done < "$confdir"/qutebrowser/quickmarks
|
||||
|
||||
# Next bookmarks
|
||||
while read -r url _; do
|
||||
printf -- '%s\n' "$url"
|
||||
done < "$confdir"/qutebrowser/bookmarks/urls
|
||||
|
||||
# Finally history
|
||||
while read -r _ url; do
|
||||
printf -- '%s\n' "$url"
|
||||
done < "$datadir"/qutebrowser/history
|
||||
}
|
||||
|
||||
get_selection() {
|
||||
opts+=(-p qutebrowser)
|
||||
#create_menu | dmenu -l 10 "${opts[@]}"
|
||||
create_menu | bemenu -l 10 "${opts[@]}"
|
||||
}
|
||||
|
||||
# Main
|
||||
# https://github.com/halfwit/dotfiles/blob/master/.config/dmenu/font
|
||||
if [[ -s $confdir/dmenu/font ]]; then
|
||||
read -r font < "$confdir"/dmenu/font
|
||||
fi
|
||||
|
||||
if [[ $font ]]; then
|
||||
opts+=(-fn "$font")
|
||||
fi
|
||||
|
||||
if [[ -s $optsfile ]]; then
|
||||
source "$optsfile"
|
||||
fi
|
||||
|
||||
url=$(get_selection)
|
||||
url=${url/*http/http}
|
||||
|
||||
# If no selection is made, exit (escape pressed, e.g.)
|
||||
[[ ! $url ]] && exit 0
|
||||
|
||||
case $1 in
|
||||
open) printf '%s' "open $url" >> "$QUTE_FIFO" || qutebrowser "$url" ;;
|
||||
tab) printf '%s' "open -t $url" >> "$QUTE_FIFO" || qutebrowser "$url" ;;
|
||||
esac
|
||||
141
misc/userscripts/view_in_mpv
Executable file
141
misc/userscripts/view_in_mpv
Executable file
@@ -0,0 +1,141 @@
|
||||
#!/bin/bash -e
|
||||
#
|
||||
# Behaviour:
|
||||
# Userscript for qutebrowser which views the current web page in mpv using
|
||||
# sensible mpv-flags. While viewing the page in MPV, all <video>, <embed>,
|
||||
# and <object> tags in the original page are temporarily removed. Clicking on
|
||||
# such a removed video restores the respective video.
|
||||
#
|
||||
# In order to use this script, just start it using `spawn --userscript` from
|
||||
# qutebrowser. I recommend using an alias, e.g. put this in the
|
||||
# [alias]-section of qutebrowser.conf:
|
||||
#
|
||||
# mpv = spawn --userscript /path/to/view_in_mpv
|
||||
#
|
||||
# Background:
|
||||
# Most of my machines are too slow to play youtube videos using html5, but
|
||||
# they work fine in mpv (and mpv has further advantages like video scaling,
|
||||
# etc). Of course, I don't want the video to be played (or even to be
|
||||
# downloaded) twice — in MPV and in qwebkit. So I often close the tab after
|
||||
# opening it in mpv. However, I actually want to keep the rest of the page
|
||||
# (comments and video suggestions), i.e. only the videos should disappear
|
||||
# when mpv is started. And that's precisely what the present script does.
|
||||
#
|
||||
# Thorsten Wißmann, 2015 (thorsten` on freenode)
|
||||
# Any feedback is welcome!
|
||||
|
||||
if [ -z "$QUTE_FIFO" ] ; then
|
||||
cat 1>&2 <<EOF
|
||||
Error: $0 can not be run as a standalone script.
|
||||
|
||||
It is a qutebrowser userscript. In order to use it, call it using
|
||||
'spawn --userscript' as described in qute://help/userscripts.html
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
msg() {
|
||||
local cmd="$1"
|
||||
shift
|
||||
local msg="$*"
|
||||
if [ -z "$QUTE_FIFO" ] ; then
|
||||
echo "$cmd: $msg" >&2
|
||||
else
|
||||
echo "message-$cmd '${msg//\'/\\\'}'" >> "$QUTE_FIFO"
|
||||
fi
|
||||
}
|
||||
|
||||
MPV_COMMAND=${MPV_COMMAND:-mpv}
|
||||
# Warning: spaces in single flags are not supported
|
||||
MPV_FLAGS=${MPV_FLAGS:- --force-window --no-terminal --keep-open=yes --ytdl }
|
||||
video_command=( "$MPV_COMMAND" $MPV_FLAGS )
|
||||
|
||||
js() {
|
||||
cat <<EOF
|
||||
|
||||
function descendantOfTagName(child, ancestorTagName) {
|
||||
// tells whether child has some (proper) ancestor
|
||||
// with the tag name ancestorTagName
|
||||
while (child.parentNode != null) {
|
||||
child = child.parentNode;
|
||||
if (typeof child.tagName === 'undefined') break;
|
||||
if (child.tagName.toUpperCase() == ancestorTagName.toUpperCase()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var App = {};
|
||||
|
||||
var all_videos = [];
|
||||
all_videos.push.apply(all_videos, document.getElementsByTagName("video"));
|
||||
all_videos.push.apply(all_videos, document.getElementsByTagName("object"));
|
||||
all_videos.push.apply(all_videos, document.getElementsByTagName("embed"));
|
||||
App.backup_videos = Array();
|
||||
App.all_replacements = Array();
|
||||
for (i = 0; i < all_videos.length; i++) {
|
||||
var video = all_videos[i];
|
||||
if (descendantOfTagName(video, "object")) {
|
||||
// skip tags that are contained in an object, because we hide
|
||||
// the object anyway.
|
||||
continue;
|
||||
}
|
||||
var replacement = document.createElement("div");
|
||||
replacement.innerHTML = "
|
||||
<p style=\\"margin-bottom: 0.5em\\">
|
||||
Opening page with:
|
||||
<span style=\\"font-family: monospace;\\">${video_command[*]}</span>
|
||||
</p>
|
||||
<p>
|
||||
In order to restore this particular video
|
||||
<a style=\\"font-weight: bold;
|
||||
color: white;
|
||||
background: transparent;
|
||||
\\"
|
||||
onClick=\\"restore_video(this, " + i + ");\\"
|
||||
href=\\"javascript: restore_video(this, " + i + ")\\"
|
||||
>click here</a>.
|
||||
</p>
|
||||
";
|
||||
replacement.style.position = "relative";
|
||||
replacement.style.zIndex = "100003000000";
|
||||
replacement.style.fontSize = "1rem";
|
||||
replacement.style.textAlign = "center";
|
||||
replacement.style.verticalAlign = "middle";
|
||||
replacement.style.height = "100%";
|
||||
replacement.style.background = "#101010";
|
||||
replacement.style.color = "white";
|
||||
replacement.style.border = "4px dashed #545454";
|
||||
replacement.style.padding = "2em";
|
||||
replacement.style.margin = "auto";
|
||||
App.all_replacements[i] = replacement;
|
||||
App.backup_videos[i] = video;
|
||||
video.parentNode.replaceChild(replacement, video);
|
||||
}
|
||||
|
||||
function restore_video(obj, index) {
|
||||
obj = App.all_replacements[index];
|
||||
video = App.backup_videos[index];
|
||||
console.log(video);
|
||||
obj.parentNode.replaceChild(video, obj);
|
||||
}
|
||||
|
||||
/** force repainting the video, thanks to:
|
||||
* http://martinwolf.org/2014/06/10/force-repaint-of-an-element-with-javascript/
|
||||
*/
|
||||
var siteHeader = document.getElementById('header');
|
||||
siteHeader.style.display='none';
|
||||
siteHeader.offsetHeight; // no need to store this anywhere, the reference is enough
|
||||
siteHeader.style.display='block';
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
printjs() {
|
||||
js | sed 's,//.*$,,' | tr '\n' ' '
|
||||
}
|
||||
echo "jseval -q $(printjs)" >> "$QUTE_FIFO"
|
||||
|
||||
msg info "Opening $QUTE_URL with mpv"
|
||||
"${video_command[@]}" "$QUTE_URL"
|
||||
@@ -6,7 +6,9 @@ markers =
|
||||
windows: Tests which only can run on Windows.
|
||||
linux: Tests which only can run on Linux.
|
||||
osx: Tests which only can run on OS X.
|
||||
not_osx: Tests which can not run on OS X.
|
||||
not_frozen: Tests which can't be run if sys.frozen is True.
|
||||
not_xvfb: Tests which can't be run with Xvfb.
|
||||
frozen: Tests which can only be run if sys.frozen is True.
|
||||
integration: Tests which test a bigger portion of code, run without coverage.
|
||||
flakes-ignore =
|
||||
@@ -29,4 +31,11 @@ qt_log_ignore =
|
||||
^QWindowsWindow::setGeometryDp: Unable to set geometry .*
|
||||
^QProcess: Destroyed while process .* is still running\.
|
||||
^"Method "GetAll" with signature "s" on interface "org\.freedesktop\.DBus\.Properties" doesn't exist
|
||||
^virtual void QSslSocketBackendPrivate::transmit\(\) SSL write failed with error: -9805
|
||||
^virtual void QSslSocketBackendPrivate::transmit\(\) SSLRead failed with: -9805
|
||||
^Type conversion already registered from type .*
|
||||
^QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once\.
|
||||
^QWaitCondition: Destroyed while threads are still waiting
|
||||
^QXcbXSettings::QXcbXSettings\(QXcbScreen\*\) Failed to get selection owner for XSETTINGS_S atom
|
||||
^QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to .*
|
||||
^QXcbClipboard: SelectionRequest too old
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -24,12 +24,12 @@
|
||||
import os.path
|
||||
|
||||
__author__ = "Florian Bruhin"
|
||||
__copyright__ = "Copyright 2014-2015 Florian Bruhin (The Compiler)"
|
||||
__copyright__ = "Copyright 2014-2016 Florian Bruhin (The Compiler)"
|
||||
__license__ = "GPL"
|
||||
__maintainer__ = __author__
|
||||
__email__ = "mail@qutebrowser.org"
|
||||
__version_info__ = (0, 4, 0)
|
||||
__version__ = '.'.join(map(str, __version_info__))
|
||||
__version_info__ = (0, 5, 0)
|
||||
__version__ = '.'.join(str(e) for e in __version_info__)
|
||||
__description__ = "A keyboard-driven, vim-like browser based on PyQt5 and QtWebKit."
|
||||
|
||||
basedir = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 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-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -29,8 +29,10 @@ import shutil
|
||||
import tempfile
|
||||
import atexit
|
||||
import datetime
|
||||
import tokenize
|
||||
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from PyQt5.QtWebKit import QWebSettings
|
||||
from PyQt5.QtGui import QDesktopServices, QPixmap, QIcon, QCursor, QWindow
|
||||
from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QTimer, QUrl,
|
||||
QObject, Qt, QEvent)
|
||||
@@ -40,7 +42,7 @@ except ImportError:
|
||||
hunter = None
|
||||
|
||||
import qutebrowser
|
||||
import qutebrowser.resources # pylint: disable=unused-import
|
||||
import qutebrowser.resources
|
||||
from qutebrowser.completion.models import instances as completionmodels
|
||||
from qutebrowser.commands import cmdutils, runners, cmdexc
|
||||
from qutebrowser.config import style, config, websettings, configexc
|
||||
@@ -58,8 +60,7 @@ qApp = None
|
||||
|
||||
|
||||
def run(args):
|
||||
"""Initialize everthing and run the application."""
|
||||
# pylint: disable=too-many-statements
|
||||
"""Initialize everything and run the application."""
|
||||
if args.version:
|
||||
print(version.version(short=True))
|
||||
print()
|
||||
@@ -102,8 +103,9 @@ def run(args):
|
||||
if server is None:
|
||||
sys.exit(usertypes.Exit.ok)
|
||||
else:
|
||||
server.got_args.connect(lambda args, cwd:
|
||||
process_pos_args(args, cwd=cwd, via_ipc=True))
|
||||
server.got_args.connect(lambda args, target_arg, cwd:
|
||||
process_pos_args(args, cwd=cwd, via_ipc=True,
|
||||
target_arg=target_arg))
|
||||
|
||||
init(args, crash_handler)
|
||||
ret = qt_mainloop()
|
||||
@@ -228,7 +230,7 @@ def _load_session(name):
|
||||
session_manager.delete('_restart')
|
||||
|
||||
|
||||
def process_pos_args(args, via_ipc=False, cwd=None):
|
||||
def process_pos_args(args, via_ipc=False, cwd=None, target_arg=None):
|
||||
"""Process positional commandline args.
|
||||
|
||||
URLs to open have no prefix, commands to execute begin with a colon.
|
||||
@@ -237,6 +239,14 @@ def process_pos_args(args, via_ipc=False, cwd=None):
|
||||
args: A list of arguments to process.
|
||||
via_ipc: Whether the arguments were transmitted over IPC.
|
||||
cwd: The cwd to use for fuzzy_url.
|
||||
target_arg: Command line argument received by a running instance via
|
||||
ipc. If the --target argument was not specified, target_arg
|
||||
will be an empty string instead of None. This behavior is
|
||||
caused by the PyQt signal
|
||||
``got_args = pyqtSignal(list, str, str)``
|
||||
used in the misc.ipc.IPCServer class. PyQt converts the
|
||||
None value into a null QString and then back to an empty
|
||||
python string
|
||||
"""
|
||||
if via_ipc and not args:
|
||||
win_id = mainwindow.get_window(via_ipc, force_window=True)
|
||||
@@ -254,7 +264,11 @@ def process_pos_args(args, via_ipc=False, cwd=None):
|
||||
log.init.debug("Empty argument")
|
||||
win_id = mainwindow.get_window(via_ipc, force_window=True)
|
||||
else:
|
||||
win_id = mainwindow.get_window(via_ipc)
|
||||
if via_ipc and target_arg and target_arg != 'auto':
|
||||
open_target = target_arg
|
||||
else:
|
||||
open_target = config.get('general', 'new-instance-open-target')
|
||||
win_id = mainwindow.get_window(via_ipc, force_target=open_target)
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
log.init.debug("Startup URL {}".format(cmd))
|
||||
@@ -264,7 +278,6 @@ def process_pos_args(args, via_ipc=False, cwd=None):
|
||||
message.error('current', "Error in startup argument '{}': "
|
||||
"{}".format(cmd, e))
|
||||
else:
|
||||
open_target = config.get('general', 'new-instance-open-target')
|
||||
background = open_target in ('tab-bg', 'tab-bg-silent')
|
||||
tabbed_browser.tabopen(url, background=background)
|
||||
|
||||
@@ -413,30 +426,34 @@ def _init_modules(args, crash_handler):
|
||||
cookie_jar = cookies.CookieJar(qApp)
|
||||
objreg.register('cookie-jar', cookie_jar)
|
||||
log.init.debug("Initializing cache...")
|
||||
diskcache = cache.DiskCache(qApp)
|
||||
diskcache = cache.DiskCache(standarddir.cache(), parent=qApp)
|
||||
objreg.register('cache', diskcache)
|
||||
log.init.debug("Initializing completions...")
|
||||
completionmodels.init()
|
||||
log.init.debug("Misc initialization...")
|
||||
if config.get('ui', 'hide-wayland-decoration'):
|
||||
os.environ['QT_WAYLAND_DISABLE_WINDOWDECORATION'] = '1'
|
||||
else:
|
||||
os.environ.pop('QT_WAYLAND_DISABLE_WINDOWDECORATION', None)
|
||||
_maybe_hide_mouse_cursor()
|
||||
objreg.get('config').changed.connect(_maybe_hide_mouse_cursor)
|
||||
|
||||
|
||||
def _init_late_modules(args):
|
||||
"""Initialize modules which can be inited after the window is shown."""
|
||||
try:
|
||||
log.init.debug("Reading web history...")
|
||||
reader = objreg.get('web-history').async_read()
|
||||
with debug.log_time(log.init, 'Reading history'):
|
||||
while True:
|
||||
QApplication.processEvents()
|
||||
log.init.debug("Reading web history...")
|
||||
reader = objreg.get('web-history').async_read()
|
||||
with debug.log_time(log.init, 'Reading history'):
|
||||
while True:
|
||||
QApplication.processEvents()
|
||||
try:
|
||||
next(reader)
|
||||
except StopIteration:
|
||||
pass
|
||||
except (OSError, UnicodeDecodeError) as e:
|
||||
error.handle_fatal_exc(e, args, "Error while initializing!",
|
||||
pre_text="Error while initializing")
|
||||
sys.exit(usertypes.Exit.err_init)
|
||||
except StopIteration:
|
||||
break
|
||||
except (OSError, UnicodeDecodeError) as e:
|
||||
error.handle_fatal_exc(e, args, "Error while initializing!",
|
||||
pre_text="Error while initializing")
|
||||
sys.exit(usertypes.Exit.err_init)
|
||||
|
||||
|
||||
class Quitter:
|
||||
@@ -463,6 +480,25 @@ class Quitter:
|
||||
"""Slot which gets invoked when the last window was closed."""
|
||||
self.shutdown(last_window=True)
|
||||
|
||||
def _compile_modules(self):
|
||||
"""Compile all modules to catch SyntaxErrors."""
|
||||
if os.path.basename(sys.argv[0]) == 'qutebrowser':
|
||||
# Launched via launcher script
|
||||
return
|
||||
elif hasattr(sys, 'frozen'):
|
||||
return
|
||||
else:
|
||||
path = os.path.abspath(os.path.dirname(qutebrowser.__file__))
|
||||
if not os.path.isdir(path):
|
||||
# Probably running from an python egg.
|
||||
return
|
||||
|
||||
for dirpath, _dirnames, filenames in os.walk(path):
|
||||
for fn in filenames:
|
||||
if os.path.splitext(fn)[1] == '.py':
|
||||
with tokenize.open(os.path.join(dirpath, fn)) as f:
|
||||
compile(f.read(), fn, 'exec')
|
||||
|
||||
def _get_restart_args(self, pages=(), session=None):
|
||||
"""Get the current working directory and args to relaunch qutebrowser.
|
||||
|
||||
@@ -531,6 +567,10 @@ class Quitter:
|
||||
except sessions.SessionError as e:
|
||||
log.destroy.exception("Failed to save session!")
|
||||
raise cmdexc.CommandError("Failed to save session: {}!".format(e))
|
||||
except SyntaxError as e:
|
||||
log.destroy.exception("Got SyntaxError")
|
||||
raise cmdexc.CommandError("SyntaxError in {}:{}: {}".format(
|
||||
e.filename, e.lineno, e))
|
||||
if ok:
|
||||
self.shutdown()
|
||||
|
||||
@@ -551,6 +591,7 @@ class Quitter:
|
||||
Return:
|
||||
True if the restart succeeded, False otherwise.
|
||||
"""
|
||||
self._compile_modules()
|
||||
log.destroy.debug("sys.executable: {}".format(sys.executable))
|
||||
log.destroy.debug("sys.path: {}".format(sys.path))
|
||||
log.destroy.debug("sys.argv: {}".format(sys.argv))
|
||||
@@ -650,14 +691,19 @@ class Quitter:
|
||||
error.handle_fatal_exc(
|
||||
e, self._args, "Error while saving!",
|
||||
pre_text="Error while saving {}".format(key))
|
||||
# Disable storage so removing tempdir will work
|
||||
QWebSettings.setIconDatabasePath('')
|
||||
QWebSettings.setOfflineWebApplicationCachePath('')
|
||||
QWebSettings.globalSettings().setLocalStoragePath('')
|
||||
# Re-enable faulthandler to stdout, then remove crash log
|
||||
log.destroy.debug("Deactivating crash log...")
|
||||
objreg.get('crash-handler').destroy_crashlogfile()
|
||||
# Delete temp basedir
|
||||
if self._args.temp_basedir:
|
||||
atexit.register(shutil.rmtree, self._args.basedir)
|
||||
atexit.register(shutil.rmtree, self._args.basedir,
|
||||
ignore_errors=True)
|
||||
# If we don't kill our custom handler here we might get segfaults
|
||||
log.destroy.debug("Deactiving message handler...")
|
||||
log.destroy.debug("Deactivating message handler...")
|
||||
qInstallMessageHandler(None)
|
||||
# Now we can hopefully quit without segfaults
|
||||
log.destroy.debug("Deferring QApplication::exit...")
|
||||
@@ -702,6 +748,12 @@ class Application(QApplication):
|
||||
objreg.register('app', self)
|
||||
|
||||
self.launch_time = datetime.datetime.now()
|
||||
self.focusObjectChanged.connect(self.on_focus_object_changed)
|
||||
|
||||
@pyqtSlot(QObject)
|
||||
def on_focus_object_changed(self, obj):
|
||||
"""Log when the focus object changed."""
|
||||
log.misc.debug("Focus object changed: {!r}".format(obj))
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self)
|
||||
@@ -770,12 +822,9 @@ class EventFilter(QObject):
|
||||
Return:
|
||||
True if the event should be filtered, False if it's passed through.
|
||||
"""
|
||||
if qApp.overrideCursor() is None:
|
||||
# Mouse cursor shown -> don't filter event
|
||||
return False
|
||||
else:
|
||||
# Mouse cursor hidden -> filter event
|
||||
return True
|
||||
# Mouse cursor shown (overrideCursor None) -> don't filter event
|
||||
# Mouse cursor hidden (overrideCursor not None) -> filter event
|
||||
return qApp.overrideCursor() is not None
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
"""Handle an event.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 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-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -24,6 +24,7 @@ import os.path
|
||||
import functools
|
||||
import posixpath
|
||||
import zipfile
|
||||
import fnmatch
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import objreg, standarddir, log, message
|
||||
@@ -59,6 +60,22 @@ def get_fileobj(byte_io):
|
||||
return io.TextIOWrapper(byte_io, encoding='utf-8')
|
||||
|
||||
|
||||
def is_whitelisted_host(host):
|
||||
"""Check if the given host is on the adblock whitelist.
|
||||
|
||||
Args:
|
||||
host: The host of the request as string.
|
||||
"""
|
||||
whitelist = config.get('content', 'host-blocking-whitelist')
|
||||
if whitelist is None:
|
||||
return False
|
||||
|
||||
for pattern in whitelist:
|
||||
if fnmatch.fnmatch(host, pattern.lower()):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class FakeDownload:
|
||||
|
||||
"""A download stub to use on_download_finished with local files."""
|
||||
@@ -74,7 +91,7 @@ class HostBlocker:
|
||||
"""Manage blocked hosts based from /etc/hosts-like files.
|
||||
|
||||
Attributes:
|
||||
blocked_hosts: A set of blocked hosts.
|
||||
_blocked_hosts: A set of blocked hosts.
|
||||
_in_progress: The DownloadItems which are currently downloading.
|
||||
_done_count: How many files have been read successfully.
|
||||
_hosts_file: The path to the blocked-hosts file.
|
||||
@@ -87,7 +104,7 @@ class HostBlocker:
|
||||
'local')
|
||||
|
||||
def __init__(self):
|
||||
self.blocked_hosts = set()
|
||||
self._blocked_hosts = set()
|
||||
self._in_progress = []
|
||||
self._done_count = 0
|
||||
data_dir = standarddir.data()
|
||||
@@ -97,16 +114,23 @@ class HostBlocker:
|
||||
self._hosts_file = os.path.join(data_dir, 'blocked-hosts')
|
||||
objreg.get('config').changed.connect(self.on_config_changed)
|
||||
|
||||
def is_blocked(self, url):
|
||||
"""Check if the given URL (as QUrl) is blocked."""
|
||||
if not config.get('content', 'host-blocking-enabled'):
|
||||
return False
|
||||
host = url.host()
|
||||
return host in self._blocked_hosts and not is_whitelisted_host(host)
|
||||
|
||||
def read_hosts(self):
|
||||
"""Read hosts from the existing blocked-hosts file."""
|
||||
self.blocked_hosts = set()
|
||||
self._blocked_hosts = set()
|
||||
if self._hosts_file is None:
|
||||
return
|
||||
if os.path.exists(self._hosts_file):
|
||||
try:
|
||||
with open(self._hosts_file, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
self.blocked_hosts.add(line.strip())
|
||||
self._blocked_hosts.add(line.strip())
|
||||
except OSError:
|
||||
log.misc.exception("Failed to read host blocklist!")
|
||||
else:
|
||||
@@ -121,7 +145,7 @@ class HostBlocker:
|
||||
"""Update the adblock block lists."""
|
||||
if self._hosts_file is None:
|
||||
raise cmdexc.CommandError("No data storage is configured!")
|
||||
self.blocked_hosts = set()
|
||||
self._blocked_hosts = set()
|
||||
self._done_count = 0
|
||||
urls = config.get('content', 'host-block-lists')
|
||||
download_manager = objreg.get('download-manager', scope='window',
|
||||
@@ -189,7 +213,7 @@ class HostBlocker:
|
||||
error_count += 1
|
||||
continue
|
||||
if host not in self.WHITELISTED:
|
||||
self.blocked_hosts.add(host)
|
||||
self._blocked_hosts.add(host)
|
||||
log.misc.debug("{}: read {} lines".format(byte_io.name, line_count))
|
||||
if error_count > 0:
|
||||
message.error('current', "adblock: {} read errors for {}".format(
|
||||
@@ -198,10 +222,10 @@ class HostBlocker:
|
||||
def on_lists_downloaded(self):
|
||||
"""Install block lists after files have been downloaded."""
|
||||
with open(self._hosts_file, 'w', encoding='utf-8') as f:
|
||||
for host in sorted(self.blocked_hosts):
|
||||
for host in sorted(self._blocked_hosts):
|
||||
f.write(host + '\n')
|
||||
message.info('current', "adblock: Read {} hosts from {} sources."
|
||||
.format(len(self.blocked_hosts), self._done_count))
|
||||
.format(len(self._blocked_hosts), self._done_count))
|
||||
|
||||
@config.change_filter('content', 'host-block-lists')
|
||||
def on_config_changed(self):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -25,7 +25,7 @@ from PyQt5.QtCore import pyqtSlot
|
||||
from PyQt5.QtNetwork import QNetworkDiskCache, QNetworkCacheMetaData
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import utils, standarddir, objreg
|
||||
from qutebrowser.utils import utils, objreg
|
||||
|
||||
|
||||
class DiskCache(QNetworkDiskCache):
|
||||
@@ -34,17 +34,15 @@ class DiskCache(QNetworkDiskCache):
|
||||
|
||||
Attributes:
|
||||
_activated: Whether the cache should be used.
|
||||
_cache_dir: The base directory for cache files (standarddir.cache())
|
||||
_http_cache_dir: the HTTP subfolder in _cache_dir.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, cache_dir, parent=None):
|
||||
super().__init__(parent)
|
||||
cache_dir = standarddir.cache()
|
||||
if config.get('general', 'private-browsing') or cache_dir is None:
|
||||
self._activated = False
|
||||
else:
|
||||
self._activated = True
|
||||
self.setCacheDirectory(os.path.join(standarddir.cache(), 'http'))
|
||||
self.setMaximumCacheSize(config.get('storage', 'cache-size'))
|
||||
self._cache_dir = cache_dir
|
||||
self._http_cache_dir = os.path.join(cache_dir, 'http')
|
||||
self._maybe_activate()
|
||||
objreg.get('config').changed.connect(self.on_config_changed)
|
||||
|
||||
def __repr__(self):
|
||||
@@ -52,19 +50,24 @@ class DiskCache(QNetworkDiskCache):
|
||||
maxsize=self.maximumCacheSize(),
|
||||
path=self.cacheDirectory())
|
||||
|
||||
def _maybe_activate(self):
|
||||
"""Activate/deactivate the cache based on the config."""
|
||||
if (config.get('general', 'private-browsing') or
|
||||
self._cache_dir is None):
|
||||
self._activated = False
|
||||
else:
|
||||
self._activated = True
|
||||
self.setCacheDirectory(self._http_cache_dir)
|
||||
self.setMaximumCacheSize(config.get('storage', 'cache-size'))
|
||||
|
||||
@pyqtSlot(str, str)
|
||||
def on_config_changed(self, section, option):
|
||||
"""Update cache size/activated if the config was changed."""
|
||||
if (section, option) == ('storage', 'cache-size'):
|
||||
self.setMaximumCacheSize(config.get('storage', 'cache-size'))
|
||||
elif (section, option) == ('general', 'private-browsing'):
|
||||
if (config.get('general', 'private-browsing') or
|
||||
standarddir.cache() is None):
|
||||
self._activated = False
|
||||
else:
|
||||
self._activated = True
|
||||
self.setCacheDirectory(
|
||||
os.path.join(standarddir.cache(), 'http'))
|
||||
elif (section, option) == ('general', # pragma: no branch
|
||||
'private-browsing'):
|
||||
self._maybe_activate()
|
||||
|
||||
def cacheSize(self):
|
||||
"""Return the current size taken up by the cache.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -20,6 +20,8 @@
|
||||
"""Command dispatcher for TabbedBrowser."""
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
import shlex
|
||||
import posixpath
|
||||
import functools
|
||||
@@ -37,7 +39,7 @@ import pygments.formatters
|
||||
|
||||
from qutebrowser.commands import userscripts, cmdexc, cmdutils, runners
|
||||
from qutebrowser.config import config, configexc
|
||||
from qutebrowser.browser import webelem, inspector, urlmarks
|
||||
from qutebrowser.browser import webelem, inspector, urlmarks, downloads, mhtml
|
||||
from qutebrowser.keyinput import modeman
|
||||
from qutebrowser.utils import (message, usertypes, log, qtutils, urlutils,
|
||||
objreg, utils)
|
||||
@@ -152,59 +154,6 @@ class CommandDispatcher:
|
||||
else:
|
||||
return None
|
||||
|
||||
def _scroll_percent(self, perc=None, count=None, orientation=None):
|
||||
"""Inner logic for scroll_percent_(x|y).
|
||||
|
||||
Args:
|
||||
perc: How many percent to scroll, or None
|
||||
count: How many percent to scroll, or None
|
||||
orientation: Qt.Horizontal or Qt.Vertical
|
||||
"""
|
||||
if perc is None and count is None:
|
||||
perc = 100
|
||||
elif perc is None:
|
||||
perc = count
|
||||
if perc == 0:
|
||||
self.scroll('top')
|
||||
elif perc == 100:
|
||||
self.scroll('bottom')
|
||||
else:
|
||||
perc = qtutils.check_overflow(perc, 'int', fatal=False)
|
||||
frame = self._current_widget().page().currentFrame()
|
||||
m = frame.scrollBarMaximum(orientation)
|
||||
if m == 0:
|
||||
return
|
||||
frame.setScrollBarValue(orientation, int(m * perc / 100))
|
||||
|
||||
def _tab_move_absolute(self, idx):
|
||||
"""Get an index for moving a tab absolutely.
|
||||
|
||||
Args:
|
||||
idx: The index to get, as passed as count.
|
||||
"""
|
||||
if idx is None:
|
||||
return 0
|
||||
elif idx == 0:
|
||||
return self._count() - 1
|
||||
else:
|
||||
return idx - 1
|
||||
|
||||
def _tab_move_relative(self, direction, delta):
|
||||
"""Get an index for moving a tab relatively.
|
||||
|
||||
Args:
|
||||
direction: + or - for relative moving, None for absolute.
|
||||
delta: Delta to the current tab.
|
||||
"""
|
||||
if delta is None:
|
||||
# We don't set delta to 1 in the function arguments because this
|
||||
# gets called from tab_move which has delta set to None by default.
|
||||
delta = 1
|
||||
if direction == '-':
|
||||
return self._current_index() - delta
|
||||
elif direction == '+':
|
||||
return self._current_index() + delta
|
||||
|
||||
def _tab_focus_last(self):
|
||||
"""Select the tab which was last focused."""
|
||||
try:
|
||||
@@ -217,14 +166,6 @@ class CommandDispatcher:
|
||||
raise cmdexc.CommandError("Last focused tab vanished!")
|
||||
self._set_current_index(idx)
|
||||
|
||||
def _editor_cleanup(self, oshandle, filename):
|
||||
"""Clean up temporary file when the editor was closed."""
|
||||
try:
|
||||
os.close(oshandle)
|
||||
os.remove(filename)
|
||||
except OSError:
|
||||
raise cmdexc.CommandError("Failed to delete tempfile...")
|
||||
|
||||
def _get_selection_override(self, left, right, opposite):
|
||||
"""Helper function for tab_close to get the tab to select.
|
||||
|
||||
@@ -253,6 +194,9 @@ class CommandDispatcher:
|
||||
raise cmdexc.CommandError(
|
||||
"-o is not supported with 'tabs->select-on-remove' set to "
|
||||
"'previous'!")
|
||||
else: # pragma: no cover
|
||||
raise ValueError("Invalid select-on-remove value "
|
||||
"{!r}!".format(conf_selection))
|
||||
return None
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
@@ -387,8 +331,7 @@ class CommandDispatcher:
|
||||
Return:
|
||||
The new QWebView.
|
||||
"""
|
||||
if bg and window:
|
||||
raise cmdexc.CommandError("Only one of -b/-w can be given!")
|
||||
cmdutils.check_exclusive((bg, window), 'bw')
|
||||
curtab = self._current_widget()
|
||||
cur_title = self._tabbed_browser.page_title(self._current_index())
|
||||
# The new tab could be in a new tabbed_browser (e.g. because of
|
||||
@@ -402,7 +345,8 @@ class CommandDispatcher:
|
||||
window=newtab.win_id)
|
||||
idx = new_tabbed_browser.indexOf(newtab)
|
||||
new_tabbed_browser.set_page_title(idx, cur_title)
|
||||
new_tabbed_browser.setTabIcon(idx, curtab.icon())
|
||||
if config.get('tabs', 'show-favicons'):
|
||||
new_tabbed_browser.setTabIcon(idx, curtab.icon())
|
||||
newtab.keep_icon = True
|
||||
newtab.setZoomFactor(curtab.zoomFactor())
|
||||
history = qtutils.serialize(curtab.history())
|
||||
@@ -419,20 +363,27 @@ class CommandDispatcher:
|
||||
|
||||
def _back_forward(self, tab, bg, window, count, forward):
|
||||
"""Helper function for :back/:forward."""
|
||||
if (not forward and not
|
||||
self._current_widget().page().history().canGoBack()):
|
||||
# Catch common cases before e.g. cloning tab
|
||||
history = self._current_widget().page().history()
|
||||
if not forward and not history.canGoBack():
|
||||
raise cmdexc.CommandError("At beginning of history.")
|
||||
if (forward and not
|
||||
self._current_widget().page().history().canGoForward()):
|
||||
elif forward and not history.canGoForward():
|
||||
raise cmdexc.CommandError("At end of history.")
|
||||
|
||||
if tab or bg or window:
|
||||
widget = self.tab_clone(bg, window)
|
||||
else:
|
||||
widget = self._current_widget()
|
||||
|
||||
history = widget.page().history()
|
||||
for _ in range(count):
|
||||
if forward:
|
||||
if not history.canGoForward():
|
||||
raise cmdexc.CommandError("At end of history.")
|
||||
widget.forward()
|
||||
else:
|
||||
if not history.canGoBack():
|
||||
raise cmdexc.CommandError("At beginning of history.")
|
||||
widget.back()
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
@@ -471,8 +422,9 @@ class CommandDispatcher:
|
||||
background: Open the link in a new background tab.
|
||||
window: Open the link in a new window.
|
||||
"""
|
||||
segments = set(config.get('general', 'url-incdec-segments'))
|
||||
try:
|
||||
new_url = urlutils.incdec_number(url, incdec)
|
||||
new_url = urlutils.incdec_number(url, incdec, segments=segments)
|
||||
except urlutils.IncDecError as error:
|
||||
raise cmdexc.CommandError(error.msg)
|
||||
self._open(new_url, tab, background, window)
|
||||
@@ -534,13 +486,13 @@ class CommandDispatcher:
|
||||
self._navigate_up(url, tab, bg, window)
|
||||
elif where in ('decrement', 'increment'):
|
||||
self._navigate_incdec(url, where, tab, bg, window)
|
||||
else:
|
||||
else: # pragma: no cover
|
||||
raise ValueError("Got called with invalid value {} for "
|
||||
"`where'.".format(where))
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
scope='window', count='count')
|
||||
def scroll_px(self, dx: {'type': float}, dy: {'type': float}, count=1):
|
||||
def scroll_px(self, dx: {'type': int}, dy: {'type': int}, count=1):
|
||||
"""Scroll the current tab by 'count * dx/dy' pixels.
|
||||
|
||||
Args:
|
||||
@@ -556,31 +508,14 @@ class CommandDispatcher:
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
scope='window', count='count')
|
||||
def scroll(self,
|
||||
direction: {'type': (str, float)},
|
||||
dy: {'type': float, 'hide': True}=None,
|
||||
count=1):
|
||||
def scroll(self, direction: {'type': (str, int)}, count=1):
|
||||
"""Scroll the current tab in the given direction.
|
||||
|
||||
Args:
|
||||
direction: In which direction to scroll
|
||||
(up/down/left/right/top/bottom).
|
||||
dy: Deprecated argument to support the old dx/dy form.
|
||||
count: multiplier
|
||||
"""
|
||||
try:
|
||||
# Check for deprecated dx/dy form (like with scroll-px).
|
||||
dx = float(direction)
|
||||
dy = float(dy)
|
||||
except (ValueError, TypeError):
|
||||
# Invalid values will get handled later.
|
||||
pass
|
||||
else:
|
||||
message.warning(self._win_id, ":scroll with dx/dy arguments is "
|
||||
"deprecated - use :scroll-px instead!")
|
||||
self.scroll_px(dx, dy, count=count)
|
||||
return
|
||||
|
||||
fake_keys = {
|
||||
'up': Qt.Key_Up,
|
||||
'down': Qt.Key_Down,
|
||||
@@ -594,9 +529,10 @@ class CommandDispatcher:
|
||||
try:
|
||||
key = fake_keys[direction]
|
||||
except KeyError:
|
||||
expected_values = ', '.join(sorted(fake_keys))
|
||||
raise cmdexc.CommandError("Invalid value {!r} for direction - "
|
||||
"expected one of: {}".format(
|
||||
direction, ', '.join(fake_keys)))
|
||||
direction, expected_values))
|
||||
widget = self._current_widget()
|
||||
frame = widget.page().currentFrame()
|
||||
|
||||
@@ -643,8 +579,24 @@ class CommandDispatcher:
|
||||
horizontal: Scroll horizontally instead of vertically.
|
||||
count: Percentage to scroll.
|
||||
"""
|
||||
self._scroll_percent(perc, count,
|
||||
Qt.Horizontal if horizontal else Qt.Vertical)
|
||||
if perc is None and count is None:
|
||||
perc = 100
|
||||
elif perc is None:
|
||||
perc = count
|
||||
|
||||
orientation = Qt.Horizontal if horizontal else Qt.Vertical
|
||||
|
||||
if perc == 0 and orientation == Qt.Vertical:
|
||||
self.scroll('top')
|
||||
elif perc == 100 and orientation == Qt.Vertical:
|
||||
self.scroll('bottom')
|
||||
else:
|
||||
perc = qtutils.check_overflow(perc, 'int', fatal=False)
|
||||
frame = self._current_widget().page().currentFrame()
|
||||
m = frame.scrollBarMaximum(orientation)
|
||||
if m == 0:
|
||||
return
|
||||
frame.setScrollBarValue(orientation, int(m * perc / 100))
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
scope='window', count='count')
|
||||
@@ -686,7 +638,7 @@ class CommandDispatcher:
|
||||
pass
|
||||
elif mult_y < 0:
|
||||
self.scroll('page-up', count=-int(mult_y))
|
||||
elif mult_y > 0:
|
||||
elif mult_y > 0: # pragma: no branch
|
||||
self.scroll('page-down', count=int(mult_y))
|
||||
mult_y = 0
|
||||
if mult_x == 0 and mult_y == 0:
|
||||
@@ -728,9 +680,16 @@ class CommandDispatcher:
|
||||
mode = QClipboard.Clipboard
|
||||
target = "clipboard"
|
||||
log.misc.debug("Yanking to {}: '{}'".format(target, s))
|
||||
|
||||
msg = "Yanked {} to {}: {}".format(what, target, s)
|
||||
clipboard.changed.connect(functools.partial(
|
||||
self._display_yank_msg, clipboard, msg))
|
||||
clipboard.setText(s, mode)
|
||||
message.info(self._win_id, "Yanked {} to {}: {}".format(
|
||||
what, target, s))
|
||||
|
||||
def _display_yank_msg(self, clipboard, msg):
|
||||
"""Display a message when something was yanked."""
|
||||
message.info(self._win_id, msg)
|
||||
clipboard.changed.disconnect()
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
count='count')
|
||||
@@ -915,19 +874,25 @@ class CommandDispatcher:
|
||||
If moving relatively: Offset.
|
||||
"""
|
||||
if direction is None:
|
||||
new_idx = self._tab_move_absolute(count)
|
||||
# absolute moving
|
||||
new_idx = 0 if count is None else count - 1
|
||||
elif direction in '+-':
|
||||
try:
|
||||
new_idx = self._tab_move_relative(direction, count)
|
||||
except ValueError:
|
||||
raise cmdexc.CommandError("Count must be given for relative "
|
||||
"moving!")
|
||||
else:
|
||||
raise cmdexc.CommandError("Invalid direction '{}'!".format(
|
||||
direction))
|
||||
# relative moving
|
||||
delta = 1 if count is None else count
|
||||
if direction == '-':
|
||||
new_idx = self._current_index() - delta
|
||||
elif direction == '+': # pragma: no branch
|
||||
new_idx = self._current_index() + delta
|
||||
|
||||
if config.get('tabs', 'wrap'):
|
||||
new_idx %= self._count()
|
||||
else: # pragma: no cover
|
||||
raise ValueError("Invalid direction '{}'!".format(direction))
|
||||
|
||||
if not 0 <= new_idx < self._count():
|
||||
raise cmdexc.CommandError("Can't move tab to position {}!".format(
|
||||
new_idx))
|
||||
new_idx + 1))
|
||||
|
||||
tab = self._current_widget()
|
||||
cur_idx = self._current_index()
|
||||
icon = self._tabbed_browser.tabIcon(cur_idx)
|
||||
@@ -951,7 +916,9 @@ class CommandDispatcher:
|
||||
useful here.
|
||||
|
||||
Args:
|
||||
userscript: Run the command as a userscript.
|
||||
userscript: Run the command as a userscript. Either store the
|
||||
userscript in `~/.local/share/qutebrowser/userscripts`
|
||||
(or `$XDG_DATA_DIR`), or use an absolute path.
|
||||
verbose: Show notifications when the command started/exited.
|
||||
detach: Whether the command should be detached from qutebrowser.
|
||||
cmdline: The commandline to execute.
|
||||
@@ -968,7 +935,7 @@ class CommandDispatcher:
|
||||
cmd, args, userscript))
|
||||
if userscript:
|
||||
# ~ expansion is handled by the userscript module.
|
||||
self.run_userscript(cmd, *args, verbose=verbose)
|
||||
self._run_userscript(cmd, *args, verbose=verbose)
|
||||
else:
|
||||
cmd = os.path.expanduser(cmd)
|
||||
proc = guiprocess.GUIProcess(self._win_id, what='command',
|
||||
@@ -984,9 +951,7 @@ class CommandDispatcher:
|
||||
"""Open main startpage in current tab."""
|
||||
self.openurl(config.get('general', 'startpage')[0])
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
deprecated='Use :spawn --userscript instead!')
|
||||
def run_userscript(self, cmd, *args: {'nargs': '*'}, verbose=False):
|
||||
def _run_userscript(self, cmd, *args, verbose=False):
|
||||
"""Run a userscript given as argument.
|
||||
|
||||
Args:
|
||||
@@ -1138,34 +1103,72 @@ class CommandDispatcher:
|
||||
cur.inspector.show()
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def download(self, url=None, dest=None):
|
||||
def download(self, url=None, dest_old: {'hide': True}=None, *,
|
||||
mhtml_=False, dest=None):
|
||||
"""Download a given URL, or current page if no URL given.
|
||||
|
||||
The form `:download [url] [dest]` is deprecated, use `:download --dest
|
||||
[dest] [url]` instead.
|
||||
|
||||
Args:
|
||||
url: The URL to download. If not given, download the current page.
|
||||
dest_old: (deprecated) Same as dest.
|
||||
dest: The file path to write the download to, or None to ask.
|
||||
mhtml_: Download the current page and all assets as mhtml file.
|
||||
"""
|
||||
if dest_old is not None:
|
||||
message.warning(
|
||||
self._win_id, ":download [url] [dest] is deprecated - use"
|
||||
" download --dest [dest] [url]")
|
||||
if dest is not None:
|
||||
raise cmdexc.CommandError("Can't give two destinations for the"
|
||||
" download.")
|
||||
dest = dest_old
|
||||
|
||||
download_manager = objreg.get('download-manager', scope='window',
|
||||
window=self._win_id)
|
||||
if url:
|
||||
if mhtml_:
|
||||
raise cmdexc.CommandError("Can only download the current page"
|
||||
" as mhtml.")
|
||||
url = urlutils.qurl_from_user_input(url)
|
||||
urlutils.raise_cmdexc_if_invalid(url)
|
||||
download_manager.get(url, filename=dest)
|
||||
else:
|
||||
page = self._current_widget().page()
|
||||
download_manager.get(self._current_url(), page=page)
|
||||
if mhtml_:
|
||||
self._download_mhtml(dest)
|
||||
else:
|
||||
page = self._current_widget().page()
|
||||
download_manager.get(self._current_url(), page=page,
|
||||
filename=dest)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
deprecated="Use :download instead.")
|
||||
def download_page(self):
|
||||
"""Download the current page."""
|
||||
self.download()
|
||||
def _download_mhtml(self, dest=None):
|
||||
"""Download the current page as a MHTML file, including all assets.
|
||||
|
||||
Args:
|
||||
dest: The file path to write the download to.
|
||||
"""
|
||||
web_view = self._current_widget()
|
||||
if dest is None:
|
||||
suggested_fn = self._current_title() + ".mht"
|
||||
suggested_fn = utils.sanitize_filename(suggested_fn)
|
||||
filename, q = downloads.ask_for_filename(
|
||||
suggested_fn, self._win_id, parent=web_view,
|
||||
)
|
||||
if filename is not None:
|
||||
mhtml.start_download_checked(filename, web_view=web_view)
|
||||
else:
|
||||
q.answered.connect(functools.partial(
|
||||
mhtml.start_download_checked, web_view=web_view))
|
||||
q.ask()
|
||||
else:
|
||||
mhtml.start_download_checked(dest, web_view=web_view)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def view_source(self):
|
||||
"""Show the source of the current page."""
|
||||
# pylint: disable=no-member
|
||||
# https://bitbucket.org/logilab/pylint/issue/491/
|
||||
# WORKAROUND for https://bitbucket.org/logilab/pylint/issue/491/
|
||||
widget = self._current_widget()
|
||||
if widget.viewing_source:
|
||||
raise cmdexc.CommandError("Already viewing source!")
|
||||
@@ -1180,6 +1183,32 @@ class CommandDispatcher:
|
||||
tab.setHtml(highlighted, current_url)
|
||||
tab.viewing_source = True
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
debug=True)
|
||||
def debug_dump_page(self, dest, plain=False):
|
||||
"""Dump the current page's content to a file.
|
||||
|
||||
Args:
|
||||
dest: Where to write the file to.
|
||||
plain: Write plain text instead of HTML.
|
||||
"""
|
||||
web_view = self._current_widget()
|
||||
mainframe = web_view.page().mainFrame()
|
||||
if plain:
|
||||
data = mainframe.toPlainText()
|
||||
else:
|
||||
data = mainframe.toHtml()
|
||||
|
||||
dest = os.path.expanduser(dest)
|
||||
|
||||
try:
|
||||
with open(dest, 'w', encoding='utf-8') as f:
|
||||
f.write(data)
|
||||
except OSError as e:
|
||||
raise cmdexc.CommandError('Could not write page: {}'.format(e))
|
||||
else:
|
||||
message.info(self._win_id, "Dumped page to {}.".format(dest))
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', name='help',
|
||||
completion=[usertypes.Completion.helptopic],
|
||||
scope='window')
|
||||
@@ -1269,6 +1298,17 @@ class CommandDispatcher:
|
||||
except webelem.IsNullError:
|
||||
raise cmdexc.CommandError("Element vanished while editing!")
|
||||
|
||||
def _clear_search(self, view, text):
|
||||
"""Clear search string/highlights for the given view.
|
||||
|
||||
This does nothing if the view's search text is the same as the given
|
||||
text.
|
||||
"""
|
||||
if view.search_text is not None and view.search_text != text:
|
||||
# We first clear the marked text, then the highlights
|
||||
view.search('', 0)
|
||||
view.search('', QWebPage.HighlightAllOccurrences)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
maxsplit=0)
|
||||
def search(self, text="", reverse=False):
|
||||
@@ -1279,11 +1319,7 @@ class CommandDispatcher:
|
||||
reverse: Reverse search direction.
|
||||
"""
|
||||
view = self._current_widget()
|
||||
if view.search_text is not None and view.search_text != text:
|
||||
# We first clear the marked text, then the highlights
|
||||
view.search('', 0)
|
||||
view.search('', QWebPage.HighlightAllOccurrences)
|
||||
|
||||
self._clear_search(view, text)
|
||||
flags = 0
|
||||
ignore_case = config.get('general', 'ignore-case')
|
||||
if ignore_case == 'smart':
|
||||
@@ -1301,6 +1337,8 @@ class CommandDispatcher:
|
||||
view.search(text, flags | QWebPage.HighlightAllOccurrences)
|
||||
view.search_text = text
|
||||
view.search_flags = flags
|
||||
self._tabbed_browser.search_text = text
|
||||
self._tabbed_browser.search_flags = flags
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
scope='window', count='count')
|
||||
@@ -1311,7 +1349,14 @@ class CommandDispatcher:
|
||||
count: How many elements to ignore.
|
||||
"""
|
||||
view = self._current_widget()
|
||||
if view.search_text is not None:
|
||||
|
||||
self._clear_search(view, self._tabbed_browser.search_text)
|
||||
|
||||
if self._tabbed_browser.search_text is not None:
|
||||
view.search_text = self._tabbed_browser.search_text
|
||||
view.search_flags = self._tabbed_browser.search_flags
|
||||
view.search(view.search_text,
|
||||
view.search_flags | QWebPage.HighlightAllOccurrences)
|
||||
for _ in range(count):
|
||||
view.search(view.search_text, view.search_flags)
|
||||
|
||||
@@ -1324,8 +1369,13 @@ class CommandDispatcher:
|
||||
count: How many elements to ignore.
|
||||
"""
|
||||
view = self._current_widget()
|
||||
if view.search_text is None:
|
||||
return
|
||||
self._clear_search(view, self._tabbed_browser.search_text)
|
||||
|
||||
if self._tabbed_browser.search_text is not None:
|
||||
view.search_text = self._tabbed_browser.search_text
|
||||
view.search_flags = self._tabbed_browser.search_flags
|
||||
view.search(view.search_text,
|
||||
view.search_flags | QWebPage.HighlightAllOccurrences)
|
||||
# The int() here serves as a QFlags constructor to create a copy of the
|
||||
# QFlags instance rather as a reference. I don't know why it works this
|
||||
# way, but it does.
|
||||
@@ -1411,11 +1461,16 @@ class CommandDispatcher:
|
||||
"""
|
||||
webview = self._current_widget()
|
||||
if not webview.selection_enabled:
|
||||
act = QWebPage.MoveToNextWord
|
||||
act = [QWebPage.MoveToNextWord]
|
||||
if sys.platform == 'win32':
|
||||
act.append(QWebPage.MoveToPreviousChar)
|
||||
else:
|
||||
act = QWebPage.SelectNextWord
|
||||
act = [QWebPage.SelectNextWord]
|
||||
if sys.platform == 'win32':
|
||||
act.append(QWebPage.SelectPreviousChar)
|
||||
for _ in range(count):
|
||||
webview.triggerPageAction(act)
|
||||
for a in act:
|
||||
webview.triggerPageAction(a)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
modes=[KeyMode.caret], scope='window', count='count')
|
||||
@@ -1427,9 +1482,13 @@ class CommandDispatcher:
|
||||
"""
|
||||
webview = self._current_widget()
|
||||
if not webview.selection_enabled:
|
||||
act = [QWebPage.MoveToNextWord, QWebPage.MoveToNextChar]
|
||||
act = [QWebPage.MoveToNextWord]
|
||||
if sys.platform != 'win32':
|
||||
act.append(QWebPage.MoveToNextChar)
|
||||
else:
|
||||
act = [QWebPage.SelectNextWord, QWebPage.SelectNextChar]
|
||||
act = [QWebPage.SelectNextWord]
|
||||
if sys.platform != 'win32':
|
||||
act.append(QWebPage.SelectNextChar)
|
||||
for _ in range(count):
|
||||
for a in act:
|
||||
webview.triggerPageAction(a)
|
||||
@@ -1482,10 +1541,10 @@ class CommandDispatcher:
|
||||
"""
|
||||
webview = self._current_widget()
|
||||
if not webview.selection_enabled:
|
||||
act = [QWebPage.MoveToEndOfBlock, QWebPage.MoveToNextLine,
|
||||
act = [QWebPage.MoveToNextLine,
|
||||
QWebPage.MoveToStartOfBlock]
|
||||
else:
|
||||
act = [QWebPage.SelectEndOfBlock, QWebPage.SelectNextLine,
|
||||
act = [QWebPage.SelectNextLine,
|
||||
QWebPage.SelectStartOfBlock]
|
||||
for _ in range(count):
|
||||
for a in act:
|
||||
@@ -1501,10 +1560,10 @@ class CommandDispatcher:
|
||||
"""
|
||||
webview = self._current_widget()
|
||||
if not webview.selection_enabled:
|
||||
act = [QWebPage.MoveToStartOfBlock, QWebPage.MoveToPreviousLine,
|
||||
act = [QWebPage.MoveToPreviousLine,
|
||||
QWebPage.MoveToStartOfBlock]
|
||||
else:
|
||||
act = [QWebPage.SelectStartOfBlock, QWebPage.SelectPreviousLine,
|
||||
act = [QWebPage.SelectPreviousLine,
|
||||
QWebPage.SelectStartOfBlock]
|
||||
for _ in range(count):
|
||||
for a in act:
|
||||
@@ -1520,10 +1579,10 @@ class CommandDispatcher:
|
||||
"""
|
||||
webview = self._current_widget()
|
||||
if not webview.selection_enabled:
|
||||
act = [QWebPage.MoveToEndOfBlock, QWebPage.MoveToNextLine,
|
||||
act = [QWebPage.MoveToNextLine,
|
||||
QWebPage.MoveToEndOfBlock]
|
||||
else:
|
||||
act = [QWebPage.SelectEndOfBlock, QWebPage.SelectNextLine,
|
||||
act = [QWebPage.SelectNextLine,
|
||||
QWebPage.SelectEndOfBlock]
|
||||
for _ in range(count):
|
||||
for a in act:
|
||||
@@ -1539,11 +1598,9 @@ class CommandDispatcher:
|
||||
"""
|
||||
webview = self._current_widget()
|
||||
if not webview.selection_enabled:
|
||||
act = [QWebPage.MoveToStartOfBlock, QWebPage.MoveToPreviousLine,
|
||||
QWebPage.MoveToEndOfBlock]
|
||||
act = [QWebPage.MoveToPreviousLine, QWebPage.MoveToEndOfBlock]
|
||||
else:
|
||||
act = [QWebPage.SelectStartOfBlock, QWebPage.SelectPreviousLine,
|
||||
QWebPage.SelectEndOfBlock]
|
||||
act = [QWebPage.SelectPreviousLine, QWebPage.SelectEndOfBlock]
|
||||
for _ in range(count):
|
||||
for a in act:
|
||||
webview.triggerPageAction(a)
|
||||
@@ -1590,7 +1647,6 @@ class CommandDispatcher:
|
||||
else:
|
||||
mode = QClipboard.Clipboard
|
||||
target = "clipboard"
|
||||
log.misc.debug("Yanking to {}: '{}'".format(target, s))
|
||||
clipboard.setText(s, mode)
|
||||
message.info(self._win_id, "{} {} yanked to {}".format(
|
||||
len(s), "char" if len(s) == 1 else "chars", target))
|
||||
@@ -1662,3 +1718,40 @@ class CommandDispatcher:
|
||||
message.info(self._win_id, out[:5000] + ' [...trimmed...]')
|
||||
else:
|
||||
message.info(self._win_id, out)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def fake_key(self, keystring, global_=False):
|
||||
"""Send a fake keypress or key string to the website or qutebrowser.
|
||||
|
||||
:fake-key xy - sends the keychain 'xy'
|
||||
:fake-key <Ctrl-x> - sends Ctrl-x
|
||||
:fake-key <Escape> - sends the escape key
|
||||
|
||||
Args:
|
||||
keystring: The keystring to send.
|
||||
global_: If given, the keys are sent to the qutebrowser UI.
|
||||
"""
|
||||
try:
|
||||
keyinfos = utils.parse_keystring(keystring)
|
||||
except utils.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)
|
||||
|
||||
if global_:
|
||||
receiver = QApplication.focusWindow()
|
||||
if receiver is None:
|
||||
raise cmdexc.CommandError("No focused window!")
|
||||
else:
|
||||
try:
|
||||
receiver = objreg.get('webview', scope='tab',
|
||||
tab='current')
|
||||
except objreg.RegistryUnavailableError:
|
||||
raise cmdexc.CommandError("No focused webview!")
|
||||
|
||||
QApplication.postEvent(receiver, press_event)
|
||||
QApplication.postEvent(receiver, release_event)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -68,19 +68,27 @@ class CookieJar(RAMCookieJar):
|
||||
_lineparser: The LineParser managing the cookies file.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, parent=None, *, line_parser=None):
|
||||
super().__init__(parent)
|
||||
self._lineparser = lineparser.LineParser(
|
||||
standarddir.data(), 'cookies', binary=True, parent=self)
|
||||
cookies = []
|
||||
for line in self._lineparser:
|
||||
cookies += QNetworkCookie.parseCookies(line)
|
||||
self.setAllCookies(cookies)
|
||||
|
||||
if line_parser:
|
||||
self._lineparser = line_parser
|
||||
else:
|
||||
self._lineparser = lineparser.LineParser(
|
||||
standarddir.data(), 'cookies', binary=True, parent=self)
|
||||
self.parse_cookies()
|
||||
objreg.get('config').changed.connect(self.cookies_store_changed)
|
||||
objreg.get('save-manager').add_saveable(
|
||||
'cookies', self.save, self.changed,
|
||||
config_opt=('content', 'cookies-store'))
|
||||
|
||||
def parse_cookies(self):
|
||||
"""Parse cookies from lineparser and store them."""
|
||||
cookies = []
|
||||
for line in self._lineparser:
|
||||
cookies += QNetworkCookie.parseCookies(line)
|
||||
self.setAllCookies(cookies)
|
||||
|
||||
def purge_old_cookies(self):
|
||||
"""Purge expired cookies from the cookie jar."""
|
||||
# Based on:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -48,8 +48,13 @@ ModelRole = usertypes.enum('ModelRole', ['item'], start=Qt.UserRole,
|
||||
|
||||
RetryInfo = collections.namedtuple('RetryInfo', ['request', 'manager'])
|
||||
|
||||
|
||||
DownloadPath = collections.namedtuple('DownloadPath', ['filename',
|
||||
'question'])
|
||||
|
||||
|
||||
# Remember the last used directory
|
||||
_last_used_directory = None
|
||||
last_used_directory = None
|
||||
|
||||
|
||||
# All REFRESH_INTERVAL milliseconds, speeds will be recalculated and downloads
|
||||
@@ -57,20 +62,20 @@ _last_used_directory = None
|
||||
REFRESH_INTERVAL = 500
|
||||
|
||||
|
||||
def _download_dir():
|
||||
def download_dir():
|
||||
"""Get the download directory to use."""
|
||||
directory = config.get('storage', 'download-directory')
|
||||
remember_dir = config.get('storage', 'remember-download-directory')
|
||||
|
||||
if remember_dir and _last_used_directory is not None:
|
||||
return _last_used_directory
|
||||
if remember_dir and last_used_directory is not None:
|
||||
return last_used_directory
|
||||
elif directory is None:
|
||||
return standarddir.download()
|
||||
else:
|
||||
return directory
|
||||
|
||||
|
||||
def _path_suggestion(filename):
|
||||
def path_suggestion(filename):
|
||||
"""Get the suggested file path.
|
||||
|
||||
Args:
|
||||
@@ -79,15 +84,79 @@ def _path_suggestion(filename):
|
||||
suggestion = config.get('completion', 'download-path-suggestion')
|
||||
if suggestion == 'path':
|
||||
# add trailing '/' if not present
|
||||
return os.path.join(_download_dir(), '')
|
||||
return os.path.join(download_dir(), '')
|
||||
elif suggestion == 'filename':
|
||||
return filename
|
||||
elif suggestion == 'both':
|
||||
return os.path.join(_download_dir(), filename)
|
||||
return os.path.join(download_dir(), filename)
|
||||
else:
|
||||
raise ValueError("Invalid suggestion value {}!".format(suggestion))
|
||||
|
||||
|
||||
def create_full_filename(basename, filename):
|
||||
"""Create a full filename based on the given basename and filename.
|
||||
|
||||
Args:
|
||||
basename: The basename to use if filename is a directory.
|
||||
filename: The path to a folder or file where you want to save.
|
||||
|
||||
Return:
|
||||
The full absolute path, or None if filename creation was not possible.
|
||||
"""
|
||||
if os.path.isabs(filename) and os.path.isdir(filename):
|
||||
# We got an absolute directory from the user, so we save it under
|
||||
# the default filename in that directory.
|
||||
return os.path.join(filename, basename)
|
||||
elif os.path.isabs(filename):
|
||||
# We got an absolute filename from the user, so we save it under
|
||||
# that filename.
|
||||
return filename
|
||||
return None
|
||||
|
||||
|
||||
def ask_for_filename(suggested_filename, win_id, *, parent=None,
|
||||
prompt_download_directory=None):
|
||||
"""Prepare a question for a download-path.
|
||||
|
||||
If a filename can be determined directly, it is returned instead.
|
||||
|
||||
Returns a (filename, question)-namedtuple, in which one component is
|
||||
None. filename is a string, question is a usertypes.Question. The
|
||||
question has a special .ask() method that takes no arguments for
|
||||
convenience, as this function does not yet ask the question, it
|
||||
only prepares it.
|
||||
|
||||
Args:
|
||||
suggested_filename: The "default"-name that is pre-entered as path.
|
||||
win_id: The window where the question will be asked.
|
||||
parent: The parent of the question (a QObject).
|
||||
prompt_download_directory: If this is something else than None, it
|
||||
will overwrite the
|
||||
storage->prompt-download-directory setting.
|
||||
"""
|
||||
if prompt_download_directory is None:
|
||||
prompt_download_directory = config.get('storage',
|
||||
'prompt-download-directory')
|
||||
|
||||
if not prompt_download_directory:
|
||||
return DownloadPath(filename=download_dir(), question=None)
|
||||
|
||||
encoding = sys.getfilesystemencoding()
|
||||
suggested_filename = utils.force_encoding(suggested_filename,
|
||||
encoding)
|
||||
|
||||
q = usertypes.Question(parent)
|
||||
q.text = "Save file to:"
|
||||
q.mode = usertypes.PromptMode.text
|
||||
q.completed.connect(q.deleteLater)
|
||||
q.default = path_suggestion(suggested_filename)
|
||||
|
||||
message_bridge = objreg.get('message-bridge', scope='window',
|
||||
window=win_id)
|
||||
q.ask = lambda: message_bridge.ask(q, blocking=False)
|
||||
return DownloadPath(filename=None, question=q)
|
||||
|
||||
|
||||
class DownloadItemStats(QObject):
|
||||
|
||||
"""Statistics (bytes done, total bytes, time, etc.) about a download.
|
||||
@@ -201,6 +270,7 @@ class DownloadItem(QObject):
|
||||
fileobj: The file object to download the file to.
|
||||
reply: The QNetworkReply associated with this download.
|
||||
retry_info: A RetryInfo instance.
|
||||
raw_headers: The headers sent by the server.
|
||||
_filename: The filename of the download.
|
||||
_redirects: How many time we were redirected already.
|
||||
_buffer: A BytesIO object to buffer incoming data until we know the
|
||||
@@ -255,6 +325,7 @@ class DownloadItem(QObject):
|
||||
self._filename = None
|
||||
self.init_reply(reply)
|
||||
self._win_id = win_id
|
||||
self.raw_headers = {}
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, basename=self.basename)
|
||||
@@ -354,6 +425,7 @@ class DownloadItem(QObject):
|
||||
reply.finished.connect(self.on_reply_finished)
|
||||
reply.error.connect(self.on_reply_error)
|
||||
reply.readyRead.connect(self.on_ready_read)
|
||||
reply.metaDataChanged.connect(self.on_meta_data_changed)
|
||||
self.retry_info = RetryInfo(request=reply.request(),
|
||||
manager=reply.manager())
|
||||
if not self.fileobj:
|
||||
@@ -420,6 +492,8 @@ class DownloadItem(QObject):
|
||||
@pyqtSlot()
|
||||
def retry(self):
|
||||
"""Retry a failed download."""
|
||||
assert self.done
|
||||
assert not self.successful
|
||||
download_manager = objreg.get('download-manager', scope='window',
|
||||
window=self._win_id)
|
||||
new_reply = self.retry_info.manager.get(self.retry_info.request)
|
||||
@@ -442,7 +516,7 @@ class DownloadItem(QObject):
|
||||
filename: The full filename to save the download to.
|
||||
None: special value to stop the download.
|
||||
"""
|
||||
global _last_used_directory
|
||||
global last_used_directory
|
||||
if self.fileobj is not None:
|
||||
raise ValueError("fileobj was already set! filename: {}, "
|
||||
"existing: {}, fileobj {}".format(
|
||||
@@ -452,13 +526,16 @@ class DownloadItem(QObject):
|
||||
# See https://github.com/The-Compiler/qutebrowser/issues/427
|
||||
encoding = sys.getfilesystemencoding()
|
||||
filename = utils.force_encoding(filename, encoding)
|
||||
if not self._create_full_filename(filename):
|
||||
self._filename = create_full_filename(self.basename, filename)
|
||||
if self._filename is None:
|
||||
# We only got a filename (without directory) or a relative path
|
||||
# from the user, so we append that to the default directory and
|
||||
# try again.
|
||||
self._create_full_filename(os.path.join(_download_dir(), filename))
|
||||
self._filename = create_full_filename(
|
||||
self.basename, os.path.join(download_dir(), filename))
|
||||
|
||||
_last_used_directory = os.path.dirname(self._filename)
|
||||
self.basename = os.path.basename(self._filename)
|
||||
last_used_directory = os.path.dirname(self._filename)
|
||||
|
||||
log.downloads.debug("Setting filename to {}".format(filename))
|
||||
if os.path.isfile(self._filename):
|
||||
@@ -475,25 +552,6 @@ class DownloadItem(QObject):
|
||||
else:
|
||||
self._create_fileobj()
|
||||
|
||||
def _create_full_filename(self, filename):
|
||||
"""Try to create the full filename.
|
||||
|
||||
Return:
|
||||
True if the full filename was created, False otherwise.
|
||||
"""
|
||||
if os.path.isabs(filename) and os.path.isdir(filename):
|
||||
# We got an absolute directory from the user, so we save it under
|
||||
# the default filename in that directory.
|
||||
self._filename = os.path.join(filename, self.basename)
|
||||
return True
|
||||
elif os.path.isabs(filename):
|
||||
# We got an absolute filename from the user, so we save it under
|
||||
# that filename.
|
||||
self._filename = filename
|
||||
self.basename = os.path.basename(self._filename)
|
||||
return True
|
||||
return False
|
||||
|
||||
def set_fileobj(self, fileobj):
|
||||
""""Set the file object to write the download to.
|
||||
|
||||
@@ -591,6 +649,15 @@ class DownloadItem(QObject):
|
||||
if data is not None:
|
||||
self._buffer.write(data)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_meta_data_changed(self):
|
||||
"""Update the download's metadata."""
|
||||
if self.reply is None:
|
||||
return
|
||||
self.raw_headers = {}
|
||||
for key, value in self.reply.rawHeaderPairs():
|
||||
self.raw_headers[bytes(key)] = bytes(value)
|
||||
|
||||
def _handle_redirect(self):
|
||||
"""Handle a HTTP redirect.
|
||||
|
||||
@@ -649,15 +716,10 @@ class DownloadManager(QAbstractListModel):
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, downloads=len(self.downloads))
|
||||
|
||||
def _prepare_question(self):
|
||||
"""Prepare a Question object to be asked."""
|
||||
q = usertypes.Question(self)
|
||||
q.text = "Save file to:"
|
||||
q.mode = usertypes.PromptMode.text
|
||||
q.completed.connect(q.deleteLater)
|
||||
def _postprocess_question(self, q):
|
||||
"""Postprocess a Question object that is asked."""
|
||||
q.destroyed.connect(functools.partial(self.questions.remove, q))
|
||||
self.questions.append(q)
|
||||
return q
|
||||
|
||||
@pyqtSlot()
|
||||
def update_gui(self):
|
||||
@@ -714,11 +776,12 @@ class DownloadManager(QAbstractListModel):
|
||||
QNetworkRequest.AlwaysNetwork)
|
||||
suggested_fn = urlutils.filename_from_url(request.url())
|
||||
|
||||
if prompt_download_directory is None:
|
||||
prompt_download_directory = config.get(
|
||||
'storage', 'prompt-download-directory')
|
||||
if not prompt_download_directory and not fileobj:
|
||||
filename = config.get('storage', 'download-directory')
|
||||
# We won't need a question if a filename or fileobj is already given
|
||||
if fileobj is None and filename is None:
|
||||
filename, q = ask_for_filename(
|
||||
suggested_fn, self._win_id, parent=self,
|
||||
prompt_download_directory=prompt_download_directory
|
||||
)
|
||||
|
||||
if fileobj is not None or filename is not None:
|
||||
return self.fetch_request(request,
|
||||
@@ -726,21 +789,13 @@ class DownloadManager(QAbstractListModel):
|
||||
filename=filename,
|
||||
suggested_filename=suggested_fn,
|
||||
**kwargs)
|
||||
if suggested_fn is None:
|
||||
suggested_fn = 'qutebrowser-download'
|
||||
else:
|
||||
encoding = sys.getfilesystemencoding()
|
||||
suggested_fn = utils.force_encoding(suggested_fn, encoding)
|
||||
q = self._prepare_question()
|
||||
q.default = _path_suggestion(suggested_fn)
|
||||
message_bridge = objreg.get('message-bridge', scope='window',
|
||||
window=self._win_id)
|
||||
q.answered.connect(
|
||||
lambda fn: self.fetch_request(request,
|
||||
filename=fn,
|
||||
suggested_filename=suggested_fn,
|
||||
**kwargs))
|
||||
message_bridge.ask(q, blocking=False)
|
||||
self._postprocess_question(q)
|
||||
q.ask()
|
||||
return None
|
||||
|
||||
def fetch_request(self, request, *, page=None, **kwargs):
|
||||
@@ -771,7 +826,7 @@ class DownloadManager(QAbstractListModel):
|
||||
fileobj: The file object to write the answer to.
|
||||
filename: A path to write the data to.
|
||||
auto_remove: Whether to remove the download even if
|
||||
ui -> remove-finished-downloads is set to false.
|
||||
ui -> remove-finished-downloads is set to -1.
|
||||
|
||||
Return:
|
||||
The created DownloadItem.
|
||||
@@ -790,9 +845,15 @@ class DownloadManager(QAbstractListModel):
|
||||
download = DownloadItem(reply, self._win_id, self)
|
||||
download.cancelled.connect(
|
||||
functools.partial(self.remove_item, download))
|
||||
if config.get('ui', 'remove-finished-downloads') or auto_remove:
|
||||
|
||||
delay = config.get('ui', 'remove-finished-downloads')
|
||||
if delay > -1:
|
||||
download.finished.connect(
|
||||
functools.partial(self.remove_item_delayed, download, delay))
|
||||
elif auto_remove:
|
||||
download.finished.connect(
|
||||
functools.partial(self.remove_item, download))
|
||||
|
||||
download.data_changed.connect(
|
||||
functools.partial(self.on_data_changed, download))
|
||||
download.error.connect(self.on_error)
|
||||
@@ -808,26 +869,33 @@ class DownloadManager(QAbstractListModel):
|
||||
if not self._update_timer.isActive():
|
||||
self._update_timer.start()
|
||||
|
||||
prompt_download_directory = config.get('storage',
|
||||
'prompt-download-directory')
|
||||
if not prompt_download_directory and not fileobj:
|
||||
filename = config.get('storage', 'download-directory')
|
||||
if fileobj is not None:
|
||||
download.set_fileobj(fileobj)
|
||||
download.autoclose = False
|
||||
return download
|
||||
|
||||
if filename is not None:
|
||||
download.set_filename(filename)
|
||||
elif fileobj is not None:
|
||||
download.set_fileobj(fileobj)
|
||||
download.autoclose = False
|
||||
else:
|
||||
q = self._prepare_question()
|
||||
q.default = _path_suggestion(suggested_filename)
|
||||
q.answered.connect(download.set_filename)
|
||||
q.cancelled.connect(download.cancel)
|
||||
download.cancelled.connect(q.abort)
|
||||
download.error.connect(q.abort)
|
||||
message_bridge = objreg.get('message-bridge', scope='window',
|
||||
window=self._win_id)
|
||||
message_bridge.ask(q, blocking=False)
|
||||
return download
|
||||
|
||||
# Neither filename nor fileobj were given, prepare a question
|
||||
filename, q = ask_for_filename(
|
||||
suggested_filename, self._win_id, parent=self,
|
||||
prompt_download_directory=prompt_download_directory,
|
||||
)
|
||||
|
||||
# User doesn't want to be asked, so just use the download_dir
|
||||
if filename is not None:
|
||||
download.set_filename(filename)
|
||||
return download
|
||||
|
||||
# Ask the user for a filename
|
||||
self._postprocess_question(q)
|
||||
q.answered.connect(download.set_filename)
|
||||
q.cancelled.connect(download.cancel)
|
||||
download.cancelled.connect(q.abort)
|
||||
download.error.connect(q.abort)
|
||||
q.ask()
|
||||
|
||||
return download
|
||||
|
||||
@@ -879,17 +947,6 @@ class DownloadManager(QAbstractListModel):
|
||||
download.delete()
|
||||
self.remove_item(download)
|
||||
|
||||
@cmdutils.register(instance='download-manager', scope='window',
|
||||
deprecated="Use :download-cancel instead.",
|
||||
count='count')
|
||||
def cancel_download(self, count=1):
|
||||
"""Cancel the first/[count]th download.
|
||||
|
||||
Args:
|
||||
count: The index of the download to cancel.
|
||||
"""
|
||||
self.download_cancel(count)
|
||||
|
||||
@cmdutils.register(instance='download-manager', scope='window',
|
||||
count='count')
|
||||
def download_open(self, count=0):
|
||||
@@ -908,6 +965,31 @@ class DownloadManager(QAbstractListModel):
|
||||
raise cmdexc.CommandError("Download {} is not done!".format(count))
|
||||
download.open_file()
|
||||
|
||||
@cmdutils.register(instance='download-manager', scope='window',
|
||||
count='count')
|
||||
def download_retry(self, count=0):
|
||||
"""Retry the first failed/[count]th download.
|
||||
|
||||
Args:
|
||||
count: The index of the download to cancel.
|
||||
"""
|
||||
if count:
|
||||
try:
|
||||
download = self.downloads[count - 1]
|
||||
except IndexError:
|
||||
self.raise_no_download(count)
|
||||
if download.successful or not download.done:
|
||||
raise cmdexc.CommandError("Download {} did not fail!".format(
|
||||
count))
|
||||
else:
|
||||
to_retry = [d for d in self.downloads
|
||||
if d.done and not d.successful]
|
||||
if not to_retry:
|
||||
raise cmdexc.CommandError("No failed downloads!")
|
||||
else:
|
||||
download = to_retry[0]
|
||||
download.retry()
|
||||
|
||||
@pyqtSlot(QNetworkRequest, QNetworkReply)
|
||||
def on_redirect(self, download, request, reply):
|
||||
"""Handle a HTTP redirect of a download.
|
||||
@@ -963,18 +1045,25 @@ class DownloadManager(QAbstractListModel):
|
||||
"""Check if there are finished downloads to clear."""
|
||||
return any(download.done for download in self.downloads)
|
||||
|
||||
@cmdutils.register(instance='download-manager', scope='window')
|
||||
def download_clear(self):
|
||||
"""Remove all finished downloads from the list."""
|
||||
finished_items = [d for d in self.downloads if d.done]
|
||||
self.remove_items(finished_items)
|
||||
|
||||
@cmdutils.register(instance='download-manager', scope='window',
|
||||
count='count')
|
||||
def download_remove(self, all_=False, count=0):
|
||||
"""Remove the last/[count]th download from the list.
|
||||
|
||||
Args:
|
||||
all_: If given removes all finished downloads.
|
||||
all_: Deprecated argument for removing all finished downloads.
|
||||
count: The index of the download to cancel.
|
||||
"""
|
||||
if all_:
|
||||
finished_items = [d for d in self.downloads if d.done]
|
||||
self.remove_items(finished_items)
|
||||
message.warning(self._win_id, ":download-remove --all is "
|
||||
"deprecated - use :download-clear instead!")
|
||||
self.download_clear()
|
||||
else:
|
||||
try:
|
||||
download = self.downloads[count - 1]
|
||||
@@ -1011,6 +1100,10 @@ class DownloadManager(QAbstractListModel):
|
||||
if not self.downloads:
|
||||
self._update_timer.stop()
|
||||
|
||||
def remove_item_delayed(self, download, delay):
|
||||
"""Remove a given download after a short delay."""
|
||||
QTimer.singleShot(delay, functools.partial(self.remove_item, download))
|
||||
|
||||
def remove_items(self, downloads):
|
||||
"""Remove an iterable of downloads."""
|
||||
# On the first pass, we only generate the indices so we get the
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -64,8 +64,8 @@ class DownloadView(QListView):
|
||||
|
||||
STYLESHEET = """
|
||||
QListView {
|
||||
{{ color['downloads.bg.bar'] }}
|
||||
{{ font['downloads'] }}
|
||||
background-color: {{ color['downloads.bg.bar'] }};
|
||||
font: {{ font['downloads'] }};
|
||||
}
|
||||
|
||||
QListView::item {
|
||||
@@ -125,6 +125,7 @@ class DownloadView(QListView):
|
||||
- (QAction, callable) tuples.
|
||||
- (None, None) for a separator
|
||||
"""
|
||||
model = self.model()
|
||||
actions = []
|
||||
if item is None:
|
||||
pass
|
||||
@@ -134,13 +135,12 @@ class DownloadView(QListView):
|
||||
else:
|
||||
actions.append(("Retry", item.retry))
|
||||
actions.append(("Remove",
|
||||
functools.partial(self.model().remove_item, item)))
|
||||
functools.partial(model.remove_item, item)))
|
||||
else:
|
||||
actions.append(("Cancel", item.cancel))
|
||||
if self.model().can_clear():
|
||||
if model.can_clear():
|
||||
actions.append((None, None))
|
||||
actions.append(("Remove all finished", functools.partial(
|
||||
self.model().download_remove, True)))
|
||||
actions.append(("Remove all finished", model.download_clear))
|
||||
return actions
|
||||
|
||||
@pyqtSlot('QPoint')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -43,8 +43,8 @@ ElemTuple = collections.namedtuple('ElemTuple', ['elem', 'label'])
|
||||
|
||||
Target = usertypes.enum('Target', ['normal', 'tab', 'tab_fg', 'tab_bg',
|
||||
'window', 'yank', 'yank_primary', 'run',
|
||||
'fill', 'hover', 'rapid', 'rapid_win',
|
||||
'download', 'userscript', 'spawn'])
|
||||
'fill', 'hover', 'download', 'userscript',
|
||||
'spawn'])
|
||||
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
@@ -69,7 +69,6 @@ class HintContext:
|
||||
yank/yank_primary: Yank to clipboard/primary selection.
|
||||
run: Run a command.
|
||||
fill: Fill commandline with link.
|
||||
rapid: Rapid mode with background tabs
|
||||
download: Download the link.
|
||||
userscript: Call a custom userscript.
|
||||
spawn: Spawn a simple command.
|
||||
@@ -415,8 +414,6 @@ class HintManager(QObject):
|
||||
context: The HintContext to use.
|
||||
"""
|
||||
target_mapping = {
|
||||
Target.rapid: usertypes.ClickTarget.tab_bg,
|
||||
Target.rapid_win: usertypes.ClickTarget.window,
|
||||
Target.normal: usertypes.ClickTarget.normal,
|
||||
Target.tab_fg: usertypes.ClickTarget.tab,
|
||||
Target.tab_bg: usertypes.ClickTarget.tab_bg,
|
||||
@@ -437,7 +434,7 @@ class HintManager(QObject):
|
||||
action, elem, pos.x(), pos.y()))
|
||||
self.start_hinting.emit(target_mapping[context.target])
|
||||
if context.target in [Target.tab, Target.tab_fg, Target.tab_bg,
|
||||
Target.window, Target.rapid, Target.rapid_win]:
|
||||
Target.window]:
|
||||
modifiers = Qt.ControlModifier
|
||||
else:
|
||||
modifiers = Qt.NoModifier
|
||||
@@ -749,7 +746,11 @@ class HintManager(QObject):
|
||||
- With `spawn`: The executable and arguments to spawn.
|
||||
`{hint-url}` will get replaced by the selected
|
||||
URL.
|
||||
- With `userscript`: The userscript to execute.
|
||||
- With `userscript`: The userscript to execute. Either store
|
||||
the userscript in
|
||||
`~/.local/share/qutebrowser/userscripts`
|
||||
(or `$XDG_DATA_DIR`), or use an absolute
|
||||
path.
|
||||
- With `fill`: The command to fill the statusbar with.
|
||||
`{hint-url}` will get replaced by the selected
|
||||
URL.
|
||||
@@ -798,7 +799,6 @@ class HintManager(QObject):
|
||||
self._context.args = args
|
||||
self._context.mainframe = mainframe
|
||||
self._context.group = group
|
||||
self._handle_old_rapid_targets(win_id)
|
||||
self._init_elements()
|
||||
message_bridge = objreg.get('message-bridge', scope='window',
|
||||
window=self._win_id)
|
||||
@@ -807,29 +807,6 @@ class HintManager(QObject):
|
||||
modeman.enter(self._win_id, usertypes.KeyMode.hint,
|
||||
'HintManager.start')
|
||||
|
||||
def _handle_old_rapid_targets(self, win_id):
|
||||
"""Switch to the new way for rapid hinting with a rapid target.
|
||||
|
||||
Args:
|
||||
win_id: The window ID to display the warning in.
|
||||
|
||||
DEPRECATED.
|
||||
"""
|
||||
old_rapid_targets = {
|
||||
Target.rapid: Target.tab_bg,
|
||||
Target.rapid_win: Target.window,
|
||||
}
|
||||
target = self._context.target
|
||||
if target in old_rapid_targets:
|
||||
self._context.target = old_rapid_targets[target]
|
||||
self._context.rapid = True
|
||||
name = target.name.replace('_', '-')
|
||||
group_name = self._context.group.name.replace('_', '-')
|
||||
new_name = self._context.target.name.replace('_', '-')
|
||||
message.warning(
|
||||
win_id, ':hint with target {} is deprecated, use :hint '
|
||||
'--rapid {} {} instead!'.format(name, group_name, new_name))
|
||||
|
||||
def handle_partial_key(self, keystr):
|
||||
"""Handle a new partial keypress."""
|
||||
log.hints.debug("Handling new keystring: '{}'".format(keystr))
|
||||
@@ -855,12 +832,12 @@ class HintManager(QObject):
|
||||
"""Filter displayed hints according to a text.
|
||||
|
||||
Args:
|
||||
filterstr: The string to filer with, or None to show all.
|
||||
filterstr: The string to filter with, or None to show all.
|
||||
"""
|
||||
for elems in self._context.elems.values():
|
||||
try:
|
||||
if (filterstr is None or
|
||||
str(elems.elem).lower().startswith(filterstr)):
|
||||
filterstr.casefold() in str(elems.elem).casefold()):
|
||||
if self._is_hidden(elems.label):
|
||||
# hidden element which matches again -> show it
|
||||
self._show_elem(elems.label)
|
||||
@@ -942,12 +919,22 @@ class HintManager(QObject):
|
||||
elems.label.setInnerXml(string)
|
||||
handler()
|
||||
|
||||
@cmdutils.register(instance='hintmanager', scope='tab', hide=True)
|
||||
def follow_hint(self):
|
||||
"""Follow the currently selected hint."""
|
||||
if not self._context.to_follow:
|
||||
raise cmdexc.CommandError("No hint to follow")
|
||||
self.fire(self._context.to_follow, force=True)
|
||||
@cmdutils.register(instance='hintmanager', scope='tab', hide=True,
|
||||
modes=[usertypes.KeyMode.hint])
|
||||
def follow_hint(self, keystring=None):
|
||||
"""Follow a hint.
|
||||
|
||||
Args:
|
||||
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")
|
||||
else:
|
||||
keystring = self._context.to_follow
|
||||
elif keystring not in self._context.elems:
|
||||
raise cmdexc.CommandError("No hint {}!".format(keystring))
|
||||
self.fire(keystring, force=True)
|
||||
|
||||
@pyqtSlot('QSize')
|
||||
def on_contents_size_changed(self, _size):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2015-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -52,11 +52,6 @@ class HistoryEntry:
|
||||
def __str__(self):
|
||||
return '{} {}'.format(int(self.atime), self.url_string)
|
||||
|
||||
@classmethod
|
||||
def from_str(cls, s):
|
||||
"""Get a history based on a 'TIME URL' string."""
|
||||
return cls(*s.split(' ', maxsplit=1))
|
||||
|
||||
|
||||
class WebHistory(QWebHistoryInterface):
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 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 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2015-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
532
qutebrowser/browser/mhtml.py
Normal file
532
qutebrowser/browser/mhtml.py
Normal file
@@ -0,0 +1,532 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2015-2016 Daniel Schadt
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
"""Utils for writing a MHTML file."""
|
||||
|
||||
import functools
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import collections
|
||||
import uuid
|
||||
import email.policy
|
||||
import email.generator
|
||||
import email.encoders
|
||||
import email.mime.multipart
|
||||
import email.message
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
|
||||
from qutebrowser.browser import webelem, downloads
|
||||
from qutebrowser.utils import log, objreg, message, usertypes, utils, urlutils
|
||||
|
||||
try:
|
||||
import cssutils
|
||||
except (ImportError, re.error):
|
||||
# Catching re.error because cssutils in earlier releases (<= 1.0) is broken
|
||||
# on Python 3.5
|
||||
# See https://bitbucket.org/cthedot/cssutils/issues/52
|
||||
cssutils = None
|
||||
|
||||
_File = collections.namedtuple('_File',
|
||||
['content', 'content_type', 'content_location',
|
||||
'transfer_encoding'])
|
||||
|
||||
|
||||
_CSS_URL_PATTERNS = [re.compile(x) for x in [
|
||||
r"@import\s+'(?P<url>[^']+)'",
|
||||
r'@import\s+"(?P<url>[^"]+)"',
|
||||
r'''url\((?P<url>[^'"][^)]*)\)''',
|
||||
r'url\("(?P<url>[^"]+)"\)',
|
||||
r"url\('(?P<url>[^']+)'\)",
|
||||
]]
|
||||
|
||||
|
||||
def _get_css_imports_regex(data):
|
||||
"""Return all assets that are referenced in the given CSS document.
|
||||
|
||||
The returned URLs are relative to the stylesheet's URL.
|
||||
|
||||
Args:
|
||||
data: The content of the stylesheet to scan as string.
|
||||
"""
|
||||
urls = []
|
||||
for pattern in _CSS_URL_PATTERNS:
|
||||
for match in pattern.finditer(data):
|
||||
url = match.group("url")
|
||||
if url:
|
||||
urls.append(url)
|
||||
return urls
|
||||
|
||||
|
||||
def _get_css_imports_cssutils(data, inline=False):
|
||||
"""Return all assets that are referenced in the given CSS document.
|
||||
|
||||
The returned URLs are relative to the stylesheet's URL.
|
||||
|
||||
Args:
|
||||
data: The content of the stylesheet to scan as string.
|
||||
inline: True if the argument is a inline HTML style attribute.
|
||||
"""
|
||||
# We don't care about invalid CSS data, this will only litter the log
|
||||
# output with CSS errors
|
||||
parser = cssutils.CSSParser(loglevel=100,
|
||||
fetcher=lambda url: (None, ""), validate=False)
|
||||
if not inline:
|
||||
sheet = parser.parseString(data)
|
||||
return list(cssutils.getUrls(sheet))
|
||||
else:
|
||||
urls = []
|
||||
declaration = parser.parseStyle(data)
|
||||
# prop = background, color, margin, ...
|
||||
for prop in declaration:
|
||||
# value = red, 10px, url(foobar), ...
|
||||
for value in prop.propertyValue:
|
||||
if isinstance(value, cssutils.css.URIValue):
|
||||
if value.uri:
|
||||
urls.append(value.uri)
|
||||
return urls
|
||||
|
||||
|
||||
def _get_css_imports(data, inline=False):
|
||||
"""Return all assets that are referenced in the given CSS document.
|
||||
|
||||
The returned URLs are relative to the stylesheet's URL.
|
||||
|
||||
Args:
|
||||
data: The content of the stylesheet to scan as string.
|
||||
inline: True if the argument is a inline HTML style attribute.
|
||||
"""
|
||||
if cssutils is None:
|
||||
return _get_css_imports_regex(data)
|
||||
else:
|
||||
return _get_css_imports_cssutils(data, inline)
|
||||
|
||||
|
||||
def _check_rel(element):
|
||||
"""Return true if the element's rel attribute fits our criteria.
|
||||
|
||||
rel has to contain 'stylesheet' or 'icon'. Also returns True if the rel
|
||||
attribute is unset.
|
||||
|
||||
Args:
|
||||
element: The WebElementWrapper which should be checked.
|
||||
"""
|
||||
if 'rel' not in element:
|
||||
return True
|
||||
must_have = {'stylesheet', 'icon'}
|
||||
rels = [rel.lower() for rel in element['rel'].split(' ')]
|
||||
return any(rel in rels for rel in must_have)
|
||||
|
||||
|
||||
MHTMLPolicy = email.policy.default.clone(linesep='\r\n', max_line_length=0)
|
||||
|
||||
|
||||
# Encode the file using base64 encoding.
|
||||
E_BASE64 = email.encoders.encode_base64
|
||||
|
||||
|
||||
# Encode the file using MIME quoted-printable encoding.
|
||||
E_QUOPRI = email.encoders.encode_quopri
|
||||
|
||||
|
||||
class MHTMLWriter:
|
||||
|
||||
"""A class for outputting multiple files to a MHTML document.
|
||||
|
||||
Attributes:
|
||||
root_content: The root content as bytes.
|
||||
content_location: The url of the page as str.
|
||||
content_type: The MIME-type of the root content as str.
|
||||
_files: Mapping of location->_File namedtuple.
|
||||
"""
|
||||
|
||||
def __init__(self, root_content, content_location, content_type):
|
||||
self.root_content = root_content
|
||||
self.content_location = content_location
|
||||
self.content_type = content_type
|
||||
self._files = {}
|
||||
|
||||
def add_file(self, location, content, content_type=None,
|
||||
transfer_encoding=E_QUOPRI):
|
||||
"""Add a file to the given MHTML collection.
|
||||
|
||||
Args:
|
||||
location: The original location (URL) of the file.
|
||||
content: The binary content of the file.
|
||||
content_type: The MIME-type of the content (if available)
|
||||
transfer_encoding: The transfer encoding to use for this file.
|
||||
"""
|
||||
self._files[location] = _File(
|
||||
content=content, content_type=content_type,
|
||||
content_location=location, transfer_encoding=transfer_encoding,
|
||||
)
|
||||
|
||||
def write_to(self, fp):
|
||||
"""Output the MHTML file to the given file-like object.
|
||||
|
||||
Args:
|
||||
fp: The file-object, opened in "wb" mode.
|
||||
"""
|
||||
msg = email.mime.multipart.MIMEMultipart(
|
||||
'related', '---=_qute-{}'.format(uuid.uuid4()))
|
||||
|
||||
root = self._create_root_file()
|
||||
msg.attach(root)
|
||||
|
||||
for _, file_data in sorted(self._files.items()):
|
||||
msg.attach(self._create_file(file_data))
|
||||
|
||||
gen = email.generator.BytesGenerator(fp, policy=MHTMLPolicy)
|
||||
gen.flatten(msg)
|
||||
|
||||
def _create_root_file(self):
|
||||
"""Return the root document as MIMEMultipart."""
|
||||
root_file = _File(
|
||||
content=self.root_content, content_type=self.content_type,
|
||||
content_location=self.content_location, transfer_encoding=E_QUOPRI,
|
||||
)
|
||||
return self._create_file(root_file)
|
||||
|
||||
def _create_file(self, f):
|
||||
"""Return the single given file as email.message.Message."""
|
||||
msg = email.message.Message()
|
||||
msg['MIME-Version'] = '1.0'
|
||||
msg['Content-Location'] = f.content_location
|
||||
if f.content_type:
|
||||
msg.set_type(f.content_type)
|
||||
msg.set_payload(f.content)
|
||||
f.transfer_encoding(msg)
|
||||
return msg
|
||||
|
||||
|
||||
class _Downloader:
|
||||
|
||||
"""A class to download whole websites.
|
||||
|
||||
Attributes:
|
||||
web_view: The QWebView which contains the website that will be saved.
|
||||
dest: Destination filename.
|
||||
writer: The MHTMLWriter object which is used to save the page.
|
||||
loaded_urls: A set of QUrls of finished asset downloads.
|
||||
pending_downloads: A set of unfinished (url, DownloadItem) tuples.
|
||||
_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, web_view, dest):
|
||||
self.web_view = web_view
|
||||
self.dest = dest
|
||||
self.writer = None
|
||||
self.loaded_urls = {web_view.url()}
|
||||
self.pending_downloads = set()
|
||||
self._finished_file = False
|
||||
self._used = False
|
||||
self._win_id = web_view.win_id
|
||||
|
||||
def run(self):
|
||||
"""Download and save the page.
|
||||
|
||||
The object must not be reused, you should create a new one if
|
||||
you want to download another page.
|
||||
"""
|
||||
if self._used:
|
||||
raise ValueError("Downloader already used")
|
||||
self._used = True
|
||||
web_url = self.web_view.url()
|
||||
web_frame = self.web_view.page().mainFrame()
|
||||
|
||||
self.writer = MHTMLWriter(
|
||||
web_frame.toHtml().encode('utf-8'),
|
||||
content_location=urlutils.encoded_url(web_url),
|
||||
# I've found no way of getting the content type of a QWebView, but
|
||||
# since we're using .toHtml, it's probably safe to say that the
|
||||
# content-type is HTML
|
||||
content_type='text/html; charset="UTF-8"',
|
||||
)
|
||||
# Currently only downloading <link> (stylesheets), <script>
|
||||
# (javascript) and <img> (image) elements.
|
||||
elements = web_frame.findAllElements('link, script, img')
|
||||
|
||||
for element in elements:
|
||||
element = webelem.WebElementWrapper(element)
|
||||
# Websites are free to set whatever rel=... attribute they want.
|
||||
# We just care about stylesheets and icons.
|
||||
if not _check_rel(element):
|
||||
continue
|
||||
if 'src' in element:
|
||||
element_url = element['src']
|
||||
elif 'href' in element:
|
||||
element_url = element['href']
|
||||
else:
|
||||
# Might be a local <script> tag or something else
|
||||
continue
|
||||
absolute_url = web_url.resolved(QUrl(element_url))
|
||||
self._fetch_url(absolute_url)
|
||||
|
||||
styles = web_frame.findAllElements('style')
|
||||
for style in styles:
|
||||
style = webelem.WebElementWrapper(style)
|
||||
# The Mozilla Developer Network says:
|
||||
# type: This attribute defines the styling language as a MIME type
|
||||
# (charset should not be specified). This attribute is optional and
|
||||
# default to text/css if it's missing.
|
||||
# https://developer.mozilla.org/en/docs/Web/HTML/Element/style
|
||||
if 'type' in style and style['type'] != 'text/css':
|
||||
continue
|
||||
for element_url in _get_css_imports(str(style)):
|
||||
self._fetch_url(web_url.resolved(QUrl(element_url)))
|
||||
|
||||
# Search for references in inline styles
|
||||
for element in web_frame.findAllElements('[style]'):
|
||||
element = webelem.WebElementWrapper(element)
|
||||
style = element['style']
|
||||
for element_url in _get_css_imports(style, inline=True):
|
||||
self._fetch_url(web_url.resolved(QUrl(element_url)))
|
||||
|
||||
# Shortcut if no assets need to be downloaded, otherwise the file would
|
||||
# never be saved. Also might happen if the downloads are fast enough to
|
||||
# complete before connecting their finished signal.
|
||||
self._collect_zombies()
|
||||
if not self.pending_downloads and not self._finished_file:
|
||||
self._finish_file()
|
||||
|
||||
def _fetch_url(self, url):
|
||||
"""Download the given url and add the file to the collection.
|
||||
|
||||
Args:
|
||||
url: The file to download as QUrl.
|
||||
"""
|
||||
if url.scheme() not in {'http', 'https'}:
|
||||
return
|
||||
# Prevent loading an asset twice
|
||||
if url in self.loaded_urls:
|
||||
return
|
||||
self.loaded_urls.add(url)
|
||||
|
||||
log.downloads.debug("loading asset at {}".format(url))
|
||||
|
||||
# Using the download manager to download host-blocked urls might crash
|
||||
# qute, see the comments/discussion on
|
||||
# https://github.com/The-Compiler/qutebrowser/pull/962#discussion_r40256987
|
||||
# and https://github.com/The-Compiler/qutebrowser/issues/1053
|
||||
host_blocker = objreg.get('host-blocker')
|
||||
if host_blocker.is_blocked(url):
|
||||
log.downloads.debug("Skipping {}, host-blocked".format(url))
|
||||
# We still need an empty file in the output, QWebView can be pretty
|
||||
# picky about displaying a file correctly when not all assets are
|
||||
# at least referenced in the mhtml file.
|
||||
self.writer.add_file(urlutils.encoded_url(url), b'')
|
||||
return
|
||||
|
||||
download_manager = objreg.get('download-manager', scope='window',
|
||||
window=self._win_id)
|
||||
item = download_manager.get(url, fileobj=_NoCloseBytesIO(),
|
||||
auto_remove=True)
|
||||
self.pending_downloads.add((url, item))
|
||||
item.finished.connect(
|
||||
functools.partial(self._finished, url, item))
|
||||
item.error.connect(
|
||||
functools.partial(self._error, url, item))
|
||||
item.cancelled.connect(
|
||||
functools.partial(self._error, url, item))
|
||||
|
||||
def _finished(self, url, item):
|
||||
"""Callback when a single asset is downloaded.
|
||||
|
||||
Args:
|
||||
url: The original url of the asset as QUrl.
|
||||
item: The DownloadItem given by the DownloadManager
|
||||
"""
|
||||
self.pending_downloads.remove((url, item))
|
||||
mime = item.raw_headers.get(b'Content-Type', b'')
|
||||
|
||||
# Note that this decoding always works and doesn't produce errors
|
||||
# RFC 7230 (https://tools.ietf.org/html/rfc7230) states:
|
||||
# Historically, HTTP has allowed field content with text in the
|
||||
# ISO-8859-1 charset [ISO-8859-1], supporting other charsets only
|
||||
# through use of [RFC2047] encoding. In practice, most HTTP header
|
||||
# field values use only a subset of the US-ASCII charset [USASCII].
|
||||
# Newly defined header fields SHOULD limit their field values to
|
||||
# US-ASCII octets. A recipient SHOULD treat other octets in field
|
||||
# content (obs-text) as opaque data.
|
||||
mime = mime.decode('iso-8859-1')
|
||||
|
||||
if mime.lower() == 'text/css' or url.fileName().endswith('.css'):
|
||||
# We can't always assume that CSS files are UTF-8, but CSS files
|
||||
# shouldn't contain many non-ASCII characters anyway (in most
|
||||
# cases). Using "ignore" lets us decode the file even if it's
|
||||
# invalid UTF-8 data.
|
||||
# The file written to the MHTML file won't be modified by this
|
||||
# decoding, since there we're taking the original bytestream.
|
||||
try:
|
||||
css_string = item.fileobj.getvalue().decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
log.downloads.warning("Invalid UTF-8 data in {}".format(url))
|
||||
css_string = item.fileobj.getvalue().decode('utf-8', 'ignore')
|
||||
import_urls = _get_css_imports(css_string)
|
||||
for import_url in import_urls:
|
||||
absolute_url = url.resolved(QUrl(import_url))
|
||||
self._fetch_url(absolute_url)
|
||||
|
||||
encode = E_QUOPRI if mime.startswith('text/') else E_BASE64
|
||||
# Our MHTML handler refuses non-ASCII headers. This will replace every
|
||||
# non-ASCII char with '?'. This is probably okay, as official Content-
|
||||
# Type headers contain ASCII only anyway. Anything else is madness.
|
||||
mime = utils.force_encoding(mime, 'ascii')
|
||||
self.writer.add_file(urlutils.encoded_url(url),
|
||||
item.fileobj.getvalue(), mime, encode)
|
||||
item.fileobj.actual_close()
|
||||
if self.pending_downloads:
|
||||
return
|
||||
self._finish_file()
|
||||
|
||||
def _error(self, url, item, *_args):
|
||||
"""Callback when a download error occurred.
|
||||
|
||||
Args:
|
||||
url: The orignal url of the asset as QUrl.
|
||||
item: The DownloadItem given by the DownloadManager.
|
||||
"""
|
||||
try:
|
||||
self.pending_downloads.remove((url, item))
|
||||
except KeyError:
|
||||
# This might happen if .collect_zombies() calls .finished() and the
|
||||
# error handler will be called after .collect_zombies
|
||||
log.downloads.debug("Oops! Download already gone: {}".format(item))
|
||||
return
|
||||
item.fileobj.actual_close()
|
||||
# Add a stub file, see comment in .fetch_url() for more information
|
||||
self.writer.add_file(urlutils.encoded_url(url), b'')
|
||||
if self.pending_downloads:
|
||||
return
|
||||
self._finish_file()
|
||||
|
||||
def _finish_file(self):
|
||||
"""Save the file to the filename given in __init__."""
|
||||
if self._finished_file:
|
||||
log.downloads.debug("finish_file called twice, ignored!")
|
||||
return
|
||||
self._finished_file = True
|
||||
log.downloads.debug("All assets downloaded, ready to finish off!")
|
||||
try:
|
||||
with open(self.dest, 'wb') as file_output:
|
||||
self.writer.write_to(file_output)
|
||||
except OSError as error:
|
||||
message.error(self._win_id,
|
||||
"Could not save file: {}".format(error))
|
||||
return
|
||||
log.downloads.debug("File successfully written.")
|
||||
message.info(self._win_id, "Page saved as {}".format(self.dest))
|
||||
|
||||
def _collect_zombies(self):
|
||||
"""Collect done downloads and add their data to the MHTML file.
|
||||
|
||||
This is needed if a download finishes before attaching its
|
||||
finished signal.
|
||||
"""
|
||||
items = set((url, item) for url, item in self.pending_downloads
|
||||
if item.done)
|
||||
log.downloads.debug("Zombie downloads: {}".format(items))
|
||||
for url, item in items:
|
||||
self._finished(url, item)
|
||||
|
||||
|
||||
class _NoCloseBytesIO(io.BytesIO):
|
||||
|
||||
"""BytesIO that can't be .closed().
|
||||
|
||||
This is needed to prevent the DownloadManager from closing the stream, thus
|
||||
discarding the data.
|
||||
"""
|
||||
|
||||
def close(self):
|
||||
"""Do nothing."""
|
||||
pass
|
||||
|
||||
def actual_close(self):
|
||||
"""Close the stream."""
|
||||
super().close()
|
||||
|
||||
|
||||
def _start_download(dest, web_view):
|
||||
"""Start downloading the current page and all assets to a MHTML file.
|
||||
|
||||
This will overwrite dest if it already exists.
|
||||
|
||||
Args:
|
||||
dest: The filename where the resulting file should be saved.
|
||||
web_view: Specify the webview whose page should be loaded.
|
||||
"""
|
||||
loader = _Downloader(web_view, dest)
|
||||
loader.run()
|
||||
|
||||
|
||||
def start_download_checked(dest, web_view):
|
||||
"""First check if dest is already a file, then start the download.
|
||||
|
||||
Args:
|
||||
dest: The filename where the resulting file should be saved.
|
||||
web_view: Specify the webview whose page should be loaded.
|
||||
"""
|
||||
# The default name is 'page title.mht'
|
||||
title = web_view.title()
|
||||
default_name = utils.sanitize_filename(title + '.mht')
|
||||
|
||||
# Remove characters which cannot be expressed in the file system encoding
|
||||
encoding = sys.getfilesystemencoding()
|
||||
default_name = utils.force_encoding(default_name, encoding)
|
||||
dest = utils.force_encoding(dest, encoding)
|
||||
|
||||
dest = os.path.expanduser(dest)
|
||||
|
||||
# See if we already have an absolute path
|
||||
path = downloads.create_full_filename(default_name, dest)
|
||||
if path is None:
|
||||
# We still only have a relative path, prepend download_dir and
|
||||
# try again.
|
||||
path = downloads.create_full_filename(
|
||||
default_name, os.path.join(downloads.download_dir(), dest))
|
||||
downloads.last_used_directory = os.path.dirname(path)
|
||||
|
||||
# Avoid downloading files if we can't save the output anyway...
|
||||
# Yes, this is prone to race conditions, but we're checking again before
|
||||
# saving the file anyway.
|
||||
if not os.path.isdir(os.path.dirname(path)):
|
||||
folder = os.path.dirname(path)
|
||||
message.error(web_view.win_id,
|
||||
"Directory {} does not exist.".format(folder))
|
||||
return
|
||||
|
||||
if not os.path.isfile(path):
|
||||
_start_download(path, web_view=web_view)
|
||||
return
|
||||
|
||||
q = usertypes.Question()
|
||||
q.mode = usertypes.PromptMode.yesno
|
||||
q.text = "{} exists. Overwrite?".format(path)
|
||||
q.completed.connect(q.deleteLater)
|
||||
q.answered_yes.connect(functools.partial(
|
||||
_start_download, path, web_view=web_view))
|
||||
message_bridge = objreg.get('message-bridge', scope='window',
|
||||
window=web_view.win_id)
|
||||
message_bridge.ask(q, blocking=False)
|
||||
@@ -1,7 +1,7 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2015 Antoni Boucher (antoyo) <bouanto@zoho.com>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2015-2016 Antoni Boucher (antoyo) <bouanto@zoho.com>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -25,10 +25,8 @@
|
||||
|
||||
import os
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
|
||||
from qutebrowser.browser.network import schemehandler, networkreply
|
||||
from qutebrowser.utils import utils, jinja
|
||||
from qutebrowser.utils import jinja
|
||||
|
||||
|
||||
def get_file_list(basedir, all_files, filterfunc):
|
||||
@@ -74,13 +72,7 @@ def dirbrowser_html(path):
|
||||
title = "Browse directory: {}".format(path)
|
||||
template = jinja.env.get_template('dirbrowser.html')
|
||||
# pylint: disable=no-member
|
||||
# https://bitbucket.org/logilab/pylint/issue/490/
|
||||
|
||||
folder_icon = utils.resource_filename('img/folder.svg')
|
||||
file_icon = utils.resource_filename('img/file.svg')
|
||||
|
||||
folder_url = QUrl.fromLocalFile(folder_icon).toString(QUrl.FullyEncoded)
|
||||
file_url = QUrl.fromLocalFile(file_icon).toString(QUrl.FullyEncoded)
|
||||
# WORKAROUND for https://bitbucket.org/logilab/pylint/issue/490/
|
||||
|
||||
if is_root(path):
|
||||
parent = None
|
||||
@@ -101,8 +93,7 @@ def dirbrowser_html(path):
|
||||
directories = get_file_list(path, all_files, os.path.isdir)
|
||||
html = template.render(title=title, url=path, icon='',
|
||||
parent=parent, files=files,
|
||||
directories=directories, folder_url=folder_url,
|
||||
file_url=file_url)
|
||||
directories=directories)
|
||||
return html.encode('UTF-8', errors='xmlcharrefreplace')
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -20,6 +20,7 @@
|
||||
"""Our own QNetworkAccessManager."""
|
||||
|
||||
import collections
|
||||
import netrc
|
||||
|
||||
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, PYQT_VERSION, QCoreApplication,
|
||||
QUrl, QByteArray)
|
||||
@@ -56,6 +57,7 @@ class SslError(QSslError):
|
||||
def __hash__(self):
|
||||
try:
|
||||
# Qt >= 5.4
|
||||
# pylint: disable=not-callable,useless-suppression
|
||||
return super().__hash__()
|
||||
except TypeError:
|
||||
return hash((self.certificate().toDer(), self.error()))
|
||||
@@ -241,12 +243,32 @@ class NetworkManager(QNetworkAccessManager):
|
||||
@pyqtSlot('QNetworkReply', 'QAuthenticator')
|
||||
def on_authentication_required(self, reply, authenticator):
|
||||
"""Called when a website needs authentication."""
|
||||
answer = self._ask("Username ({}):".format(authenticator.realm()),
|
||||
mode=usertypes.PromptMode.user_pwd,
|
||||
owner=reply)
|
||||
if answer is not None:
|
||||
authenticator.setUser(answer.user)
|
||||
authenticator.setPassword(answer.password)
|
||||
user, password = None, None
|
||||
if not hasattr(reply, "netrc_used"):
|
||||
reply.netrc_used = True
|
||||
try:
|
||||
net = netrc.netrc()
|
||||
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 None:
|
||||
# netrc check failed
|
||||
answer = self._ask(
|
||||
"Username ({}):".format(authenticator.realm()),
|
||||
mode=usertypes.PromptMode.user_pwd,
|
||||
owner=reply)
|
||||
if answer is not None:
|
||||
user, password = answer.user, answer.password
|
||||
if user is not None:
|
||||
authenticator.setUser(user)
|
||||
authenticator.setPassword(password)
|
||||
|
||||
@pyqtSlot('QNetworkProxy', 'QAuthenticator')
|
||||
def on_proxy_authentication_required(self, proxy, authenticator):
|
||||
@@ -346,8 +368,7 @@ class NetworkManager(QNetworkAccessManager):
|
||||
|
||||
host_blocker = objreg.get('host-blocker')
|
||||
if (op == QNetworkAccessManager.GetOperation and
|
||||
req.url().host() in host_blocker.blocked_hosts and
|
||||
config.get('content', 'host-blocking-enabled')):
|
||||
host_blocker.is_blocked(req.url())):
|
||||
log.webview.info("Request to {} blocked by host blocker.".format(
|
||||
req.url().host()))
|
||||
return networkreply.ErrorNetworkReply(
|
||||
@@ -361,12 +382,21 @@ class NetworkManager(QNetworkAccessManager):
|
||||
req.setRawHeader('DNT'.encode('ascii'), dnt)
|
||||
req.setRawHeader('X-Do-Not-Track'.encode('ascii'), dnt)
|
||||
|
||||
if self._tab_id is None:
|
||||
current_url = QUrl() # generic NetworkManager, e.g. for downloads
|
||||
else:
|
||||
webview = objreg.get('webview', scope='tab', window=self._win_id,
|
||||
tab=self._tab_id)
|
||||
current_url = webview.url()
|
||||
# There are some scenarios where we can't figure out current_url:
|
||||
# - There's a generic NetworkManager, e.g. for downloads
|
||||
# - The download was in a tab which is now closed.
|
||||
current_url = QUrl()
|
||||
|
||||
if self._tab_id is not None:
|
||||
try:
|
||||
webview = objreg.get('webview', scope='tab',
|
||||
window=self._win_id, tab=self._tab_id)
|
||||
current_url = webview.url()
|
||||
except (KeyError, RuntimeError, TypeError):
|
||||
# https://github.com/The-Compiler/qutebrowser/issues/889
|
||||
# Catching RuntimeError and TypeError because we could be in
|
||||
# the middle of the webpage shutdown here.
|
||||
current_url = QUrl()
|
||||
|
||||
self.set_referer(req, current_url)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# Based on the Eric5 helpviewer,
|
||||
# Copyright (c) 2009 - 2014 Detlev Offenbach <detlev@die-offenbachs.de>
|
||||
@@ -127,7 +127,7 @@ class ErrorNetworkReply(QNetworkReply):
|
||||
"""We always have 0 bytes available."""
|
||||
return 0
|
||||
|
||||
def readData(self):
|
||||
def readData(self, _maxlen):
|
||||
"""No data available."""
|
||||
return bytes()
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 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-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 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-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -21,7 +21,7 @@
|
||||
# up for this whole module.
|
||||
|
||||
# pylint: disable=no-member
|
||||
# https://bitbucket.org/logilab/pylint/issue/490/
|
||||
# WORKAROUND for https://bitbucket.org/logilab/pylint/issue/490/
|
||||
|
||||
"""Handler functions for different qute:... pages.
|
||||
|
||||
@@ -31,11 +31,13 @@ Module attributes:
|
||||
|
||||
import functools
|
||||
import configparser
|
||||
import mimetypes
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QObject
|
||||
from PyQt5.QtNetwork import QNetworkReply
|
||||
|
||||
import qutebrowser
|
||||
from qutebrowser.browser import pdfjs
|
||||
from qutebrowser.browser.network import schemehandler, networkreply
|
||||
from qutebrowser.utils import (version, utils, jinja, log, message, docutils,
|
||||
objreg)
|
||||
@@ -45,6 +47,17 @@ from qutebrowser.config import configexc, configdata
|
||||
pyeval_output = ":pyeval was never called"
|
||||
|
||||
|
||||
HANDLERS = {}
|
||||
|
||||
|
||||
def add_handler(name):
|
||||
"""Add a handler to the qute: scheme."""
|
||||
def namedecorator(function):
|
||||
HANDLERS[name] = function
|
||||
return function
|
||||
return namedecorator
|
||||
|
||||
|
||||
class QuteSchemeHandler(schemehandler.SchemeHandler):
|
||||
|
||||
"""Scheme handler for qute: URLs."""
|
||||
@@ -82,8 +95,11 @@ class QuteSchemeHandler(schemehandler.SchemeHandler):
|
||||
return networkreply.ErrorNetworkReply(
|
||||
request, str(e), QNetworkReply.ContentNotFoundError,
|
||||
self.parent())
|
||||
mimetype, _encoding = mimetypes.guess_type(request.url().fileName())
|
||||
if mimetype is None:
|
||||
mimetype = 'text/html'
|
||||
return networkreply.FixedDataNetworkReply(
|
||||
request, data, 'text/html', self.parent())
|
||||
request, data, mimetype, self.parent())
|
||||
|
||||
|
||||
class JSBridge(QObject):
|
||||
@@ -108,6 +124,7 @@ class JSBridge(QObject):
|
||||
message.error(win_id, e)
|
||||
|
||||
|
||||
@add_handler('pyeval')
|
||||
def qute_pyeval(_win_id, _request):
|
||||
"""Handler for qute:pyeval. Return HTML content as bytes."""
|
||||
html = jinja.env.get_template('pre.html').render(
|
||||
@@ -115,6 +132,7 @@ def qute_pyeval(_win_id, _request):
|
||||
return html.encode('UTF-8', errors='xmlcharrefreplace')
|
||||
|
||||
|
||||
@add_handler('version')
|
||||
def qute_version(_win_id, _request):
|
||||
"""Handler for qute:version. Return HTML content as bytes."""
|
||||
html = jinja.env.get_template('version.html').render(
|
||||
@@ -123,6 +141,7 @@ def qute_version(_win_id, _request):
|
||||
return html.encode('UTF-8', errors='xmlcharrefreplace')
|
||||
|
||||
|
||||
@add_handler('plainlog')
|
||||
def qute_plainlog(_win_id, _request):
|
||||
"""Handler for qute:plainlog. Return HTML content as bytes."""
|
||||
if log.ram_handler is None:
|
||||
@@ -133,6 +152,7 @@ def qute_plainlog(_win_id, _request):
|
||||
return html.encode('UTF-8', errors='xmlcharrefreplace')
|
||||
|
||||
|
||||
@add_handler('log')
|
||||
def qute_log(_win_id, _request):
|
||||
"""Handler for qute:log. Return HTML content as bytes."""
|
||||
if log.ram_handler is None:
|
||||
@@ -144,11 +164,13 @@ def qute_log(_win_id, _request):
|
||||
return html.encode('UTF-8', errors='xmlcharrefreplace')
|
||||
|
||||
|
||||
@add_handler('gpl')
|
||||
def qute_gpl(_win_id, _request):
|
||||
"""Handler for qute:gpl. Return HTML content as bytes."""
|
||||
return utils.read_file('html/COPYING.html').encode('ASCII')
|
||||
|
||||
|
||||
@add_handler('help')
|
||||
def qute_help(win_id, request):
|
||||
"""Handler for qute:help. Return HTML content as bytes."""
|
||||
try:
|
||||
@@ -176,6 +198,7 @@ def qute_help(win_id, request):
|
||||
return utils.read_file(path).encode('UTF-8', errors='xmlcharrefreplace')
|
||||
|
||||
|
||||
@add_handler('settings')
|
||||
def qute_settings(win_id, _request):
|
||||
"""Handler for qute:settings. View/change qute configuration."""
|
||||
config_getter = functools.partial(objreg.get('config').get, raw=True)
|
||||
@@ -185,12 +208,8 @@ def qute_settings(win_id, _request):
|
||||
return html.encode('UTF-8', errors='xmlcharrefreplace')
|
||||
|
||||
|
||||
HANDLERS = {
|
||||
'pyeval': qute_pyeval,
|
||||
'version': qute_version,
|
||||
'plainlog': qute_plainlog,
|
||||
'log': qute_log,
|
||||
'gpl': qute_gpl,
|
||||
'help': qute_help,
|
||||
'settings': qute_settings,
|
||||
}
|
||||
@add_handler('pdfjs')
|
||||
def qute_pdfjs(_win_id, request):
|
||||
"""Handler for qute://pdfjs. Return the pdf.js viewer."""
|
||||
urlpath = request.url().path()
|
||||
return pdfjs.get_pdfjs_res(urlpath)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# Based on the Eric5 helpviewer,
|
||||
# Copyright (c) 2009 - 2014 Detlev Offenbach <detlev@die-offenbachs.de>
|
||||
|
||||
175
qutebrowser/browser/pdfjs.py
Normal file
175
qutebrowser/browser/pdfjs.py
Normal file
@@ -0,0 +1,175 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2015 Daniel Schadt
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
"""pdf.js integration for qutebrowser."""
|
||||
|
||||
import os
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
|
||||
from qutebrowser.browser import webelem
|
||||
from qutebrowser.utils import utils
|
||||
|
||||
|
||||
class PDFJSNotFound(Exception):
|
||||
|
||||
"""Raised when no pdf.js installation is found."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def generate_pdfjs_page(url):
|
||||
"""Return the html content of a page that displays url with pdfjs.
|
||||
|
||||
Returns a string.
|
||||
|
||||
Args:
|
||||
url: The url of the pdf as QUrl.
|
||||
"""
|
||||
viewer = get_pdfjs_res('web/viewer.html').decode('utf-8')
|
||||
script = _generate_pdfjs_script(url)
|
||||
html_page = viewer.replace(
|
||||
'</body>', '</body><script>{}</script>'.format(script)
|
||||
)
|
||||
return html_page
|
||||
|
||||
|
||||
def _generate_pdfjs_script(url):
|
||||
"""Generate the script that shows the pdf with pdf.js.
|
||||
|
||||
Args:
|
||||
url: The url of the pdf page as QUrl.
|
||||
"""
|
||||
return (
|
||||
'PDFJS.verbosity = PDFJS.VERBOSITY_LEVELS.info;\n'
|
||||
'PDFView.open("{url}");\n'
|
||||
).format(url=webelem.javascript_escape(url.toString(QUrl.FullyEncoded)))
|
||||
|
||||
|
||||
def fix_urls(asset):
|
||||
"""Take a html page and replace each relative URL wth an absolute.
|
||||
|
||||
This is specialized for pdf.js files and not a general purpose function.
|
||||
|
||||
Args:
|
||||
asset: js file or html page as string.
|
||||
"""
|
||||
new_urls = {
|
||||
'viewer.css': 'qute://pdfjs/web/viewer.css',
|
||||
'compatibility.js': 'qute://pdfjs/web/compatibility.js',
|
||||
'locale/locale.properties':
|
||||
'qute://pdfjs/web/locale/locale.properties',
|
||||
'l10n.js': 'qute://pdfjs/web/l10n.js',
|
||||
'../build/pdf.js': 'qute://pdfjs/build/pdf.js',
|
||||
'debugger.js': 'qute://pdfjs/web/debugger.js',
|
||||
'viewer.js': 'qute://pdfjs/web/viewer.js',
|
||||
'compressed.tracemonkey-pldi-09.pdf': '',
|
||||
'./images/': 'qute://pdfjs/web/images/',
|
||||
'../build/pdf.worker.js': 'qute://pdfjs/build/pdf.worker.js',
|
||||
'../web/cmaps/': 'qute://pdfjs/web/cmaps/',
|
||||
}
|
||||
for original, new in new_urls.items():
|
||||
asset = asset.replace(original, new)
|
||||
return asset
|
||||
|
||||
|
||||
SYSTEM_PDFJS_PATHS = [
|
||||
'/usr/share/pdf.js/', # Debian pdf.js-common
|
||||
'/usr/share/javascript/pdf/', # Debian libjs-pdf
|
||||
os.path.expanduser('~/.local/share/qutebrowser/pdfjs/'), # fallback
|
||||
]
|
||||
|
||||
|
||||
def get_pdfjs_res(path):
|
||||
"""Get a pdf.js resource in binary format.
|
||||
|
||||
Args:
|
||||
path: The path inside the pdfjs directory.
|
||||
"""
|
||||
path = path.lstrip('/')
|
||||
content = None
|
||||
|
||||
# First try a system wide installation
|
||||
# System installations might strip off the 'build/' or 'web/' prefixes.
|
||||
# qute expects them, so we need to adjust for it.
|
||||
names_to_try = [path, _remove_prefix(path)]
|
||||
for system_path in SYSTEM_PDFJS_PATHS:
|
||||
content = _read_from_system(system_path, names_to_try)
|
||||
if content is not None:
|
||||
break
|
||||
|
||||
# Fallback to bundled pdf.js
|
||||
if content is None:
|
||||
res_path = '3rdparty/pdfjs/{}'.format(path)
|
||||
try:
|
||||
content = utils.read_file(res_path, binary=True)
|
||||
except FileNotFoundError:
|
||||
raise PDFJSNotFound
|
||||
|
||||
try:
|
||||
# Might be script/html or might be binary
|
||||
text_content = content.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
return content
|
||||
text_content = fix_urls(text_content)
|
||||
return text_content.encode('utf-8')
|
||||
|
||||
|
||||
def _remove_prefix(path):
|
||||
"""Remove the web/ or build/ prefix of a pdfjs-file-path.
|
||||
|
||||
Args:
|
||||
path: Path as string where the prefix should be stripped off.
|
||||
"""
|
||||
prefixes = {'web/', 'build/'}
|
||||
if any(path.startswith(prefix) for prefix in prefixes):
|
||||
return path.split('/', maxsplit=1)[1]
|
||||
# Return the unchanged path if no prefix is found
|
||||
return path
|
||||
|
||||
|
||||
def _read_from_system(system_path, names):
|
||||
"""Try to read a file with one of the given names in system_path.
|
||||
|
||||
Each file in names is considered equal, the first file that is found
|
||||
is read and its binary content returned.
|
||||
|
||||
Returns None if no file could be found
|
||||
|
||||
Args:
|
||||
system_path: The folder where the file should be searched.
|
||||
names: List of possible file names.
|
||||
"""
|
||||
for name in names:
|
||||
try:
|
||||
with open(os.path.join(system_path, name), 'rb') as f:
|
||||
return f.read()
|
||||
except OSError:
|
||||
continue
|
||||
return None
|
||||
|
||||
|
||||
def is_available():
|
||||
"""Return true if a pdfjs installation is available."""
|
||||
try:
|
||||
get_pdfjs_res('build/pdf.js')
|
||||
except PDFJSNotFound:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -40,9 +40,7 @@ class UniqueNamespace(peg.Namespace):
|
||||
|
||||
|
||||
# RFC 2616
|
||||
separator_chars = "()<>@,;:\\\"/[]?={} \t"
|
||||
ctl_chars = ''.join(chr(i) for i in range(32)) + chr(127)
|
||||
nontoken_chars = separator_chars + ctl_chars
|
||||
|
||||
|
||||
# RFC 5987
|
||||
@@ -288,7 +286,7 @@ def normalize_ws(text):
|
||||
|
||||
def parse_headers(content_disposition):
|
||||
"""Build a _ContentDisposition from header values."""
|
||||
# https://bitbucket.org/logilab/pylint/issue/492/
|
||||
# WORKAROUND for https://bitbucket.org/logilab/pylint/issue/492/
|
||||
# pylint: disable=no-member
|
||||
|
||||
# We allow non-ascii here (it will only be parsed inside of qdtext, and
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -55,7 +55,7 @@ class SignalFilter(QObject):
|
||||
tab: The WebView to create filters for.
|
||||
|
||||
Return:
|
||||
A partial functon calling _filter_signals with a signal.
|
||||
A partial function calling _filter_signals with a signal.
|
||||
"""
|
||||
return functools.partial(self._filter_signals, signal, tab)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2015-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -138,7 +138,7 @@ def serialize(items):
|
||||
|
||||
Return:
|
||||
A (stream, data, user_data) tuple.
|
||||
stream: The reseted QDataStream.
|
||||
stream: The reset QDataStream.
|
||||
data: The QByteArray with the raw data.
|
||||
user_data: A list with each item's user data.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2015 Antoni Boucher <bouanto@zoho.com>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2015-2016 Antoni Boucher <bouanto@zoho.com>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -247,11 +247,13 @@ class BookmarkManager(UrlMarkManager):
|
||||
bookmarks_directory = os.path.join(standarddir.config(), 'bookmarks')
|
||||
if not os.path.isdir(bookmarks_directory):
|
||||
os.makedirs(bookmarks_directory)
|
||||
|
||||
bookmarks_subdir = os.path.join('bookmarks', 'urls')
|
||||
self._lineparser = lineparser.LineParser(
|
||||
standarddir.config(), 'bookmarks/urls', parent=self)
|
||||
standarddir.config(), bookmarks_subdir, parent=self)
|
||||
|
||||
def _init_savemanager(self, save_manager):
|
||||
filename = os.path.join(standarddir.config(), 'bookmarks/urls')
|
||||
filename = os.path.join(standarddir.config(), 'bookmarks', 'urls')
|
||||
save_manager.add_saveable('bookmark-manager', self.save, self.changed,
|
||||
filename=filename)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -106,7 +106,6 @@ class WebElementWrapper(collections.abc.MutableMapping):
|
||||
method = getattr(self._elem, name)
|
||||
|
||||
def _wrapper(meth, *args, **kwargs):
|
||||
# pylint: disable=missing-docstring
|
||||
self._check_vanished()
|
||||
return meth(*args, **kwargs)
|
||||
|
||||
@@ -308,6 +307,10 @@ def javascript_escape(text):
|
||||
('\n', r'\n'), # We also need to escape newlines for some reason.
|
||||
('\r', r'\r'),
|
||||
('\x00', r'\x00'),
|
||||
('\ufeff', r'\ufeff'),
|
||||
# http://stackoverflow.com/questions/2965293/
|
||||
('\u2028', r'\u2028'),
|
||||
('\u2029', r'\u2029'),
|
||||
)
|
||||
for orig, repl in replacements:
|
||||
text = text.replace(orig, repl)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -30,7 +30,7 @@ from PyQt5.QtPrintSupport import QPrintDialog
|
||||
from PyQt5.QtWebKitWidgets import QWebPage
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.browser import http, tabhistory
|
||||
from qutebrowser.browser import http, tabhistory, pdfjs
|
||||
from qutebrowser.browser.network import networkmanager
|
||||
from qutebrowser.utils import (message, usertypes, log, jinja, qtutils, utils,
|
||||
objreg, debug)
|
||||
@@ -168,7 +168,7 @@ class BrowserPage(QWebPage):
|
||||
title = "Error loading page: {}".format(urlstr)
|
||||
template = jinja.env.get_template('error.html')
|
||||
# pylint: disable=no-member
|
||||
# https://bitbucket.org/logilab/pylint/issue/490/
|
||||
# WORKAROUND for https://bitbucket.org/logilab/pylint/issue/490/
|
||||
html = template.render(
|
||||
title=title, url=urlstr, error=error_str, icon='')
|
||||
errpage.content = html.encode('utf-8')
|
||||
@@ -218,6 +218,19 @@ class BrowserPage(QWebPage):
|
||||
q.deleteLater()
|
||||
return q.answer
|
||||
|
||||
def _show_pdfjs(self, reply):
|
||||
"""Show the reply with pdfjs."""
|
||||
try:
|
||||
page = pdfjs.generate_pdfjs_page(reply.url()).encode('utf-8')
|
||||
except pdfjs.PDFJSNotFound:
|
||||
# pylint: disable=no-member
|
||||
# WORKAROUND for https://bitbucket.org/logilab/pylint/issue/490/
|
||||
page = (jinja.env.get_template('no_pdfjs.html')
|
||||
.render(url=reply.url().toDisplayString())
|
||||
.encode('utf-8'))
|
||||
self.mainFrame().setContent(page, 'text/html', reply.url())
|
||||
reply.deleteLater()
|
||||
|
||||
def shutdown(self):
|
||||
"""Prepare the web page for being deleted."""
|
||||
self._is_shutting_down = True
|
||||
@@ -305,6 +318,10 @@ class BrowserPage(QWebPage):
|
||||
else:
|
||||
reply.finished.connect(functools.partial(
|
||||
self.display_content, reply, 'image/jpeg'))
|
||||
elif (mimetype in {'application/pdf', 'application/x-pdf'} and
|
||||
config.get('content', 'enable-pdfjs')):
|
||||
# Use pdf.js to display the page
|
||||
self._show_pdfjs(reply)
|
||||
else:
|
||||
# Unknown mimetype, so download anyways.
|
||||
download_manager.fetch(reply,
|
||||
@@ -503,8 +520,15 @@ class BrowserPage(QWebPage):
|
||||
|
||||
def javaScriptConsoleMessage(self, msg, line, source):
|
||||
"""Override javaScriptConsoleMessage to use debug log."""
|
||||
if config.get('general', 'log-javascript-console'):
|
||||
log.js.debug("[{}:{}] {}".format(source, line, msg))
|
||||
log_javascript_console = config.get('general',
|
||||
'log-javascript-console')
|
||||
logstring = "[{}:{}] {}".format(source, line, msg)
|
||||
logmap = {
|
||||
'debug': log.js.debug,
|
||||
'info': log.js.info,
|
||||
'none': lambda arg: None
|
||||
}
|
||||
logmap[log_javascript_console](logstring)
|
||||
|
||||
def chooseFile(self, _frame, suggested_file):
|
||||
"""Override QWebPage's chooseFile to be able to chose a file to upload.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -35,8 +35,8 @@ from qutebrowser.utils import message, log, usertypes, utils, qtutils, objreg
|
||||
from qutebrowser.browser import webpage, hints, webelem
|
||||
|
||||
|
||||
LoadStatus = usertypes.enum('LoadStatus', ['none', 'success', 'error', 'warn',
|
||||
'loading'])
|
||||
LoadStatus = usertypes.enum('LoadStatus', ['none', 'success', 'success_https',
|
||||
'error', 'warn', 'loading'])
|
||||
|
||||
|
||||
tab_id_gen = itertools.count(0)
|
||||
@@ -64,13 +64,14 @@ class WebView(QWebView):
|
||||
win_id: The window ID of the view.
|
||||
search_text: The text of the last search.
|
||||
search_flags: The search flags of the last search.
|
||||
_cur_url: The current URL (accessed via cur_url property).
|
||||
_has_ssl_errors: Whether SSL errors occurred during loading.
|
||||
_zoom: A NeighborList with the zoom levels.
|
||||
_old_scroll_pos: The old scroll position.
|
||||
_check_insertmode: If True, in mouseReleaseEvent we should check if we
|
||||
need to enter/leave insert mode.
|
||||
_default_zoom_changed: Whether the zoom was changed from the default.
|
||||
_ignore_wheel_event: Ignore the next wheel event.
|
||||
See https://github.com/The-Compiler/qutebrowser/issues/395
|
||||
|
||||
Signals:
|
||||
scroll_pos_changed: Scroll percentage of current tab changed.
|
||||
@@ -103,6 +104,7 @@ class WebView(QWebView):
|
||||
self._old_scroll_pos = (-1, -1)
|
||||
self._zoom = None
|
||||
self._has_ssl_errors = False
|
||||
self._ignore_wheel_event = False
|
||||
self.keep_icon = False
|
||||
self.search_text = None
|
||||
self.search_flags = 0
|
||||
@@ -116,7 +118,6 @@ class WebView(QWebView):
|
||||
# See https://github.com/The-Compiler/qutebrowser/issues/390
|
||||
self.destroyed.connect(functools.partial(
|
||||
cfg.changed.disconnect, self.init_neighborlist))
|
||||
self._cur_url = None
|
||||
self.cur_url = QUrl()
|
||||
self.progress = 0
|
||||
self.registry = objreg.ObjectRegistry()
|
||||
@@ -158,7 +159,7 @@ class WebView(QWebView):
|
||||
return page
|
||||
|
||||
def __repr__(self):
|
||||
url = utils.elide(self.url().toDisplayString(), 50)
|
||||
url = utils.elide(self.url().toDisplayString(QUrl.EncodeUnicode), 100)
|
||||
return utils.get_repr(self, tab_id=self.tab_id, url=url)
|
||||
|
||||
def __del__(self):
|
||||
@@ -291,7 +292,7 @@ class WebView(QWebView):
|
||||
try:
|
||||
elem = webelem.focus_elem(self.page().currentFrame())
|
||||
except (webelem.IsNullError, RuntimeError):
|
||||
log.mouse.warning("Element/page vanished!")
|
||||
log.mouse.debug("Element/page vanished!")
|
||||
return
|
||||
if elem.is_editable():
|
||||
log.mouse.debug("Clicked editable element (delayed)!")
|
||||
@@ -422,7 +423,11 @@ class WebView(QWebView):
|
||||
"""
|
||||
ok = not self.page().error_occurred
|
||||
if ok and not self._has_ssl_errors:
|
||||
self._set_load_status(LoadStatus.success)
|
||||
if self.cur_url.scheme() == 'https':
|
||||
self._set_load_status(LoadStatus.success_https)
|
||||
else:
|
||||
self._set_load_status(LoadStatus.success)
|
||||
|
||||
elif ok:
|
||||
self._set_load_status(LoadStatus.warn)
|
||||
else:
|
||||
@@ -525,8 +530,8 @@ class WebView(QWebView):
|
||||
"match for: {}".format(text),
|
||||
immediately=True)
|
||||
else:
|
||||
message.error(self.win_id, "Text '{}' not found on "
|
||||
"page!".format(text), immediately=True)
|
||||
message.warning(self.win_id, "Text '{}' not found on "
|
||||
"page!".format(text), immediately=True)
|
||||
else:
|
||||
def check_scroll_pos():
|
||||
"""Check if the scroll position got smaller and show info."""
|
||||
@@ -616,6 +621,7 @@ class WebView(QWebView):
|
||||
return
|
||||
self._mousepress_insertmode(e)
|
||||
self._mousepress_opentarget(e)
|
||||
self._ignore_wheel_event = True
|
||||
super().mousePressEvent(e)
|
||||
|
||||
def mouseReleaseEvent(self, e):
|
||||
@@ -638,6 +644,10 @@ class WebView(QWebView):
|
||||
Args:
|
||||
e: The QWheelEvent.
|
||||
"""
|
||||
if self._ignore_wheel_event:
|
||||
self._ignore_wheel_event = False
|
||||
# See https://github.com/The-Compiler/qutebrowser/issues/395
|
||||
return
|
||||
if e.modifiers() & Qt.ControlModifier:
|
||||
e.accept()
|
||||
divider = config.get('input', 'mouse-zoom-divider')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 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-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -38,7 +38,7 @@ class ArgumentParserError(Exception):
|
||||
|
||||
class ArgumentParserExit(Exception):
|
||||
|
||||
"""Exception raised when the argument parser exitted.
|
||||
"""Exception raised when the argument parser exited.
|
||||
|
||||
Attributes:
|
||||
status: The exit status.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -42,13 +42,6 @@ class NoSuchCommandError(CommandMetaError):
|
||||
pass
|
||||
|
||||
|
||||
class ArgumentCountError(CommandMetaError):
|
||||
|
||||
"""Raised when a command was called with an invalid count of arguments."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ArgumentTypeError(CommandMetaError):
|
||||
|
||||
"""Raised when an argument had an invalid type."""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 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-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -75,7 +75,7 @@ class Command:
|
||||
deprecated=False, no_cmd_split=False, scope='global',
|
||||
count=None, win_id=None):
|
||||
# I really don't know how to solve this in a better way, I tried.
|
||||
# pylint: disable=too-many-arguments,too-many-locals
|
||||
# pylint: disable=too-many-locals
|
||||
if modes is not None and not_modes is not None:
|
||||
raise ValueError("Only modes or not_modes can be given!")
|
||||
if modes is not None:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -29,7 +29,8 @@ from qutebrowser.utils import message, log, objreg, qtutils
|
||||
from qutebrowser.misc import split
|
||||
|
||||
|
||||
ParseResult = collections.namedtuple('ParseResult', 'cmd, args, cmdline')
|
||||
ParseResult = collections.namedtuple('ParseResult', ['cmd', 'args', 'cmdline',
|
||||
'count'])
|
||||
|
||||
|
||||
def replace_variables(win_id, arglist):
|
||||
@@ -117,6 +118,26 @@ class CommandRunner(QObject):
|
||||
for sub in sub_texts:
|
||||
yield self.parse(sub, *args, **kwargs)
|
||||
|
||||
def _parse_count(self, cmdstr):
|
||||
"""Split a count prefix off from a command for parse().
|
||||
|
||||
Args:
|
||||
cmdstr: The command/args including the count.
|
||||
|
||||
Return:
|
||||
A (count, cmdstr) tuple, with count being None or int.
|
||||
"""
|
||||
if ':' not in cmdstr:
|
||||
return (None, cmdstr)
|
||||
|
||||
count, cmdstr = cmdstr.split(':', maxsplit=1)
|
||||
try:
|
||||
count = int(count)
|
||||
except ValueError:
|
||||
# We just ignore invalid prefixes
|
||||
count = None
|
||||
return (count, cmdstr)
|
||||
|
||||
def parse(self, text, *, aliases=True, fallback=False, keep=False):
|
||||
"""Split the commandline text into command and arguments.
|
||||
|
||||
@@ -128,9 +149,11 @@ class CommandRunner(QObject):
|
||||
keep: Whether to keep special chars and whitespace
|
||||
|
||||
Return:
|
||||
A (cmd, args, cmdline) ParseResult tuple.
|
||||
A ParseResult tuple.
|
||||
"""
|
||||
cmdstr, sep, argstr = text.partition(' ')
|
||||
count, cmdstr = self._parse_count(cmdstr)
|
||||
|
||||
if not cmdstr and not fallback:
|
||||
raise cmdexc.NoSuchCommandError("No command given")
|
||||
if aliases:
|
||||
@@ -161,7 +184,7 @@ class CommandRunner(QObject):
|
||||
cmdline = [cmdstr, sep]
|
||||
else:
|
||||
cmdline = [cmdstr] + args[:]
|
||||
return ParseResult(cmd=cmd, args=args, cmdline=cmdline)
|
||||
return ParseResult(cmd=cmd, args=args, cmdline=cmdline, count=count)
|
||||
|
||||
def _split_args(self, cmd, argstr, keep):
|
||||
"""Split the arguments from an arg string.
|
||||
@@ -172,7 +195,7 @@ class CommandRunner(QObject):
|
||||
keep: Whether to keep special chars and whitespace
|
||||
|
||||
Return:
|
||||
A list containing the splitted strings.
|
||||
A list containing the split strings.
|
||||
"""
|
||||
if not argstr:
|
||||
return []
|
||||
@@ -201,10 +224,10 @@ class CommandRunner(QObject):
|
||||
maxsplit = i + cmd.maxsplit + flag_arg_count
|
||||
return split.simple_split(argstr, keep=keep,
|
||||
maxsplit=maxsplit)
|
||||
else: # pylint: disable=useless-else-on-loop
|
||||
# If there are only flags, we got it right on the first try
|
||||
# already.
|
||||
return split_args
|
||||
|
||||
# If there are only flags, we got it right on the first try
|
||||
# already.
|
||||
return split_args
|
||||
|
||||
def run(self, text, count=None):
|
||||
"""Parse a command from a line of text and run it.
|
||||
@@ -216,7 +239,12 @@ class CommandRunner(QObject):
|
||||
for result in self.parse_all(text):
|
||||
args = replace_variables(self._win_id, result.args)
|
||||
if count is not None:
|
||||
if result.count is not None:
|
||||
raise cmdexc.CommandMetaError("Got count via command and "
|
||||
"prefix!")
|
||||
result.cmd.run(self._win_id, args, count=count)
|
||||
elif result.count is not None:
|
||||
result.cmd.run(self._win_id, args, count=result.count)
|
||||
else:
|
||||
result.cmd.run(self._win_id, args)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -33,7 +33,16 @@ from qutebrowser.misc import guiprocess
|
||||
|
||||
class _QtFIFOReader(QObject):
|
||||
|
||||
"""A FIFO reader based on a QSocketNotifier."""
|
||||
"""A FIFO reader based on a QSocketNotifier.
|
||||
|
||||
Attributes:
|
||||
_filepath: The path to the opened FIFO.
|
||||
_fifo: The Python file object for the FIFO.
|
||||
_notifier: The QSocketNotifier used.
|
||||
|
||||
Signals:
|
||||
got_line: Emitted when a whole line arrived.
|
||||
"""
|
||||
|
||||
got_line = pyqtSignal(str)
|
||||
|
||||
@@ -44,9 +53,9 @@ class _QtFIFOReader(QObject):
|
||||
# See http://www.outflux.net/blog/archives/2008/03/09/using-select-on-a-fifo/
|
||||
# We also use os.open and os.fdopen rather than built-in open so we
|
||||
# can add O_NONBLOCK.
|
||||
fd = os.open(filepath, os.O_RDWR |
|
||||
os.O_NONBLOCK) # pylint: disable=no-member
|
||||
self.fifo = os.fdopen(fd, 'r')
|
||||
# pylint: disable=no-member,useless-suppression
|
||||
fd = os.open(filepath, os.O_RDWR | os.O_NONBLOCK)
|
||||
self._fifo = os.fdopen(fd, 'r')
|
||||
self._notifier = QSocketNotifier(fd, QSocketNotifier.Read, self)
|
||||
self._notifier.activated.connect(self.read_line)
|
||||
|
||||
@@ -55,13 +64,14 @@ class _QtFIFOReader(QObject):
|
||||
"""(Try to) read a line from the FIFO."""
|
||||
log.procs.debug("QSocketNotifier triggered!")
|
||||
self._notifier.setEnabled(False)
|
||||
for line in self.fifo:
|
||||
for line in self._fifo:
|
||||
self.got_line.emit(line.rstrip('\r\n'))
|
||||
self._notifier.setEnabled(True)
|
||||
|
||||
def cleanup(self):
|
||||
"""Clean up so the FIFO can be closed."""
|
||||
self._notifier.setEnabled(False)
|
||||
self._fifo.close()
|
||||
|
||||
|
||||
class _BaseUserscriptRunner(QObject):
|
||||
@@ -98,11 +108,11 @@ class _BaseUserscriptRunner(QObject):
|
||||
verbose: Show notifications when the command started/exited.
|
||||
"""
|
||||
self._env = {'QUTE_FIFO': self._filepath}
|
||||
self._env.update(env)
|
||||
if env is not None:
|
||||
self._env.update(env)
|
||||
self._proc = guiprocess.GUIProcess(self._win_id, 'userscript',
|
||||
additional_env=self._env,
|
||||
verbose=verbose, parent=self)
|
||||
self._proc.error.connect(self.on_proc_error)
|
||||
self._proc.finished.connect(self.on_proc_finished)
|
||||
self._proc.start(cmd, args)
|
||||
|
||||
@@ -147,10 +157,6 @@ class _BaseUserscriptRunner(QObject):
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def on_proc_error(self, error):
|
||||
"""Called when the process encountered an error."""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class _POSIXUserscriptRunner(_BaseUserscriptRunner):
|
||||
|
||||
@@ -175,7 +181,8 @@ class _POSIXUserscriptRunner(_BaseUserscriptRunner):
|
||||
# exist, it shouldn't be a big issue.
|
||||
self._filepath = tempfile.mktemp(prefix='qutebrowser-userscript-',
|
||||
dir=standarddir.runtime())
|
||||
os.mkfifo(self._filepath) # pylint: disable=no-member
|
||||
# pylint: disable=no-member,useless-suppression
|
||||
os.mkfifo(self._filepath)
|
||||
except OSError as e:
|
||||
message.error(self._win_id, "Error while creating FIFO: {}".format(
|
||||
e))
|
||||
@@ -190,15 +197,10 @@ class _POSIXUserscriptRunner(_BaseUserscriptRunner):
|
||||
"""Interrupt the reader when the process finished."""
|
||||
self.finish()
|
||||
|
||||
def on_proc_error(self, error):
|
||||
"""Interrupt the reader when the process had an error."""
|
||||
self.finish()
|
||||
|
||||
def finish(self):
|
||||
"""Quit the thread and clean up when the reader finished."""
|
||||
log.procs.debug("Cleaning up")
|
||||
self._reader.cleanup()
|
||||
self._reader.fifo.close()
|
||||
self._reader.deleteLater()
|
||||
self._reader = None
|
||||
super()._cleanup()
|
||||
@@ -245,11 +247,6 @@ class _WindowsUserscriptRunner(_BaseUserscriptRunner):
|
||||
self._cleanup()
|
||||
self.finished.emit()
|
||||
|
||||
def on_proc_error(self, error):
|
||||
"""Clean up when the process had an error."""
|
||||
self._cleanup()
|
||||
self.finished.emit()
|
||||
|
||||
def run(self, cmd, *args, env=None, verbose=False):
|
||||
try:
|
||||
self._oshandle, self._filepath = tempfile.mkstemp(text=True)
|
||||
@@ -260,7 +257,7 @@ class _WindowsUserscriptRunner(_BaseUserscriptRunner):
|
||||
self._run_process(cmd, *args, env=env, verbose=verbose)
|
||||
|
||||
|
||||
class _DummyUserscriptRunner:
|
||||
class _DummyUserscriptRunner(QObject):
|
||||
|
||||
"""Simple dummy runner which displays an error when using userscripts.
|
||||
|
||||
@@ -273,6 +270,10 @@ class _DummyUserscriptRunner:
|
||||
|
||||
finished = pyqtSignal()
|
||||
|
||||
def __init__(self, win_id, parent=None):
|
||||
# pylint: disable=unused-argument
|
||||
super().__init__(parent)
|
||||
|
||||
def run(self, cmd, *args, env=None, verbose=False):
|
||||
"""Print an error as userscripts are not supported."""
|
||||
# pylint: disable=unused-argument,unused-variable
|
||||
@@ -285,9 +286,9 @@ class _DummyUserscriptRunner:
|
||||
# right thing depending on the platform.
|
||||
if os.name == 'posix':
|
||||
UserscriptRunner = _POSIXUserscriptRunner
|
||||
elif os.name == 'nt':
|
||||
elif os.name == 'nt': # pragma: no cover
|
||||
UserscriptRunner = _WindowsUserscriptRunner
|
||||
else:
|
||||
else: # pragma: no cover
|
||||
UserscriptRunner = _DummyUserscriptRunner
|
||||
|
||||
|
||||
@@ -344,6 +345,13 @@ def run(cmd, *args, win_id, env, verbose=False):
|
||||
if user_agent is not None:
|
||||
env['QUTE_USER_AGENT'] = user_agent
|
||||
cmd = os.path.expanduser(cmd)
|
||||
|
||||
# if cmd is not given as an absolute path, look it up
|
||||
# ~/.local/share/qutebrowser/userscripts (or $XDG_DATA_DIR)
|
||||
if not os.path.isabs(cmd):
|
||||
log.misc.debug("{} is no absolute path".format(cmd))
|
||||
cmd = os.path.join(standarddir.data(), "userscripts", cmd)
|
||||
|
||||
runner.run(cmd, *args, env=env, verbose=verbose)
|
||||
runner.finished.connect(commandrunner.deleteLater)
|
||||
runner.finished.connect(runner.deleteLater)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 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-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -124,7 +124,7 @@ class Completer(QObject):
|
||||
self.update_completion()
|
||||
|
||||
def _model(self):
|
||||
"""Convienience method to get the current completion model."""
|
||||
"""Convenience method to get the current completion model."""
|
||||
completion = objreg.get('completion', scope='window',
|
||||
window=self._win_id)
|
||||
return completion.model()
|
||||
@@ -254,7 +254,7 @@ class Completer(QObject):
|
||||
|
||||
Args:
|
||||
selected: New selection.
|
||||
_delected: Previous selection.
|
||||
_deselected: Previous selection.
|
||||
"""
|
||||
indexes = selected.indexes()
|
||||
if not indexes:
|
||||
@@ -383,10 +383,7 @@ class Completer(QObject):
|
||||
"""Get the part index of the commandline where the cursor is over."""
|
||||
cursor_pos = self._cmd.cursorPosition()
|
||||
snippet = slice(cursor_pos - 1, cursor_pos + 1)
|
||||
if self._cmd.text()[snippet] == ' ':
|
||||
spaces = True
|
||||
else:
|
||||
spaces = False
|
||||
spaces = self._cmd.text()[snippet] == ' '
|
||||
cursor_pos -= len(self._cmd.prefix())
|
||||
parts = self.split(keep=True)
|
||||
log.completion.vdebug(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -188,7 +188,7 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
||||
self._doc.setDefaultTextOption(text_option)
|
||||
self._doc.setDefaultStyleSheet(style.get_stylesheet("""
|
||||
.highlight {
|
||||
{{ color['completion.match.fg'] }}
|
||||
color: {{ color['completion.match.fg'] }};
|
||||
}
|
||||
"""))
|
||||
self._doc.setDocumentMargin(2)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -55,15 +55,15 @@ class CompletionView(QTreeView):
|
||||
# don't define that in this stylesheet.
|
||||
STYLESHEET = """
|
||||
QTreeView {
|
||||
{{ font['completion'] }}
|
||||
{{ color['completion.bg'] }}
|
||||
font: {{ font['completion'] }};
|
||||
background-color: {{ color['completion.bg'] }};
|
||||
alternate-background-color: {{ color['completion.alternate-bg'] }};
|
||||
outline: 0;
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
QTreeView::item:disabled {
|
||||
{{ color['completion.category.bg'] }}
|
||||
background-color: {{ color['completion.category.bg'] }};
|
||||
border-top: 1px solid
|
||||
{{ color['completion.category.border.top'] }};
|
||||
border-bottom: 1px solid
|
||||
@@ -75,16 +75,30 @@ class CompletionView(QTreeView):
|
||||
{{ color['completion.item.selected.border.top'] }};
|
||||
border-bottom: 1px solid
|
||||
{{ color['completion.item.selected.border.bottom'] }};
|
||||
{{ color['completion.item.selected.bg'] }}
|
||||
background-color: {{ color['completion.item.selected.bg'] }};
|
||||
}
|
||||
|
||||
QTreeView:item::hover {
|
||||
border: 0px;
|
||||
}
|
||||
"""
|
||||
|
||||
# FIXME style scrollbar
|
||||
# https://github.com/The-Compiler/qutebrowser/issues/117
|
||||
QTreeView QScrollBar {
|
||||
width: {{ config.get('completion', 'scrollbar-width') }}px;
|
||||
background: {{ color['completion.scrollbar.bg'] }};
|
||||
}
|
||||
|
||||
QTreeView QScrollBar::handle {
|
||||
background: {{ color['completion.scrollbar.fg'] }};
|
||||
border: {{ config.get('completion', 'scrollbar-padding') }}px solid
|
||||
{{ color['completion.scrollbar.bg'] }};
|
||||
min-height: 10px;
|
||||
}
|
||||
|
||||
QTreeView QScrollBar::sub-line, QScrollBar::add-line {
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
"""
|
||||
|
||||
resize_completion = pyqtSignal()
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 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-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 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-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -30,6 +30,7 @@ class SettingSectionCompletionModel(base.BaseCompletionModel):
|
||||
|
||||
"""A CompletionModel filled with settings sections."""
|
||||
|
||||
# https://github.com/The-Compiler/qutebrowser/issues/545
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
COLUMN_WIDTHS = (20, 70, 10)
|
||||
@@ -37,7 +38,7 @@ class SettingSectionCompletionModel(base.BaseCompletionModel):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
cat = self.new_category("Sections")
|
||||
for name in configdata.DATA.keys():
|
||||
for name in configdata.DATA:
|
||||
desc = configdata.SECTION_DESC[name].splitlines()[0].strip()
|
||||
self.new_item(cat, name, desc)
|
||||
|
||||
@@ -51,6 +52,7 @@ class SettingOptionCompletionModel(base.BaseCompletionModel):
|
||||
_section: The config section this model shows.
|
||||
"""
|
||||
|
||||
# https://github.com/The-Compiler/qutebrowser/issues/545
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
COLUMN_WIDTHS = (20, 70, 10)
|
||||
@@ -62,7 +64,7 @@ class SettingOptionCompletionModel(base.BaseCompletionModel):
|
||||
self._misc_items = {}
|
||||
self._section = section
|
||||
objreg.get('config').changed.connect(self.update_misc_column)
|
||||
for name in sectdata.keys():
|
||||
for name in sectdata:
|
||||
try:
|
||||
desc = sectdata.descriptions[name]
|
||||
except (KeyError, AttributeError):
|
||||
@@ -106,6 +108,7 @@ class SettingValueCompletionModel(base.BaseCompletionModel):
|
||||
_option: The config option this model shows.
|
||||
"""
|
||||
|
||||
# https://github.com/The-Compiler/qutebrowser/issues/545
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
COLUMN_WIDTHS = (20, 70, 10)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2015-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -67,12 +67,12 @@ def _init_setting_completions():
|
||||
_instances[usertypes.Completion.option] = {}
|
||||
_instances[usertypes.Completion.value] = {}
|
||||
for sectname in configdata.DATA:
|
||||
model = configmodel.SettingOptionCompletionModel(sectname)
|
||||
_instances[usertypes.Completion.option][sectname] = model
|
||||
opt_model = configmodel.SettingOptionCompletionModel(sectname)
|
||||
_instances[usertypes.Completion.option][sectname] = opt_model
|
||||
_instances[usertypes.Completion.value][sectname] = {}
|
||||
for opt in configdata.DATA[sectname].keys():
|
||||
model = configmodel.SettingValueCompletionModel(sectname, opt)
|
||||
_instances[usertypes.Completion.value][sectname][opt] = model
|
||||
for opt in configdata.DATA[sectname]:
|
||||
val_model = configmodel.SettingValueCompletionModel(sectname, opt)
|
||||
_instances[usertypes.Completion.value][sectname][opt] = val_model
|
||||
|
||||
|
||||
@pyqtSlot()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -29,6 +29,7 @@ class CommandCompletionModel(base.BaseCompletionModel):
|
||||
|
||||
"""A CompletionModel filled with all commands and descriptions."""
|
||||
|
||||
# https://github.com/The-Compiler/qutebrowser/issues/545
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
def __init__(self, parent=None):
|
||||
@@ -52,6 +53,7 @@ class HelpCompletionModel(base.BaseCompletionModel):
|
||||
|
||||
"""A CompletionModel filled with help topics."""
|
||||
|
||||
# https://github.com/The-Compiler/qutebrowser/issues/545
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
def __init__(self, parent=None):
|
||||
@@ -77,7 +79,7 @@ class HelpCompletionModel(base.BaseCompletionModel):
|
||||
"""Fill completion with section->option entries."""
|
||||
cat = self.new_category("Settings")
|
||||
for sectname, sectdata in configdata.DATA.items():
|
||||
for optname in sectdata.keys():
|
||||
for optname in sectdata:
|
||||
try:
|
||||
desc = sectdata.descriptions[optname]
|
||||
except (KeyError, AttributeError):
|
||||
@@ -94,6 +96,7 @@ class QuickmarkCompletionModel(base.BaseCompletionModel):
|
||||
|
||||
"""A CompletionModel filled with all quickmarks."""
|
||||
|
||||
# https://github.com/The-Compiler/qutebrowser/issues/545
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
def __init__(self, parent=None):
|
||||
@@ -108,6 +111,7 @@ class BookmarkCompletionModel(base.BaseCompletionModel):
|
||||
|
||||
"""A CompletionModel filled with all bookmarks."""
|
||||
|
||||
# https://github.com/The-Compiler/qutebrowser/issues/545
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
def __init__(self, parent=None):
|
||||
@@ -122,6 +126,7 @@ class SessionCompletionModel(base.BaseCompletionModel):
|
||||
|
||||
"""A CompletionModel filled with session names."""
|
||||
|
||||
# https://github.com/The-Compiler/qutebrowser/issues/545
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
def __init__(self, parent=None):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 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-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -34,6 +34,7 @@ class UrlCompletionModel(base.BaseCompletionModel):
|
||||
|
||||
Used for the `open` command."""
|
||||
|
||||
# https://github.com/The-Compiler/qutebrowser/issues/545
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
URL_COLUMN = 0
|
||||
@@ -69,8 +70,8 @@ class UrlCompletionModel(base.BaseCompletionModel):
|
||||
bookmark_manager.removed.connect(self.on_bookmark_removed)
|
||||
|
||||
self._history = objreg.get('web-history')
|
||||
max_history = config.get('completion', 'web-history-max-items')
|
||||
history = utils.newest_slice(self._history, max_history)
|
||||
self._max_history = config.get('completion', 'web-history-max-items')
|
||||
history = utils.newest_slice(self._history, self._max_history)
|
||||
for entry in history:
|
||||
self._add_history_entry(entry)
|
||||
self._history.add_completion_item.connect(
|
||||
@@ -92,12 +93,20 @@ class UrlCompletionModel(base.BaseCompletionModel):
|
||||
else:
|
||||
return dt.strftime(fmt)
|
||||
|
||||
def _remove_oldest_history(self):
|
||||
"""Remove the oldest history entry."""
|
||||
self._history_cat.removeRow(0)
|
||||
|
||||
def _add_history_entry(self, entry):
|
||||
"""Add a new history entry to the completion."""
|
||||
self.new_item(self._history_cat, entry.url.toDisplayString(), "",
|
||||
self._fmt_atime(entry.atime), sort=int(entry.atime),
|
||||
userdata=entry.url)
|
||||
|
||||
if (self._max_history != -1 and
|
||||
self._history_cat.rowCount() > self._max_history):
|
||||
self._remove_oldest_history()
|
||||
|
||||
@config.change_filter('completion', 'timestamp-format')
|
||||
def reformat_timestamps(self):
|
||||
"""Reformat the timestamps if the config option was changed."""
|
||||
@@ -161,8 +170,9 @@ class UrlCompletionModel(base.BaseCompletionModel):
|
||||
"""
|
||||
index = completion.currentIndex()
|
||||
qtutils.ensure_valid(index)
|
||||
url = index.data()
|
||||
category = index.parent()
|
||||
index = category.child(index.row(), self.URL_COLUMN)
|
||||
url = index.data()
|
||||
qtutils.ensure_valid(category)
|
||||
|
||||
if category.data() == 'Bookmarks':
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 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-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -29,6 +29,7 @@ import sys
|
||||
import os.path
|
||||
import functools
|
||||
import configparser
|
||||
import contextlib
|
||||
import collections
|
||||
import collections.abc
|
||||
|
||||
@@ -42,6 +43,9 @@ from qutebrowser.utils import (message, objreg, utils, standarddir, log,
|
||||
from qutebrowser.utils.usertypes import Completion
|
||||
|
||||
|
||||
UNSET = object()
|
||||
|
||||
|
||||
class change_filter: # pylint: disable=invalid-name
|
||||
|
||||
"""Decorator to filter calls based on a config section/option matching.
|
||||
@@ -93,7 +97,6 @@ class change_filter: # pylint: disable=invalid-name
|
||||
@pyqtSlot(str, str)
|
||||
@functools.wraps(func)
|
||||
def wrapper(sectname=None, optname=None):
|
||||
# pylint: disable=missing-docstring
|
||||
if sectname is None and optname is None:
|
||||
# Called directly, not from a config change event.
|
||||
return func()
|
||||
@@ -107,7 +110,6 @@ class change_filter: # pylint: disable=invalid-name
|
||||
@pyqtSlot(str, str)
|
||||
@functools.wraps(func)
|
||||
def wrapper(wrapper_self, sectname=None, optname=None):
|
||||
# pylint: disable=missing-docstring
|
||||
if sectname is None and optname is None:
|
||||
# Called directly, not from a config change event.
|
||||
return func(wrapper_self)
|
||||
@@ -138,15 +140,16 @@ def _init_main_config(parent=None):
|
||||
parent: The parent to pass to ConfigManager.
|
||||
"""
|
||||
args = objreg.get('args')
|
||||
config_obj = ConfigManager(parent=parent)
|
||||
try:
|
||||
config_obj = ConfigManager(standarddir.config(), 'qutebrowser.conf',
|
||||
args.relaxed_config, parent=parent)
|
||||
config_obj.read(standarddir.config(), 'qutebrowser.conf',
|
||||
relaxed=args.relaxed_config)
|
||||
except (configexc.Error, configparser.Error, UnicodeDecodeError) as e:
|
||||
log.init.exception(e)
|
||||
errstr = "Error while reading config:"
|
||||
try:
|
||||
errstr += "\n\n{} -> {}:".format(
|
||||
e.section, e.option) # pylint: disable=no-member
|
||||
e.section, e.option)
|
||||
except AttributeError:
|
||||
pass
|
||||
errstr += "\n"
|
||||
@@ -252,21 +255,20 @@ def init(parent=None):
|
||||
_init_misc()
|
||||
|
||||
|
||||
def _get_value_transformer(old, new):
|
||||
def _get_value_transformer(mapping):
|
||||
"""Get a function which transforms a value for CHANGED_OPTIONS.
|
||||
|
||||
Args:
|
||||
old: The old value - if the supplied value doesn't match this, it's
|
||||
returned untransformed.
|
||||
new: The new value.
|
||||
mapping: A dictionary mapping old values to new values. Value is not
|
||||
transformed if the supplied value doesn't match the old value.
|
||||
|
||||
Return:
|
||||
A function which takes a value and transforms it.
|
||||
"""
|
||||
def transformer(val):
|
||||
if val == old:
|
||||
return new
|
||||
else:
|
||||
try:
|
||||
return mapping[val]
|
||||
except KeyError:
|
||||
return val
|
||||
return transformer
|
||||
|
||||
@@ -324,16 +326,17 @@ class ConfigManager(QObject):
|
||||
RENAMED_OPTIONS = {
|
||||
('colors', 'tab.fg.odd'): 'tabs.fg.odd',
|
||||
('colors', 'tab.fg.even'): 'tabs.fg.even',
|
||||
('colors', 'tab.fg.selected'): 'tabs.fg.selected',
|
||||
('colors', 'tab.fg.selected'): 'tabs.fg.selected.odd',
|
||||
('colors', 'tabs.fg.selected'): 'tabs.fg.selected.odd',
|
||||
('colors', 'tab.bg.odd'): 'tabs.bg.odd',
|
||||
('colors', 'tab.bg.even'): 'tabs.bg.even',
|
||||
('colors', 'tab.bg.selected'): 'tabs.bg.selected',
|
||||
('colors', 'tab.bg.selected'): 'tabs.bg.selected.odd',
|
||||
('colors', 'tabs.bg.selected'): 'tabs.bg.selected.odd',
|
||||
('colors', 'tab.bg.bar'): 'tabs.bg.bar',
|
||||
('colors', 'tab.indicator.start'): 'tabs.indicator.start',
|
||||
('colors', 'tab.indicator.stop'): 'tabs.indicator.stop',
|
||||
('colors', 'tab.indicator.error'): 'tabs.indicator.error',
|
||||
('colors', 'tab.indicator.system'): 'tabs.indicator.system',
|
||||
('tabs', 'auto-hide'): 'hide-auto',
|
||||
('completion', 'history-length'): 'cmd-history-max-items',
|
||||
('colors', 'downloads.fg'): 'downloads.fg.start',
|
||||
}
|
||||
@@ -343,36 +346,33 @@ class ConfigManager(QObject):
|
||||
('colors', 'completion.item.bg'),
|
||||
('tabs', 'indicator-space'),
|
||||
('tabs', 'hide-auto'),
|
||||
('tabs', 'auto-hide'),
|
||||
('tabs', 'hide-always'),
|
||||
]
|
||||
CHANGED_OPTIONS = {
|
||||
('content', 'cookies-accept'):
|
||||
_get_value_transformer('default', 'no-3rdparty'),
|
||||
_get_value_transformer({'default': 'no-3rdparty'}),
|
||||
('tabs', 'position'): _transform_position,
|
||||
('ui', 'downloads-position'): _transform_position,
|
||||
('ui', 'remove-finished-downloads'):
|
||||
_get_value_transformer({'false': '-1', 'true': '1000'}),
|
||||
('general', 'log-javascript-console'):
|
||||
_get_value_transformer({'false': 'none', 'true': 'debug'}),
|
||||
}
|
||||
|
||||
changed = pyqtSignal(str, str)
|
||||
style_changed = pyqtSignal(str, str)
|
||||
|
||||
def __init__(self, configdir, fname, relaxed=False, parent=None):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._initialized = False
|
||||
self._configdir = None
|
||||
self._fname = None
|
||||
self.sections = configdata.data()
|
||||
self._interpolation = configparser.ExtendedInterpolation()
|
||||
self._proxies = {}
|
||||
for sectname in self.sections.keys():
|
||||
for sectname in self.sections:
|
||||
self._proxies[sectname] = SectionProxy(self, sectname)
|
||||
self._fname = fname
|
||||
if configdir is None:
|
||||
self._configdir = None
|
||||
self._initialized = True
|
||||
else:
|
||||
self._configdir = configdir
|
||||
parser = ini.ReadConfigParser(configdir, fname)
|
||||
self._from_cp(parser, relaxed)
|
||||
self._initialized = True
|
||||
self._validate_all()
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Get a section from the config."""
|
||||
@@ -414,10 +414,7 @@ class ConfigManager(QObject):
|
||||
for optname, option in sect.items():
|
||||
|
||||
lines.append('#')
|
||||
if option.typ.special:
|
||||
typestr = ''
|
||||
else:
|
||||
typestr = ' ({})'.format(option.typ.__class__.__name__)
|
||||
typestr = ' ({})'.format(option.typ.__class__.__name__)
|
||||
lines.append("# {}{}:".format(optname, typestr))
|
||||
|
||||
try:
|
||||
@@ -553,7 +550,8 @@ class ConfigManager(QObject):
|
||||
|
||||
def _after_set(self, changed_sect, changed_opt):
|
||||
"""Clean up caches and emit signals after an option has been set."""
|
||||
self.get.cache_clear()
|
||||
# WORKAROUND for https://bitbucket.org/logilab/pylint/issues/659/
|
||||
self.get.cache_clear() # pylint: disable=no-member
|
||||
self._changed(changed_sect, changed_opt)
|
||||
# Options in the same section and ${optname} interpolation.
|
||||
for optname, option in self.sections[changed_sect].items():
|
||||
@@ -566,6 +564,19 @@ class ConfigManager(QObject):
|
||||
option.value()):
|
||||
self._changed(sectname, optname)
|
||||
|
||||
def read(self, configdir, fname, relaxed=False):
|
||||
"""Read the config from the given directory/file."""
|
||||
self._fname = fname
|
||||
if configdir is None:
|
||||
self._configdir = None
|
||||
self._initialized = True
|
||||
else:
|
||||
self._configdir = configdir
|
||||
parser = ini.ReadConfigParser(configdir, fname)
|
||||
self._from_cp(parser, relaxed)
|
||||
self._initialized = True
|
||||
self._validate_all()
|
||||
|
||||
def items(self, sectname, raw=True):
|
||||
"""Get a list of (optname, value) tuples for a section.
|
||||
|
||||
@@ -614,14 +625,19 @@ class ConfigManager(QObject):
|
||||
optname = self.optionxform(optname)
|
||||
existed = optname in sectdict
|
||||
if existed:
|
||||
del sectdict[optname]
|
||||
self.get.cache_clear()
|
||||
sectdict.delete(optname)
|
||||
# WORKAROUND for https://bitbucket.org/logilab/pylint/issues/659/
|
||||
self.get.cache_clear() # pylint: disable=no-member
|
||||
return existed
|
||||
|
||||
@functools.lru_cache()
|
||||
def get(self, sectname, optname, raw=False, transformed=True):
|
||||
def get(self, sectname, optname, raw=False, transformed=True,
|
||||
fallback=UNSET):
|
||||
"""Get the value from a section/option.
|
||||
|
||||
We don't support the vars argument from configparser.get as it's not
|
||||
hashable.
|
||||
|
||||
Args:
|
||||
sectname: The section to get the option from.
|
||||
optname: The option name
|
||||
@@ -634,13 +650,18 @@ class ConfigManager(QObject):
|
||||
if not self._initialized:
|
||||
raise Exception("get got called before initialization was "
|
||||
"complete!")
|
||||
|
||||
try:
|
||||
sect = self.sections[sectname]
|
||||
except KeyError:
|
||||
if fallback is not UNSET:
|
||||
return fallback
|
||||
raise configexc.NoSectionError(sectname)
|
||||
try:
|
||||
val = sect[optname]
|
||||
except KeyError:
|
||||
if fallback is not UNSET:
|
||||
return fallback
|
||||
raise configexc.NoOptionError(optname, sectname)
|
||||
if raw:
|
||||
return val.value()
|
||||
@@ -651,6 +672,18 @@ class ConfigManager(QObject):
|
||||
newval = val.typ.transform(newval)
|
||||
return newval
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _handle_config_error(self):
|
||||
"""Catch errors in set_command and raise CommandError."""
|
||||
try:
|
||||
yield
|
||||
except (configexc.NoOptionError, configexc.NoSectionError,
|
||||
configexc.ValidationError) as e:
|
||||
raise cmdexc.CommandError("set: {}".format(e))
|
||||
except (configexc.Error, configparser.Error) as e:
|
||||
raise cmdexc.CommandError("set: {} - {}".format(
|
||||
e.__class__.__name__, e))
|
||||
|
||||
@cmdutils.register(name='set', instance='config', win_id='win_id',
|
||||
completion=[Completion.section, Completion.option,
|
||||
Completion.value])
|
||||
@@ -684,12 +717,12 @@ class ConfigManager(QObject):
|
||||
tabbed_browser.openurl(QUrl('qute:settings'), newtab=False)
|
||||
return
|
||||
|
||||
if option.endswith('?'):
|
||||
if option.endswith('?') and option != '?':
|
||||
option = option[:-1]
|
||||
print_ = True
|
||||
else:
|
||||
try:
|
||||
if option.endswith('!') and value is None:
|
||||
with self._handle_config_error():
|
||||
if option.endswith('!') and option != '!' and value is None:
|
||||
option = option[:-1]
|
||||
val = self.get(section_, option)
|
||||
layer = 'temp' if temp else 'conf'
|
||||
@@ -704,12 +737,10 @@ class ConfigManager(QObject):
|
||||
else:
|
||||
raise cmdexc.CommandError("set: The following arguments "
|
||||
"are required: value")
|
||||
except (configexc.Error, configparser.Error) as e:
|
||||
raise cmdexc.CommandError("set: {} - {}".format(
|
||||
e.__class__.__name__, e))
|
||||
|
||||
if print_:
|
||||
val = self.get(section_, option, transformed=False)
|
||||
with self._handle_config_error():
|
||||
val = self.get(section_, option, transformed=False)
|
||||
message.info(win_id, "{} {} = {}".format(
|
||||
section_, option, val), immediately=True)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -140,7 +140,7 @@ def data(readonly=False):
|
||||
"end."),
|
||||
|
||||
('startpage',
|
||||
SettingValue(typ.List(), 'https://www.duckduckgo.com'),
|
||||
SettingValue(typ.List(), 'https://duckduckgo.com'),
|
||||
"The default page(s) to open at the start, separated by commas."),
|
||||
|
||||
('default-page',
|
||||
@@ -165,8 +165,9 @@ def data(readonly=False):
|
||||
SettingValue(typ.ShellCommand(placeholder=True), 'gvim -f "{}"'),
|
||||
"The editor (and arguments) to use for the `open-editor` "
|
||||
"command.\n\n"
|
||||
"Use `{}` for the filename. The value gets split like in a "
|
||||
"shell, so you can use `\"` or `'` to quote arguments."),
|
||||
"The arguments get split like in a shell, so you can use `\"` or "
|
||||
"`'` to quote them.\n"
|
||||
"`{}` gets replaced by the filename of the file to be edited."),
|
||||
|
||||
('editor-encoding',
|
||||
SettingValue(typ.Encoding(), 'utf-8'),
|
||||
@@ -208,13 +209,33 @@ def data(readonly=False):
|
||||
"be used."),
|
||||
|
||||
('new-instance-open-target',
|
||||
SettingValue(typ.NewInstanceOpenTarget(), 'tab'),
|
||||
SettingValue(typ.String(
|
||||
valid_values=typ.ValidValues(
|
||||
('tab', "Open a new tab in the existing "
|
||||
"window and activate the window."),
|
||||
('tab-bg', "Open a new background tab in the "
|
||||
"existing window and activate the "
|
||||
"window."),
|
||||
('tab-silent', "Open a new tab in the existing "
|
||||
"window without activating "
|
||||
"the window."),
|
||||
('tab-bg-silent', "Open a new background tab "
|
||||
"in the existing window "
|
||||
"without activating the "
|
||||
"window."),
|
||||
('window', "Open in a new window.")
|
||||
)), 'tab'),
|
||||
"How to open links in an existing instance if a new one is "
|
||||
"launched."),
|
||||
|
||||
('log-javascript-console',
|
||||
SettingValue(typ.Bool(), 'false'),
|
||||
"Whether to log javascript console messages."),
|
||||
SettingValue(typ.String(
|
||||
valid_values=typ.ValidValues(
|
||||
('none', "Don't log messages."),
|
||||
('debug', "Log messages with debug level."),
|
||||
('info', "Log messages with info level.")
|
||||
)), 'debug'),
|
||||
"How to log javascript console messages."),
|
||||
|
||||
('save-session',
|
||||
SettingValue(typ.Bool(), 'false'),
|
||||
@@ -225,6 +246,14 @@ def data(readonly=False):
|
||||
"The name of the session to save by default, or empty for the "
|
||||
"last loaded session."),
|
||||
|
||||
('url-incdec-segments',
|
||||
SettingValue(
|
||||
typ.FlagList(valid_values=typ.ValidValues(
|
||||
'host', 'path', 'query', 'anchor')),
|
||||
'path,query'),
|
||||
"The URL segments where `:navigate increment/decrement` will "
|
||||
"search for a number."),
|
||||
|
||||
readonly=readonly
|
||||
)),
|
||||
|
||||
@@ -286,8 +315,9 @@ def data(readonly=False):
|
||||
"Whether to enable smooth scrolling for webpages."),
|
||||
|
||||
('remove-finished-downloads',
|
||||
SettingValue(typ.Bool(), 'false'),
|
||||
"Whether to remove finished downloads automatically."),
|
||||
SettingValue(typ.Int(minval=-1), '-1'),
|
||||
"Number of milliseconds to wait before removing finished "
|
||||
"downloads. Will not be removed if value is -1."),
|
||||
|
||||
('hide-statusbar',
|
||||
SettingValue(typ.Bool(), 'false'),
|
||||
@@ -299,7 +329,8 @@ def data(readonly=False):
|
||||
|
||||
('window-title-format',
|
||||
SettingValue(typ.FormatString(fields=['perc', 'perc_raw', 'title',
|
||||
'title_sep', 'id']),
|
||||
'title_sep', 'id',
|
||||
'scroll_pos']),
|
||||
'{perc}{title}{title_sep}qutebrowser'),
|
||||
"The format to use for the window title. The following "
|
||||
"placeholders are defined:\n\n"
|
||||
@@ -308,7 +339,8 @@ def data(readonly=False):
|
||||
"* `{title}`: The title of the current web page\n"
|
||||
"* `{title_sep}`: The string ` - ` if a title is set, empty "
|
||||
"otherwise.\n"
|
||||
"* `{id}`: The internal window ID of this window."),
|
||||
"* `{id}`: The internal window ID of this window.\n"
|
||||
"* `{scroll_pos}`: The page scroll position."),
|
||||
|
||||
('hide-mouse-cursor',
|
||||
SettingValue(typ.Bool(), 'false'),
|
||||
@@ -318,6 +350,11 @@ def data(readonly=False):
|
||||
SettingValue(typ.Bool(), 'false'),
|
||||
"Use standard JavaScript modal dialog for alert() and confirm()"),
|
||||
|
||||
('hide-wayland-decoration',
|
||||
SettingValue(typ.Bool(), 'false'),
|
||||
"Hide the window decoration when using wayland "
|
||||
"(requires restart)"),
|
||||
|
||||
readonly=readonly
|
||||
)),
|
||||
|
||||
@@ -331,7 +368,15 @@ def data(readonly=False):
|
||||
"Value to send in the `accept-language` header."),
|
||||
|
||||
('referer-header',
|
||||
SettingValue(typ.Referer(), 'same-domain'),
|
||||
SettingValue(typ.String(
|
||||
valid_values=typ.ValidValues(
|
||||
('always', "Always send."),
|
||||
('never', "Never send; this is not recommended,"
|
||||
" as some sites may break."),
|
||||
('same-domain', "Only send for the same domain."
|
||||
" This will still protect your privacy, but"
|
||||
" shouldn't break any sites.")
|
||||
)), 'same-domain'),
|
||||
"Send the Referer header"),
|
||||
|
||||
('user-agent',
|
||||
@@ -365,11 +410,16 @@ def data(readonly=False):
|
||||
"Automatically open completion when typing."),
|
||||
|
||||
('download-path-suggestion',
|
||||
SettingValue(typ.DownloadPathSuggestion(), 'path'),
|
||||
SettingValue(
|
||||
typ.String(valid_values=typ.ValidValues(
|
||||
('path', "Show only the download path."),
|
||||
('filename', "Show only download filename."),
|
||||
('both', "Show download path and filename."))),
|
||||
'path'),
|
||||
"What to display in the download filename input."),
|
||||
|
||||
('timestamp-format',
|
||||
SettingValue(typ.String(none_ok=True), '%Y-%m-%d'),
|
||||
SettingValue(typ.TimestampTemplate(none_ok=True), '%Y-%m-%d'),
|
||||
"How to format timestamps (e.g. for history)"),
|
||||
|
||||
('show',
|
||||
@@ -402,6 +452,14 @@ def data(readonly=False):
|
||||
"Whether to shrink the completion to be smaller than the "
|
||||
"configured size if there are no scrollbars."),
|
||||
|
||||
('scrollbar-width',
|
||||
SettingValue(typ.Int(minval=0), '12'),
|
||||
"Width of the scrollbar in the completion window (in px)."),
|
||||
|
||||
('scrollbar-padding',
|
||||
SettingValue(typ.Int(minval=0), '2'),
|
||||
"Padding of scrollbar handle in completion window (in px)."),
|
||||
|
||||
readonly=readonly
|
||||
)),
|
||||
|
||||
@@ -430,7 +488,13 @@ def data(readonly=False):
|
||||
"element is focused after page load."),
|
||||
|
||||
('forward-unbound-keys',
|
||||
SettingValue(typ.ForwardUnboundKeys(), 'auto'),
|
||||
SettingValue(typ.String(
|
||||
valid_values=typ.ValidValues(
|
||||
('all', "Forward all unbound keys."),
|
||||
('auto', "Forward unbound non-alphanumeric "
|
||||
"keys."),
|
||||
('none', "Don't forward any keys.")
|
||||
)), 'auto'),
|
||||
"Whether to forward unbound keys to the webview in normal mode."),
|
||||
|
||||
('spatial-navigation',
|
||||
@@ -480,11 +544,26 @@ def data(readonly=False):
|
||||
"How new tabs opened explicitly are positioned."),
|
||||
|
||||
('last-close',
|
||||
SettingValue(typ.LastClose(), 'ignore'),
|
||||
SettingValue(typ.String(
|
||||
valid_values=typ.ValidValues(
|
||||
('ignore', "Don't do anything."),
|
||||
('blank', "Load a blank page."),
|
||||
('startpage', "Load the start page."),
|
||||
('default-page', "Load the default page."),
|
||||
('close', "Close the window.")
|
||||
)), 'ignore'),
|
||||
"Behavior when the last tab is closed."),
|
||||
|
||||
('show',
|
||||
SettingValue(typ.TabBarShow(), 'always'),
|
||||
SettingValue(
|
||||
typ.String(valid_values=typ.ValidValues(
|
||||
('always', "Always show the tab bar."),
|
||||
('never', "Always hide the tab bar."),
|
||||
('multiple', "Hide the tab bar if only one tab "
|
||||
"is open."),
|
||||
('switching', "Show the tab bar when switching "
|
||||
"tabs.")
|
||||
)), 'always'),
|
||||
"When to show the tab bar"),
|
||||
|
||||
('show-switching-delay',
|
||||
@@ -501,7 +580,12 @@ def data(readonly=False):
|
||||
"Whether tabs should be movable."),
|
||||
|
||||
('close-mouse-button',
|
||||
SettingValue(typ.CloseButton(), 'middle'),
|
||||
SettingValue(typ.String(
|
||||
valid_values=typ.ValidValues(
|
||||
('right', "Close tabs on right-click."),
|
||||
('middle', "Close tabs on middle-click."),
|
||||
('none', "Don't close tabs using the mouse.")
|
||||
)), 'middle'),
|
||||
"On which mouse button to close tabs."),
|
||||
|
||||
('position',
|
||||
@@ -529,7 +613,7 @@ def data(readonly=False):
|
||||
('title-format',
|
||||
SettingValue(typ.FormatString(
|
||||
fields=['perc', 'perc_raw', 'title', 'title_sep', 'index',
|
||||
'id']), '{index}: {title}'),
|
||||
'id', 'scroll_pos']), '{index}: {title}'),
|
||||
"The format to use for the tab title. The following placeholders "
|
||||
"are defined:\n\n"
|
||||
"* `{perc}`: The percentage as a string like `[10%]`.\n"
|
||||
@@ -538,7 +622,12 @@ def data(readonly=False):
|
||||
"* `{title_sep}`: The string ` - ` if a title is set, empty "
|
||||
"otherwise.\n"
|
||||
"* `{index}`: The index of this tab.\n"
|
||||
"* `{id}`: The internal tab ID of this tab."),
|
||||
"* `{id}`: The internal tab ID of this tab.\n"
|
||||
"* `{scroll_pos}`: The page scroll position."),
|
||||
|
||||
('title-alignment',
|
||||
SettingValue(typ.TextAlignment(), 'left'),
|
||||
"Alignment of the text inside of tabs"),
|
||||
|
||||
('mousewheel-tab-switching',
|
||||
SettingValue(typ.Bool(), 'true'),
|
||||
@@ -708,7 +797,16 @@ def data(readonly=False):
|
||||
"local urls."),
|
||||
|
||||
('cookies-accept',
|
||||
SettingValue(typ.AcceptCookies(), 'no-3rdparty'),
|
||||
SettingValue(typ.String(
|
||||
valid_values=typ.ValidValues(
|
||||
('all', "Accept all cookies."),
|
||||
('no-3rdparty', "Accept cookies from the same"
|
||||
" origin only."),
|
||||
('no-unknown-3rdparty', "Accept cookies from "
|
||||
"the same origin only, unless a cookie is "
|
||||
"already set for the domain."),
|
||||
('never', "Don't accept cookies at all.")
|
||||
)), 'no-3rdparty'),
|
||||
"Control which cookies to accept."),
|
||||
|
||||
('cookies-store',
|
||||
@@ -735,6 +833,19 @@ def data(readonly=False):
|
||||
SettingValue(typ.Bool(), 'true'),
|
||||
"Whether host blocking is enabled."),
|
||||
|
||||
('host-blocking-whitelist',
|
||||
SettingValue(typ.List(none_ok=True), 'piwik.org'),
|
||||
"List of domains that should always be loaded, despite being "
|
||||
"ad-blocked.\n\n"
|
||||
"Domains may contain * and ? wildcards and are otherwise "
|
||||
"required to exactly match the requested domain.\n\n"
|
||||
"Local domains are always exempt from hostblocking."),
|
||||
|
||||
('enable-pdfjs', SettingValue(typ.Bool(), 'false'),
|
||||
"Enable pdf.js to view PDF files in the browser.\n\n"
|
||||
"Note that the files can still be downloaded by clicking"
|
||||
" the download button in the pdf.js viewer."),
|
||||
|
||||
readonly=readonly
|
||||
)),
|
||||
|
||||
@@ -748,16 +859,25 @@ def data(readonly=False):
|
||||
"Opacity for hints."),
|
||||
|
||||
('mode',
|
||||
SettingValue(typ.HintMode(), 'letter'),
|
||||
SettingValue(typ.String(
|
||||
valid_values=typ.ValidValues(
|
||||
('number', "Use numeric hints."),
|
||||
('letter', "Use the chars in the hints -> "
|
||||
"chars setting.")
|
||||
)), 'letter'),
|
||||
"Mode to use for hints."),
|
||||
|
||||
('chars',
|
||||
SettingValue(typ.String(minlen=2), 'asdfghjkl'),
|
||||
SettingValue(typ.String(minlen=2, completions=[
|
||||
('asdfghjkl', "Home row"),
|
||||
('dhtnaoeu', "Home row (Dvorak)"),
|
||||
('abcdefghijklmnopqrstuvwxyz', "All letters"),
|
||||
]), 'asdfghjkl'),
|
||||
"Chars used for hint strings."),
|
||||
|
||||
('min-chars',
|
||||
SettingValue(typ.Int(minval=1), '1'),
|
||||
"Mininum number of chars used for hint strings."),
|
||||
"Minimum number of chars used for hint strings."),
|
||||
|
||||
('scatter',
|
||||
SettingValue(typ.Bool(), 'true'),
|
||||
@@ -851,13 +971,21 @@ def data(readonly=False):
|
||||
SettingValue(typ.QssColor(), '#ff4444'),
|
||||
"Foreground color of the matched text in the completion."),
|
||||
|
||||
('completion.scrollbar.fg',
|
||||
SettingValue(typ.QssColor(), '${completion.fg}'),
|
||||
"Color of the scrollbar handle in completion view."),
|
||||
|
||||
('completion.scrollbar.bg',
|
||||
SettingValue(typ.QssColor(), '${completion.bg}'),
|
||||
"Color of the scrollbar in completion view"),
|
||||
|
||||
('statusbar.fg',
|
||||
SettingValue(typ.QssColor(), 'white'),
|
||||
"Foreground color of the statusbar."),
|
||||
|
||||
('statusbar.bg',
|
||||
SettingValue(typ.QssColor(), 'black'),
|
||||
"Foreground color of the statusbar."),
|
||||
"Background color of the statusbar."),
|
||||
|
||||
('statusbar.fg.error',
|
||||
SettingValue(typ.QssColor(), '${statusbar.fg}'),
|
||||
@@ -926,9 +1054,14 @@ def data(readonly=False):
|
||||
"Default foreground color of the URL in the statusbar."),
|
||||
|
||||
('statusbar.url.fg.success',
|
||||
SettingValue(typ.QssColor(), 'white'),
|
||||
"Foreground color of the URL in the statusbar on successful "
|
||||
"load (http)."),
|
||||
|
||||
('statusbar.url.fg.success.https',
|
||||
SettingValue(typ.QssColor(), 'lime'),
|
||||
"Foreground color of the URL in the statusbar on successful "
|
||||
"load."),
|
||||
"load (https)."),
|
||||
|
||||
('statusbar.url.fg.error',
|
||||
SettingValue(typ.QssColor(), 'orange'),
|
||||
@@ -960,13 +1093,21 @@ def data(readonly=False):
|
||||
SettingValue(typ.QtColor(), 'darkgrey'),
|
||||
"Background color of unselected even tabs."),
|
||||
|
||||
('tabs.fg.selected',
|
||||
('tabs.fg.selected.odd',
|
||||
SettingValue(typ.QtColor(), 'white'),
|
||||
"Foreground color of selected tabs."),
|
||||
"Foreground color of selected odd tabs."),
|
||||
|
||||
('tabs.bg.selected',
|
||||
('tabs.bg.selected.odd',
|
||||
SettingValue(typ.QtColor(), 'black'),
|
||||
"Background color of selected tabs."),
|
||||
"Background color of selected odd tabs."),
|
||||
|
||||
('tabs.fg.selected.even',
|
||||
SettingValue(typ.QtColor(), '${tabs.fg.selected.odd}'),
|
||||
"Foreground color of selected even tabs."),
|
||||
|
||||
('tabs.bg.selected.even',
|
||||
SettingValue(typ.QtColor(), '${tabs.bg.selected.odd}'),
|
||||
"Background color of selected even tabs."),
|
||||
|
||||
('tabs.bg.bar',
|
||||
SettingValue(typ.QtColor(), '#555555'),
|
||||
@@ -1165,6 +1306,14 @@ KEY_FIRST_COMMENT = """
|
||||
# with Shift. For special keys (with `<>`-signs), you need to explicitly add
|
||||
# `Shift-` to match a key pressed with shift. You can bind multiple commands
|
||||
# by separating them with `;;`.
|
||||
#
|
||||
# Note that default keybindings are always bound, and need to be explicitly
|
||||
# unbound if you wish to remove them:
|
||||
#
|
||||
# <unbound>
|
||||
# keychain
|
||||
# keychain2
|
||||
# ...
|
||||
"""
|
||||
|
||||
KEY_SECTION_DESC = {
|
||||
@@ -1318,7 +1467,7 @@ KEY_DATA = collections.OrderedDict([
|
||||
('inspector', ['wi']),
|
||||
('download', ['gd']),
|
||||
('download-cancel', ['ad']),
|
||||
('download-remove --all', ['cd']),
|
||||
('download-clear', ['cd']),
|
||||
('view-source', ['gf']),
|
||||
('tab-focus last', ['<Ctrl-Tab>']),
|
||||
('enter-mode passthrough', ['<Ctrl-V>']),
|
||||
@@ -1442,4 +1591,6 @@ CHANGED_KEY_COMMANDS = [
|
||||
|
||||
(re.compile(r'^search *;; *clear-keychain$'), r'clear-keychain ;; search'),
|
||||
(re.compile(r'^leave-mode$'), r'clear-keychain ;; leave-mode'),
|
||||
|
||||
(re.compile(r'^download-remove --all$'), r'download-clear'),
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 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-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -24,11 +24,12 @@ import shlex
|
||||
import base64
|
||||
import codecs
|
||||
import os.path
|
||||
import sre_constants
|
||||
import itertools
|
||||
import collections
|
||||
import warnings
|
||||
import datetime
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtCore import QUrl, Qt
|
||||
from PyQt5.QtGui import QColor, QFont
|
||||
from PyQt5.QtNetwork import QNetworkProxy
|
||||
from PyQt5.QtWidgets import QTabWidget, QTabBar
|
||||
@@ -45,6 +46,33 @@ BOOLEAN_STATES = {'1': True, 'yes': True, 'true': True, 'on': True,
|
||||
'0': False, 'no': False, 'false': False, 'off': False}
|
||||
|
||||
|
||||
def _validate_regex(pattern, flags):
|
||||
"""Check if the given regex is valid.
|
||||
|
||||
This is more complicated than it could be since there's a warning on
|
||||
invalid escapes with newer Python versions, and we want to catch that case
|
||||
and treat it as invalid.
|
||||
"""
|
||||
with warnings.catch_warnings(record=True) as recorded_warnings:
|
||||
warnings.simplefilter('always')
|
||||
try:
|
||||
re.compile(pattern, flags)
|
||||
except re.error as e:
|
||||
raise configexc.ValidationError(
|
||||
pattern, "must be a valid regex - " + str(e))
|
||||
except RuntimeError:
|
||||
raise configexc.ValidationError(
|
||||
pattern, "must be a valid regex - recursion depth exceeded")
|
||||
|
||||
for w in recorded_warnings:
|
||||
if (issubclass(w.category, DeprecationWarning) and
|
||||
str(w.message).startswith('bad escape')):
|
||||
raise configexc.ValidationError(
|
||||
pattern, "must be a valid regex - " + str(w.message))
|
||||
else:
|
||||
warnings.warn(w.message)
|
||||
|
||||
|
||||
class ValidValues:
|
||||
|
||||
"""Container for valid values for a given type.
|
||||
@@ -55,6 +83,8 @@ class ValidValues:
|
||||
"""
|
||||
|
||||
def __init__(self, *vals):
|
||||
if not vals:
|
||||
raise ValueError("ValidValues with no values makes no sense!")
|
||||
self.descriptions = {}
|
||||
self.values = []
|
||||
for v in vals:
|
||||
@@ -87,15 +117,11 @@ class BaseType:
|
||||
Class attributes:
|
||||
valid_values: Possible values if they can be expressed as a fixed
|
||||
string. ValidValues instance.
|
||||
special: If set, the type is only used for one option and isn't
|
||||
mentioned in the config file.
|
||||
"""
|
||||
|
||||
valid_values = None
|
||||
special = False
|
||||
|
||||
def __init__(self, none_ok=False):
|
||||
self.none_ok = none_ok
|
||||
self.valid_values = None
|
||||
|
||||
def _basic_validation(self, value):
|
||||
"""Do some basic validation for the value (empty, non-printable chars).
|
||||
@@ -186,11 +212,10 @@ class MappingType(BaseType):
|
||||
|
||||
MAPPING = {}
|
||||
|
||||
def __init__(self, none_ok=False):
|
||||
def __init__(self, none_ok=False,
|
||||
valid_values=None):
|
||||
super().__init__(none_ok)
|
||||
if list(sorted(self.MAPPING)) != list(sorted(self.valid_values)):
|
||||
raise ValueError("Mapping {!r} doesn't match valid values "
|
||||
"{!r}".format(self.MAPPING, self.valid_values))
|
||||
self.valid_values = valid_values
|
||||
|
||||
def validate(self, value):
|
||||
super().validate(value.lower())
|
||||
@@ -209,11 +234,14 @@ class String(BaseType):
|
||||
minlen: Minimum length (inclusive).
|
||||
maxlen: Maximum length (inclusive).
|
||||
forbidden: Forbidden chars in the string.
|
||||
_completions: completions to be used, or None
|
||||
"""
|
||||
|
||||
def __init__(self, minlen=None, maxlen=None, forbidden=None,
|
||||
none_ok=False):
|
||||
none_ok=False, completions=None, valid_values=None):
|
||||
super().__init__(none_ok)
|
||||
self.valid_values = valid_values
|
||||
|
||||
if minlen is not None and minlen < 1:
|
||||
raise ValueError("minlen ({}) needs to be >= 1!".format(minlen))
|
||||
elif maxlen is not None and maxlen < 1:
|
||||
@@ -224,6 +252,7 @@ class String(BaseType):
|
||||
self.minlen = minlen
|
||||
self.maxlen = maxlen
|
||||
self.forbidden = forbidden
|
||||
self._completions = completions
|
||||
|
||||
def validate(self, value):
|
||||
self._basic_validation(value)
|
||||
@@ -240,11 +269,18 @@ class String(BaseType):
|
||||
raise configexc.ValidationError(value, "must be at most {} chars "
|
||||
"long!".format(self.maxlen))
|
||||
|
||||
def complete(self):
|
||||
return self._completions
|
||||
|
||||
|
||||
class List(BaseType):
|
||||
|
||||
"""Base class for a (string-)list setting."""
|
||||
|
||||
def __init__(self, none_ok=False, valid_values=None):
|
||||
super().__init__(none_ok)
|
||||
self.valid_values = valid_values
|
||||
|
||||
def transform(self, value):
|
||||
if not value:
|
||||
return None
|
||||
@@ -260,11 +296,65 @@ class List(BaseType):
|
||||
raise configexc.ValidationError(value, "items may not be empty!")
|
||||
|
||||
|
||||
class FlagList(List):
|
||||
|
||||
"""Base class for a list setting that contains one or more flags.
|
||||
|
||||
Lists with duplicate flags are invalid and each item is checked against
|
||||
self.valid_values (if not empty).
|
||||
"""
|
||||
|
||||
combinable_values = None
|
||||
|
||||
def validate(self, value):
|
||||
self._basic_validation(value)
|
||||
if not value:
|
||||
return
|
||||
|
||||
vals = self.transform(value)
|
||||
if None in vals and not self.none_ok:
|
||||
raise configexc.ValidationError(
|
||||
value, "May not contain empty values!")
|
||||
|
||||
# Check for duplicate values
|
||||
if len(set(vals)) != len(vals):
|
||||
raise configexc.ValidationError(
|
||||
value, "List contains duplicate values!")
|
||||
|
||||
# Check if each value is valid, ignores None values
|
||||
set_vals = set(val for val in vals if val)
|
||||
if (self.valid_values is not None and
|
||||
not set_vals.issubset(set(self.valid_values))):
|
||||
raise configexc.ValidationError(
|
||||
value, "List contains invalid values!")
|
||||
|
||||
def complete(self):
|
||||
if self.valid_values is None:
|
||||
return None
|
||||
|
||||
out = []
|
||||
# Single value completions
|
||||
for value in self.valid_values:
|
||||
desc = self.valid_values.descriptions.get(value, "")
|
||||
out.append((value, desc))
|
||||
|
||||
combinables = self.combinable_values
|
||||
if combinables is None:
|
||||
combinables = list(self.valid_values)
|
||||
# Generate combinations of each possible value combination
|
||||
for size in range(2, len(combinables) + 1):
|
||||
for combination in itertools.combinations(combinables, size):
|
||||
out.append((','.join(combination), ''))
|
||||
return out
|
||||
|
||||
|
||||
class Bool(BaseType):
|
||||
|
||||
"""Base class for a boolean setting."""
|
||||
|
||||
valid_values = ValidValues('true', 'false')
|
||||
def __init__(self, none_ok=False):
|
||||
super().__init__(none_ok)
|
||||
self.valid_values = ValidValues('true', 'false')
|
||||
|
||||
def transform(self, value):
|
||||
if not value:
|
||||
@@ -284,7 +374,9 @@ class BoolAsk(Bool):
|
||||
|
||||
"""A yes/no/ask question."""
|
||||
|
||||
valid_values = ValidValues('true', 'false', 'ask')
|
||||
def __init__(self, none_ok=False):
|
||||
super().__init__(none_ok)
|
||||
self.valid_values = ValidValues('true', 'false', 'ask')
|
||||
|
||||
def transform(self, value):
|
||||
if value.lower() == 'ask':
|
||||
@@ -547,8 +639,8 @@ class Command(BaseType):
|
||||
self._basic_validation(value)
|
||||
if not value:
|
||||
return
|
||||
splitted = value.split()
|
||||
if not splitted or splitted[0] not in cmdutils.cmd_dict:
|
||||
split = value.split()
|
||||
if not split or split[0] not in cmdutils.cmd_dict:
|
||||
raise configexc.ValidationError(value, "must be a valid command!")
|
||||
|
||||
def complete(self):
|
||||
@@ -562,15 +654,20 @@ class ColorSystem(MappingType):
|
||||
|
||||
"""Color systems for interpolation."""
|
||||
|
||||
special = True
|
||||
valid_values = ValidValues(('rgb', "Interpolate in the RGB color system."),
|
||||
('hsv', "Interpolate in the HSV color system."),
|
||||
('hsl', "Interpolate in the HSL color system."))
|
||||
def __init__(self, none_ok=False):
|
||||
super().__init__(
|
||||
none_ok,
|
||||
valid_values=ValidValues(
|
||||
('rgb', "Interpolate in the RGB color system."),
|
||||
('hsv', "Interpolate in the HSV color system."),
|
||||
('hsl', "Interpolate in the HSL color system."),
|
||||
('none', "Don't show a gradient.")))
|
||||
|
||||
MAPPING = {
|
||||
'rgb': QColor.Rgb,
|
||||
'hsv': QColor.Hsv,
|
||||
'hsl': QColor.Hsl,
|
||||
'none': None,
|
||||
}
|
||||
|
||||
|
||||
@@ -751,11 +848,7 @@ class Regex(BaseType):
|
||||
self._basic_validation(value)
|
||||
if not value:
|
||||
return
|
||||
try:
|
||||
re.compile(value, self.flags)
|
||||
except sre_constants.error as e:
|
||||
raise configexc.ValidationError(value, "must be a valid regex - " +
|
||||
str(e))
|
||||
_validate_regex(value, self.flags)
|
||||
|
||||
def transform(self, value):
|
||||
if not value:
|
||||
@@ -783,13 +876,15 @@ class RegexList(List):
|
||||
self._basic_validation(value)
|
||||
if not value:
|
||||
return
|
||||
try:
|
||||
vals = self.transform(value)
|
||||
except sre_constants.error as e:
|
||||
raise configexc.ValidationError(value, "must be a list valid "
|
||||
"regexes - " + str(e))
|
||||
if not self.none_ok and None in vals:
|
||||
raise configexc.ValidationError(value, "items may not be empty!")
|
||||
vals = super().transform(value)
|
||||
|
||||
for val in vals:
|
||||
if val is None:
|
||||
if not self.none_ok:
|
||||
raise configexc.ValidationError(
|
||||
value, "items may not be empty!")
|
||||
else:
|
||||
_validate_regex(val, self.flags)
|
||||
|
||||
|
||||
class File(BaseType):
|
||||
@@ -1000,30 +1095,22 @@ class ShellCommand(BaseType):
|
||||
return shlex.split(value)
|
||||
|
||||
|
||||
class HintMode(BaseType):
|
||||
|
||||
"""Base class for the hints -> mode setting."""
|
||||
|
||||
special = True
|
||||
valid_values = ValidValues(('number', "Use numeric hints."),
|
||||
('letter', "Use the chars in the hints -> "
|
||||
"chars setting."))
|
||||
|
||||
|
||||
class Proxy(BaseType):
|
||||
|
||||
"""A proxy URL or special value."""
|
||||
|
||||
special = True
|
||||
valid_values = ValidValues(('system', "Use the system wide proxy."),
|
||||
('none', "Don't use any proxy"))
|
||||
|
||||
PROXY_TYPES = {
|
||||
'http': QNetworkProxy.HttpProxy,
|
||||
'socks': QNetworkProxy.Socks5Proxy,
|
||||
'socks5': QNetworkProxy.Socks5Proxy,
|
||||
}
|
||||
|
||||
def __init__(self, none_ok=False):
|
||||
super().__init__(none_ok)
|
||||
self.valid_values = ValidValues(
|
||||
('system', "Use the system wide proxy."),
|
||||
('none', "Don't use any proxy"))
|
||||
|
||||
def validate(self, value):
|
||||
self._basic_validation(value)
|
||||
if not value:
|
||||
@@ -1070,8 +1157,6 @@ class SearchEngineName(BaseType):
|
||||
|
||||
"""A search engine name."""
|
||||
|
||||
special = True
|
||||
|
||||
def validate(self, value):
|
||||
self._basic_validation(value)
|
||||
|
||||
@@ -1080,8 +1165,6 @@ class SearchEngineUrl(BaseType):
|
||||
|
||||
"""A search engine URL."""
|
||||
|
||||
special = True
|
||||
|
||||
def validate(self, value):
|
||||
self._basic_validation(value)
|
||||
if not value:
|
||||
@@ -1173,8 +1256,6 @@ class UserStyleSheet(File):
|
||||
|
||||
"""QWebSettings UserStyleSheet."""
|
||||
|
||||
special = True
|
||||
|
||||
def transform(self, value):
|
||||
if not value:
|
||||
return None
|
||||
@@ -1209,14 +1290,13 @@ class AutoSearch(BaseType):
|
||||
|
||||
"""Whether to start a search when something else than a URL is entered."""
|
||||
|
||||
special = True
|
||||
valid_values = ValidValues(('naive', "Use simple/naive check."),
|
||||
('dns', "Use DNS requests (might be slow!)."),
|
||||
('false', "Never search automatically."))
|
||||
|
||||
def __init__(self, none_ok=False):
|
||||
super().__init__(none_ok)
|
||||
self.booltype = Bool(none_ok=none_ok)
|
||||
self.valid_values = ValidValues(
|
||||
('naive', "Use simple/naive check."),
|
||||
('dns', "Use DNS requests (might be slow!)."),
|
||||
('false', "Never search automatically."))
|
||||
|
||||
def validate(self, value):
|
||||
self._basic_validation(value)
|
||||
@@ -1243,8 +1323,6 @@ class Position(MappingType):
|
||||
|
||||
"""The position of the tab bar."""
|
||||
|
||||
valid_values = ValidValues('top', 'bottom', 'left', 'right')
|
||||
|
||||
MAPPING = {
|
||||
'top': QTabWidget.North,
|
||||
'bottom': QTabWidget.South,
|
||||
@@ -1252,12 +1330,35 @@ class Position(MappingType):
|
||||
'right': QTabWidget.East,
|
||||
}
|
||||
|
||||
def __init__(self, none_ok=False):
|
||||
super().__init__(
|
||||
none_ok,
|
||||
valid_values=ValidValues('top', 'bottom', 'left', 'right'))
|
||||
|
||||
|
||||
class TextAlignment(MappingType):
|
||||
|
||||
"""Alignment of text."""
|
||||
|
||||
MAPPING = {
|
||||
'left': Qt.AlignLeft,
|
||||
'right': Qt.AlignRight,
|
||||
'center': Qt.AlignCenter,
|
||||
}
|
||||
|
||||
def __init__(self, none_ok=False):
|
||||
super().__init__(
|
||||
none_ok,
|
||||
valid_values=ValidValues('left', 'right', 'center'))
|
||||
|
||||
|
||||
class VerticalPosition(BaseType):
|
||||
|
||||
"""The position of the download bar."""
|
||||
|
||||
valid_values = ValidValues('top', 'bottom')
|
||||
def __init__(self, none_ok=False):
|
||||
super().__init__(none_ok)
|
||||
self.valid_values = ValidValues('top', 'bottom')
|
||||
|
||||
|
||||
class UrlList(List):
|
||||
@@ -1289,8 +1390,6 @@ class SessionName(BaseType):
|
||||
|
||||
"""The name of a session."""
|
||||
|
||||
special = True
|
||||
|
||||
def validate(self, value):
|
||||
self._basic_validation(value)
|
||||
if value.startswith('_'):
|
||||
@@ -1301,72 +1400,44 @@ class SelectOnRemove(MappingType):
|
||||
|
||||
"""Which tab to select when the focused tab is removed."""
|
||||
|
||||
special = True
|
||||
valid_values = ValidValues(
|
||||
('left', "Select the tab on the left."),
|
||||
('right', "Select the tab on the right."),
|
||||
('previous', "Select the previously selected tab."))
|
||||
|
||||
MAPPING = {
|
||||
'left': QTabBar.SelectLeftTab,
|
||||
'right': QTabBar.SelectRightTab,
|
||||
'previous': QTabBar.SelectPreviousTab,
|
||||
}
|
||||
|
||||
|
||||
class LastClose(BaseType):
|
||||
|
||||
"""Behavior when the last tab is closed."""
|
||||
|
||||
special = True
|
||||
valid_values = ValidValues(('ignore', "Don't do anything."),
|
||||
('blank', "Load a blank page."),
|
||||
('startpage', "Load the start page."),
|
||||
('default-page', "Load the default page."),
|
||||
('close', "Close the window."))
|
||||
def __init__(self, none_ok=False):
|
||||
super().__init__(
|
||||
none_ok,
|
||||
valid_values=ValidValues(
|
||||
('left', "Select the tab on the left."),
|
||||
('right', "Select the tab on the right."),
|
||||
('previous', "Select the previously selected tab.")))
|
||||
|
||||
|
||||
class AcceptCookies(BaseType):
|
||||
|
||||
"""Control which cookies to accept."""
|
||||
|
||||
special = True
|
||||
valid_values = ValidValues(('all', "Accept all cookies."),
|
||||
('no-3rdparty', "Accept cookies from the same"
|
||||
" origin only."),
|
||||
('no-unknown-3rdparty', "Accept cookies from "
|
||||
"the same origin only, unless a cookie is "
|
||||
"already set for the domain."),
|
||||
('never', "Don't accept cookies at all."))
|
||||
|
||||
|
||||
class ConfirmQuit(List):
|
||||
class ConfirmQuit(FlagList):
|
||||
|
||||
"""Whether to display a confirmation when the window is closed."""
|
||||
|
||||
special = True
|
||||
valid_values = ValidValues(('always', "Always show a confirmation."),
|
||||
('multiple-tabs', "Show a confirmation if "
|
||||
"multiple tabs are opened."),
|
||||
('downloads', "Show a confirmation if "
|
||||
"downloads are running"),
|
||||
('never', "Never show a confirmation."))
|
||||
# Values that can be combined with commas
|
||||
combinable_values = ('multiple-tabs', 'downloads')
|
||||
|
||||
def __init__(self, none_ok=False):
|
||||
super().__init__(none_ok)
|
||||
self.valid_values = ValidValues(
|
||||
('always', "Always show a confirmation."),
|
||||
('multiple-tabs', "Show a confirmation if "
|
||||
"multiple tabs are opened."),
|
||||
('downloads', "Show a confirmation if "
|
||||
"downloads are running"),
|
||||
('never', "Never show a confirmation."))
|
||||
|
||||
def validate(self, value):
|
||||
self._basic_validation(value)
|
||||
super().validate(value)
|
||||
if not value:
|
||||
return
|
||||
values = []
|
||||
for v in self.transform(value):
|
||||
if v:
|
||||
values.append(v)
|
||||
elif self.none_ok:
|
||||
pass
|
||||
else:
|
||||
raise configexc.ValidationError(value, "May not contain empty "
|
||||
"values!")
|
||||
values = [x for x in self.transform(value) if x]
|
||||
|
||||
# Never can't be set with other options
|
||||
if 'never' in values and len(values) > 1:
|
||||
raise configexc.ValidationError(
|
||||
@@ -1375,73 +1446,32 @@ class ConfirmQuit(List):
|
||||
elif 'always' in values and len(values) > 1:
|
||||
raise configexc.ValidationError(
|
||||
value, "List cannot contain always!")
|
||||
# Values have to be valid
|
||||
elif not set(values).issubset(set(self.valid_values.values)):
|
||||
raise configexc.ValidationError(
|
||||
value, "List contains invalid values!")
|
||||
# List can't have duplicates
|
||||
elif len(set(values)) != len(values):
|
||||
raise configexc.ValidationError(
|
||||
value, "List contains duplicate values!")
|
||||
|
||||
def complete(self):
|
||||
combinations = []
|
||||
# Generate combinations of the options that can be combined
|
||||
for size in range(2, len(self.combinable_values) + 1):
|
||||
combinations += list(
|
||||
itertools.combinations(self.combinable_values, size))
|
||||
out = []
|
||||
# Add valid single values
|
||||
for val in self.valid_values:
|
||||
out.append((val, self.valid_values.descriptions[val]))
|
||||
# Add combinations to list of options
|
||||
for val in combinations:
|
||||
desc = ''
|
||||
out.append((','.join(val), desc))
|
||||
return out
|
||||
|
||||
|
||||
class ForwardUnboundKeys(BaseType):
|
||||
|
||||
"""Whether to forward unbound keys."""
|
||||
|
||||
special = True
|
||||
valid_values = ValidValues(('all', "Forward all unbound keys."),
|
||||
('auto', "Forward unbound non-alphanumeric "
|
||||
"keys."),
|
||||
('none', "Don't forward any keys."))
|
||||
|
||||
|
||||
class CloseButton(BaseType):
|
||||
|
||||
"""Mouse button used to close tabs."""
|
||||
|
||||
special = True
|
||||
valid_values = ValidValues(('right', "Close tabs on right-click."),
|
||||
('middle', "Close tabs on middle-click."),
|
||||
('none', "Don't close tabs using the mouse."))
|
||||
|
||||
|
||||
class NewTabPosition(BaseType):
|
||||
|
||||
"""How new tabs are positioned."""
|
||||
|
||||
special = True
|
||||
valid_values = ValidValues(('left', "On the left of the current tab."),
|
||||
('right', "On the right of the current tab."),
|
||||
('first', "At the left end."),
|
||||
('last', "At the right end."))
|
||||
def __init__(self, none_ok=False):
|
||||
super().__init__(none_ok)
|
||||
self.valid_values = ValidValues(
|
||||
('left', "On the left of the current tab."),
|
||||
('right', "On the right of the current tab."),
|
||||
('first', "At the left end."),
|
||||
('last', "At the right end."))
|
||||
|
||||
|
||||
class IgnoreCase(Bool):
|
||||
|
||||
"""Whether to ignore case when searching."""
|
||||
|
||||
special = True
|
||||
valid_values = ValidValues(('true', "Search case-insensitively"),
|
||||
('false', "Search case-sensitively"),
|
||||
('smart', "Search case-sensitively if there "
|
||||
"are capital chars"))
|
||||
def __init__(self, none_ok=False):
|
||||
super().__init__(none_ok)
|
||||
self.valid_values = ValidValues(
|
||||
('true', "Search case-insensitively"),
|
||||
('false', "Search case-sensitively"),
|
||||
('smart', "Search case-sensitively if there "
|
||||
"are capital chars"))
|
||||
|
||||
def transform(self, value):
|
||||
if value.lower() == 'smart':
|
||||
@@ -1459,103 +1489,48 @@ class IgnoreCase(Bool):
|
||||
super().validate(value)
|
||||
|
||||
|
||||
class NewInstanceOpenTarget(BaseType):
|
||||
|
||||
"""How to open links in an existing instance if a new one is launched."""
|
||||
|
||||
special = True
|
||||
valid_values = ValidValues(('tab', "Open a new tab in the existing "
|
||||
"window and activate the window."),
|
||||
('tab-bg', "Open a new background tab in the "
|
||||
"existing window and activate the "
|
||||
"window."),
|
||||
('tab-silent', "Open a new tab in the existing "
|
||||
"window without activating "
|
||||
"the window."),
|
||||
('tab-bg-silent', "Open a new background tab "
|
||||
"in the existing window "
|
||||
"without activating the "
|
||||
"window."),
|
||||
('window', "Open in a new window."))
|
||||
|
||||
|
||||
class DownloadPathSuggestion(BaseType):
|
||||
|
||||
"""How to format the question when downloading."""
|
||||
|
||||
special = True
|
||||
valid_values = ValidValues(('path', "Show only the download path."),
|
||||
('filename', "Show only download filename."),
|
||||
('both', "Show download path and filename."))
|
||||
|
||||
|
||||
class Referer(BaseType):
|
||||
|
||||
"""Send the Referer header."""
|
||||
|
||||
valid_values = ValidValues(('always', "Always send."),
|
||||
('never', "Never send; this is not recommended,"
|
||||
" as some sites may break."),
|
||||
('same-domain', "Only send for the same domain."
|
||||
" This will still protect your privacy, but"
|
||||
" shouldn't break any sites."))
|
||||
|
||||
|
||||
class UserAgent(BaseType):
|
||||
|
||||
"""The user agent to use."""
|
||||
|
||||
special = True
|
||||
|
||||
def __init__(self, none_ok=False):
|
||||
super().__init__(none_ok)
|
||||
|
||||
def validate(self, value):
|
||||
self._basic_validation(value)
|
||||
|
||||
# To update the following list of user agents, run the script 'ua_fetch.py'
|
||||
# Vim-protip: Place your cursor below this comment and run
|
||||
# :r!python scripts/dev/ua_fetch.py
|
||||
def complete(self):
|
||||
"""Complete a list of common user agents."""
|
||||
out = [
|
||||
('Mozilla/5.0 (Windows NT 6.1; WOW64; rv:35.0) Gecko/20100101 '
|
||||
'Firefox/35.0',
|
||||
"Firefox 35.0 Win7 64-bit"),
|
||||
('Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:35.0) Gecko/20100101 '
|
||||
'Firefox/35.0',
|
||||
"Firefox 35.0 Ubuntu"),
|
||||
('Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:35.0) '
|
||||
'Gecko/20100101 Firefox/35.0',
|
||||
"Firefox 35.0 MacOSX"),
|
||||
('Mozilla/5.0 (Windows NT 6.1; WOW64; rv:41.0) Gecko/20100101 '
|
||||
'Firefox/41.0',
|
||||
"Firefox 41.0 Win7 64-bit"),
|
||||
('Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:41.0) '
|
||||
'Gecko/20100101 Firefox/41.0',
|
||||
"Firefox 41.0 MacOSX"),
|
||||
('Mozilla/5.0 (X11; Linux x86_64; rv:41.0) Gecko/20100101 '
|
||||
'Firefox/41.0',
|
||||
"Firefox 41.0 Linux"),
|
||||
|
||||
('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) '
|
||||
'AppleWebKit/600.3.18 (KHTML, like Gecko) Version/8.0.3 '
|
||||
'Safari/600.3.18',
|
||||
"Safari 8.0 MacOSX"),
|
||||
('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) '
|
||||
'AppleWebKit/601.2.7 (KHTML, like Gecko) Version/9.0.1 '
|
||||
'Safari/601.2.7',
|
||||
"Safari Generic MacOSX"),
|
||||
('Mozilla/5.0 (iPad; CPU OS 9_1 like Mac OS X) '
|
||||
'AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 '
|
||||
'Mobile/13B143 Safari/601.1',
|
||||
"Mobile Safari Generic iOS"),
|
||||
|
||||
('Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, '
|
||||
'like Gecko) Chrome/40.0.2214.111 Safari/537.36',
|
||||
"Chrome 40.0 Win7 64-bit"),
|
||||
('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) '
|
||||
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 '
|
||||
'like Gecko) Chrome/46.0.2490.80 Safari/537.36',
|
||||
"Chrome 46.0 Win7 64-bit"),
|
||||
('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) '
|
||||
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 '
|
||||
'Safari/537.36',
|
||||
"Chrome 40.0 MacOSX"),
|
||||
('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 '
|
||||
'(KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36',
|
||||
"Chrome 40.0 Linux"),
|
||||
|
||||
('Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like '
|
||||
'Gecko',
|
||||
"IE 11.0 Win7 64-bit"),
|
||||
|
||||
('Mozilla/5.0 (iPhone; CPU iPhone OS 8_1_2 like Mac OS X) '
|
||||
'AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 '
|
||||
'Mobile/12B440 Safari/600.1.4',
|
||||
"Mobile Safari 8.0 iOS"),
|
||||
('Mozilla/5.0 (Android; Mobile; rv:35.0) Gecko/35.0 Firefox/35.0',
|
||||
"Firefox 35, Android"),
|
||||
('Mozilla/5.0 (Linux; Android 5.0.2; One Build/KTU84L.H4) '
|
||||
'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 '
|
||||
'Chrome/37.0.0.0 Mobile Safari/537.36',
|
||||
"Android Browser"),
|
||||
"Chrome 46.0 MacOSX"),
|
||||
('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, '
|
||||
'like Gecko) Chrome/46.0.2490.80 Safari/537.36',
|
||||
"Chrome 46.0 Linux"),
|
||||
|
||||
('Mozilla/5.0 (compatible; Googlebot/2.1; '
|
||||
'+http://www.google.com/bot.html',
|
||||
@@ -1563,18 +1538,32 @@ class UserAgent(BaseType):
|
||||
('Wget/1.16.1 (linux-gnu)',
|
||||
"wget 1.16.1"),
|
||||
('curl/7.40.0',
|
||||
"curl 7.40.0")
|
||||
"curl 7.40.0"),
|
||||
|
||||
('Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like '
|
||||
'Gecko',
|
||||
"IE 11.0 for Desktop Win7 64-bit")
|
||||
]
|
||||
return out
|
||||
|
||||
|
||||
class TabBarShow(BaseType):
|
||||
class TimestampTemplate(BaseType):
|
||||
|
||||
"""When to show the tab bar."""
|
||||
"""A strftime-like template for timestamps.
|
||||
|
||||
valid_values = ValidValues(('always', "Always show the tab bar."),
|
||||
('never', "Always hide the tab bar."),
|
||||
('multiple', "Hide the tab bar if only one tab "
|
||||
"is open."),
|
||||
('switching', "Show the tab bar when switching "
|
||||
"tabs."))
|
||||
See
|
||||
https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior
|
||||
for reference.
|
||||
"""
|
||||
|
||||
def validate(self, value):
|
||||
self._basic_validation(value)
|
||||
if not value:
|
||||
return
|
||||
try:
|
||||
# Dummy check to see if the template is valid
|
||||
datetime.datetime.now().strftime(value)
|
||||
except ValueError as error:
|
||||
# thrown on invalid template string
|
||||
raise configexc.ValidationError(
|
||||
value, "Invalid format string: {}".format(error))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 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-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 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-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -81,7 +81,7 @@ class KeyConfigParser(QObject):
|
||||
Args:
|
||||
configdir: The directory to save the configs in.
|
||||
fname: The filename of the config.
|
||||
relaxed: If given, unknwon commands are ignored.
|
||||
relaxed: If given, unknown commands are ignored.
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.is_dirty = False
|
||||
@@ -273,10 +273,8 @@ class KeyConfigParser(QObject):
|
||||
return True
|
||||
if keychain in bindings:
|
||||
return False
|
||||
elif command in bindings.values():
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
return command not in bindings.values()
|
||||
|
||||
def _read(self, relaxed=False):
|
||||
"""Read the config file from disk and parse it.
|
||||
@@ -364,7 +362,7 @@ class KeyConfigParser(QObject):
|
||||
|
||||
def _add_binding(self, sectname, keychain, command, *, force=False):
|
||||
"""Add a new binding from keychain to command in section sectname."""
|
||||
log.keyboard.debug("Adding binding {} -> {} in mode {}.".format(
|
||||
log.keyboard.vdebug("Adding binding {} -> {} in mode {}.".format(
|
||||
keychain, command, sectname))
|
||||
if sectname not in self.keybindings:
|
||||
self.keybindings[sectname] = collections.OrderedDict()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -72,6 +72,10 @@ class Section:
|
||||
"""Get value keys."""
|
||||
return self.values.keys()
|
||||
|
||||
def delete(self, key):
|
||||
"""Delete item with given key."""
|
||||
del self.values[key]
|
||||
|
||||
def setv(self, layer, key, value, interpolated):
|
||||
"""Set the value on a layer.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -41,9 +41,9 @@ def get_stylesheet(template_str):
|
||||
The formatted template as string.
|
||||
"""
|
||||
colordict = ColorDict(config.section('colors'))
|
||||
fontdict = FontDict(config.section('fonts'))
|
||||
template = jinja2.Template(template_str)
|
||||
return template.render(color=colordict, font=fontdict)
|
||||
return template.render(color=colordict, font=config.section('fonts'),
|
||||
config=objreg.get('config'))
|
||||
|
||||
|
||||
def set_register_stylesheet(obj):
|
||||
@@ -83,10 +83,7 @@ class ColorDict(collections.UserDict):
|
||||
If a value wasn't found, return an empty string.
|
||||
(Color not defined, so no output in the stylesheet)
|
||||
|
||||
If the key has a .fg. element in it, return color: X;.
|
||||
If the key has a .bg. element in it, return background-color: X;.
|
||||
|
||||
In all other cases, return the plain value.
|
||||
else, return the plain value.
|
||||
"""
|
||||
try:
|
||||
val = self.data[key]
|
||||
@@ -98,33 +95,5 @@ class ColorDict(collections.UserDict):
|
||||
# QtColor instead of Color in the config, and it'd go unnoticed as
|
||||
# the CSS is invalid then.
|
||||
raise TypeError("QColor passed to ColorDict!")
|
||||
if 'fg' in key.split('.'):
|
||||
return 'color: {};'.format(val)
|
||||
elif 'bg' in key.split('.'):
|
||||
return 'background-color: {};'.format(val)
|
||||
else:
|
||||
return val
|
||||
|
||||
|
||||
class FontDict(collections.UserDict):
|
||||
|
||||
"""A dict aimed at Qt stylesheet fonts."""
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Override dict __getitem__.
|
||||
|
||||
Args:
|
||||
key: The key to get from the dict.
|
||||
|
||||
Return:
|
||||
If a value wasn't found, return an empty string.
|
||||
(Color not defined, so no output in the stylesheet)
|
||||
|
||||
In all other cases, return font: <value>.
|
||||
"""
|
||||
try:
|
||||
val = self.data[key]
|
||||
except KeyError:
|
||||
return ''
|
||||
else:
|
||||
return 'font: {};'.format(val)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 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-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -78,9 +78,7 @@ class SettingValue:
|
||||
for val in d.values():
|
||||
if val is not None:
|
||||
return val
|
||||
else: # pylint: disable=useless-else-on-loop
|
||||
# https://bitbucket.org/logilab/pylint/issue/489/
|
||||
raise ValueError("No valid config value found!")
|
||||
raise ValueError("No valid config value found!")
|
||||
|
||||
def transformed(self):
|
||||
"""Get the transformed value."""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -32,11 +32,11 @@ ul > li {
|
||||
}
|
||||
|
||||
ul > li {
|
||||
background-image: url('{{folder_url}}');
|
||||
background-image: url('{{ resource_url('img/folder.svg') }}');
|
||||
}
|
||||
|
||||
ul.files > li {
|
||||
background-image: url('{{file_url}}');
|
||||
background-image: url('{{ resource_url('img/file.svg') }}');
|
||||
}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -1,25 +1,59 @@
|
||||
{% extends "base.html" %}
|
||||
{% block style %}
|
||||
{{ super() }}
|
||||
#errorContainer {
|
||||
background: #fff;
|
||||
min-width: 35em;
|
||||
max-width: 35em;
|
||||
position: absolute;
|
||||
top: 2em;
|
||||
left: 1em;
|
||||
padding: 10px;
|
||||
border: 2px solid #eee;
|
||||
-webkit-border-radius: 5px;
|
||||
* {
|
||||
margin: 0px 0px;
|
||||
padding: 0px 0px;
|
||||
}
|
||||
|
||||
#errorTitleText {
|
||||
font-size: 118%;
|
||||
font-weight: bold;
|
||||
body {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
-webkit-text-size-adjust: none;
|
||||
color: #333333;
|
||||
background-color: #EEEEEE;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
#errorMessageText {
|
||||
font-size: 80%;
|
||||
#error-container {
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
margin-top: 20px;
|
||||
border: 1px solid #CCCCCC;
|
||||
box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.20);
|
||||
border-radius: 5px;
|
||||
background-color: #FFFFFF;
|
||||
padding: 20px 20px;
|
||||
}
|
||||
|
||||
#header {
|
||||
border-bottom: 1px solid #CCC;
|
||||
}
|
||||
|
||||
.qutebrowser-broken {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
td {
|
||||
margin-top: 20px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: normal;
|
||||
color: #1e89c6;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-left: 20px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
{% endblock %}
|
||||
|
||||
@@ -35,19 +69,22 @@ function searchFor(uri) {
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="errorContainer">
|
||||
<div id="errorTitle">
|
||||
<p id="errorTitleText">Unable to load page</p>
|
||||
</div>
|
||||
<div id="errorMessage">
|
||||
<p>Problem occurred while loading the URL {{ url }}</p>
|
||||
<p id="errorMessageText">{{ error }}</p>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form name="bl">
|
||||
<input type="button" value="Try again" onclick="javascript:tryagain()" />
|
||||
<!--<input type="button" value="Search" style="visibility:%s" onclick="javascript:searchFor('%s')" />-->
|
||||
</form>
|
||||
<div id="error-container">
|
||||
<table>
|
||||
<tr>
|
||||
<td style="width: 10%; vertical-align: top;">
|
||||
<img style="width: 100%; display: block; max-width: 256px;" src="{{ resource_url('img/broken_qutebrowser_logo.png') }}" />
|
||||
</td>
|
||||
<td style="padding-left: 40px;">
|
||||
<h1>Unable to load page</h1>
|
||||
Error while opening {{ url }}: <br>
|
||||
<p id="error-message-text" style="color: #a31a1a;">{{ error }}</p><br><br>
|
||||
|
||||
<form name="bl">
|
||||
<input type="button" value="Try again" onclick="javascript:tryagain()" />
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
129
qutebrowser/html/no_pdfjs.html
Normal file
129
qutebrowser/html/no_pdfjs.html
Normal file
@@ -0,0 +1,129 @@
|
||||
{% extends "base.html" %}
|
||||
{% block style %}
|
||||
{{ super() }}
|
||||
* {
|
||||
margin: 0px 0px;
|
||||
padding: 0px 0px;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
-webkit-text-size-adjust: none;
|
||||
color: #333333;
|
||||
background-color: #EEEEEE;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
#error-container {
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
margin-top: 20px;
|
||||
border: 1px solid #CCCCCC;
|
||||
box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.20);
|
||||
border-radius: 5px;
|
||||
background-color: #FFFFFF;
|
||||
padding: 20px 20px;
|
||||
}
|
||||
|
||||
#header {
|
||||
border-bottom: 1px solid #CCC;
|
||||
}
|
||||
|
||||
.qutebrowser-broken {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
span.warning {
|
||||
text-weigth: bold;
|
||||
color: red;
|
||||
}
|
||||
|
||||
td {
|
||||
margin-top: 20px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
font-weight: normal;
|
||||
color: #1e89c6;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-left: 20px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="error-container">
|
||||
<table>
|
||||
<tr>
|
||||
<td style="width: 10%; vertical-align: top;">
|
||||
<img style="width: 100%; display: block; max-width: 256px;" src="{{ resource_url('img/broken_qutebrowser_logo.png') }}" />
|
||||
</td>
|
||||
<td style="padding-left: 40px;">
|
||||
<h1>No pdf.js installation found</h1>
|
||||
<p>Error while opening {{ url }}: <br>
|
||||
<p id="error-message-text" style="color: #a31a1a;">qutebrowser can't find a suitable pdf.js installation</p></p>
|
||||
|
||||
<p>It looks like you set <code>content -> enable-pdfjs</code>
|
||||
to <em>true</em> but qutebrowser can't find the required files.</p>
|
||||
|
||||
<br>
|
||||
|
||||
<h2>Possible fixes</h2>
|
||||
<ul>
|
||||
<li>
|
||||
Disable <code>content -> enable-pdfjs</code> and reload the page.
|
||||
You will need to download the pdf-file and open it with an external
|
||||
tool instead.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
If you have installed a packaged version of qutebrowser, make sure
|
||||
the required packages for pdf.js are also installed.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
If you have installed a pdf.js package and qutebrowser still can't
|
||||
find it, please send us a report with your system and the package
|
||||
name, so we can add it to the list of supported packages.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
If you're running a self-built version or the source version, make
|
||||
sure you have pdf.js in <code>qutebrowser/3rdparty/pdfjs</code>.
|
||||
You can use the <code>scripts/dev/update_3rdparty.py</code> script
|
||||
to download the latest version.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
You can manually download the pdf.js archive
|
||||
<a href="https://mozilla.github.io/pdf.js/getting_started/#download">here</a>
|
||||
and extract it to <code>~/.local/share/qutebrowser/pdfjs</code>
|
||||
<br>
|
||||
<span class="warning">Warning:</span> Using this method you are
|
||||
responsible for yourself to keep the installation updated! If a
|
||||
vulnerability is found in pdf.js, neither qutebrowser nor your
|
||||
system's package manager will update your pdf.js installation.
|
||||
Use it at your own risk!
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
If none of these fixes work for you, please send us a bug report so
|
||||
we can fix the issue.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
BIN
qutebrowser/img/broken_qutebrowser_logo.png
Normal file
BIN
qutebrowser/img/broken_qutebrowser_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 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-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 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-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 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-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -168,7 +168,9 @@ class ModeManager(QObject):
|
||||
"{}".format(curmode, utils.qualname(parser)))
|
||||
handled = parser.handle(event)
|
||||
|
||||
is_non_alnum = bool(event.modifiers()) or not event.text().strip()
|
||||
is_non_alnum = (
|
||||
event.modifiers() not in (Qt.NoModifier, Qt.ShiftModifier) or
|
||||
not event.text().strip())
|
||||
focus_widget = QApplication.instance().focusWidget()
|
||||
is_tab = event.key() in (Qt.Key_Tab, Qt.Key_Backtab)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 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-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 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-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -41,13 +41,15 @@ from qutebrowser.misc import crashsignal
|
||||
win_id_gen = itertools.count(0)
|
||||
|
||||
|
||||
def get_window(via_ipc, force_window=False, force_tab=False):
|
||||
def get_window(via_ipc, force_window=False, force_tab=False,
|
||||
force_target=None):
|
||||
"""Helper function for app.py to get a window id.
|
||||
|
||||
Args:
|
||||
via_ipc: Whether the request was made via IPC.
|
||||
force_window: Whether to force opening in a window.
|
||||
force_tab: Whether to force opening in a tab.
|
||||
force_target: Override the new-instance-open-target config
|
||||
"""
|
||||
if force_window and force_tab:
|
||||
raise ValueError("force_window and force_tab are mutually exclusive!")
|
||||
@@ -55,7 +57,10 @@ def get_window(via_ipc, force_window=False, force_tab=False):
|
||||
# Initial main window
|
||||
return 0
|
||||
window_to_raise = None
|
||||
open_target = config.get('general', 'new-instance-open-target')
|
||||
if force_target is not None:
|
||||
open_target = force_target
|
||||
else:
|
||||
open_target = config.get('general', 'new-instance-open-target')
|
||||
if (open_target == 'window' or force_window) and not force_tab:
|
||||
window = MainWindow()
|
||||
window.show()
|
||||
@@ -259,7 +264,6 @@ class MainWindow(QWidget):
|
||||
|
||||
def _connect_signals(self):
|
||||
"""Connect all mainwindow signals."""
|
||||
# pylint: disable=too-many-statements
|
||||
key_config = objreg.get('key-config')
|
||||
|
||||
status = self._get_object('statusbar')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 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-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -110,58 +110,58 @@ class StatusBar(QWidget):
|
||||
QWidget#StatusBar,
|
||||
QWidget#StatusBar QLabel,
|
||||
QWidget#StatusBar QLineEdit {
|
||||
{{ font['statusbar'] }}
|
||||
{{ color['statusbar.bg'] }}
|
||||
{{ color['statusbar.fg'] }}
|
||||
font: {{ font['statusbar'] }};
|
||||
background-color: {{ color['statusbar.bg'] }};
|
||||
color: {{ color['statusbar.fg'] }};
|
||||
}
|
||||
|
||||
QWidget#StatusBar[caret_mode="on"],
|
||||
QWidget#StatusBar[caret_mode="on"] QLabel,
|
||||
QWidget#StatusBar[caret_mode="on"] QLineEdit {
|
||||
{{ color['statusbar.fg.caret'] }}
|
||||
{{ color['statusbar.bg.caret'] }}
|
||||
color: {{ color['statusbar.fg.caret'] }};
|
||||
background-color: {{ color['statusbar.bg.caret'] }};
|
||||
}
|
||||
|
||||
QWidget#StatusBar[caret_mode="selection"],
|
||||
QWidget#StatusBar[caret_mode="selection"] QLabel,
|
||||
QWidget#StatusBar[caret_mode="selection"] QLineEdit {
|
||||
{{ color['statusbar.fg.caret-selection'] }}
|
||||
{{ color['statusbar.bg.caret-selection'] }}
|
||||
color: {{ color['statusbar.fg.caret-selection'] }};
|
||||
background-color: {{ color['statusbar.bg.caret-selection'] }};
|
||||
}
|
||||
|
||||
QWidget#StatusBar[severity="error"],
|
||||
QWidget#StatusBar[severity="error"] QLabel,
|
||||
QWidget#StatusBar[severity="error"] QLineEdit {
|
||||
{{ color['statusbar.fg.error'] }}
|
||||
{{ color['statusbar.bg.error'] }}
|
||||
color: {{ color['statusbar.fg.error'] }};
|
||||
background-color: {{ color['statusbar.bg.error'] }};
|
||||
}
|
||||
|
||||
QWidget#StatusBar[severity="warning"],
|
||||
QWidget#StatusBar[severity="warning"] QLabel,
|
||||
QWidget#StatusBar[severity="warning"] QLineEdit {
|
||||
{{ color['statusbar.fg.warning'] }}
|
||||
{{ color['statusbar.bg.warning'] }}
|
||||
color: {{ color['statusbar.fg.warning'] }};
|
||||
background-color: {{ color['statusbar.bg.warning'] }};
|
||||
}
|
||||
|
||||
QWidget#StatusBar[prompt_active="true"],
|
||||
QWidget#StatusBar[prompt_active="true"] QLabel,
|
||||
QWidget#StatusBar[prompt_active="true"] QLineEdit {
|
||||
{{ color['statusbar.fg.prompt'] }}
|
||||
{{ color['statusbar.bg.prompt'] }}
|
||||
color: {{ color['statusbar.fg.prompt'] }};
|
||||
background-color: {{ color['statusbar.bg.prompt'] }};
|
||||
}
|
||||
|
||||
QWidget#StatusBar[insert_active="true"],
|
||||
QWidget#StatusBar[insert_active="true"] QLabel,
|
||||
QWidget#StatusBar[insert_active="true"] QLineEdit {
|
||||
{{ color['statusbar.fg.insert'] }}
|
||||
{{ color['statusbar.bg.insert'] }}
|
||||
color: {{ color['statusbar.fg.insert'] }};
|
||||
background-color: {{ color['statusbar.bg.insert'] }};
|
||||
}
|
||||
|
||||
QWidget#StatusBar[command_active="true"],
|
||||
QWidget#StatusBar[command_active="true"] QLabel,
|
||||
QWidget#StatusBar[command_active="true"] QLineEdit {
|
||||
{{ color['statusbar.fg.command'] }}
|
||||
{{ color['statusbar.bg.command'] }}
|
||||
color: {{ color['statusbar.fg.command'] }};
|
||||
background-color: {{ color['statusbar.bg.command'] }};
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -92,7 +92,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
|
||||
@cmdutils.register(instance='status-command', name='set-cmd-text',
|
||||
scope='window', maxsplit=0)
|
||||
def set_cmd_text_command(self, text, space=False):
|
||||
def set_cmd_text_command(self, text, space=False, append=False):
|
||||
"""Preset the statusbar to some text.
|
||||
|
||||
//
|
||||
@@ -103,6 +103,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
Args:
|
||||
text: The commandline to set.
|
||||
space: If given, a space is added to the end.
|
||||
append: If given, the text is appended to the current text.
|
||||
"""
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=self._win_id)
|
||||
@@ -122,8 +123,14 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
# I'm not sure what's the best thing to do here
|
||||
# https://github.com/The-Compiler/qutebrowser/issues/123
|
||||
text = text.replace('{url}', url)
|
||||
|
||||
if space:
|
||||
text += ' '
|
||||
if append:
|
||||
if not self.text():
|
||||
raise cmdexc.CommandError("No current text!")
|
||||
text = self.text() + text
|
||||
|
||||
if not text or text[0] not in modeparsers.STARTCHARS:
|
||||
raise cmdexc.CommandError(
|
||||
"Invalid command text '{}'.".format(text))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 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-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 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-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -31,19 +31,15 @@ class Progress(QProgressBar):
|
||||
|
||||
"""The progress bar part of the status bar."""
|
||||
|
||||
# FIXME for some reason, margin-left is not shown
|
||||
# https://github.com/The-Compiler/qutebrowser/issues/125
|
||||
|
||||
STYLESHEET = """
|
||||
QProgressBar {
|
||||
border-radius: 0px;
|
||||
border: 2px solid transparent;
|
||||
margin-left: 1px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
QProgressBar::chunk {
|
||||
{{ color['statusbar.progress.bg'] }}
|
||||
background-color: {{ color['statusbar.progress.bg'] }};
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user