Compare commits
973 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8486efa940 | ||
|
|
ea22ad9f49 | ||
|
|
90229208c0 | ||
|
|
d298818b85 | ||
|
|
d134e0b1d0 | ||
|
|
4e8abaa2d1 | ||
|
|
9d83bfe5cd | ||
|
|
501e4dba9c | ||
|
|
de147b5a93 | ||
|
|
fbbb9ae940 | ||
|
|
d3f3be03dd | ||
|
|
8fd6a2ff77 | ||
|
|
af163eca09 | ||
|
|
f2beaa455e | ||
|
|
8c8cb3bc29 | ||
|
|
1f8d6e2168 | ||
|
|
6b9edefb05 | ||
|
|
0518a03b1e | ||
|
|
c1855e1741 | ||
|
|
b2ed0c0081 | ||
|
|
05b005ce5c | ||
|
|
7b348fe17c | ||
|
|
70ad02b79b | ||
|
|
6a3b5a9d4e | ||
|
|
8459c662e7 | ||
|
|
f836b615c1 | ||
|
|
6c91633293 | ||
|
|
0101596148 | ||
|
|
089a3990ff | ||
|
|
a90730bea7 | ||
|
|
c290b3f80f | ||
|
|
c3bcb1d9ba | ||
|
|
9363bc3f24 | ||
|
|
673919451e | ||
|
|
a98cd16ff3 | ||
|
|
8cec185e4b | ||
|
|
9c256740c6 | ||
|
|
26bbd10952 | ||
|
|
f5edd4941e | ||
|
|
28889cf099 | ||
|
|
e7af961be2 | ||
|
|
a6adbdf167 | ||
|
|
7fe0f9fb16 | ||
|
|
2a7423a515 | ||
|
|
53575aaeed | ||
|
|
9cb25e0c76 | ||
|
|
2ef6e740d9 | ||
|
|
b5a9612840 | ||
|
|
d2f0c5dcac | ||
|
|
73b6dea1f4 | ||
|
|
aa68e97922 | ||
|
|
501cabc6a0 | ||
|
|
0d61c75ce4 | ||
|
|
c5c3684581 | ||
|
|
d9b00acdc6 | ||
|
|
46f6336e6e | ||
|
|
1d0b91475d | ||
|
|
611f3621ec | ||
|
|
252fbf651f | ||
|
|
f90342741a | ||
|
|
feb327e80e | ||
|
|
125af531cb | ||
|
|
fa10b76ce8 | ||
|
|
4a6c9ecc34 | ||
|
|
d6c56b83a8 | ||
|
|
5bfab54828 | ||
|
|
5c00eea122 | ||
|
|
801b6b90ce | ||
|
|
c8ae405bfe | ||
|
|
3c0d51c253 | ||
|
|
4ffdd9da0e | ||
|
|
fde0516ccf | ||
|
|
f2c93a0061 | ||
|
|
12ba642547 | ||
|
|
23e3a5ef7d | ||
|
|
7d3645adc2 | ||
|
|
dbb89b1073 | ||
|
|
f203535e00 | ||
|
|
ef2de8201a | ||
|
|
31c2988693 | ||
|
|
e66dce2360 | ||
|
|
3775bf929f | ||
|
|
d03e314762 | ||
|
|
b8a5c04b69 | ||
|
|
c506ffa4cd | ||
|
|
430126dcc8 | ||
|
|
f5dd392701 | ||
|
|
21bf446147 | ||
|
|
e65c0dd8a7 | ||
|
|
93cd200bb7 | ||
|
|
4c24b9ed4a | ||
|
|
26a622c46d | ||
|
|
f4ed31b295 | ||
|
|
016fc0ebb1 | ||
|
|
97a4e8d847 | ||
|
|
1a4a9b4392 | ||
|
|
fc987ea9c0 | ||
|
|
79717528ec | ||
|
|
7cebd95936 | ||
|
|
bb44f1d4cc | ||
|
|
e76732693c | ||
|
|
a4101662b2 | ||
|
|
3f9ded3bed | ||
|
|
03a339b93a | ||
|
|
8173a48b8a | ||
|
|
76db8d6f81 | ||
|
|
dc1d5036b6 | ||
|
|
58043b5653 | ||
|
|
f0ad24b08a | ||
|
|
dce4c68827 | ||
|
|
43588b2818 | ||
|
|
5620acb81f | ||
|
|
ac7b56b2b8 | ||
|
|
57e2d407ce | ||
|
|
7a3554e77a | ||
|
|
f34bdfbb50 | ||
|
|
12112e0fc7 | ||
|
|
0a612db733 | ||
|
|
ddcdfa54aa | ||
|
|
cc4e8c1aae | ||
|
|
922b1e8f10 | ||
|
|
07d07c7fae | ||
|
|
2b3250144b | ||
|
|
cdb90cbee7 | ||
|
|
afb0807064 | ||
|
|
f32b4d88ba | ||
|
|
84e0ce757a | ||
|
|
6420037dd9 | ||
|
|
2e36e5151e | ||
|
|
28961ab177 | ||
|
|
4caf2fd8b7 | ||
|
|
c8aef015b0 | ||
|
|
31e3356d01 | ||
|
|
bea71ed3a2 | ||
|
|
ae294e92ad | ||
|
|
8358c76f86 | ||
|
|
158cfa1194 | ||
|
|
16a9948759 | ||
|
|
19596e3104 | ||
|
|
4844a68bfc | ||
|
|
8f5394934f | ||
|
|
aff6510e35 | ||
|
|
bcd9d13684 | ||
|
|
9f511fe18c | ||
|
|
8451899a76 | ||
|
|
4eebd2a85d | ||
|
|
bb2fcddcd4 | ||
|
|
77054cc063 | ||
|
|
c1094b6660 | ||
|
|
9ca6baca4f | ||
|
|
22434f4d1b | ||
|
|
dadf6c0e0a | ||
|
|
b07a4c8c28 | ||
|
|
3cf4e8ba67 | ||
|
|
6a20f9d4c9 | ||
|
|
5fe91c30cc | ||
|
|
2e8acf4825 | ||
|
|
6655793e6a | ||
|
|
5a97e79099 | ||
|
|
12f6304659 | ||
|
|
8e319a762f | ||
|
|
4f2f1a6494 | ||
|
|
1587181a76 | ||
|
|
81bfa81448 | ||
|
|
f74832328f | ||
|
|
747a9bc5b6 | ||
|
|
481dec067d | ||
|
|
d114deac70 | ||
|
|
d57a81a3d3 | ||
|
|
22fe42d38e | ||
|
|
5d8e3a969f | ||
|
|
713a2ef2c1 | ||
|
|
519dc6a7c9 | ||
|
|
dd7a082265 | ||
|
|
a2bcd68d56 | ||
|
|
8909e03f1c | ||
|
|
72d847d687 | ||
|
|
6a7d2f4275 | ||
|
|
f7a94b946f | ||
|
|
444f0a36df | ||
|
|
2a8b74cbec | ||
|
|
6c7b8ce895 | ||
|
|
3b10584749 | ||
|
|
d32a4ea99e | ||
|
|
038bb85a67 | ||
|
|
9f8dbe95e4 | ||
|
|
9f9311840a | ||
|
|
9685eb36b6 | ||
|
|
25526f00bf | ||
|
|
2483b8315c | ||
|
|
18609f1a24 | ||
|
|
d1a00eb934 | ||
|
|
20ac618752 | ||
|
|
d4cadcc62e | ||
|
|
4d13941290 | ||
|
|
02b24e8dfb | ||
|
|
f0de3601cb | ||
|
|
94809032a4 | ||
|
|
0f20f16b15 | ||
|
|
f033b228b1 | ||
|
|
30b25da273 | ||
|
|
04d2004528 | ||
|
|
eb90f9835f | ||
|
|
d6039a0e34 | ||
|
|
6aafe02320 | ||
|
|
a37ecc353c | ||
|
|
2633dcc0d5 | ||
|
|
dd63508be7 | ||
|
|
0c792d228e | ||
|
|
3cd2910fa2 | ||
|
|
6b3e16b163 | ||
|
|
ead108eeeb | ||
|
|
129f97873a | ||
|
|
7d7c841250 | ||
|
|
1a3f8662e6 | ||
|
|
9ec4e749f1 | ||
|
|
a3ba7b9f60 | ||
|
|
16e09d18fa | ||
|
|
549a3a8f70 | ||
|
|
6ea250dc83 | ||
|
|
b326f12427 | ||
|
|
9c042e4313 | ||
|
|
0df1d07558 | ||
|
|
58212a7b15 | ||
|
|
7a6d568c8c | ||
|
|
7f81f0c0ab | ||
|
|
dcb4448594 | ||
|
|
00a09354c3 | ||
|
|
636f9edff6 | ||
|
|
e97fbfdf56 | ||
|
|
8a3437c6a4 | ||
|
|
29c2e7b45f | ||
|
|
0ce9a355ae | ||
|
|
62228752aa | ||
|
|
a8f4444c24 | ||
|
|
b554e1f763 | ||
|
|
9675ea93ee | ||
|
|
02104a318e | ||
|
|
28caddf3c1 | ||
|
|
3a04de62ae | ||
|
|
86c37538d7 | ||
|
|
4467f51e42 | ||
|
|
b6466b7410 | ||
|
|
6973a703c5 | ||
|
|
2cdc32ca58 | ||
|
|
956c257d19 | ||
|
|
71095da975 | ||
|
|
a0caa2b7b1 | ||
|
|
f7ccb8061b | ||
|
|
96b6f7c443 | ||
|
|
a3612a624a | ||
|
|
905748f2d0 | ||
|
|
a3f57b9a9b | ||
|
|
7ef64c0f87 | ||
|
|
2c2d7fe734 | ||
|
|
6b65d96fe1 | ||
|
|
fe60556a34 | ||
|
|
a137a29cce | ||
|
|
9dfff43d99 | ||
|
|
bbc2f14e45 | ||
|
|
38b2d42b40 | ||
|
|
b610563e7f | ||
|
|
97054ca35d | ||
|
|
f07301cfb5 | ||
|
|
59c9a2b243 | ||
|
|
22e4a800a1 | ||
|
|
ccb8e74998 | ||
|
|
dd589f180b | ||
|
|
31710b7045 | ||
|
|
595a53ad3b | ||
|
|
b91a39be22 | ||
|
|
ce46b30a1e | ||
|
|
f1f573d651 | ||
|
|
003ec31848 | ||
|
|
a41db970e8 | ||
|
|
689fe96393 | ||
|
|
e9be357104 | ||
|
|
fbd325f8d1 | ||
|
|
5607cc2be8 | ||
|
|
df6ff55b7a | ||
|
|
780ac3f4c2 | ||
|
|
3cfa0f7586 | ||
|
|
1102ae4d7e | ||
|
|
822f6bae2c | ||
|
|
49485ca220 | ||
|
|
6b76203780 | ||
|
|
a5d0b9851a | ||
|
|
b58cfead05 | ||
|
|
2f231c86ac | ||
|
|
f7b0ac503e | ||
|
|
4497f710f9 | ||
|
|
45a1989a1f | ||
|
|
d29cf1ee4d | ||
|
|
ac89ab23a9 | ||
|
|
2752055281 | ||
|
|
b465c109ee | ||
|
|
fcad40ceb7 | ||
|
|
e09a8c77e9 | ||
|
|
81045fb1bd | ||
|
|
af638ec430 | ||
|
|
911616707e | ||
|
|
edba3f83bc | ||
|
|
e7c4df7a9c | ||
|
|
afb4a6be51 | ||
|
|
938198e92a | ||
|
|
d4291dd4ae | ||
|
|
19cecbdeae | ||
|
|
43ff35eaea | ||
|
|
26edf55f85 | ||
|
|
3857491cf9 | ||
|
|
ca74991900 | ||
|
|
01a9405391 | ||
|
|
9dfc6b0f61 | ||
|
|
b6dcd4d387 | ||
|
|
b407f4ab41 | ||
|
|
9a8d68aa25 | ||
|
|
93bdff5d40 | ||
|
|
56759cca6b | ||
|
|
0850aab96f | ||
|
|
1d17e7ab03 | ||
|
|
ef1825efb0 | ||
|
|
73587b1e16 | ||
|
|
4fed8518e1 | ||
|
|
814a88b741 | ||
|
|
40e4e73e36 | ||
|
|
9a8bac18ae | ||
|
|
b820b9d530 | ||
|
|
5f30016901 | ||
|
|
54db781b97 | ||
|
|
5df2025745 | ||
|
|
6c47b5d2d7 | ||
|
|
a4b96c3443 | ||
|
|
0381d74e9a | ||
|
|
0e80be2d30 | ||
|
|
db353c4030 | ||
|
|
6933bc05b4 | ||
|
|
df624944f9 | ||
|
|
8a5b42ffbd | ||
|
|
92b48e77c7 | ||
|
|
dd59f8d724 | ||
|
|
361a1ed6e4 | ||
|
|
9aeb5775c1 | ||
|
|
4c3461038d | ||
|
|
7c497427ce | ||
|
|
c0832eb04b | ||
|
|
fb019b2dab | ||
|
|
efde31aa57 | ||
|
|
209e43e0ba | ||
|
|
d318178567 | ||
|
|
5e49e7eef2 | ||
|
|
d93c583c0d | ||
|
|
a7f41b4564 | ||
|
|
fd5d44182b | ||
|
|
c1b912f567 | ||
|
|
edf737ff7d | ||
|
|
41035cb5ca | ||
|
|
799730f686 | ||
|
|
325c595b89 | ||
|
|
f26377351c | ||
|
|
be9f8bd0de | ||
|
|
25f626a436 | ||
|
|
13728387d7 | ||
|
|
ecdde7663f | ||
|
|
568d60753e | ||
|
|
75a8938e83 | ||
|
|
248a12a8b9 | ||
|
|
1981239478 | ||
|
|
5490f70b25 | ||
|
|
b5dd647678 | ||
|
|
7520a365eb | ||
|
|
0f3cff60fa | ||
|
|
95f34d755f | ||
|
|
38f8cacd2b | ||
|
|
4c1f6158bd | ||
|
|
67253726fa | ||
|
|
54fffc8264 | ||
|
|
2bb8d404d2 | ||
|
|
e05dabefdf | ||
|
|
650b0051e6 | ||
|
|
5ed8019115 | ||
|
|
e8db59a9ef | ||
|
|
c9af36909f | ||
|
|
9df149fe8f | ||
|
|
03a9cbdfb4 | ||
|
|
765a22189c | ||
|
|
06fc52321e | ||
|
|
15c7ede916 | ||
|
|
6cc78aeaee | ||
|
|
0afd6b23c9 | ||
|
|
28d7c5e204 | ||
|
|
739cfc03ba | ||
|
|
8eab402820 | ||
|
|
b31360b6e3 | ||
|
|
750d2c490f | ||
|
|
5d8c9577a7 | ||
|
|
72d466d236 | ||
|
|
487951cd31 | ||
|
|
cbf5fc01fa | ||
|
|
221b81ae90 | ||
|
|
12f4940ef3 | ||
|
|
3b3acba34e | ||
|
|
2581be051f | ||
|
|
401a37bf4b | ||
|
|
e5cabb6d23 | ||
|
|
fd93e7ba6c | ||
|
|
94b2a41ed7 | ||
|
|
aa417019ae | ||
|
|
8acfe501fe | ||
|
|
c4291a8ed5 | ||
|
|
e2d5a443cc | ||
|
|
358c888760 | ||
|
|
7532db83c4 | ||
|
|
b684e50cdf | ||
|
|
9744a3d0bc | ||
|
|
8440303d82 | ||
|
|
2f12233cb7 | ||
|
|
6c9f496edf | ||
|
|
a451e8ac9d | ||
|
|
c9d42c8bea | ||
|
|
203b6c354f | ||
|
|
75555fc244 | ||
|
|
cff557d2fc | ||
|
|
56d1c5c7dd | ||
|
|
54af872825 | ||
|
|
dc00bc1774 | ||
|
|
273747624f | ||
|
|
c3128494a1 | ||
|
|
607cd9ba6e | ||
|
|
aa40842848 | ||
|
|
d90d626ac4 | ||
|
|
25be4d4383 | ||
|
|
203233c894 | ||
|
|
a5d9661d73 | ||
|
|
5495380580 | ||
|
|
feb02769ad | ||
|
|
8dcd5708e3 | ||
|
|
934fb5f7d5 | ||
|
|
b3d757d034 | ||
|
|
8f0332bcf6 | ||
|
|
1cc6e7190e | ||
|
|
abb5c9f638 | ||
|
|
3b680d0bff | ||
|
|
b6bfe7c171 | ||
|
|
8555b86e3b | ||
|
|
a2c549b954 | ||
|
|
87b174b418 | ||
|
|
112800bab9 | ||
|
|
62f37df573 | ||
|
|
740d629b36 | ||
|
|
c14135a6ce | ||
|
|
21e731ebeb | ||
|
|
9a58fe229c | ||
|
|
4644642c38 | ||
|
|
ba6d90aa7a | ||
|
|
6a90cebe85 | ||
|
|
28d3771005 | ||
|
|
5e20aa668a | ||
|
|
91c909cb80 | ||
|
|
8cd9cdea84 | ||
|
|
8c7bf12b88 | ||
|
|
c2973ebca3 | ||
|
|
1a1f0fc1ee | ||
|
|
2b063f577e | ||
|
|
8fb03208e7 | ||
|
|
4a37e40fcc | ||
|
|
c5eab53a87 | ||
|
|
51ce534638 | ||
|
|
d145b304d0 | ||
|
|
13dc24f6ca | ||
|
|
cf8130bd22 | ||
|
|
2debeafe1b | ||
|
|
1a33c88c96 | ||
|
|
c150c5481a | ||
|
|
c4bb134313 | ||
|
|
6338810396 | ||
|
|
51dea053f4 | ||
|
|
ade7004f8f | ||
|
|
95f8c07d7f | ||
|
|
6c241f96ed | ||
|
|
d8887f12c0 | ||
|
|
0f93d53210 | ||
|
|
3131d3d3bc | ||
|
|
274c92a64b | ||
|
|
96599b9684 | ||
|
|
3a012ca1e3 | ||
|
|
b26f2290bc | ||
|
|
e856639db7 | ||
|
|
b3085f5d60 | ||
|
|
e3a3edaf9a | ||
|
|
47480d07f0 | ||
|
|
625a2c3060 | ||
|
|
e2169d2d92 | ||
|
|
8104869ab6 | ||
|
|
233cea4b62 | ||
|
|
dbefaccf06 | ||
|
|
e4be834b39 | ||
|
|
92a6e61b52 | ||
|
|
ccdd1e5f06 | ||
|
|
ea71f0e318 | ||
|
|
404f9ea1d0 | ||
|
|
2b6763ad13 | ||
|
|
b3b768f4a8 | ||
|
|
e72e8b8556 | ||
|
|
9febcc2e76 | ||
|
|
62f35ee064 | ||
|
|
ea70a0dea1 | ||
|
|
111cc7093f | ||
|
|
1203be2a44 | ||
|
|
28572ce3b1 | ||
|
|
4419e59d46 | ||
|
|
4845180689 | ||
|
|
ae48fa68a8 | ||
|
|
36c8ca9790 | ||
|
|
5913552dfe | ||
|
|
408ceefad1 | ||
|
|
3c9bd25f3d | ||
|
|
870c15a02c | ||
|
|
b72343d126 | ||
|
|
0f17e6633d | ||
|
|
ebe44e5f65 | ||
|
|
a3f9991ce2 | ||
|
|
8407d0a227 | ||
|
|
327613d02a | ||
|
|
ccc671b998 | ||
|
|
b9f807011a | ||
|
|
df4a011d48 | ||
|
|
5c43dca0da | ||
|
|
8057f5c281 | ||
|
|
f6cc6677dd | ||
|
|
009fa845f4 | ||
|
|
ec6017b3b2 | ||
|
|
7f0ecaa89e | ||
|
|
fc8f2090f0 | ||
|
|
ccb564de36 | ||
|
|
23716f1212 | ||
|
|
f6e6f9d27b | ||
|
|
c1fcabe846 | ||
|
|
f242fc5cd7 | ||
|
|
e00a072d15 | ||
|
|
324c537a3d | ||
|
|
83e28a70c5 | ||
|
|
d39dda38ce | ||
|
|
155ee198cd | ||
|
|
5ac8e5ad3e | ||
|
|
c47f0402ab | ||
|
|
456c854f06 | ||
|
|
d19d896881 | ||
|
|
8df759ecad | ||
|
|
fa456b0c6e | ||
|
|
9bb3f8d677 | ||
|
|
74af52a0c0 | ||
|
|
78f4abf5a1 | ||
|
|
2f9a857a27 | ||
|
|
04b66e1a0a | ||
|
|
03eae9140e | ||
|
|
9a1d10ca11 | ||
|
|
e7fdff5632 | ||
|
|
2b5e8daba0 | ||
|
|
b37517e55f | ||
|
|
b5bf114ad4 | ||
|
|
2f7a705015 | ||
|
|
6ecd429d8f | ||
|
|
b8b49637e2 | ||
|
|
785bf24652 | ||
|
|
5688fc9910 | ||
|
|
2b7210f6d1 | ||
|
|
8a695648d3 | ||
|
|
59cebae28e | ||
|
|
7cc3fb4a4e | ||
|
|
222c51aa6e | ||
|
|
180fb0d65a | ||
|
|
6e719d1796 | ||
|
|
b9aa5df5ed | ||
|
|
be08cee63c | ||
|
|
fdc43438ef | ||
|
|
743dbbbcd6 | ||
|
|
fdc9dfdf87 | ||
|
|
9f898611f3 | ||
|
|
32914cfaf8 | ||
|
|
3eafdc13d7 | ||
|
|
4a4948e601 | ||
|
|
73a6b6b730 | ||
|
|
0d68b8bb8f | ||
|
|
4f0d8a84ee | ||
|
|
9f40b40942 | ||
|
|
52be2dd665 | ||
|
|
8ddf843c64 | ||
|
|
bc8d767f6e | ||
|
|
aaed6549b3 | ||
|
|
f5d719dfd4 | ||
|
|
ecf6f4bca0 | ||
|
|
3e8c84c018 | ||
|
|
b1f1248a05 | ||
|
|
0053b10e4d | ||
|
|
82fd26268b | ||
|
|
409f8327c2 | ||
|
|
360e0aaa67 | ||
|
|
b42346b616 | ||
|
|
0fdf0ff3b1 | ||
|
|
b759dbfc27 | ||
|
|
b61a9c9512 | ||
|
|
0cc855019e | ||
|
|
cd27fb6af6 | ||
|
|
120fa41009 | ||
|
|
6a415aee4a | ||
|
|
1fbd209213 | ||
|
|
ad4caeac3a | ||
|
|
e7da2142f3 | ||
|
|
18cd608493 | ||
|
|
29cbf75615 | ||
|
|
c74cb22374 | ||
|
|
f56692d836 | ||
|
|
378b280f9a | ||
|
|
55815bd17b | ||
|
|
b55bb5dc6f | ||
|
|
2514b009af | ||
|
|
4d8ac7486c | ||
|
|
22f3fade24 | ||
|
|
f52930c857 | ||
|
|
4fdf2d6f40 | ||
|
|
d8392d4852 | ||
|
|
341e8ca864 | ||
|
|
4857374fb0 | ||
|
|
f222aa30a6 | ||
|
|
e03fffe604 | ||
|
|
fef6b8e5fb | ||
|
|
691a152ec8 | ||
|
|
ca0aa68f74 | ||
|
|
9153bf8c19 | ||
|
|
855d0312b5 | ||
|
|
966aa810df | ||
|
|
f6b6b2ed7d | ||
|
|
db3b1aee0d | ||
|
|
bb63f9fd92 | ||
|
|
3cb06f9a81 | ||
|
|
51a61cf02d | ||
|
|
9ea986a569 | ||
|
|
3ac2cfdf73 | ||
|
|
5215321e64 | ||
|
|
af93c0fbc0 | ||
|
|
87c339587f | ||
|
|
990be79933 | ||
|
|
5689a3c0dc | ||
|
|
2dc0115c81 | ||
|
|
16ad9182f1 | ||
|
|
b8e1ed752f | ||
|
|
c22a27d47f | ||
|
|
6233358b71 | ||
|
|
864249d798 | ||
|
|
568bb5d540 | ||
|
|
0f28804032 | ||
|
|
1c2f0c5359 | ||
|
|
600d2a543d | ||
|
|
4ec2e5485a | ||
|
|
22dcd775da | ||
|
|
1536c098cf | ||
|
|
a96e4490ee | ||
|
|
ee6b4bc7ee | ||
|
|
ce1494e5ec | ||
|
|
72c57d16f4 | ||
|
|
d53a96d9f2 | ||
|
|
be325853d8 | ||
|
|
25bda66260 | ||
|
|
7e07f5c996 | ||
|
|
af9c94bd23 | ||
|
|
c6d7509220 | ||
|
|
87ec7a1a0b | ||
|
|
aebe4c8f9e | ||
|
|
a6451c05d7 | ||
|
|
01b29cd640 | ||
|
|
0a908206bc | ||
|
|
494aceec45 | ||
|
|
f76af6c949 | ||
|
|
b187a83d59 | ||
|
|
56671e5787 | ||
|
|
e744a7c25a | ||
|
|
fe8b9519c8 | ||
|
|
fd52409d0c | ||
|
|
e126faf8c1 | ||
|
|
7672fb5241 | ||
|
|
99ad1547bc | ||
|
|
31e7d8dd3f | ||
|
|
a55d12bf70 | ||
|
|
0c01d9b61a | ||
|
|
a9926e44f0 | ||
|
|
47447c047a | ||
|
|
78eb7b5da8 | ||
|
|
acfb3aa26f | ||
|
|
a14ef88acf | ||
|
|
1cf66976d2 | ||
|
|
c28d681736 | ||
|
|
bb208f4e77 | ||
|
|
5469a238de | ||
|
|
98c6b49cde | ||
|
|
6e624bcd3c | ||
|
|
16fefc1c7b | ||
|
|
c0eae5d4e4 | ||
|
|
9a69ccc9e3 | ||
|
|
4a1cdef887 | ||
|
|
3385585ca5 | ||
|
|
4498141c8b | ||
|
|
f620ffd9d4 | ||
|
|
1c39715267 | ||
|
|
2e051ab008 | ||
|
|
0e3c42db69 | ||
|
|
6d37e4671a | ||
|
|
09d55cb271 | ||
|
|
c97b416cb1 | ||
|
|
19fc4de484 | ||
|
|
bb54a954fe | ||
|
|
385337eb90 | ||
|
|
cb7e6ab02d | ||
|
|
24231ae405 | ||
|
|
bc0c877b87 | ||
|
|
370405c0ed | ||
|
|
35597a7c01 | ||
|
|
dcc53026a3 | ||
|
|
fa8476f762 | ||
|
|
dc26808a94 | ||
|
|
905c984148 | ||
|
|
d4d791f14e | ||
|
|
0b86b302a2 | ||
|
|
95539961a4 | ||
|
|
2becc17099 | ||
|
|
dad7e7b9d2 | ||
|
|
e9483bc485 | ||
|
|
f70740cc3a | ||
|
|
95b41b311f | ||
|
|
3adc2e0f83 | ||
|
|
34b27437d0 | ||
|
|
0540a43995 | ||
|
|
64b6852ae3 | ||
|
|
4c9482be84 | ||
|
|
aab7496916 | ||
|
|
43aa7423ab | ||
|
|
9d415587bc | ||
|
|
08965399c5 | ||
|
|
daee729fc2 | ||
|
|
3d53ffb7ef | ||
|
|
2fe1a1db89 | ||
|
|
51d48f6b00 | ||
|
|
0d1e716760 | ||
|
|
023c59f8c0 | ||
|
|
f44985548b | ||
|
|
493468e08f | ||
|
|
2a4163b2c7 | ||
|
|
249164eb9b | ||
|
|
f5f11f7f4e | ||
|
|
2947b75ab9 | ||
|
|
8f068dda1b | ||
|
|
17e0f6d23c | ||
|
|
24f466b2c3 | ||
|
|
97d719b179 | ||
|
|
879e8dfb2c | ||
|
|
dc01b4eaf0 | ||
|
|
d7dac40c2c | ||
|
|
6519f500a9 | ||
|
|
02c996a785 | ||
|
|
eee5f8263f | ||
|
|
c883d6b429 | ||
|
|
1c9dc581a4 | ||
|
|
c443def24e | ||
|
|
5d2975293b | ||
|
|
ff7edf79e7 | ||
|
|
ae2dad7d18 | ||
|
|
3fd7fb3e14 | ||
|
|
3d87f4ebdf | ||
|
|
df3f0124fc | ||
|
|
f195b7e4d2 | ||
|
|
d8461d79cc | ||
|
|
2ab441a5a3 | ||
|
|
4c7f6e5339 | ||
|
|
048b792c6f | ||
|
|
66c5350989 | ||
|
|
2051a5d95e | ||
|
|
94f8bb9574 | ||
|
|
38e3c1ee8f | ||
|
|
137a7114e1 | ||
|
|
4ed7fe731d | ||
|
|
ed2f473a8e | ||
|
|
cb4aea2f69 | ||
|
|
cb6f4313d7 | ||
|
|
984dd1ba8c | ||
|
|
1d18e808b1 | ||
|
|
6b1519ed52 | ||
|
|
43bca9793e | ||
|
|
c94327748e | ||
|
|
570f1a849f | ||
|
|
f67c445f3d | ||
|
|
d2b315cac1 | ||
|
|
9fd53fd445 | ||
|
|
989d6d2b44 | ||
|
|
bc9d305354 | ||
|
|
4862b2faf9 | ||
|
|
1f521da134 | ||
|
|
96bbdb19e6 | ||
|
|
8b91a74aef | ||
|
|
f53d8135b0 | ||
|
|
96eff65690 | ||
|
|
af98f9a77d | ||
|
|
288fe3f808 | ||
|
|
bd0289423e | ||
|
|
a704526582 | ||
|
|
31f1025ff8 | ||
|
|
e5f2d27ed9 | ||
|
|
56d29a1b5f | ||
|
|
49daa7aab8 | ||
|
|
bdfb9c60cc | ||
|
|
34fc5335d9 | ||
|
|
13116b2679 | ||
|
|
33df4eb865 | ||
|
|
5ba4e13cab | ||
|
|
b499474599 | ||
|
|
caae1c7008 | ||
|
|
fde4495bc7 | ||
|
|
dee0799e15 | ||
|
|
e705ea7e56 | ||
|
|
f6cc9d53b8 | ||
|
|
4c2aeb01a8 | ||
|
|
589e9b7153 | ||
|
|
3dc06aad24 | ||
|
|
128a16173e | ||
|
|
5fe6e60ffd | ||
|
|
c3e9343a6d | ||
|
|
8504d41db3 | ||
|
|
dd927ded6b | ||
|
|
280dddda6b | ||
|
|
fd8e5e30c6 | ||
|
|
455b90ecad | ||
|
|
2bfa853847 | ||
|
|
9613deb89d | ||
|
|
0436526203 | ||
|
|
9b177ae8e7 | ||
|
|
937d0d0688 | ||
|
|
0d7a557396 | ||
|
|
cf04219f79 | ||
|
|
7907840ead | ||
|
|
addccd7492 | ||
|
|
378498bbd7 | ||
|
|
5a9042ab3e | ||
|
|
34787edf4e | ||
|
|
354c3c8c9b | ||
|
|
6f1b9b7984 | ||
|
|
06b990c0d1 | ||
|
|
f710536092 | ||
|
|
233e72fef1 | ||
|
|
9dc9bcaf39 | ||
|
|
e508224a46 | ||
|
|
b3445bc35a | ||
|
|
af8a5c58da | ||
|
|
73c5666ff9 | ||
|
|
a6ed079011 | ||
|
|
84b2b05254 | ||
|
|
d85a15f0a2 | ||
|
|
aa0613c6d8 | ||
|
|
799fe5deb3 | ||
|
|
898dde566d | ||
|
|
c163f702c2 | ||
|
|
31bbc8c5b3 | ||
|
|
828ffd4979 | ||
|
|
11f97f71f4 | ||
|
|
807b7701d5 | ||
|
|
62b6d62cd7 | ||
|
|
5d11a1fd75 | ||
|
|
95761c5023 | ||
|
|
5c6a821b1e | ||
|
|
96bec9f9d7 | ||
|
|
12c9590524 | ||
|
|
4984c9d05c | ||
|
|
161b96be1e | ||
|
|
bf1af698bd | ||
|
|
2fccc083af | ||
|
|
4f263505ee | ||
|
|
2e64dda592 | ||
|
|
52423fa426 | ||
|
|
c3441ae4a8 | ||
|
|
c233099bca | ||
|
|
44e5dc1c5a | ||
|
|
458a45c172 | ||
|
|
f1ddf58260 | ||
|
|
f84af0a6fb | ||
|
|
ced4713fda | ||
|
|
2c86788901 | ||
|
|
cce4ff6d53 | ||
|
|
8d169597ae | ||
|
|
9470bff464 | ||
|
|
a8eae03ee9 | ||
|
|
373ad28d2e | ||
|
|
14a63b8a82 | ||
|
|
6bc35a1842 | ||
|
|
dd683ea08c | ||
|
|
db874d8bba | ||
|
|
1a7612e559 | ||
|
|
d8384ced0a | ||
|
|
544c508fac | ||
|
|
8acd014d39 | ||
|
|
2ad7bafcdf | ||
|
|
914d72a216 | ||
|
|
30f7f7b03c | ||
|
|
18776456f3 | ||
|
|
3084e7be02 | ||
|
|
a76fdfe205 | ||
|
|
1d5d6acdea | ||
|
|
039edd8d85 | ||
|
|
50983f01b8 | ||
|
|
eec129807e | ||
|
|
dd70019d4c | ||
|
|
4d780e23af | ||
|
|
674269724f | ||
|
|
e89fda189a | ||
|
|
e766fe14fc | ||
|
|
7adab9ec78 | ||
|
|
5307b97ca5 | ||
|
|
caeab959a5 | ||
|
|
8756997dc8 | ||
|
|
09868c1e7f | ||
|
|
3797b0cfed | ||
|
|
3d02feac2b | ||
|
|
2a65cadb67 | ||
|
|
e003b11670 | ||
|
|
fa4a66f7b3 | ||
|
|
57e1135abe | ||
|
|
e90a5f509e | ||
|
|
392ea8825b | ||
|
|
af3c9a2b9e | ||
|
|
16b2df56df | ||
|
|
2eba2cc8f5 | ||
|
|
f4796b5ec6 | ||
|
|
e3a305a814 | ||
|
|
71f48a1e30 | ||
|
|
69d4bb6c6a | ||
|
|
4ff44eff7b | ||
|
|
8f9bb67762 | ||
|
|
08b562ea0c | ||
|
|
8ca0c87b1f | ||
|
|
ffab9e263f | ||
|
|
f5cccfb097 | ||
|
|
14005e3684 | ||
|
|
57c4285dbc | ||
|
|
4d2ca878ea | ||
|
|
ee3d7463f6 | ||
|
|
440740d30b | ||
|
|
dde50c23bc | ||
|
|
630384e07f | ||
|
|
ad9ac2191b | ||
|
|
6425061b3a | ||
|
|
cdf4f69251 | ||
|
|
67e41af875 | ||
|
|
f43a597370 | ||
|
|
0e527d2584 | ||
|
|
69ced4e033 | ||
|
|
efef588c30 | ||
|
|
10388b0515 | ||
|
|
0a753915ff | ||
|
|
7c584e7b6c | ||
|
|
249e497d36 | ||
|
|
29f66dcd95 | ||
|
|
67437a0d5d | ||
|
|
b7061dc7db | ||
|
|
c8d41a4f87 | ||
|
|
052c527e4c | ||
|
|
787e3db3d5 | ||
|
|
5078080bb0 | ||
|
|
0226025308 | ||
|
|
019d66a4c6 | ||
|
|
0578349e29 | ||
|
|
71048a1b55 | ||
|
|
9d0dfd5726 | ||
|
|
92f9a8503e | ||
|
|
665a76561e | ||
|
|
4dc232f259 | ||
|
|
c6d140a40a | ||
|
|
8cb6b832d1 |
@@ -12,6 +12,7 @@ exclude_lines =
|
||||
def __repr__
|
||||
raise AssertionError
|
||||
raise NotImplementedError
|
||||
raise utils\.Unreachable
|
||||
if __name__ == ["']__main__["']:
|
||||
|
||||
[xml]
|
||||
|
||||
30
.flake8
30
.flake8
@@ -1,5 +1,8 @@
|
||||
[flake8]
|
||||
exclude = .*,__pycache__,resources.py
|
||||
# B001: bare except
|
||||
# B008: Do not perform calls in argument defaults. (fine with some Qt stuff)
|
||||
# B305: .next() (false-positives)
|
||||
# E128: continuation line under-indented for visual indent
|
||||
# E226: missing whitespace around arithmetic operator
|
||||
# E265: Block comment should start with '#'
|
||||
@@ -19,32 +22,33 @@ exclude = .*,__pycache__,resources.py
|
||||
# D103: Missing docstring in public function (will be handled by others)
|
||||
# D104: Missing docstring in public package (will be handled by others)
|
||||
# D105: Missing docstring in magic method (will be handled by others)
|
||||
# D106: Missing docstring in public nested class (will be handled by others)
|
||||
# D107: Missing docstring in __init__ (will be handled by others)
|
||||
# D209: Blank line before closing """ (removed from PEP257)
|
||||
# D211: No blank lines allowed before class docstring
|
||||
# (PEP257 got changed, but let's stick to the old standard)
|
||||
# D401: First line should be in imperative mood (okay sometimes)
|
||||
# D402: First line should not be function's signature (false-positives)
|
||||
# D403: First word of the first line should be properly capitalized
|
||||
# (false-positives)
|
||||
# D413: Missing blank line after last section (not in pep257?)
|
||||
# A003: Builtin name for class attribute (needed for attrs)
|
||||
ignore =
|
||||
B001,B008,B305,
|
||||
E128,E226,E265,E501,E402,E266,E722,E731,
|
||||
F401,
|
||||
N802,
|
||||
P101,P102,P103,
|
||||
D102,D103,D104,D105,D209,D211,D402,D403
|
||||
D102,D103,D106,D107,D104,D105,D209,D211,D401,D402,D403,D413,
|
||||
A003
|
||||
min-version = 3.4.0
|
||||
max-complexity = 12
|
||||
putty-auto-ignore = True
|
||||
putty-ignore =
|
||||
/# pylint: disable=invalid-name/ : +N801,N806
|
||||
/# pragma: no mccabe/ : +C901
|
||||
tests/*/test_*.py : +D100,D101,D401
|
||||
tests/conftest.py : +F403
|
||||
tests/unit/browser/test_history.py : +N806
|
||||
tests/helpers/fixtures.py : +N806
|
||||
tests/unit/browser/webkit/http/test_content_disposition.py : +D400
|
||||
scripts/dev/ci/appveyor_install.py : +FI53
|
||||
# FIXME:conf
|
||||
tests/unit/completion/test_models.py : +F821
|
||||
per-file-ignores =
|
||||
tests/*/test_*.py : D100,D101,D401
|
||||
tests/unit/browser/test_history.py : N806
|
||||
tests/helpers/fixtures.py : N806
|
||||
tests/unit/browser/webkit/http/test_content_disposition.py : D400
|
||||
scripts/dev/ci/appveyor_install.py : FI53
|
||||
copyright-check = True
|
||||
copyright-regexp = # Copyright [\d-]+ .*
|
||||
copyright-min-file-size = 110
|
||||
|
||||
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -6,3 +6,5 @@ tests/end2end/features/test_completion_bdd.py @rcorre
|
||||
tests/unit/browser/test_history.py @rcorre
|
||||
tests/unit/completion/* @rcorre
|
||||
tests/unit/misc/test_sql.py @rcorre
|
||||
|
||||
qutebrowser/config/configdata.yml @mschilli87
|
||||
|
||||
46
.github/CODE_OF_CONDUCT.md
vendored
Normal file
46
.github/CODE_OF_CONDUCT.md
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at mail@qutebrowser.org. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
5
.github/CONTRIBUTING.asciidoc
vendored
5
.github/CONTRIBUTING.asciidoc
vendored
@@ -4,6 +4,9 @@
|
||||
- Either run the testsuite locally, or keep an eye on Travis CI / AppVeyor
|
||||
after pushing changes.
|
||||
|
||||
See the full contribution docs for details:
|
||||
- If you are stuck somewhere or have questions,
|
||||
https://github.com/qutebrowser/qutebrowser#getting-help[please ask]!
|
||||
|
||||
See the full contribution documentation for details and other useful hints:
|
||||
|
||||
include::../doc/contributing.asciidoc[]
|
||||
|
||||
17
.gitignore
vendored
17
.gitignore
vendored
@@ -27,15 +27,16 @@ __pycache__
|
||||
/.cache
|
||||
/.testmondata
|
||||
/.hypothesis
|
||||
/.mypy_cache
|
||||
/prof
|
||||
/venv
|
||||
TODO
|
||||
/scripts/testbrowser_cpp/webkit/Makefile
|
||||
/scripts/testbrowser_cpp/webkit/main.o
|
||||
/scripts/testbrowser_cpp/webkit/testbrowser
|
||||
/scripts/testbrowser_cpp/webkit/.qmake.stash
|
||||
/scripts/testbrowser_cpp/webengine/Makefile
|
||||
/scripts/testbrowser_cpp/webengine/main.o
|
||||
/scripts/testbrowser_cpp/webengine/testbrowser
|
||||
/scripts/testbrowser_cpp/webengine/.qmake.stash
|
||||
/scripts/testbrowser/cpp/webkit/Makefile
|
||||
/scripts/testbrowser/cpp/webkit/main.o
|
||||
/scripts/testbrowser/cpp/webkit/testbrowser
|
||||
/scripts/testbrowser/cpp/webkit/.qmake.stash
|
||||
/scripts/testbrowser/cpp/webengine/Makefile
|
||||
/scripts/testbrowser/cpp/webengine/main.o
|
||||
/scripts/testbrowser/cpp/webengine/testbrowser
|
||||
/scripts/testbrowser/cpp/webengine/.qmake.stash
|
||||
/scripts/dev/pylint_checkers/qute_pylint.egg-info
|
||||
|
||||
37
.pylintrc
37
.pylintrc
@@ -13,34 +13,33 @@ persistent=n
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
enable=all
|
||||
disable=no-self-use,
|
||||
fixme,
|
||||
global-statement,
|
||||
locally-disabled,
|
||||
disable=locally-disabled,
|
||||
locally-enabled,
|
||||
too-many-ancestors,
|
||||
too-few-public-methods,
|
||||
too-many-public-methods,
|
||||
suppressed-message,
|
||||
fixme,
|
||||
no-self-use,
|
||||
cyclic-import,
|
||||
bad-continuation,
|
||||
too-many-instance-attributes,
|
||||
blacklisted-name,
|
||||
too-many-lines,
|
||||
logging-format-interpolation,
|
||||
logging-not-lazy,
|
||||
broad-except,
|
||||
bare-except,
|
||||
eval-used,
|
||||
exec-used,
|
||||
ungrouped-imports,
|
||||
suppressed-message,
|
||||
too-many-return-statements,
|
||||
duplicate-code,
|
||||
global-statement,
|
||||
wrong-import-position,
|
||||
duplicate-code,
|
||||
no-else-return,
|
||||
# https://github.com/PyCQA/pylint/issues/1698
|
||||
unsupported-membership-test,
|
||||
unsupported-assignment-operation,
|
||||
unsubscriptable-object
|
||||
too-many-ancestors,
|
||||
too-many-public-methods,
|
||||
too-many-instance-attributes,
|
||||
too-many-lines,
|
||||
too-many-return-statements,
|
||||
too-many-boolean-expressions,
|
||||
too-many-locals,
|
||||
too-many-branches,
|
||||
too-many-statements,
|
||||
too-few-public-methods
|
||||
|
||||
[BASIC]
|
||||
function-rgx=[a-z_][a-z0-9_]{2,50}$
|
||||
@@ -69,10 +68,10 @@ valid-metaclass-classmethod-first-arg=cls
|
||||
|
||||
[TYPECHECK]
|
||||
ignored-modules=PyQt5,PyQt5.QtWebKit
|
||||
ignored-classes=_CountingAttr
|
||||
|
||||
[IMPORTS]
|
||||
# WORKAROUND
|
||||
# For some reason, pylint doesn't know about some Python 3 modules on
|
||||
# AppVeyor...
|
||||
known-standard-library=faulthandler,http,enum,tokenize,posixpath,importlib,types
|
||||
known-third-party=sip
|
||||
|
||||
@@ -23,7 +23,7 @@ matrix:
|
||||
env: TESTENV=py36-pyqt59-cov
|
||||
- os: osx
|
||||
env: TESTENV=py36 OSX=sierra
|
||||
osx_image: xcode8.3
|
||||
osx_image: xcode9.2
|
||||
language: generic
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/2013
|
||||
# - os: osx
|
||||
@@ -51,7 +51,11 @@ matrix:
|
||||
env: TESTENV=eslint
|
||||
language: node_js
|
||||
python: null
|
||||
node_js: node
|
||||
node_js: "lts/*"
|
||||
- os: linux
|
||||
language: generic
|
||||
env: TESTENV=shellcheck
|
||||
services: docker
|
||||
fast_finish: true
|
||||
|
||||
cache:
|
||||
|
||||
@@ -13,6 +13,8 @@ include qutebrowser/utils/testfile
|
||||
include qutebrowser/git-commit-id
|
||||
include LICENSE doc/* README.asciidoc
|
||||
include misc/qutebrowser.desktop
|
||||
include misc/qutebrowser.appdata.xml
|
||||
include misc/Makefile
|
||||
include requirements.txt
|
||||
include tox.ini
|
||||
include qutebrowser.py
|
||||
@@ -21,7 +23,7 @@ include qutebrowser/config/configdata.yml
|
||||
|
||||
prune www
|
||||
prune scripts/dev
|
||||
prune scripts/testbrowser_cpp
|
||||
prune scripts/testbrowser/cpp
|
||||
prune .github
|
||||
exclude scripts/asciidoc2html.py
|
||||
exclude doc/notes
|
||||
|
||||
@@ -15,7 +15,7 @@ image:https://travis-ci.org/qutebrowser/qutebrowser.svg?branch=master["Build Sta
|
||||
image:https://ci.appveyor.com/api/projects/status/5pyauww2k68bbow2/branch/master?svg=true["AppVeyor build status", link="https://ci.appveyor.com/project/qutebrowser/qutebrowser"]
|
||||
image:https://codecov.io/github/qutebrowser/qutebrowser/coverage.svg?branch=master["coverage badge",link="https://codecov.io/github/qutebrowser/qutebrowser?branch=master"]
|
||||
|
||||
link:https://www.qutebrowser.org[website] | link:https://blog.qutebrowser.org[blog] | link:https://github.com/qutebrowser/qutebrowser/releases[releases]
|
||||
link:https://www.qutebrowser.org[website] | link:https://blog.qutebrowser.org[blog] | https://github.com/qutebrowser/qutebrowser/blob/master/doc/faq.asciidoc[FAQ] | https://www.qutebrowser.org/doc/contributing.html[contributing] | link:https://github.com/qutebrowser/qutebrowser/releases[releases] | https://github.com/qutebrowser/qutebrowser/blob/master/doc/install.asciidoc[installing]
|
||||
// QUTE_WEB_HIDE_END
|
||||
|
||||
qutebrowser is a keyboard-focused browser with a minimal GUI. It's based
|
||||
@@ -109,7 +109,7 @@ The following software and libraries are required to run qutebrowser:
|
||||
link:https://github.com/annulen/webkit/wiki[updated fork] (5.212) is
|
||||
supported
|
||||
* http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.7.0 or newer
|
||||
(5.9 recommended) for Python 3
|
||||
(5.9.2 recommended) for Python 3
|
||||
* https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools]
|
||||
* http://fdik.org/pyPEG/[pyPEG2]
|
||||
* http://jinja.pocoo.org/[jinja2]
|
||||
@@ -182,7 +182,9 @@ Active
|
||||
* Firefox addons (based on WebExtensions):
|
||||
https://addons.mozilla.org/en-GB/firefox/addon/vimium-ff/[Vimium-FF] (experimental),
|
||||
https://key.saka.io[Saka Key],
|
||||
https://github.com/cmcaine/tridactyl[Tridactyl] (in early development, working
|
||||
https://github.com/ueokande/vim-vixen[Vim Vixen],
|
||||
https://github.com/shinglyu/QuantumVim[QuantumVim],
|
||||
https://github.com/cmcaine/tridactyl[Tridactyl] (working
|
||||
on a https://bugzilla.mozilla.org/show_bug.cgi?id=1215061[better API] for
|
||||
keyboard integration in Firefox).
|
||||
|
||||
@@ -200,7 +202,6 @@ main inspiration for qutebrowser)
|
||||
http://www.vimperator.org/[Vimperator],
|
||||
http://5digits.org/pentadactyl/[Pentadactyl],
|
||||
https://github.com/akhodakivskiy/VimFx[VimFx],
|
||||
https://github.com/shinglyu/QuantumVim[QuantumVim]
|
||||
* Chrome/Chromium addons:
|
||||
https://chrome.google.com/webstore/detail/vichrome/gghkfhpblkcmlkmpcpgaajbbiikbhpdi?hl=en[ViChrome],
|
||||
https://github.com/jinzhu/vrome[Vrome]
|
||||
|
||||
@@ -118,7 +118,7 @@ TODO: people with t-shirts or higher pledge levels
|
||||
- toml
|
||||
- vimja
|
||||
- wiz
|
||||
- 43 Anonymous
|
||||
- 44 Anonymous
|
||||
|
||||
2016
|
||||
----
|
||||
@@ -212,6 +212,7 @@ Other sponsors
|
||||
- Julie Engel
|
||||
- Jörg Behrmann
|
||||
- Jørgen Skancke
|
||||
- Kevin Kainan Li
|
||||
- Kevin Velghe
|
||||
- Konstantin Shmelkov
|
||||
- Kyle Frazer
|
||||
|
||||
@@ -15,19 +15,230 @@ breaking changes (such as renamed commands) can happen in minor releases.
|
||||
// `Fixed` for any bug fixes.
|
||||
// `Security` to invite users to upgrade in case of vulnerabilities.
|
||||
|
||||
v1.0.2 (unreleased)
|
||||
-------------------
|
||||
v1.1.2
|
||||
------
|
||||
|
||||
Fixes
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- Windows/macOS releases now bundle Qt 5.10.1 which includes security fixes from
|
||||
Chromium up to version 64.0.3282.140.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Fixed workaround for black screens with Nvidia cards
|
||||
- QtWebEngine: Crash with Qt 5.10.1 when using :undo on some tabs.
|
||||
- Compatibility with Python 3.7
|
||||
|
||||
v1.1.1
|
||||
------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- The Makefile now actually works.
|
||||
- Fixed crashes with Qt 5.10 when closing a tab before it finished loading.
|
||||
|
||||
v1.1.0
|
||||
------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- Initial support for Greasemonkey scripts. There are still some rough edges,
|
||||
but many scripts should already work.
|
||||
- There's now a `misc/Makefile` file in releases, which should help
|
||||
distributions which package qutebrowser, as they can run something like
|
||||
`make -f misc/Makefile DESTDIR="$pkgdir" install` now.
|
||||
- New fields for `window.title_format` and `tabs.title.format`:
|
||||
* `{current_url}`
|
||||
* `{protocol}`
|
||||
- New settings:
|
||||
* `colors.statusbar.passthrough.fg`/`.bg`
|
||||
* `completion.delay` and `completion.min_chars` to update the completion less
|
||||
often.
|
||||
* `completion.use_best_match` to automatically use the best-matching
|
||||
command in the completion.
|
||||
* `keyhint.radius` to configure the edge rounding for the key hint widget.
|
||||
* `qt.highdpi` to turn on Qt's High-DPI scaling.
|
||||
* `tabs.pinned.shrink` (`true` by default) to make it possible
|
||||
for pinned tabs and normal tabs to have the same size.
|
||||
* `content.windowed_fullscreen` to show e.g. a fullscreened video in the
|
||||
window without fullscreening that window.
|
||||
* `tabs.persist_mode_on_change` to keep the current mode when
|
||||
switching tabs.
|
||||
* `session.lazy_restore` which allows to not load pages immediately
|
||||
when restoring a session.
|
||||
- New commands:
|
||||
* `:tab-give` and `:tab-take`, to give tabs to another window, or take them
|
||||
from another window.
|
||||
* `:completion-item-yank` (bound to `<Ctrl-C>`) to yank the current
|
||||
completion item text.
|
||||
* `:edit-command` to edit the commandline in an editor.
|
||||
* `search.incremental` for incremental text search.
|
||||
- New flags for existing commands:
|
||||
* `-o` flag for `:spawn` to show stdout/stderr in a new tab.
|
||||
* `--rapid` flag for `:command-accept` (bound to `Ctrl-Enter` by default),
|
||||
which allows executing a command in the completion without closing it.
|
||||
* `--private` and `--related` flags for `:edit-url`, which have the
|
||||
same effect they have with `:open`.
|
||||
* `--history` for `:completion-item-focus` which causes it to go
|
||||
through the command history when no text was entered. The default bindings for
|
||||
cursor keys in the completion changed to use that, so that they can be used
|
||||
again to navigate through completion items when a text was entered.
|
||||
* `--file` for `:debug-pyeval` which makes it take a filename instead of a
|
||||
line of code.
|
||||
- New `config.source(...)` method for `config.py` to source another file.
|
||||
- New `{line}` and `{column}` replacements for `editor.command` to position the
|
||||
cursor correctly.
|
||||
- New `qute-pass` userscript as alternative to `password_fill` which allows
|
||||
selecting accounts via rofi or any other dmenu-compatile application.
|
||||
- New `hist_importer.py` script to import history from Firefox/Chromium.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- Some settings got renamed:
|
||||
* `tabs.width.bar` -> `tabs.width`
|
||||
* `tabs.width.indicator` -> `tabs.indicator.width`
|
||||
* `tabs.indicator_padding` -> `tabs.indicator.padding`
|
||||
* `session_default_name` -> `session.default_name`
|
||||
* `ignore_case` -> `search.ignore_case`
|
||||
- Much improved user stylesheet handling for QtWebEngine which reduces
|
||||
flickering and updates immediately after setting a stylesheet.
|
||||
- High-DPI favicons are now used when available.
|
||||
- The `asciidoc2html.py` script now uses Pygments (which is already a dependency
|
||||
of qutebrowser) instead of `source-highlight` for syntax highlighting.
|
||||
- The `:buffer` command now doesn't require quoting anymore, similar to `:open`.
|
||||
- The `importer.py` script was largely rewritten and now also supports importing
|
||||
from Firefox' `places.sqlite` file and Chrome/Chromium profiles.
|
||||
- Various internal refactorings to use Python 3.5 and ECMAscript 6 features.
|
||||
- If the `window.hide_wayland_decoration` setting is False, but
|
||||
`QT_WAYLAND_DISABLE_WINDOWDECORATION` is set in the environment,
|
||||
the decorations are still hidden.
|
||||
- The `install_dict.py` script for QtWebEngine was renamed to `dictcli.py` and
|
||||
can now also upgrade dictionaries correctly.
|
||||
- `:undo` now can re-open multiple tabs after `:tab-only` was used.
|
||||
- `:config-write-py` with a relative path now puts the file into the config
|
||||
directory.
|
||||
- The `qute://version` page now also shows the uptime of qutebrowser.
|
||||
- qutebrowser now prompts to create a non-existing directory when starting a
|
||||
download.
|
||||
- `:jseval --file` now searches relative paths in a `js/` subdir in
|
||||
qutebrowser's data dir, e.g. `~/.local/share/qutebrowser/js`.
|
||||
- The current/default bindings are now shown in the ``:bind` completion.
|
||||
- Empty categories are now hidden in the `:open` completion.
|
||||
- Search terms for URLs and titles can now be mixed when filtering the
|
||||
completion.
|
||||
- The default font size for the UI got bumped up from 8pt to 10pt.
|
||||
- Improved matching in the completion: The words entered are now matched in any
|
||||
order, and mixed matches on URL/tite are possible.
|
||||
- The system's default encoding (rather than UTF-8) is now used to decode
|
||||
subprocess output.
|
||||
- qutebrowser now ensures it's focused again after an external editor is closed.
|
||||
- The `colors.completion.fg` setting can now be a list, allowing to specify
|
||||
different colors for the three completion columns.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- More consistent sizing for favicons with vertical tabs.
|
||||
- Using `:home` on pinned tabs is now prevented.
|
||||
- Fix crash with unknown file types loaded via `qute://help`.
|
||||
- Scrolling performance improvements.
|
||||
- Sites like `qute://help` now redirect to `qute://help/` to make sure links
|
||||
work properly.
|
||||
- Fixes for the size calculation of pinned tabs in the tab bar.
|
||||
- Worked around a crash with PyQt 5.9.1 compiled against Qt < 5.9.1 when using
|
||||
`:yank` or `qute://` URLs.
|
||||
- Fixed crash when opening `qute://help/img`.
|
||||
- Fixed `gU` (`:navigate up`) on `qute://help` and webservers not handling `..`
|
||||
in a URL.
|
||||
- Using e.g. `-s backend webkit` to set the backend now works correctly.
|
||||
- Fixed crash when closing the tab an external editor was opened in.
|
||||
- When using `:search-next` before a search is finished, no warning about no
|
||||
results being found is shown anymore.
|
||||
- Fix `:click-element` with an ID containing non-alphanumeric characters.
|
||||
- Fix crash when a subprocess outputs data which is not decodable as UTF-8.
|
||||
- Fix crash when closing a tab immediately after hinting.
|
||||
- Worked around issues in Qt 5.10 with loading progress never being finished.
|
||||
- Fixed a crash when writing a flag before a command (e.g. `:-w open `).
|
||||
- Fixed a crash when clicking certain form elements with QtWebEngine.
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- `:tab-detach` has been deprecated, as `:tab-give` without argument can be used
|
||||
instead.
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- The long-deprecated `:prompt-yes`, `:prompt-no`, `:paste-primary` and `:paste`
|
||||
commands have been removed.
|
||||
- The invocation `:download <url> <dest>` which was deprecated in v0.5.0 was
|
||||
removed, use `:download --dest <dest> <url>` instead.
|
||||
- The `messages.unfocused` option which wasn't used anymore was removed.
|
||||
- The `x[xtb]` default bindings got removed again as many users accidentally
|
||||
triggered them.
|
||||
|
||||
v1.0.4
|
||||
------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- The `qute://gpl` page now works correctly again.
|
||||
- Trying to bind an empty command now doesn't crash anymore.
|
||||
- Fixed crash when `:config-write-py` fails to write to the given path.
|
||||
- Fixed crash for some users when selecting a file with Qt 5.9.3
|
||||
- Improved handling for various SQL errors
|
||||
- Fix crash when setting content.cache.size to a big value (> 2 GB)
|
||||
|
||||
v1.0.3
|
||||
------
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- macOS and Windows builds are now built with PyQt 5.9.1 and Qt 5.9.2, including
|
||||
various bugfixes, as well as security fixes from Chromium up to version
|
||||
61.0.3163.79.
|
||||
- Performance improvements for tab rendering.
|
||||
- The :open-editor command is now not hidden anymore as it's also usable in
|
||||
normal mode.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Handle accessing a locked sqlite database gracefully
|
||||
- Abort pinned tab dialogs properly when a tab is closed e.g. by closing a
|
||||
window
|
||||
- Unbinding a default keybinding twice now doesn't bind it again
|
||||
- Completions are now sorted correctly again when filtered
|
||||
|
||||
v1.0.2
|
||||
------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Fix workaround for black screens or crashes with Nvidia cards
|
||||
- Handle a filesystem going read-only gracefully
|
||||
- Fix crash when setting `fonts.monospace`
|
||||
- Fix list options not being modifyable via `.append()` in `config.py`
|
||||
- Mark the content.notifications setting as QtWebKit only correctly
|
||||
- Fix wrong rendering of keys like `<back>` in the completion
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- Nicer error messages and other minor improvements
|
||||
|
||||
v1.0.1
|
||||
------
|
||||
|
||||
Fixes
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Fixed starting after customizing `fonts.tabs` or `fonts.debug_console`.
|
||||
@@ -65,6 +276,9 @@ Major changes
|
||||
the entire browsing history. The default for
|
||||
`completion.web_history_max_items` got changed to `-1` (unlimited). If the
|
||||
completion is too slow on your machine, try setting it to a few 1000 items.
|
||||
- Up/Down now navigates through the command history instead of selecting
|
||||
completion items. Either use Tab to cycle through the completion, or
|
||||
https://github.com/qutebrowser/qutebrowser/blob/master/doc/help/configuring.asciidoc#migrating-older-configurations[restore the old behavior].
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
@@ -904,7 +1118,7 @@ Added
|
||||
- New `:fake-key` command to send a fake keypress to a website or to
|
||||
qutebrowser.
|
||||
- New `--mhtml` argument for `:download` to download a page including all
|
||||
ressources as MHTML file.
|
||||
resources as MHTML file.
|
||||
- New option `tabs -> title-alignment` to change the alignment of tab titles.
|
||||
|
||||
Changed
|
||||
@@ -1104,7 +1318,7 @@ Added
|
||||
- New argument `--no-err-windows` to suppress all error windows.
|
||||
- New arguments `--top-navigate` and `--bottom-navigate` (`-t`/`-b`) for `:scroll-page` to specify a navigation action (e.g. automatically go to the next page when arriving at the bottom).
|
||||
- New flag `-d`/`--detach` for `:spawn` to detach the spawned process so it's not closed when qutebrowser is.
|
||||
- New flag `-v`/`--verbose` for `:spawn` to print informations when the process started/exited successfully.
|
||||
- New flag `-v`/`--verbose` for `:spawn` to print information when the process started/exited successfully.
|
||||
- Many new color settings (foreground setting for every background setting).
|
||||
- New setting `ui -> modal-js-dialog` to use the standard modal dialogs for javascript questions instead of using the statusbar.
|
||||
- New setting `colors -> webpage.bg` to set the background color to use for websites which don't set one.
|
||||
|
||||
@@ -100,16 +100,10 @@ Currently, the following tox environments are available:
|
||||
- `py35`, `py36`: Run pytest for python 3.5/3.6 with the system-wide PyQt.
|
||||
- `py36-pyqt57`, ..., `py36-pyqt59`: Run pytest with the given PyQt version (`py35-*` also works).
|
||||
- `py36-pyqt59-cov`: Run with coverage support (other Python/PyQt versions work too).
|
||||
* `flake8`: Run https://pypi.python.org/pypi/flake8[flake8] checks:
|
||||
https://pypi.python.org/pypi/pyflakes[pyflakes],
|
||||
https://pypi.python.org/pypi/pep8[pep8],
|
||||
https://pypi.python.org/pypi/mccabe[mccabe].
|
||||
* `flake8`: Run various linting checks via https://pypi.python.org/pypi/flake8[flake8].
|
||||
* `vulture`: Run https://pypi.python.org/pypi/vulture[vulture] to find
|
||||
unused code portions.
|
||||
* `pylint`: Run http://pylint.org/[pylint] static code analysis.
|
||||
* `pydocstyle`: Check
|
||||
https://www.python.org/dev/peps/pep-0257/[PEP257] compliance with
|
||||
https://github.com/PyCQA/pydocstyle[pydocstyle].
|
||||
* `pyroma`: Check packaging practices with
|
||||
https://pypi.python.org/pypi/pyroma/[pyroma].
|
||||
* `eslint`: Run http://eslint.org/[ESLint] javascript checker.
|
||||
@@ -470,7 +464,6 @@ The following arguments are supported for `@cmdutils.argument`:
|
||||
- `flag`: Customize the short flag (`-x`) the argument will get.
|
||||
- `win_id=True`: Mark the argument as special window ID argument.
|
||||
- `count=True`: Mark the argument as special count argument.
|
||||
- `hide=True`: Hide the argument from the documentation.
|
||||
- `completion`: A completion function (see `qutebrowser.completions.models.*`)
|
||||
to use when completing arguments for the given command.
|
||||
- `choices`: The allowed string choices for the argument.
|
||||
@@ -681,7 +674,6 @@ qutebrowser release
|
||||
|
||||
* Adjust `__version_info__` in `qutebrowser/__init__.py`.
|
||||
* Update changelog (remove *(unreleased)*).
|
||||
* Run tests again.
|
||||
* Commit.
|
||||
|
||||
* Create annotated git tag (`git tag -s "v1.$x.$y" -m "Release v1.$x.$y"`).
|
||||
@@ -691,9 +683,9 @@ qutebrowser release
|
||||
* Mark the milestone at https://github.com/qutebrowser/qutebrowser/milestones
|
||||
as closed.
|
||||
|
||||
* Linux: Run `python3 scripts/dev/build_release.py --upload v1.$x.$y`.
|
||||
* Windows: Run `C:\Python36-32\python scripts\dev\build_release.py --asciidoc C:\Python27\python C:\asciidoc-8.6.9\asciidoc.py --upload v1.X.Y` (replace X/Y by hand).
|
||||
* macOS: Run `python3 scripts/dev/build_release.py --upload v1.X.Y` (replace X/Y by hand).
|
||||
* Linux: Run `git checkout v1.$x.$y && python3 scripts/dev/build_release.py --upload v1.$x.$y`.
|
||||
* Windows: Run `git checkout v1.X.Y; C:\Python36-32\python scripts\dev\build_release.py --asciidoc C:\Python27\python C:\asciidoc-8.6.9\asciidoc.py --upload v1.X.Y` (replace X/Y by hand).
|
||||
* macOS: Run `git checkout v1.X.Y && python3 scripts/dev/build_release.py --upload v1.X.Y` (replace X/Y by hand).
|
||||
* On server: Run `python3 scripts/dev/download_release.py v1.X.Y` (replace X/Y by hand).
|
||||
* Update `qutebrowser-git` PKGBUILD if dependencies/install changed.
|
||||
* Announce to qutebrowser and qutebrowser-announce mailinglist.
|
||||
|
||||
@@ -28,16 +28,20 @@ What's wrong with link:http://portix.bitbucket.org/dwb/[dwb]/link:http://sourcef
|
||||
https://lists.webkit.org/pipermail/webkit-gtk/2014-March/001821.html[deprecated],
|
||||
these bugs are never going to be fixed.
|
||||
+
|
||||
The newer http://webkitgtk.org/reference/webkit2gtk/stable/index.html[WebKit2
|
||||
API] seems to lack basic features like proxy support, and almost no projects
|
||||
seem to have started porting to WebKit2 (I only know of
|
||||
http://www.uzbl.org/[uzbl]).
|
||||
When qutebrowser was created, the newer
|
||||
http://webkitgtk.org/reference/webkit2gtk/stable/index.html[WebKit2 API] lacked
|
||||
basic features like proxy support, and almost no projects have started porting
|
||||
to WebKit2. In the meantime, this situation has improved a bit, but there are
|
||||
stil only a few project which have some kind of WebKit2 support (see the
|
||||
https://github.com/qutebrowser/qutebrowser#similar-projects[list of
|
||||
alternatives]).
|
||||
+
|
||||
qutebrowser uses http://qt.io/[Qt] and http://wiki.qt.io/QtWebKit[QtWebKit]
|
||||
instead, which suffers from far less such crashes. It might switch to
|
||||
http://wiki.qt.io/QtWebEngine[QtWebEngine] in the future, which is based on
|
||||
Google's https://en.wikipedia.org/wiki/Blink_(layout_engine)[Blink] rendering
|
||||
engine.
|
||||
qutebrowser uses http://qt.io/[Qt] and
|
||||
http://wiki.qt.io/QtWebEngine[QtWebEngine] by default (and supports
|
||||
http://wiki.qt.io/QtWebKit[QtWebKit] optionally). QtWebEngine is based on
|
||||
Google's https://www.chromium.org/Home[Chromium]. With an up-to-date Qt, it has
|
||||
much more man-power behind it than WebKitGTK+ has, and thus supports more modern
|
||||
web features - it's also arguably more secure.
|
||||
|
||||
What's wrong with https://www.mozilla.org/en-US/firefox/new/[Firefox] and link:http://5digits.org/pentadactyl/[Pentadactyl]/link:http://www.vimperator.org/vimperator[Vimperator]?::
|
||||
Firefox likes to break compatibility with addons on each upgrade, gets
|
||||
@@ -146,13 +150,13 @@ For QtWebKit:
|
||||
For QtWebEngine:
|
||||
|
||||
. Make sure your versions of PyQt and Qt are 5.8 or higher.
|
||||
. Use `install_dict.py` script to install dictionaries.
|
||||
. Use `dictcli.py` script to install dictionaries.
|
||||
Run the script with `-h` for the parameter description.
|
||||
. Set `spellcheck.languages` to the desired list of languages, e.g.:
|
||||
`:set spellcheck.languages "['en-US', 'pl-PL']"`
|
||||
|
||||
How do I use Tor with qutebrowser?::
|
||||
Start tor on your machine, and do `:set network proxy socks://localhost:9050/`
|
||||
Start tor on your machine, and do `:set content.proxy socks://localhost:9050/`
|
||||
in qutebrowser. Note this won't give you the same amount of fingerprinting
|
||||
protection that the Tor Browser does, but it's useful to be able to access
|
||||
`.onion` sites.
|
||||
@@ -162,7 +166,7 @@ Why does J move to the next (right) tab, and K to the previous (left) one?::
|
||||
and qutebrowser's keybindings are designed to be compatible with dwb's.
|
||||
The rationale behind it is that J is "down" in vim, and K is "up", which
|
||||
corresponds nicely to "next"/"previous". It also makes much more sense with
|
||||
vertical tabs (e.g. `:set tabs position left`).
|
||||
vertical tabs (e.g. `:set tabs.position left`).
|
||||
|
||||
What's the difference between insert and passthrough mode?::
|
||||
They are quite similar, but insert mode has some bindings (like `Ctrl-e` to
|
||||
@@ -186,10 +190,6 @@ Why takes it longer to open an URL in qutebrowser than in chromium?::
|
||||
|
||||
== Troubleshooting
|
||||
|
||||
Configuration not saved after modifying config.::
|
||||
When editing your config file manually, qutebrowser must be exited completely.
|
||||
This can be done by issuing the command `:quit` or by pressing `Ctrl+q`.
|
||||
|
||||
Unable to view flash content.::
|
||||
If you have flash installed for on your system, it's necessary to enable plugins
|
||||
to use the flash plugin. Using the command `:set content.plugins true`
|
||||
@@ -221,5 +221,5 @@ My issue is not listed.::
|
||||
https://github.com/qutebrowser/qutebrowser/issues[the issue tracker] or
|
||||
using the `:report` command.
|
||||
If you are reporting a segfault, make sure you read the
|
||||
link:doc/stacktrace.asciidoc[guide] on how to report them with all needed
|
||||
link:stacktrace.asciidoc[guide] on how to report them with all needed
|
||||
information.
|
||||
|
||||
@@ -30,6 +30,9 @@ It is possible to run or bind multiple commands by separating them with `;;`.
|
||||
|<<bookmark-del,bookmark-del>>|Delete a bookmark.
|
||||
|<<bookmark-load,bookmark-load>>|Load a bookmark.
|
||||
|<<buffer,buffer>>|Select tab by index or url/title best match.
|
||||
|<<clear-keychain,clear-keychain>>|Clear the currently entered key chain.
|
||||
|<<clear-messages,clear-messages>>|Clear all message notifications.
|
||||
|<<click-element,click-element>>|Click the element matching the given filter.
|
||||
|<<close,close>>|Close the current window.
|
||||
|<<config-clear,config-clear>>|Set all settings back to their default.
|
||||
|<<config-cycle,config-cycle>>|Cycle an option between multiple values.
|
||||
@@ -44,10 +47,14 @@ It is possible to run or bind multiple commands by separating them with `;;`.
|
||||
|<<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.
|
||||
|<<edit-command,edit-command>>|Open an editor to modify the current command.
|
||||
|<<edit-url,edit-url>>|Navigate to a url formed in an external editor.
|
||||
|<<enter-mode,enter-mode>>|Enter a key mode.
|
||||
|<<fake-key,fake-key>>|Send a fake keypress or key string to the website or qutebrowser.
|
||||
|<<follow-selected,follow-selected>>|Follow the selected text.
|
||||
|<<forward,forward>>|Go forward in the history of the current tab.
|
||||
|<<fullscreen,fullscreen>>|Toggle fullscreen mode.
|
||||
|<<greasemonkey-reload,greasemonkey-reload>>|Re-read Greasemonkey scripts from disk.
|
||||
|<<help,help>>|Show help about a command or setting.
|
||||
|<<hint,hint>>|Start hinting.
|
||||
|<<history,history>>|Show browsing history.
|
||||
@@ -56,10 +63,16 @@ It is possible to run or bind multiple commands by separating them with `;;`.
|
||||
|<<insert-text,insert-text>>|Insert text at cursor position.
|
||||
|<<inspector,inspector>>|Toggle the web inspector.
|
||||
|<<jseval,jseval>>|Evaluate a JavaScript string.
|
||||
|<<jump-mark,jump-mark>>|Jump to the mark named by `key`.
|
||||
|<<later,later>>|Execute a command after some time.
|
||||
|<<message-error,message-error>>|Show an error message in the statusbar.
|
||||
|<<message-info,message-info>>|Show an info message in the statusbar.
|
||||
|<<message-warning,message-warning>>|Show a warning message in the statusbar.
|
||||
|<<messages,messages>>|Show a log of past messages.
|
||||
|<<navigate,navigate>>|Open typical prev/next links or navigate using the URL path.
|
||||
|<<nop,nop>>|Do nothing.
|
||||
|<<open,open>>|Open a URL in the current/[count]th tab.
|
||||
|<<open-editor,open-editor>>|Open an external editor with the currently selected form field.
|
||||
|<<print,print>>|Print the current/[count]th tab.
|
||||
|<<quickmark-add,quickmark-add>>|Add a new quickmark.
|
||||
|<<quickmark-del,quickmark-del>>|Delete a quickmark.
|
||||
@@ -69,29 +82,39 @@ It is possible to run or bind multiple commands by separating them with `;;`.
|
||||
|<<record-macro,record-macro>>|Start or stop recording a macro.
|
||||
|<<reload,reload>>|Reload the current/[count]th tab.
|
||||
|<<repeat,repeat>>|Repeat a given command.
|
||||
|<<repeat-command,repeat-command>>|Repeat the last executed command.
|
||||
|<<report,report>>|Report a bug in qutebrowser.
|
||||
|<<restart,restart>>|Restart qutebrowser while keeping existing tabs open.
|
||||
|<<run-macro,run-macro>>|Run a recorded macro.
|
||||
|<<run-with-count,run-with-count>>|Run a command with the given count.
|
||||
|<<save,save>>|Save configs and state.
|
||||
|<<scroll,scroll>>|Scroll the current tab in the given direction.
|
||||
|<<scroll-page,scroll-page>>|Scroll the frame page-wise.
|
||||
|<<scroll-px,scroll-px>>|Scroll the current tab by 'count * dx/dy' pixels.
|
||||
|<<scroll-to-perc,scroll-to-perc>>|Scroll to a specific percentage of the page.
|
||||
|<<search,search>>|Search for a text on the current page. With no text, clear results.
|
||||
|<<search-next,search-next>>|Continue the search to the ([count]th) next term.
|
||||
|<<search-prev,search-prev>>|Continue the search to the ([count]th) previous term.
|
||||
|<<session-delete,session-delete>>|Delete a session.
|
||||
|<<session-load,session-load>>|Load a session.
|
||||
|<<session-save,session-save>>|Save a session.
|
||||
|<<set,set>>|Set an option.
|
||||
|<<set-cmd-text,set-cmd-text>>|Preset the statusbar to some text.
|
||||
|<<set-mark,set-mark>>|Set a mark at the current scroll position in the current tab.
|
||||
|<<spawn,spawn>>|Spawn a command in a shell.
|
||||
|<<stop,stop>>|Stop loading in the current/[count]th tab.
|
||||
|<<tab-clone,tab-clone>>|Duplicate the current tab.
|
||||
|<<tab-close,tab-close>>|Close the current/[count]th tab.
|
||||
|<<tab-detach,tab-detach>>|Detach the current tab to its own window.
|
||||
|<<tab-focus,tab-focus>>|Select the tab given as argument/[count].
|
||||
|<<tab-give,tab-give>>|Give the current tab to a new or existing window if win_id given.
|
||||
|<<tab-move,tab-move>>|Move the current tab according to the argument and [count].
|
||||
|<<tab-next,tab-next>>|Switch to the next tab, or switch [count] tabs forward.
|
||||
|<<tab-only,tab-only>>|Close all tabs except for the current one.
|
||||
|<<tab-pin,tab-pin>>|Pin/Unpin the current/[count]th tab.
|
||||
|<<tab-prev,tab-prev>>|Switch to the previous tab, or switch [count] tabs back.
|
||||
|<<tab-take,tab-take>>|Take a tab from another window.
|
||||
|<<unbind,unbind>>|Unbind a keychain.
|
||||
|<<undo,undo>>|Re-open a closed tab.
|
||||
|<<undo,undo>>|Re-open the last closed tab or tabs.
|
||||
|<<version,version>>|Show version information.
|
||||
|<<view-source,view-source>>|Show the source of the current page in a new tab.
|
||||
|<<window-only,window-only>>|Close all windows except for the current one.
|
||||
@@ -204,6 +227,34 @@ Focuses window if necessary when index is given. If both index and count are giv
|
||||
==== count
|
||||
The tab index to focus, starting with 1.
|
||||
|
||||
==== note
|
||||
* This command does not split arguments after the last argument and handles quotes literally.
|
||||
|
||||
[[clear-keychain]]
|
||||
=== clear-keychain
|
||||
Clear the currently entered key chain.
|
||||
|
||||
[[clear-messages]]
|
||||
=== clear-messages
|
||||
Clear all message notifications.
|
||||
|
||||
[[click-element]]
|
||||
=== click-element
|
||||
Syntax: +:click-element [*--target* 'target'] [*--force-event*] 'filter' 'value'+
|
||||
|
||||
Click the element matching the given filter.
|
||||
|
||||
The given filter needs to result in exactly one element, otherwise, an error is shown.
|
||||
|
||||
==== positional arguments
|
||||
* +'filter'+: How to filter the elements. id: Get an element based on its ID.
|
||||
|
||||
* +'value'+: The value to filter for.
|
||||
|
||||
==== optional arguments
|
||||
* +*-t*+, +*--target*+: How to open the clicked element (normal/tab/tab-bg/window).
|
||||
* +*-f*+, +*--force-event*+: Force generating a fake click event.
|
||||
|
||||
[[close]]
|
||||
=== close
|
||||
Close the current window.
|
||||
@@ -283,12 +334,10 @@ Write the current configuration to a config.py file.
|
||||
|
||||
[[download]]
|
||||
=== download
|
||||
Syntax: +:download [*--mhtml*] [*--dest* 'dest'] ['url'] ['dest-old']+
|
||||
Syntax: +:download [*--mhtml*] [*--dest* 'dest'] ['url']+
|
||||
|
||||
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.
|
||||
|
||||
@@ -358,9 +407,18 @@ Retry the first failed/[count]th download.
|
||||
==== count
|
||||
The index of the download to retry.
|
||||
|
||||
[[edit-command]]
|
||||
=== edit-command
|
||||
Syntax: +:edit-command [*--run*]+
|
||||
|
||||
Open an editor to modify the current command.
|
||||
|
||||
==== optional arguments
|
||||
* +*-r*+, +*--run*+: Run the command if the editor exits successfully.
|
||||
|
||||
[[edit-url]]
|
||||
=== edit-url
|
||||
Syntax: +:edit-url [*--bg*] [*--tab*] [*--window*] ['url']+
|
||||
Syntax: +:edit-url [*--bg*] [*--tab*] [*--window*] [*--private*] [*--related*] ['url']+
|
||||
|
||||
Navigate to a url formed in an external editor.
|
||||
|
||||
@@ -373,6 +431,18 @@ The editor which should be launched can be configured via the `editor.command` c
|
||||
* +*-b*+, +*--bg*+: Open in a new background tab.
|
||||
* +*-t*+, +*--tab*+: Open in a new tab.
|
||||
* +*-w*+, +*--window*+: Open in a new window.
|
||||
* +*-p*+, +*--private*+: Open a new window in private browsing mode.
|
||||
* +*-r*+, +*--related*+: If opening a new tab, position the tab as related to the current one (like clicking on a link).
|
||||
|
||||
|
||||
[[enter-mode]]
|
||||
=== enter-mode
|
||||
Syntax: +:enter-mode 'mode'+
|
||||
|
||||
Enter a key mode.
|
||||
|
||||
==== positional arguments
|
||||
* +'mode'+: The mode to enter.
|
||||
|
||||
[[fake-key]]
|
||||
=== fake-key
|
||||
@@ -388,6 +458,15 @@ Send a fake keypress or key string to the website or qutebrowser.
|
||||
==== optional arguments
|
||||
* +*-g*+, +*--global*+: If given, the keys are sent to the qutebrowser UI.
|
||||
|
||||
[[follow-selected]]
|
||||
=== follow-selected
|
||||
Syntax: +:follow-selected [*--tab*]+
|
||||
|
||||
Follow the selected text.
|
||||
|
||||
==== optional arguments
|
||||
* +*-t*+, +*--tab*+: Load the selected link in a new tab.
|
||||
|
||||
[[forward]]
|
||||
=== forward
|
||||
Syntax: +:forward [*--tab*] [*--bg*] [*--window*]+
|
||||
@@ -411,6 +490,12 @@ Toggle fullscreen mode.
|
||||
==== optional arguments
|
||||
* +*-l*+, +*--leave*+: Only leave fullscreen if it was entered by the page.
|
||||
|
||||
[[greasemonkey-reload]]
|
||||
=== greasemonkey-reload
|
||||
Re-read Greasemonkey scripts from disk.
|
||||
|
||||
The scripts are read from a 'greasemonkey' subdirectory in qutebrowser's data directory (see `:version`).
|
||||
|
||||
[[help]]
|
||||
=== help
|
||||
Syntax: +:help [*--tab*] [*--bg*] [*--window*] ['topic']+
|
||||
@@ -431,7 +516,7 @@ Show help about a command or setting.
|
||||
|
||||
[[hint]]
|
||||
=== hint
|
||||
Syntax: +:hint [*--rapid*] [*--mode* 'mode'] [*--add-history*]
|
||||
Syntax: +:hint [*--mode* 'mode'] [*--add-history*] [*--rapid*]
|
||||
['group'] ['target'] ['args' ['args' ...]]+
|
||||
|
||||
Start hinting.
|
||||
@@ -485,9 +570,6 @@ Start hinting.
|
||||
|
||||
|
||||
==== optional arguments
|
||||
* +*-r*+, +*--rapid*+: Whether to do rapid hinting. This is only possible with targets `tab` (with `tabs.background_tabs=true`), `tab-bg`,
|
||||
`window`, `run`, `hover`, `userscript` and `spawn`.
|
||||
|
||||
* +*-m*+, +*--mode*+: The hinting mode to use.
|
||||
|
||||
- `number`: Use numeric hints.
|
||||
@@ -499,6 +581,11 @@ Start hinting.
|
||||
|
||||
* +*-a*+, +*--add-history*+: Whether to add the spawned or yanked link to the browsing history.
|
||||
|
||||
* +*-r*+, +*--rapid*+: Whether to do rapid hinting. With rapid hinting, the hint mode isn't left after a hint is followed, so you can easily
|
||||
open multiple links. This is only possible with targets
|
||||
`tab` (with `tabs.background_tabs=true`), `tab-bg`,
|
||||
`window`, `run`, `hover`, `userscript` and `spawn`.
|
||||
|
||||
|
||||
==== note
|
||||
* This command does not split arguments after the last argument and handles quotes literally.
|
||||
@@ -557,7 +644,10 @@ Evaluate a JavaScript string.
|
||||
* +'js-code'+: The string/file to evaluate.
|
||||
|
||||
==== optional arguments
|
||||
* +*-f*+, +*--file*+: Interpret js-code as a path to a file.
|
||||
* +*-f*+, +*--file*+: Interpret js-code as a path to a file. If the path is relative, the file is searched in a js/ subdir
|
||||
in qutebrowser's data dir, e.g.
|
||||
`~/.local/share/qutebrowser/js`.
|
||||
|
||||
* +*-q*+, +*--quiet*+: Don't show resulting JS object.
|
||||
* +*-w*+, +*--world*+: Ignored on QtWebKit. On QtWebEngine, a world ID or name to run the snippet in.
|
||||
|
||||
@@ -566,6 +656,15 @@ Evaluate a JavaScript string.
|
||||
* This command does not split arguments after the last argument and handles quotes literally.
|
||||
* With this command, +;;+ is interpreted literally instead of splitting off a second command.
|
||||
|
||||
[[jump-mark]]
|
||||
=== jump-mark
|
||||
Syntax: +:jump-mark 'key'+
|
||||
|
||||
Jump to the mark named by `key`.
|
||||
|
||||
==== positional arguments
|
||||
* +'key'+: mark identifier; capital indicates a global mark
|
||||
|
||||
[[later]]
|
||||
=== later
|
||||
Syntax: +:later 'ms' 'command'+
|
||||
@@ -581,6 +680,36 @@ Execute a command after some time.
|
||||
* With this command, +;;+ is interpreted literally instead of splitting off a second command.
|
||||
* This command does not replace variables like +\{url\}+.
|
||||
|
||||
[[message-error]]
|
||||
=== message-error
|
||||
Syntax: +:message-error 'text'+
|
||||
|
||||
Show an error message in the statusbar.
|
||||
|
||||
==== positional arguments
|
||||
* +'text'+: The text to show.
|
||||
|
||||
[[message-info]]
|
||||
=== message-info
|
||||
Syntax: +:message-info 'text'+
|
||||
|
||||
Show an info message in the statusbar.
|
||||
|
||||
==== positional arguments
|
||||
* +'text'+: The text to show.
|
||||
|
||||
==== count
|
||||
How many times to show the message
|
||||
|
||||
[[message-warning]]
|
||||
=== message-warning
|
||||
Syntax: +:message-warning 'text'+
|
||||
|
||||
Show a warning message in the statusbar.
|
||||
|
||||
==== positional arguments
|
||||
* +'text'+: The text to show.
|
||||
|
||||
[[messages]]
|
||||
=== messages
|
||||
Syntax: +:messages [*--plain*] [*--tab*] [*--bg*] [*--window*] ['level']+
|
||||
@@ -626,6 +755,10 @@ This tries to automatically click on typical _Previous Page_ or _Next Page_ link
|
||||
For `increment` and `decrement`, the number to change the URL by. For `up`, the number of levels to go up in the URL.
|
||||
|
||||
|
||||
[[nop]]
|
||||
=== nop
|
||||
Do nothing.
|
||||
|
||||
[[open]]
|
||||
=== open
|
||||
Syntax: +:open [*--related*] [*--bg*] [*--tab*] [*--window*] [*--secure*] [*--private*]
|
||||
@@ -653,6 +786,12 @@ The tab index to open the URL in.
|
||||
==== note
|
||||
* This command does not split arguments after the last argument and handles quotes literally.
|
||||
|
||||
[[open-editor]]
|
||||
=== open-editor
|
||||
Open an external editor with the currently selected form field.
|
||||
|
||||
The editor which should be launched can be configured via the `editor.command` config option.
|
||||
|
||||
[[print]]
|
||||
=== print
|
||||
Syntax: +:print [*--preview*] [*--pdf* 'file']+
|
||||
@@ -762,6 +901,13 @@ Repeat a given command.
|
||||
* With this command, +;;+ is interpreted literally instead of splitting off a second command.
|
||||
* This command does not replace variables like +\{url\}+.
|
||||
|
||||
[[repeat-command]]
|
||||
=== repeat-command
|
||||
Repeat the last executed command.
|
||||
|
||||
==== count
|
||||
Which count to pass the command.
|
||||
|
||||
[[report]]
|
||||
=== report
|
||||
Report a bug in qutebrowser.
|
||||
@@ -782,6 +928,26 @@ Run a recorded macro.
|
||||
==== count
|
||||
How many times to run the macro.
|
||||
|
||||
[[run-with-count]]
|
||||
=== run-with-count
|
||||
Syntax: +:run-with-count 'count-arg' 'command'+
|
||||
|
||||
Run a command with the given count.
|
||||
|
||||
If run_with_count itself is run with a count, it multiplies count_arg.
|
||||
|
||||
==== positional arguments
|
||||
* +'count-arg'+: The count to pass to the command.
|
||||
* +'command'+: The command to run, with optional args.
|
||||
|
||||
==== count
|
||||
The count that run_with_count itself received.
|
||||
|
||||
==== note
|
||||
* This command does not split arguments after the last argument and handles quotes literally.
|
||||
* With this command, +;;+ is interpreted literally instead of splitting off a second command.
|
||||
* This command does not replace variables like +\{url\}+.
|
||||
|
||||
[[save]]
|
||||
=== save
|
||||
Syntax: +:save ['what' ['what' ...]]+
|
||||
@@ -792,6 +958,70 @@ Save configs and state.
|
||||
* +'what'+: What to save (`config`/`key-config`/`cookies`/...). If not given, everything is saved.
|
||||
|
||||
|
||||
[[scroll]]
|
||||
=== scroll
|
||||
Syntax: +:scroll 'direction'+
|
||||
|
||||
Scroll the current tab in the given direction.
|
||||
|
||||
Note you can use `:run-with-count` to have a keybinding with a bigger scroll increment.
|
||||
|
||||
==== positional arguments
|
||||
* +'direction'+: In which direction to scroll (up/down/left/right/top/bottom).
|
||||
|
||||
|
||||
==== count
|
||||
multiplier
|
||||
|
||||
[[scroll-page]]
|
||||
=== scroll-page
|
||||
Syntax: +:scroll-page [*--top-navigate* 'ACTION'] [*--bottom-navigate* 'ACTION'] 'x' 'y'+
|
||||
|
||||
Scroll the frame page-wise.
|
||||
|
||||
==== positional arguments
|
||||
* +'x'+: How many pages to scroll to the right.
|
||||
* +'y'+: How many pages to scroll down.
|
||||
|
||||
==== optional arguments
|
||||
* +*-t*+, +*--top-navigate*+: :navigate action (prev, decrement) to run when scrolling up at the top of the page.
|
||||
|
||||
* +*-b*+, +*--bottom-navigate*+: :navigate action (next, increment) to run when scrolling down at the bottom of the page.
|
||||
|
||||
|
||||
==== count
|
||||
multiplier
|
||||
|
||||
[[scroll-px]]
|
||||
=== scroll-px
|
||||
Syntax: +:scroll-px 'dx' 'dy'+
|
||||
|
||||
Scroll the current tab by 'count * dx/dy' pixels.
|
||||
|
||||
==== positional arguments
|
||||
* +'dx'+: How much to scroll in x-direction.
|
||||
* +'dy'+: How much to scroll in y-direction.
|
||||
|
||||
==== count
|
||||
multiplier
|
||||
|
||||
[[scroll-to-perc]]
|
||||
=== scroll-to-perc
|
||||
Syntax: +:scroll-to-perc [*--horizontal*] ['perc']+
|
||||
|
||||
Scroll to a specific percentage of the page.
|
||||
|
||||
The percentage can be given either as argument or as count. If no percentage is given, the page is scrolled to the end.
|
||||
|
||||
==== positional arguments
|
||||
* +'perc'+: Percentage to scroll.
|
||||
|
||||
==== optional arguments
|
||||
* +*-x*+, +*--horizontal*+: Scroll horizontally instead of vertically.
|
||||
|
||||
==== count
|
||||
Percentage to scroll.
|
||||
|
||||
[[search]]
|
||||
=== search
|
||||
Syntax: +:search [*--reverse*] ['text']+
|
||||
@@ -807,6 +1037,20 @@ Search for a text on the current page. With no text, clear results.
|
||||
==== note
|
||||
* This command does not split arguments after the last argument and handles quotes literally.
|
||||
|
||||
[[search-next]]
|
||||
=== search-next
|
||||
Continue the search to the ([count]th) next term.
|
||||
|
||||
==== count
|
||||
How many elements to ignore.
|
||||
|
||||
[[search-prev]]
|
||||
=== search-prev
|
||||
Continue the search to the ([count]th) previous term.
|
||||
|
||||
==== count
|
||||
How many elements to ignore.
|
||||
|
||||
[[session-delete]]
|
||||
=== session-delete
|
||||
Syntax: +:session-delete [*--force*] 'name'+
|
||||
@@ -844,7 +1088,7 @@ Syntax: +:session-save [*--current*] [*--quiet*] [*--force*] [*--only-active-win
|
||||
Save a session.
|
||||
|
||||
==== positional arguments
|
||||
* +'name'+: The name of the session. If not given, the session configured in session_default_name is saved.
|
||||
* +'name'+: The name of the session. If not given, the session configured in session.default_name is saved.
|
||||
|
||||
|
||||
==== optional arguments
|
||||
@@ -891,9 +1135,18 @@ The count if given.
|
||||
==== note
|
||||
* This command does not split arguments after the last argument and handles quotes literally.
|
||||
|
||||
[[set-mark]]
|
||||
=== set-mark
|
||||
Syntax: +:set-mark 'key'+
|
||||
|
||||
Set a mark at the current scroll position in the current tab.
|
||||
|
||||
==== positional arguments
|
||||
* +'key'+: mark identifier; capital indicates a global mark
|
||||
|
||||
[[spawn]]
|
||||
=== spawn
|
||||
Syntax: +:spawn [*--userscript*] [*--verbose*] [*--detach*] 'cmdline'+
|
||||
Syntax: +:spawn [*--userscript*] [*--verbose*] [*--output*] [*--detach*] 'cmdline'+
|
||||
|
||||
Spawn a command in a shell.
|
||||
|
||||
@@ -908,6 +1161,7 @@ Spawn a command in a shell.
|
||||
- `/usr/share/qutebrowser/userscripts`
|
||||
|
||||
* +*-v*+, +*--verbose*+: Show notifications when the command started/exited.
|
||||
* +*-o*+, +*--output*+: Whether the output should be shown in a new tab.
|
||||
* +*-d*+, +*--detach*+: Whether the command should be detached from qutebrowser.
|
||||
|
||||
==== note
|
||||
@@ -946,10 +1200,6 @@ Close the current/[count]th tab.
|
||||
==== count
|
||||
The tab index to close
|
||||
|
||||
[[tab-detach]]
|
||||
=== tab-detach
|
||||
Detach the current tab to its own window.
|
||||
|
||||
[[tab-focus]]
|
||||
=== tab-focus
|
||||
Syntax: +:tab-focus ['index']+
|
||||
@@ -967,6 +1217,17 @@ If neither count nor index are given, it behaves like tab-next. If both are give
|
||||
==== count
|
||||
The tab index to focus, starting with 1.
|
||||
|
||||
[[tab-give]]
|
||||
=== tab-give
|
||||
Syntax: +:tab-give ['win-id']+
|
||||
|
||||
Give the current tab to a new or existing window if win_id given.
|
||||
|
||||
If no win_id is given, the tab will get detached into a new window.
|
||||
|
||||
==== positional arguments
|
||||
* +'win-id'+: The window ID of the window to give the current tab to.
|
||||
|
||||
[[tab-move]]
|
||||
=== tab-move
|
||||
Syntax: +:tab-move ['index']+
|
||||
@@ -1019,6 +1280,16 @@ Switch to the previous tab, or switch [count] tabs back.
|
||||
==== count
|
||||
How many tabs to switch back.
|
||||
|
||||
[[tab-take]]
|
||||
=== tab-take
|
||||
Syntax: +:tab-take 'index'+
|
||||
|
||||
Take a tab from another window.
|
||||
|
||||
==== positional arguments
|
||||
* +'index'+: The [win_id/]index of the tab to take. Or a substring in which case the closest match will be taken.
|
||||
|
||||
|
||||
[[unbind]]
|
||||
=== unbind
|
||||
Syntax: +:unbind [*--mode* 'mode'] 'key'+
|
||||
@@ -1034,7 +1305,7 @@ Unbind a keychain.
|
||||
|
||||
[[undo]]
|
||||
=== undo
|
||||
Re-open a closed tab.
|
||||
Re-open the last closed tab or tabs.
|
||||
|
||||
[[version]]
|
||||
=== version
|
||||
@@ -1099,28 +1370,20 @@ Decrease the zoom level for the current tab.
|
||||
How many steps to zoom out.
|
||||
|
||||
|
||||
== Hidden commands
|
||||
== Commands not usable in normal mode
|
||||
.Quick reference
|
||||
[options="header",width="75%",cols="25%,75%"]
|
||||
|==============
|
||||
|Command|Description
|
||||
|<<clear-keychain,clear-keychain>>|Clear the currently entered key chain.
|
||||
|<<clear-messages,clear-messages>>|Clear all message notifications.
|
||||
|<<click-element,click-element>>|Click the element matching the given filter.
|
||||
|<<command-accept,command-accept>>|Execute the command currently in the commandline.
|
||||
|<<command-history-next,command-history-next>>|Go forward in the commandline history.
|
||||
|<<command-history-prev,command-history-prev>>|Go back in the commandline history.
|
||||
|<<completion-item-del,completion-item-del>>|Delete the current completion item.
|
||||
|<<completion-item-focus,completion-item-focus>>|Shift the focus of the completion menu to another item.
|
||||
|<<completion-item-yank,completion-item-yank>>|Yank the current completion item into the clipboard.
|
||||
|<<drop-selection,drop-selection>>|Drop selection and keep selection mode enabled.
|
||||
|<<enter-mode,enter-mode>>|Enter a key mode.
|
||||
|<<follow-hint,follow-hint>>|Follow a hint.
|
||||
|<<follow-selected,follow-selected>>|Follow the selected text.
|
||||
|<<jump-mark,jump-mark>>|Jump to the mark named by `key`.
|
||||
|<<leave-mode,leave-mode>>|Leave the mode we're currently in.
|
||||
|<<message-error,message-error>>|Show an error message in the statusbar.
|
||||
|<<message-info,message-info>>|Show an info message in the statusbar.
|
||||
|<<message-warning,message-warning>>|Show a warning message in the statusbar.
|
||||
|<<move-to-end-of-document,move-to-end-of-document>>|Move the cursor or selection to the end of the document.
|
||||
|<<move-to-end-of-line,move-to-end-of-line>>|Move the cursor or selection to the end of line.
|
||||
|<<move-to-end-of-next-block,move-to-end-of-next-block>>|Move the cursor or selection to the end of next block.
|
||||
@@ -1136,12 +1399,9 @@ How many steps to zoom out.
|
||||
|<<move-to-start-of-line,move-to-start-of-line>>|Move the cursor or selection to the start of the line.
|
||||
|<<move-to-start-of-next-block,move-to-start-of-next-block>>|Move the cursor or selection to the start of next block.
|
||||
|<<move-to-start-of-prev-block,move-to-start-of-prev-block>>|Move the cursor or selection to the start of previous block.
|
||||
|<<nop,nop>>|Do nothing.
|
||||
|<<open-editor,open-editor>>|Open an external editor with the currently selected form field.
|
||||
|<<prompt-accept,prompt-accept>>|Accept the current prompt.
|
||||
|<<prompt-item-focus,prompt-item-focus>>|Shift the focus of the prompt file completion menu to another item.
|
||||
|<<prompt-open-download,prompt-open-download>>|Immediately open a download.
|
||||
|<<repeat-command,repeat-command>>|Repeat the last executed command.
|
||||
|<<rl-backward-char,rl-backward-char>>|Move back a character.
|
||||
|<<rl-backward-delete-char,rl-backward-delete-char>>|Delete the character before the cursor.
|
||||
|<<rl-backward-kill-word,rl-backward-kill-word>>|Remove chars from the cursor to the beginning of the word.
|
||||
@@ -1157,45 +1417,17 @@ How many steps to zoom out.
|
||||
|<<rl-unix-line-discard,rl-unix-line-discard>>|Remove chars backward from the cursor to the beginning of the line.
|
||||
|<<rl-unix-word-rubout,rl-unix-word-rubout>>|Remove chars from the cursor to the beginning of the word.
|
||||
|<<rl-yank,rl-yank>>|Paste the most recently deleted text.
|
||||
|<<run-with-count,run-with-count>>|Run a command with the given count.
|
||||
|<<scroll,scroll>>|Scroll the current tab in the given direction.
|
||||
|<<scroll-page,scroll-page>>|Scroll the frame page-wise.
|
||||
|<<scroll-px,scroll-px>>|Scroll the current tab by 'count * dx/dy' pixels.
|
||||
|<<scroll-to-perc,scroll-to-perc>>|Scroll to a specific percentage of the page.
|
||||
|<<search-next,search-next>>|Continue the search to the ([count]th) next term.
|
||||
|<<search-prev,search-prev>>|Continue the search to the ([count]th) previous term.
|
||||
|<<set-mark,set-mark>>|Set a mark at the current scroll position in the current tab.
|
||||
|<<toggle-selection,toggle-selection>>|Toggle caret selection mode.
|
||||
|==============
|
||||
[[clear-keychain]]
|
||||
=== clear-keychain
|
||||
Clear the currently entered key chain.
|
||||
|
||||
[[clear-messages]]
|
||||
=== clear-messages
|
||||
Clear all message notifications.
|
||||
|
||||
[[click-element]]
|
||||
=== click-element
|
||||
Syntax: +:click-element [*--target* 'target'] [*--force-event*] 'filter' 'value'+
|
||||
|
||||
Click the element matching the given filter.
|
||||
|
||||
The given filter needs to result in exactly one element, otherwise, an error is shown.
|
||||
|
||||
==== positional arguments
|
||||
* +'filter'+: How to filter the elements. id: Get an element based on its ID.
|
||||
|
||||
* +'value'+: The value to filter for.
|
||||
|
||||
==== optional arguments
|
||||
* +*-t*+, +*--target*+: How to open the clicked element (normal/tab/tab-bg/window).
|
||||
* +*-f*+, +*--force-event*+: Force generating a fake click event.
|
||||
|
||||
[[command-accept]]
|
||||
=== command-accept
|
||||
Syntax: +:command-accept [*--rapid*]+
|
||||
|
||||
Execute the command currently in the commandline.
|
||||
|
||||
==== optional arguments
|
||||
* +*-r*+, +*--rapid*+: Run the command without closing or clearing the command bar.
|
||||
|
||||
[[command-history-next]]
|
||||
=== command-history-next
|
||||
Go forward in the commandline history.
|
||||
@@ -1210,26 +1442,29 @@ Delete the current completion item.
|
||||
|
||||
[[completion-item-focus]]
|
||||
=== completion-item-focus
|
||||
Syntax: +:completion-item-focus 'which'+
|
||||
Syntax: +:completion-item-focus [*--history*] 'which'+
|
||||
|
||||
Shift the focus of the completion menu to another item.
|
||||
|
||||
==== positional arguments
|
||||
* +'which'+: 'next', 'prev', 'next-category', or 'prev-category'.
|
||||
|
||||
==== optional arguments
|
||||
* +*-H*+, +*--history*+: Navigate through command history if no text was typed.
|
||||
|
||||
[[completion-item-yank]]
|
||||
=== completion-item-yank
|
||||
Syntax: +:completion-item-yank [*--sel*]+
|
||||
|
||||
Yank the current completion item into the clipboard.
|
||||
|
||||
==== optional arguments
|
||||
* +*-s*+, +*--sel*+: Use the primary selection instead of the clipboard.
|
||||
|
||||
[[drop-selection]]
|
||||
=== drop-selection
|
||||
Drop selection and keep selection mode enabled.
|
||||
|
||||
[[enter-mode]]
|
||||
=== enter-mode
|
||||
Syntax: +:enter-mode 'mode'+
|
||||
|
||||
Enter a key mode.
|
||||
|
||||
==== positional arguments
|
||||
* +'mode'+: The mode to enter.
|
||||
|
||||
[[follow-hint]]
|
||||
=== follow-hint
|
||||
Syntax: +:follow-hint ['keystring']+
|
||||
@@ -1239,58 +1474,10 @@ Follow a hint.
|
||||
==== positional arguments
|
||||
* +'keystring'+: The hint to follow.
|
||||
|
||||
[[follow-selected]]
|
||||
=== follow-selected
|
||||
Syntax: +:follow-selected [*--tab*]+
|
||||
|
||||
Follow the selected text.
|
||||
|
||||
==== optional arguments
|
||||
* +*-t*+, +*--tab*+: Load the selected link in a new tab.
|
||||
|
||||
[[jump-mark]]
|
||||
=== jump-mark
|
||||
Syntax: +:jump-mark 'key'+
|
||||
|
||||
Jump to the mark named by `key`.
|
||||
|
||||
==== positional arguments
|
||||
* +'key'+: mark identifier; capital indicates a global mark
|
||||
|
||||
[[leave-mode]]
|
||||
=== leave-mode
|
||||
Leave the mode we're currently in.
|
||||
|
||||
[[message-error]]
|
||||
=== message-error
|
||||
Syntax: +:message-error 'text'+
|
||||
|
||||
Show an error message in the statusbar.
|
||||
|
||||
==== positional arguments
|
||||
* +'text'+: The text to show.
|
||||
|
||||
[[message-info]]
|
||||
=== message-info
|
||||
Syntax: +:message-info 'text'+
|
||||
|
||||
Show an info message in the statusbar.
|
||||
|
||||
==== positional arguments
|
||||
* +'text'+: The text to show.
|
||||
|
||||
==== count
|
||||
How many times to show the message
|
||||
|
||||
[[message-warning]]
|
||||
=== message-warning
|
||||
Syntax: +:message-warning 'text'+
|
||||
|
||||
Show a warning message in the statusbar.
|
||||
|
||||
==== positional arguments
|
||||
* +'text'+: The text to show.
|
||||
|
||||
[[move-to-end-of-document]]
|
||||
=== move-to-end-of-document
|
||||
Move the cursor or selection to the end of the document.
|
||||
@@ -1384,16 +1571,6 @@ Move the cursor or selection to the start of previous block.
|
||||
==== count
|
||||
How many blocks to move.
|
||||
|
||||
[[nop]]
|
||||
=== nop
|
||||
Do nothing.
|
||||
|
||||
[[open-editor]]
|
||||
=== open-editor
|
||||
Open an external editor with the currently selected form field.
|
||||
|
||||
The editor which should be launched can be configured via the `editor.command` config option.
|
||||
|
||||
[[prompt-accept]]
|
||||
=== prompt-accept
|
||||
Syntax: +:prompt-accept ['value']+
|
||||
@@ -1430,13 +1607,6 @@ If no specific command is given, this will use the system's default application
|
||||
==== note
|
||||
* This command does not split arguments after the last argument and handles quotes literally.
|
||||
|
||||
[[repeat-command]]
|
||||
=== repeat-command
|
||||
Repeat the last executed command.
|
||||
|
||||
==== count
|
||||
Which count to pass the command.
|
||||
|
||||
[[rl-backward-char]]
|
||||
=== rl-backward-char
|
||||
Move back a character.
|
||||
@@ -1527,113 +1697,6 @@ Paste the most recently deleted text.
|
||||
|
||||
This acts like readline's yank.
|
||||
|
||||
[[run-with-count]]
|
||||
=== run-with-count
|
||||
Syntax: +:run-with-count 'count-arg' 'command'+
|
||||
|
||||
Run a command with the given count.
|
||||
|
||||
If run_with_count itself is run with a count, it multiplies count_arg.
|
||||
|
||||
==== positional arguments
|
||||
* +'count-arg'+: The count to pass to the command.
|
||||
* +'command'+: The command to run, with optional args.
|
||||
|
||||
==== count
|
||||
The count that run_with_count itself received.
|
||||
|
||||
==== note
|
||||
* This command does not split arguments after the last argument and handles quotes literally.
|
||||
* With this command, +;;+ is interpreted literally instead of splitting off a second command.
|
||||
* This command does not replace variables like +\{url\}+.
|
||||
|
||||
[[scroll]]
|
||||
=== scroll
|
||||
Syntax: +:scroll 'direction'+
|
||||
|
||||
Scroll the current tab in the given direction.
|
||||
|
||||
Note you can use `:run-with-count` to have a keybinding with a bigger scroll increment.
|
||||
|
||||
==== positional arguments
|
||||
* +'direction'+: In which direction to scroll (up/down/left/right/top/bottom).
|
||||
|
||||
|
||||
==== count
|
||||
multiplier
|
||||
|
||||
[[scroll-page]]
|
||||
=== scroll-page
|
||||
Syntax: +:scroll-page [*--top-navigate* 'ACTION'] [*--bottom-navigate* 'ACTION'] 'x' 'y'+
|
||||
|
||||
Scroll the frame page-wise.
|
||||
|
||||
==== positional arguments
|
||||
* +'x'+: How many pages to scroll to the right.
|
||||
* +'y'+: How many pages to scroll down.
|
||||
|
||||
==== optional arguments
|
||||
* +*-t*+, +*--top-navigate*+: :navigate action (prev, decrement) to run when scrolling up at the top of the page.
|
||||
|
||||
* +*-b*+, +*--bottom-navigate*+: :navigate action (next, increment) to run when scrolling down at the bottom of the page.
|
||||
|
||||
|
||||
==== count
|
||||
multiplier
|
||||
|
||||
[[scroll-px]]
|
||||
=== scroll-px
|
||||
Syntax: +:scroll-px 'dx' 'dy'+
|
||||
|
||||
Scroll the current tab by 'count * dx/dy' pixels.
|
||||
|
||||
==== positional arguments
|
||||
* +'dx'+: How much to scroll in x-direction.
|
||||
* +'dy'+: How much to scroll in y-direction.
|
||||
|
||||
==== count
|
||||
multiplier
|
||||
|
||||
[[scroll-to-perc]]
|
||||
=== scroll-to-perc
|
||||
Syntax: +:scroll-to-perc [*--horizontal*] ['perc']+
|
||||
|
||||
Scroll to a specific percentage of the page.
|
||||
|
||||
The percentage can be given either as argument or as count. If no percentage is given, the page is scrolled to the end.
|
||||
|
||||
==== positional arguments
|
||||
* +'perc'+: Percentage to scroll.
|
||||
|
||||
==== optional arguments
|
||||
* +*-x*+, +*--horizontal*+: Scroll horizontally instead of vertically.
|
||||
|
||||
==== count
|
||||
Percentage to scroll.
|
||||
|
||||
[[search-next]]
|
||||
=== search-next
|
||||
Continue the search to the ([count]th) next term.
|
||||
|
||||
==== count
|
||||
How many elements to ignore.
|
||||
|
||||
[[search-prev]]
|
||||
=== search-prev
|
||||
Continue the search to the ([count]th) previous term.
|
||||
|
||||
==== count
|
||||
How many elements to ignore.
|
||||
|
||||
[[set-mark]]
|
||||
=== set-mark
|
||||
Syntax: +:set-mark 'key'+
|
||||
|
||||
Set a mark at the current scroll position in the current tab.
|
||||
|
||||
==== positional arguments
|
||||
* +'key'+: mark identifier; capital indicates a global mark
|
||||
|
||||
[[toggle-selection]]
|
||||
=== toggle-selection
|
||||
Toggle caret selection mode.
|
||||
@@ -1737,7 +1800,7 @@ Change the log level for console logging.
|
||||
|
||||
[[debug-pyeval]]
|
||||
=== debug-pyeval
|
||||
Syntax: +:debug-pyeval [*--quiet*] 's'+
|
||||
Syntax: +:debug-pyeval [*--file*] [*--quiet*] 's'+
|
||||
|
||||
Evaluate a python string and display the results as a web page.
|
||||
|
||||
@@ -1745,6 +1808,7 @@ Evaluate a python string and display the results as a web page.
|
||||
* +'s'+: The string to evaluate.
|
||||
|
||||
==== optional arguments
|
||||
* +*-f*+, +*--file*+: Interpret s as a path to file, also implies --quiet.
|
||||
* +*-q*+, +*--quiet*+: Don't show the output in a new tab.
|
||||
|
||||
==== note
|
||||
|
||||
@@ -11,19 +11,29 @@ Migrating older configurations
|
||||
------------------------------
|
||||
|
||||
qutebrowser does no automatic migration for the new configuration. However,
|
||||
there's a special link:qute://configdiff/old[configdiff] page in qutebrowser,
|
||||
which will show you the changes you did in your old configuration, compared to
|
||||
the old defaults.
|
||||
there's a special link:qute://configdiff/old[configdiff] page
|
||||
(`qute://configdiff/old`) in qutebrowser, which will show you the changes you
|
||||
did in your old configuration, compared to the old defaults.
|
||||
|
||||
Other changes in default settings:
|
||||
|
||||
- `<Up>` and `<Down>` in the completion now navigate through command history
|
||||
instead of selecting completion items. You can get back the old behavior by
|
||||
doing:
|
||||
- In v1.1.x and newer, `<Up>` and `<Down>` navigate through command history
|
||||
if no text was entered yet.
|
||||
With v1.0.x, they always navigate through command history instead of selecting
|
||||
completion items. Use `<Tab>`/`<Shift-Tab>` to cycle through the completion
|
||||
instead.
|
||||
You can get back the old behavior by doing:
|
||||
+
|
||||
----
|
||||
:bind -f -m command <Up> completion-item-focus prev
|
||||
:bind -f -m command <Down> completion-item-focus next
|
||||
:bind -m command <Up> completion-item-focus prev
|
||||
:bind -m command <Down> completion-item-focus next
|
||||
----
|
||||
+
|
||||
or always navigate through command history with
|
||||
+
|
||||
----
|
||||
:bind -m command <Up> command-history-prev
|
||||
:bind -m command <Down> command-history-next
|
||||
----
|
||||
|
||||
- The default for `completion.web_history_max_items` is now set to `-1`, showing
|
||||
@@ -237,6 +247,9 @@ that via `import utils` as well.
|
||||
While it's in some cases possible to import code from the qutebrowser
|
||||
installation, doing so is unsupported and discouraged.
|
||||
|
||||
To read config data from a different file with `c` and `config` available, you
|
||||
can use `config.source('otherfile.py')` in your `config.py`.
|
||||
|
||||
Getting the config directory
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -251,7 +264,7 @@ get a string:
|
||||
.config.py:
|
||||
[source,python]
|
||||
----
|
||||
print(str(config.configdir / 'config.py')
|
||||
print(str(config.configdir / 'config.py'))
|
||||
----
|
||||
|
||||
Handling errors
|
||||
@@ -346,15 +359,38 @@ def bind_chained(key, *commands):
|
||||
bind_chained('<Escape>', 'clear-keychain', 'search')
|
||||
----
|
||||
|
||||
Avoiding flake8 errors
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
Reading colors from Xresources
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you use an editor with flake8 integration which complains about `c` and `config` being undefined, you can use:
|
||||
You can use something like this to read colors from an `~/.Xresources` file:
|
||||
|
||||
[source,python]
|
||||
----
|
||||
c = c # noqa: F821
|
||||
config = config # noqa: F821
|
||||
def read_xresources(prefix):
|
||||
props = {}
|
||||
x = subprocess.run(['xrdb', '-query'], stdout=subprocess.PIPE)
|
||||
lines = x.stdout.decode().split('\n')
|
||||
for line in filter(lambda l : l.startswith(prefix), lines):
|
||||
prop, _, value = line.partition(':\t')
|
||||
props[prop] = value
|
||||
return props
|
||||
|
||||
xresources = read_xresources('*')
|
||||
c.colors.statusbar.normal.bg = xresources['*background']
|
||||
----
|
||||
|
||||
Avoiding flake8 errors
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you use an editor with flake8 and pylint integration, it may have some
|
||||
complaints about invalid names, undefined variables, or missing docstrings.
|
||||
You can silence those with:
|
||||
|
||||
[source,python]
|
||||
----
|
||||
# pylint: disable=C0111
|
||||
c = c # noqa: F821 pylint: disable=E0602,C0103
|
||||
config = config # noqa: F821 pylint: disable=E0602,C0103
|
||||
----
|
||||
|
||||
For type annotation support (note that those imports aren't guaranteed to be
|
||||
@@ -362,8 +398,9 @@ stable across qutebrowser versions):
|
||||
|
||||
[source,python]
|
||||
----
|
||||
# pylint: disable=C0111
|
||||
from qutebrowser.config.configfiles import ConfigAPI # noqa: F401
|
||||
from qutebrowser.config.config import ConfigContainer # noqa: F401
|
||||
config = config # type: ConfigAPI # noqa: F821
|
||||
c = c # type: ConfigContainer # noqa: F821
|
||||
config = config # type: ConfigAPI # noqa: F821 pylint: disable=E0602,C0103
|
||||
c = c # type: ConfigContainer # noqa: F821 pylint: disable=E0602,C0103
|
||||
----
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 989 KiB After Width: | Height: | Size: 1024 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 44 KiB |
@@ -21,7 +21,7 @@ Those distributions only have Python 3.4 and a too old Qt version available,
|
||||
while qutebrowser requires Python 3.5 and Qt 5.7.1 or newer.
|
||||
|
||||
It should be possible to install Python 3.5 e.g. from the
|
||||
https://launchpad.net/~deadsnakes/+archive/ubuntu/ppa[deadsnakes PPA] or via_ipca
|
||||
https://launchpad.net/~deadsnakes/+archive/ubuntu/ppa[deadsnakes PPA] or via
|
||||
https://github.com/pyenv/pyenv[pyenv], but nobody tried that yet.
|
||||
|
||||
If you get qutebrowser running on those distributions, please
|
||||
@@ -35,30 +35,37 @@ Ubuntu 16.04 doesn't come with an up-to-date engine (a new enough QtWebKit, or
|
||||
QtWebEngine). However, it comes with Python 3.5, so you can
|
||||
<<tox,install qutebrowser via tox>>.
|
||||
|
||||
Debian Stretch / Ubuntu 17.04 and newer
|
||||
Debian Stretch / Ubuntu 17.04 and 17.10
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Those versions come with QtWebEngine in the repositories. This makes it possible
|
||||
to install qutebrowser via the Debian package.
|
||||
|
||||
Install the dependencies via apt-get:
|
||||
|
||||
----
|
||||
# apt install python-tox python3-{lxml,pyqt5,sip,jinja2,pygments,yaml,attr} python3-pyqt5.qt{webengine,quick,opengl,sql} libqt5sql5-sqlite
|
||||
----
|
||||
|
||||
Get the qutebrowser package from the
|
||||
https://github.com/qutebrowser/qutebrowser/releases[release page] and download
|
||||
the https://qutebrowser.org/python3-pypeg2_2.15.2-1_all.deb[PyPEG2 package].
|
||||
|
||||
(If you are using debian testing you can just use the python3-pypeg2 package from the repos)
|
||||
|
||||
Install the packages:
|
||||
|
||||
----
|
||||
# dpkg -i python3-pypeg2_*_all.deb
|
||||
# dpkg -i qutebrowser_*_all.deb
|
||||
# apt install ./python3-pypeg2_*_all.deb
|
||||
# apt install ./qutebrowser_*_all.deb
|
||||
----
|
||||
|
||||
Some additional hints:
|
||||
Debian Testing / Ubuntu 18.04
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
On Debian Testing, qutebrowser is in the official repositories, and you can
|
||||
install it with apt:
|
||||
|
||||
----
|
||||
# apt install qutebrowser
|
||||
----
|
||||
|
||||
Additional hints
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
- Alternatively, you can <<tox,install qutebrowser via tox>> to get a newer
|
||||
QtWebEngine version.
|
||||
@@ -66,31 +73,44 @@ Some additional hints:
|
||||
`:help` command:
|
||||
+
|
||||
----
|
||||
# apt-get install --no-install-recommends asciidoc source-highlight
|
||||
# apt install --no-install-recommends asciidoc source-highlight
|
||||
$ python3 scripts/asciidoc2html.py
|
||||
----
|
||||
|
||||
- If you prefer using QtWebKit, there's an up-to-date version available in
|
||||
Debian experimental, or from http://repo.paretje.be/unstable/[this repository]
|
||||
for Debian Stretch.
|
||||
https://packages.debian.org/buster/libqt5webkit5[Debian Testing].
|
||||
- If video or sound don't work with QtWebKit, try installing the gstreamer plugins:
|
||||
+
|
||||
----
|
||||
# apt-get install gstreamer1.0-plugins-{bad,base,good,ugly}
|
||||
# apt install gstreamer1.0-plugins-{bad,base,good,ugly}
|
||||
----
|
||||
|
||||
On Fedora
|
||||
---------
|
||||
|
||||
qutebrowser is available in the official repositories for Fedora 22 and newer.
|
||||
NOTE: Fedora's packages used to be outdated for a long time, but are
|
||||
now (November 2017) maintained and up-to-date again.
|
||||
|
||||
----
|
||||
qutebrowser is available in the official repositories:
|
||||
|
||||
-----
|
||||
# dnf install qutebrowser
|
||||
----
|
||||
-----
|
||||
|
||||
It's also recommended to install `python3-qt5-webengine` and start with `--backend
|
||||
webengine` to use the new backend. v1.0.0 (which is not in the Fedora repos
|
||||
currently) uses QtWebEngine by default.
|
||||
However, note that Fedora 25/26 won't be updated to qutebrowser v1.0, so you
|
||||
might want to <<tox,install qutebrowser via tox>> instead there.
|
||||
|
||||
Additional hints
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Fedora only ships free software in the repositories.
|
||||
To be able to play videos with proprietary codecs with QtWebEngine, you will
|
||||
need to install an additional package from the RPM Fusion Free repository.
|
||||
For more information see https://rpmfusion.org/Configuration.
|
||||
|
||||
-----
|
||||
# dnf install qt5-qtwebengine-freeworld
|
||||
-----
|
||||
|
||||
On Archlinux
|
||||
------------
|
||||
@@ -125,14 +145,16 @@ If video or sound don't work with QtWebKit, try installing the gstreamer plugins
|
||||
On Gentoo
|
||||
---------
|
||||
|
||||
The Gentoo packages (even the live version) are lagging behind a lot and are
|
||||
effectively unmaintained. If you want to create and maintain an official
|
||||
qutebrowser overlay for Gentoo, please mailto:mail@qutebrowser.org[get in
|
||||
touch.]
|
||||
NOTE: Gentoo's packages used to be severely outdated for a long time, but are
|
||||
now (October 2017) maintained and up-to-date again.
|
||||
|
||||
It's recommended to <<tox,install qutebrowser via tox>> instead.
|
||||
qutebrowser is available in the main repository and can be installed with:
|
||||
|
||||
To get an up-to-date QtWebKit, you can use
|
||||
----
|
||||
# emerge -av qutebrowser
|
||||
----
|
||||
|
||||
To use QtWebKit instead of QtWebEngine, you'll need a newer QtWebKit using
|
||||
https://gist.github.com/annulen/309569fb61e5d64a703c055c1e726f71[this ebuild].
|
||||
|
||||
If video or sound don't work with QtWebKit, try installing the gstreamer
|
||||
@@ -189,6 +211,10 @@ To use the QtWebEngine backend, install `libqt5-qtwebengine`.
|
||||
On OpenBSD
|
||||
----------
|
||||
|
||||
WARNING: OpenBSD only packages a legacy unmaintained version of QtWebKit (for
|
||||
which support was dropped in qutebrowser v1.0). It's advised to not use
|
||||
qutebrowser from OpenBSD ports for untrusted websites.
|
||||
|
||||
qutebrowser is in http://cvsweb.openbsd.org/cgi-bin/cvsweb/ports/www/qutebrowser/[OpenBSD ports].
|
||||
|
||||
Install the package:
|
||||
@@ -204,6 +230,21 @@ Or alternatively, use the ports system :
|
||||
# make install
|
||||
----
|
||||
|
||||
On FreeBSD
|
||||
----------
|
||||
|
||||
qutebrowser is in https://www.freshports.org/www/qutebrowser/[FreeBSD ports].
|
||||
|
||||
It can be installed with:
|
||||
|
||||
----
|
||||
# cd /usr/ports/www/qutebrowser
|
||||
# make install clean
|
||||
----
|
||||
|
||||
At present, precompiled packages are not available for this port,
|
||||
and QtWebEngine backend is also not available.
|
||||
|
||||
On Windows
|
||||
----------
|
||||
|
||||
@@ -221,6 +262,10 @@ https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[qutebrows
|
||||
mailinglist] to get notified on new releases). You can install a newer version
|
||||
without uninstalling the older one.
|
||||
|
||||
The binary release ships with a QtWebEngine built without proprietary codec
|
||||
support. To get support for e.g. h264/h265 videos, you'll need to build
|
||||
QtWebEngine from source yourself with support for that enabled.
|
||||
|
||||
https://chocolatey.org/packages/qutebrowser[Chocolatey package]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -261,6 +306,10 @@ Note that you'll need to upgrade to new versions manually (subscribe to the
|
||||
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[qutebrowser-announce
|
||||
mailinglist] to get notified on new releases).
|
||||
|
||||
The binary release ships with a QtWebEngine built without proprietary codec
|
||||
support. To get support for e.g. h264/h265 videos, you'll need to build
|
||||
QtWebEngine from source yourself with support for that enabled.
|
||||
|
||||
This binary is also available through the
|
||||
https://caskroom.github.io/[Homebrew Cask] package manager:
|
||||
|
||||
@@ -352,20 +401,35 @@ local Qt install instead of installing PyQt in the virtualenv. However, unless
|
||||
you have a new QtWebKit or QtWebEngine available, qutebrowser will not work. It
|
||||
also typically means you'll be using an older release of QtWebEngine.
|
||||
|
||||
On Windows, run `tox -e 'mkvenv-win' instead, however make sure that ONLY
|
||||
Python3 is in your PATH before running tox.
|
||||
On Windows, run `set PYTHON=C:\path\to\python.exe` (CMD) or ``$Env:PYTHON =
|
||||
"..."` (Powershell) first.
|
||||
|
||||
Creating a wrapper script
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can then create a simple wrapper script to start qutebrowser somewhere in
|
||||
your `$PATH` (e.g. `/usr/local/bin/qutebrowser` or `~/bin/qutebrowser`):
|
||||
Running `tox` does not install a system-wide `qutebrowser` script. You can
|
||||
launch qutebrowser by doing `.venv/bin/python3 -m qutebrowser`.
|
||||
|
||||
You can create a simple wrapper script to start qutebrowser somewhere in your
|
||||
`$PATH` (e.g. `/usr/local/bin/qutebrowser` or `~/bin/qutebrowser`):
|
||||
|
||||
----
|
||||
#!/bin/bash
|
||||
~/path/to/qutebrowser/.venv/bin/python3 -m qutebrowser "$@"
|
||||
----
|
||||
|
||||
Building the docs
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
To build the documentation, install `asciidoc` (note that LaTeX which comes as
|
||||
optional/recommended dependency with some distributions is not required).
|
||||
|
||||
Then, run:
|
||||
|
||||
----
|
||||
$ python3 scripts/asciidoc2html.py
|
||||
----
|
||||
|
||||
Updating
|
||||
~~~~~~~~
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@ Basic keybindings to get you started
|
||||
What to do now
|
||||
--------------
|
||||
|
||||
* View the link:http://qutebrowser.org/img/cheatsheet-big.png[key binding cheatsheet]
|
||||
* View the link:https://qutebrowser.org/img/cheatsheet-big.png[key binding cheatsheet]
|
||||
to make yourself familiar with the key bindings: +
|
||||
image:http://qutebrowser.org/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="http://qutebrowser.org/img/cheatsheet-big.png"]
|
||||
image:https://qutebrowser.org/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="https://qutebrowser.org/img/cheatsheet-big.png"]
|
||||
* There's also a https://www.shortcutfoo.com/app/dojos/qutebrowser[free training
|
||||
course] on shortcutfoo for the keybindings - note that you need to be in
|
||||
insert mode (i) for it to work.
|
||||
|
||||
@@ -57,7 +57,7 @@ Then edit your `/etc/pacman.conf` to add the repository to the bottom:
|
||||
|
||||
----
|
||||
[qt-debug]
|
||||
Server = http://qutebrowser.org/qt-debug/$arch
|
||||
Server = https://qutebrowser.org/qt-debug/$arch
|
||||
----
|
||||
|
||||
Then install the packages:
|
||||
|
||||
@@ -31,7 +31,7 @@ The following environment variables will be set when a userscript is launched:
|
||||
|
||||
- `QUTE_MODE`: Either `hints` (started via hints) or `command` (started via
|
||||
command or key binding).
|
||||
- `QUTE_USER_AGENT`: The currently set user agent.
|
||||
- `QUTE_USER_AGENT`: The currently set user agent, if customized.
|
||||
- `QUTE_FIFO`: The FIFO or file to write commands to.
|
||||
- `QUTE_HTML`: Path of a file containing the HTML source of the current page.
|
||||
- `QUTE_TEXT`: Path of a file containing the plaintext of the current page.
|
||||
|
||||
@@ -1,207 +1,302 @@
|
||||
/* XPM */
|
||||
static char *qutebrowser[] = {
|
||||
/* columns rows colors chars-per-pixel */
|
||||
"32 32 169 2 ",
|
||||
" c #0A396E",
|
||||
". c #0B3C72",
|
||||
"X c #0B4077",
|
||||
"o c #0C437B",
|
||||
"O c #134175",
|
||||
"+ c #15467C",
|
||||
"@ c #18477B",
|
||||
"# c #1A497D",
|
||||
"$ c #0D4B86",
|
||||
"% c #0F4E8D",
|
||||
"& c #124A80",
|
||||
"* c #1F4F83",
|
||||
"= c #0E518C",
|
||||
"- c #1F5084",
|
||||
"; c #11508C",
|
||||
": c #0F5193",
|
||||
"> c #115799",
|
||||
", c #115B9C",
|
||||
"< c #204F83",
|
||||
"1 c #245287",
|
||||
"2 c #2A598C",
|
||||
"3 c #325E8F",
|
||||
"4 c #11609F",
|
||||
"5 c #346496",
|
||||
"6 c #3B6898",
|
||||
"7 c #115CA1",
|
||||
"8 c #115EAC",
|
||||
"9 c #1263A3",
|
||||
"0 c #1260AD",
|
||||
"q c #136BAC",
|
||||
"w c #136BB2",
|
||||
"e c #1366BA",
|
||||
"r c #196BB2",
|
||||
"t c #157ABB",
|
||||
"y c #1577BB",
|
||||
"u c #2E6DB0",
|
||||
"i c #387FB1",
|
||||
"p c #456E9A",
|
||||
"a c #4873A1",
|
||||
"s c #4375AA",
|
||||
"d c #507AA6",
|
||||
"f c #597EA4",
|
||||
"g c #4D7EB3",
|
||||
"h c #156FCB",
|
||||
"j c #167AC5",
|
||||
"k c #1675CA",
|
||||
"l c #177BCE",
|
||||
"z c #1777D8",
|
||||
"x c #1476E4",
|
||||
"c c #167BE6",
|
||||
"v c #167DE8",
|
||||
"b c #197EEF",
|
||||
"n c #1A7FF0",
|
||||
"m c #1A80BE",
|
||||
"M c #5F87AF",
|
||||
"N c #5D8BBA",
|
||||
"B c #5A84B1",
|
||||
"V c #6C8FB3",
|
||||
"C c #6F96BE",
|
||||
"Z c #1886CC",
|
||||
"A c #1883D7",
|
||||
"S c #198DD5",
|
||||
"D c #1987D9",
|
||||
"F c #198ADC",
|
||||
"G c #1A96DC",
|
||||
"H c #3090D9",
|
||||
"J c #1682E9",
|
||||
"K c #1983ED",
|
||||
"L c #1689E9",
|
||||
"P c #1A8DEE",
|
||||
"I c #1B95ED",
|
||||
"U c #1C9EEA",
|
||||
"Y c #1B97E4",
|
||||
"T c #1A84F2",
|
||||
"R c #1A8BF2",
|
||||
"E c #1C94F4",
|
||||
"W c #1D9CF5",
|
||||
"Q c #3388E6",
|
||||
"! c #3D90E9",
|
||||
"~ c #228EF3",
|
||||
"^ c #229FF6",
|
||||
"/ c #3294F4",
|
||||
"( c #3D9FF6",
|
||||
") c #339CF4",
|
||||
"_ c #1CA2E5",
|
||||
"` c #1DABEE",
|
||||
"' c #1DA4F6",
|
||||
"] c #1EA9F7",
|
||||
"[ c #1EADF8",
|
||||
"{ c #1FB4F9",
|
||||
"} c #1FB9FA",
|
||||
"| c #20ACF8",
|
||||
" . c #27A4F6",
|
||||
".. c #3DA9F6",
|
||||
"X. c #20B9FA",
|
||||
"o. c #2EB6F9",
|
||||
"O. c #458DC9",
|
||||
"+. c #5C8DC1",
|
||||
"@. c #5795C6",
|
||||
"#. c #709DCB",
|
||||
"$. c #74A8DD",
|
||||
"%. c #4A97EA",
|
||||
"&. c #4896EA",
|
||||
"*. c #559EEA",
|
||||
"=. c #439AF5",
|
||||
"-. c #46A3F6",
|
||||
";. c #5FA9F6",
|
||||
":. c #5EA6F3",
|
||||
">. c #47BCF9",
|
||||
",. c #51B5F8",
|
||||
"<. c #58BDF8",
|
||||
"1. c #68ABEF",
|
||||
"2. c #7DB9E7",
|
||||
"3. c #63AEF7",
|
||||
"4. c #6FB1F7",
|
||||
"5. c #66B9F8",
|
||||
"6. c #61B2F6",
|
||||
"7. c #71B4F7",
|
||||
"8. c #78B7F4",
|
||||
"9. c #72BFF9",
|
||||
"0. c #3BC0FA",
|
||||
"q. c #6FCEFB",
|
||||
"w. c #6CC5FA",
|
||||
"e. c #7BCAF9",
|
||||
"r. c #89A7C3",
|
||||
"t. c #83A2C1",
|
||||
"y. c #98B6D3",
|
||||
"u. c #9DB9D3",
|
||||
"i. c #89B6E4",
|
||||
"p. c #83B6E9",
|
||||
"a. c #81BDF7",
|
||||
"s. c #83BFF8",
|
||||
"d. c #9EC4E9",
|
||||
"f. c #8CC2F9",
|
||||
"g. c #85CDFB",
|
||||
"h. c #87C4F9",
|
||||
"j. c #92C6F9",
|
||||
"k. c #95CAFA",
|
||||
"l. c #9CCBFA",
|
||||
"z. c #89D7FC",
|
||||
"x. c #91D9FC",
|
||||
"c. c #9CDEFD",
|
||||
"v. c #9ED2FB",
|
||||
"b. c #A7CAEC",
|
||||
"n. c #B5CEE3",
|
||||
"m. c #A1CEFA",
|
||||
"M. c #AED0F0",
|
||||
"N. c #ACD6FA",
|
||||
"B. c #A0DFFC",
|
||||
"V. c #AFD8FC",
|
||||
"C. c #B5D9FB",
|
||||
"Z. c #BCDDFC",
|
||||
"A. c #BFDCF5",
|
||||
"S. c #ACE3FD",
|
||||
"D. c #B5E5FE",
|
||||
"F. c #BBE2FC",
|
||||
"G. c #CFE5F5",
|
||||
"H. c #C3E1FC",
|
||||
"J. c #CAE6FD",
|
||||
"K. c #CCEBFD",
|
||||
"L. c #C4EBFE",
|
||||
"P. c #D6EDFE",
|
||||
"I. c #DAEEFD",
|
||||
"U. c #DEF1FE",
|
||||
"Y. c #D6F2FE",
|
||||
"T. c #E4F4FE",
|
||||
"R. c #E9F6FE",
|
||||
"E. c #EBF8FF",
|
||||
"W. c None",
|
||||
/* pixels */
|
||||
"W.W.W.W.W.W.W.W.W.W.W.c.S.L.Y.E.E.S.X.} W.W.W.W.W.W.W.W.W.W.W.W.",
|
||||
"W.W.W.W.W.W.W.W.W.D.T.E.E.T.L.D.c.z.} } X.} } W.W.W.W.W.W.W.W.W.",
|
||||
"W.W.W.W.W.W.W.B.T.T.R.T.R.U.0.X.z.S.} } } } { { X.W.W.W.W.W.W.W.",
|
||||
"W.W.W.W.W.W.x.x.K.T.T.T.L.P.q.o.{ } } ` _ { { { { { W.W.W.W.W.W.",
|
||||
"W.W.W.W.W.c.P.D.G.u.r.i 9 Z _ { { G 4 X t { { { { { { W.W.W.W.W.",
|
||||
"W.W.W.W.K.U.n.f O { = t { { { { [ { { W.W.W.W.",
|
||||
"W.W.W.F.I.t.. ' t { { [ [ [ [ [ >.W.W.W.",
|
||||
"W.W.x.P.V ' X t ` [ [ [ [ [ [ o.e.W.W.",
|
||||
"W.W.J.y. X t S Y Z $ ' . y [ [ [ ] [ [ | Z.J.W.W.",
|
||||
"W.<.e.& , _ ] ] [ ] U . ' . y [ ' [ ] ] ] w.K.J.g.W.",
|
||||
"W.' S o ' ' [ ' [ ' ] o ' . y Y 9 = = 9 @.J.J.J.F.W.",
|
||||
"W.| , j ' ' ' ' ' ' ' o ' . $ p A.J.J.g.",
|
||||
"' .. G ' ' ' ' ' ' ' o ' . M H.H.h.",
|
||||
",.2. . W ' W ' ' ' ' W . ' . M.A.x.",
|
||||
"N.M.. . W W W ' W W W W .w 9 I U 0 #.Z.m.",
|
||||
" .9.O D W W W W ' W j $ % F W W W .5 d Z.C.",
|
||||
"W W ; 9 9.h.5...Q % o j W W W W W W O. 3 C.N.",
|
||||
"E W 7 B b.d.a . w E E W W W E W E A @ C.l.",
|
||||
"I E l u W E W E W E E E E A . - k.6.",
|
||||
"P E E 7 m.o E E E E E E E E l . = E P ",
|
||||
"L E E E > . O s.o E E E E E E E E 7 , E L ",
|
||||
"W.R E R ) #.5 1 6 N i.2 s.+ E E E E E E R L . k R W.",
|
||||
"W.L R E -.m.m.m.m.m.m.2 m.@ N m.m.s.( R R % X E J W.",
|
||||
"W.W.K R ~ a.m.l.l.l.l.2 s.+ < i.l.m.j.h % e K W.W.",
|
||||
"W.W.J R R / l.l.l.l.k.2 s.+ * 5 + 8 R J W.W.",
|
||||
"W.W.W.v T R 3.k.k.j.k.2 2 j.& . 8 R v W.W.W.",
|
||||
"W.W.W.W.J T ~ 7.j.j.j.g +.p.j.s.+. . . : z T v W.W.W.W.",
|
||||
"W.W.W.W.W.c T T =.f.j.j.s.j.j.j.j.$.g s u e h b T T v W.W.W.W.W.",
|
||||
"W.W.W.W.W.W.c b n 4.f.f.s.m.s.s.s.j.s.j./ T n T b c W.W.W.W.W.W.",
|
||||
"W.W.W.W.W.W.W.c x 1.s.s.s.s.s.s.s.s.4.=.n T n c c W.W.W.W.W.W.W.",
|
||||
"W.W.W.W.W.W.W.W.W.&.*.1.a.s.s.s.s.3.n n v x x W.W.W.W.W.W.W.W.W.",
|
||||
"W.W.W.W.W.W.W.W.W.W.W.%.%.%.%.*.*.Q x x x W.W.W.W.W.W.W.W.W.W.W."
|
||||
};
|
||||
static char * qutebrowser_xpm[] = {
|
||||
"32 32 267 2",
|
||||
" c None",
|
||||
". c #9FD4FD",
|
||||
"+ c #99CBFE",
|
||||
"@ c #90C3FE",
|
||||
"# c #89BFFE",
|
||||
"$ c #81BCFF",
|
||||
"% c #80BBFF",
|
||||
"& c #9BCAFD",
|
||||
"* c #A9DBFB",
|
||||
"= c #88D3FB",
|
||||
"- c #98CBFE",
|
||||
"; c #81BBFF",
|
||||
"> c #7EBAFF",
|
||||
", c #84BDFF",
|
||||
"' c #8DC2FF",
|
||||
") c #96C7FE",
|
||||
"! c #A0CCFE",
|
||||
"~ c #A9D1FE",
|
||||
"{ c #CEE5FD",
|
||||
"] c #C7E3FC",
|
||||
"^ c #8AD3FB",
|
||||
"/ c #9DCFFD",
|
||||
"( c #C3DFFD",
|
||||
"_ c #CDE4FD",
|
||||
": c #A3CEFE",
|
||||
"< c #94C6FE",
|
||||
"[ c #CAE5FC",
|
||||
"} c #7DD0FB",
|
||||
"| c #9ECDFD",
|
||||
"1 c #A1CDFE",
|
||||
"2 c #8BC1FF",
|
||||
"3 c #87BFFF",
|
||||
"4 c #ADD4FE",
|
||||
"5 c #C6E1FD",
|
||||
"6 c #CCE3FC",
|
||||
"7 c #A7DAFB",
|
||||
"8 c #9DCBFE",
|
||||
"9 c #78AFF1",
|
||||
"0 c #6096D4",
|
||||
"a c #4B82C0",
|
||||
"b c #5A84B3",
|
||||
"c c #6589B1",
|
||||
"d c #6F92B9",
|
||||
"e c #90AED0",
|
||||
"f c #C4DBF5",
|
||||
"g c #6286AE",
|
||||
"h c #7D9EC2",
|
||||
"i c #BADFFC",
|
||||
"j c #85BDFE",
|
||||
"k c #78B4F8",
|
||||
"l c #4C83C0",
|
||||
"m c #1E4F87",
|
||||
"n c #0A396E",
|
||||
"o c #345D8D",
|
||||
"p c #CDE4FC",
|
||||
"q c #88A7CA",
|
||||
"r c #1D497C",
|
||||
"s c #799BBF",
|
||||
"t c #8AC1FD",
|
||||
"u c #5E97D7",
|
||||
"v c #14457B",
|
||||
"w c #4F76A0",
|
||||
"x c #A9D5FC",
|
||||
"y c #95C9FD",
|
||||
"z c #4C82C1",
|
||||
"A c #0A3A6F",
|
||||
"B c #C9E3FD",
|
||||
"C c #95CCFC",
|
||||
"D c #629BDB",
|
||||
"E c #0B3A6F",
|
||||
"F c #0C3B6F",
|
||||
"G c #4E749F",
|
||||
"H c #8CACCE",
|
||||
"I c #6185AD",
|
||||
"J c #CBE4FD",
|
||||
"K c #89C0FF",
|
||||
"L c #98CDFA",
|
||||
"M c #27558A",
|
||||
"N c #144175",
|
||||
"O c #9BB8D8",
|
||||
"P c #335D8C",
|
||||
"Q c #AFC9E6",
|
||||
"R c #AFD4FE",
|
||||
"S c #91C7FD",
|
||||
"T c #A0C0DE",
|
||||
"U c #194779",
|
||||
"V c #80A1C5",
|
||||
"W c #C8E1F9",
|
||||
"X c #9CB9D8",
|
||||
"Y c #7799BE",
|
||||
"Z c #6489B0",
|
||||
"` c #7092B9",
|
||||
" . c #6E9DCF",
|
||||
".. c #79B5F9",
|
||||
"+. c #83BDFE",
|
||||
"@. c #7395BA",
|
||||
"#. c #315C8B",
|
||||
"$. c #7C9EC2",
|
||||
"%. c #C0D9F3",
|
||||
"&. c #7294BA",
|
||||
"*. c #5C94D4",
|
||||
"=. c #91CCFC",
|
||||
"-. c #88CBFA",
|
||||
";. c #5179A3",
|
||||
">. c #6E91B7",
|
||||
",. c #6084AC",
|
||||
"'. c #96B3D4",
|
||||
"). c #275283",
|
||||
"!. c #0C3C71",
|
||||
"~. c #629CDC",
|
||||
"{. c #94C6FD",
|
||||
"]. c #A7D2FC",
|
||||
"^. c #36659A",
|
||||
"/. c #2C5788",
|
||||
"(. c #9DBAD9",
|
||||
"_. c #B4CEEA",
|
||||
":. c #476E9A",
|
||||
"<. c #7EB9FE",
|
||||
"[. c #8DC3FD",
|
||||
"}. c #8CC2FE",
|
||||
"|. c #2F619B",
|
||||
"1. c #87A6C9",
|
||||
"2. c #7A9BC0",
|
||||
"3. c #CBE2FB",
|
||||
"4. c #C7DFF8",
|
||||
"5. c #6C8FB5",
|
||||
"6. c #113F73",
|
||||
"7. c #0F3D71",
|
||||
"8. c #547AA4",
|
||||
"9. c #9CBAD9",
|
||||
"0. c #B9D3EE",
|
||||
"a. c #A3C0DE",
|
||||
"b. c #31629A",
|
||||
"c. c #659EE0",
|
||||
"d. c #87BFFE",
|
||||
"e. c #C3E0FD",
|
||||
"f. c #4371A4",
|
||||
"g. c #7496BB",
|
||||
"h. c #90AFD1",
|
||||
"i. c #245081",
|
||||
"j. c #416A96",
|
||||
"k. c #B0CBE7",
|
||||
"l. c #CCE4FD",
|
||||
"m. c #7DB8FD",
|
||||
"n. c #1E5088",
|
||||
"o. c #497EBC",
|
||||
"p. c #C9E3FC",
|
||||
"q. c #7193B9",
|
||||
"r. c #C6E0FB",
|
||||
"s. c #A2CDFE",
|
||||
"t. c #97C8FE",
|
||||
"u. c #A7D0FE",
|
||||
"v. c #BDDCFD",
|
||||
"w. c #9EC2E8",
|
||||
"x. c #416996",
|
||||
"y. c #366AA6",
|
||||
"z. c #C0DEFC",
|
||||
"A. c #A2BFDD",
|
||||
"B. c #326299",
|
||||
"C. c #649DDF",
|
||||
"D. c #71ABED",
|
||||
"E. c #3569A4",
|
||||
"F. c #0D3C71",
|
||||
"G. c #6998CD",
|
||||
"H. c #30639D",
|
||||
"I. c #A8D3F8",
|
||||
"J. c #2B5686",
|
||||
"K. c #3A679B",
|
||||
"L. c #ADCAEA",
|
||||
"M. c #85A6C9",
|
||||
"N. c #33639B",
|
||||
"O. c #9CCBFD",
|
||||
"P. c #86C2F7",
|
||||
"Q. c #0E3C71",
|
||||
"R. c #1B4C83",
|
||||
"S. c #5D95D5",
|
||||
"T. c #557BA5",
|
||||
"U. c #85C0F6",
|
||||
"V. c #55A8EF",
|
||||
"W. c #94B3D3",
|
||||
"X. c #1C497C",
|
||||
"Y. c #13437A",
|
||||
"Z. c #487DBB",
|
||||
"`. c #7BB7FB",
|
||||
" + c #76B1F5",
|
||||
".+ c #4E85C3",
|
||||
"++ c #ACD3FE",
|
||||
"@+ c #2F5989",
|
||||
"#+ c #7597BC",
|
||||
"$+ c #53A7EF",
|
||||
"%+ c #C6E1FC",
|
||||
"&+ c #B6D5F7",
|
||||
"*+ c #5890D0",
|
||||
"=+ c #4076B2",
|
||||
"-+ c #619ADB",
|
||||
";+ c #7CB7FC",
|
||||
">+ c #7DB9FE",
|
||||
",+ c #5087C6",
|
||||
"'+ c #134479",
|
||||
")+ c #23548D",
|
||||
"!+ c #24558D",
|
||||
"~+ c #8AAACC",
|
||||
"{+ c #A2C1E1",
|
||||
"]+ c #86C1F5",
|
||||
"^+ c #B4D7FE",
|
||||
"/+ c #6CA5E8",
|
||||
"(+ c #22548C",
|
||||
"_+ c #6D94BF",
|
||||
":+ c #98B6D6",
|
||||
"<+ c #134174",
|
||||
"[+ c #84BDF5",
|
||||
"}+ c #CAE4FC",
|
||||
"|+ c #CBE3FD",
|
||||
"1+ c #8FC3FF",
|
||||
"2+ c #3F72AD",
|
||||
"3+ c #49719C",
|
||||
"4+ c #0C3B70",
|
||||
"5+ c #9CBBDB",
|
||||
"6+ c #79B7F3",
|
||||
"7+ c #BFDCFD",
|
||||
"8+ c #7FBBFF",
|
||||
"9+ c #7E9FC3",
|
||||
"0+ c #77B6F3",
|
||||
"a+ c #A5CEF7",
|
||||
"b+ c #9FCBFE",
|
||||
"c+ c #3267A1",
|
||||
"d+ c #A4CDF7",
|
||||
"e+ c #B9D9FA",
|
||||
"f+ c #C7E1FD",
|
||||
"g+ c #90C3FF",
|
||||
"h+ c #15457C",
|
||||
"i+ c #558CCB",
|
||||
"j+ c #2E5889",
|
||||
"k+ c #7B9CC1",
|
||||
"l+ c #C4DDF6",
|
||||
"m+ c #BBDAFA",
|
||||
"n+ c #CDE5FD",
|
||||
"o+ c #B3D6FE",
|
||||
"p+ c #80BAFF",
|
||||
"q+ c #4E84C3",
|
||||
"r+ c #3E73AF",
|
||||
"s+ c #78B3F7",
|
||||
"t+ c #5991D1",
|
||||
"u+ c #477DBA",
|
||||
"v+ c #4075B2",
|
||||
"w+ c #5783B6",
|
||||
"x+ c #BDD6F0",
|
||||
"y+ c #A1CBF6",
|
||||
"z+ c #90C4FF",
|
||||
"A+ c #BCDBFD",
|
||||
"B+ c #73B0F1",
|
||||
"C+ c #C5E0FB",
|
||||
"D+ c #91C5FF",
|
||||
"E+ c #AED3FE",
|
||||
"F+ c #C9E2FC",
|
||||
"G+ c #76B2F2",
|
||||
"H+ c #8BBFF9",
|
||||
"I+ c #81BBFE",
|
||||
"J+ c #9ECBFE",
|
||||
"K+ c #84B8F3",
|
||||
"L+ c #79B4F4",
|
||||
"M+ c #88BEFA",
|
||||
"N+ c #83BCFE",
|
||||
"O+ c #A4CFFC",
|
||||
"P+ c #A6CDF6",
|
||||
"Q+ c #82B8F2",
|
||||
"R+ c #529BEC",
|
||||
" . + @ # $ % & * = ",
|
||||
" - ; > > , ' ) ! ~ { { { ] ^ ",
|
||||
" / ; > > > > ; ( _ : < { { { { { [ } ",
|
||||
" | 1 2 > > > 2 3 4 5 { { { { { 6 { { { 7 ",
|
||||
" 8 $ < 9 0 a b c d e { { { { f g h { { { { i ",
|
||||
" j k l m n n n n n n o { { p q r n s { { { { { i ",
|
||||
" t u v n n n n n n n n o { { w n n n s { { { { { { x ",
|
||||
" y z A n n n n n n n n n o { { o n n n s { { { { { { B C ",
|
||||
" D E n n n F G H I n n n o { { o n n n s { { { { { J K % ",
|
||||
" L M n n n N O { { s n n n o { { o n n P Q { { { { { R > > S ",
|
||||
" T n n n n H { { { s n n n o { { o U V 6 W X Y Z ` ...> > +. ",
|
||||
" @.n n n #.{ { { { s n n n o { { $.%.W &.U n n n n n v *.> > =.",
|
||||
"-.;.n n n >.{ { { { s n n n ,.{ { { '.).n n n n n n n n !.~.> {.",
|
||||
"].^.n n n q { { { { s n /.(.{ { _.:.n n n n n n n n n n n m <.[.",
|
||||
"}.|.n n n H { { { { 1.2.3.{ 4.5.6.n n n 7.8.9.0.a.b.n n n n c.d.",
|
||||
"e.f.n n n g.{ { { { { { { h.i.n n n n j.k.{ { { l.m.n.n n n o.$ ",
|
||||
"p.q.n n n /.r.s.t.u.v.w.x.n n n n i.h.{ { { { { { u.o.n n n y.$ ",
|
||||
"z.A.n n n n B.C.D.u E.F.n n n 6.5.4.{ 3.2.1.{ { { { G.n n n H.d.",
|
||||
"I.p J.n n n n n n n n n n n K.L.{ { (./.n s { { { { M.n n n N.O.",
|
||||
"P.{ (.Q.n n n n n n n n R.S.> K _ ,.n n n s { { { { 5.n n n T.U.",
|
||||
"V.{ { W.X.n n n n n Y.Z.`. +.+> ++o n n n s { { { { @+n n n #+$+",
|
||||
" %+{ { &+*+Z.=+a -+;+>+,+'+)+> > !+n n n s { { { ~+n n n n {+ ",
|
||||
" ]+{ { ^+> > > > > /+(+n n )+> > )+n n n _+{ { :+<+n n n o [+ ",
|
||||
" }+{ |+1+> > > > l n n n )+> > )+n n n 2+~+3+E n n n 4+5+ ",
|
||||
" 6+{ { 7+8+> > > l n n n )+> > )+n n n n n n n n n F 9+0+ ",
|
||||
" a+{ { b+> > > l n n n c+> > )+n n n n n n n n r O d+ ",
|
||||
" e+{ f+g+> > l n h+i+<.> > )+n n n n n E j+k+l+m+ ",
|
||||
" e+{ n+o+p+q+r+s+> > > > t+u+v+w+2.W.x+{ { e+ ",
|
||||
" y+{ { z+>+> > > > > > > > > A+{ { { { d+ ",
|
||||
" B+C+) > > > > > > > > D+E+{ { { F+G+ ",
|
||||
" H+I+> > > > > > J+{ { { C+K+ ",
|
||||
" L+M+# N+; 8+O+P+Q+R+ "};
|
||||
|
||||
25
misc/Makefile
Normal file
25
misc/Makefile
Normal file
@@ -0,0 +1,25 @@
|
||||
PYTHON = python3
|
||||
DESTDIR = /
|
||||
ICONSIZES = 16 24 32 48 64 128 256 512
|
||||
|
||||
.PHONY: install
|
||||
|
||||
doc/qutebrowser.1.html:
|
||||
a2x -f manpage doc/qutebrowser.1.asciidoc
|
||||
|
||||
install: doc/qutebrowser.1.html
|
||||
$(PYTHON) setup.py install --root="$(DESTDIR)" --optimize=1
|
||||
install -Dm644 doc/qutebrowser.1 \
|
||||
"$(DESTDIR)/usr/share/man/man1/qutebrowser.1"
|
||||
install -Dm644 misc/qutebrowser.desktop \
|
||||
"$(DESTDIR)/usr/share/applications/qutebrowser.desktop"
|
||||
$(foreach i,$(ICONSIZES),install -Dm644 "icons/qutebrowser-$(i)x$(i).png" \
|
||||
"$(DESTDIR)/usr/share/icons/hicolor/$(i)x$(i)/apps/qutebrowser.png";)
|
||||
install -Dm644 icons/qutebrowser.svg \
|
||||
"$(DESTDIR)/usr/share/icons/hicolor/scalable/apps/qutebrowser.svg"
|
||||
install -Dm755 -t "$(DESTDIR)/usr/share/qutebrowser/userscripts/" \
|
||||
$(wildcard misc/userscripts/*)
|
||||
install -Dm755 -t "$(DESTDIR)/usr/share/qutebrowser/scripts/" \
|
||||
$(filter-out scripts/__init__.py scripts/__pycache__ scripts/dev \
|
||||
scripts/testbrowser scripts/asciidoc2html.py scripts/setupcommon.py \
|
||||
scripts/link_pyqt.py,$(wildcard scripts/*))
|
||||
@@ -13,7 +13,7 @@
|
||||
height="682.66669"
|
||||
id="svg2"
|
||||
sodipodi:version="0.32"
|
||||
inkscape:version="0.92.1 r"
|
||||
inkscape:version="0.92.2 5c3e80d, 2017-08-06"
|
||||
version="1.0"
|
||||
sodipodi:docname="cheatsheet.svg"
|
||||
inkscape:output_extension="org.inkscape.output.svg.inkscape"
|
||||
@@ -32,21 +32,21 @@
|
||||
objecttolerance="10"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.7582312"
|
||||
inkscape:cx="513.85167"
|
||||
inkscape:cy="273.37342"
|
||||
inkscape:zoom="1.24"
|
||||
inkscape:cx="305.29152"
|
||||
inkscape:cy="465.48793"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
width="1024px"
|
||||
height="640px"
|
||||
showgrid="false"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1440"
|
||||
inkscape:window-width="1024"
|
||||
inkscape:window-height="723"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:snap-text-baseline="true">
|
||||
<inkscape:grid
|
||||
id="GridFromPre046Settings"
|
||||
@@ -84,9 +84,9 @@
|
||||
height="64"
|
||||
width="74.666664"
|
||||
id="rect3328"
|
||||
style="font-size:18px;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1.06666672" />
|
||||
style="font-size:18px;fill:#eeeeec;fill-opacity:1;stroke:none;stroke-width:1.06666672" />
|
||||
<rect
|
||||
style="font-size:18px;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
style="font-size:18px;fill:#eeeeec;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
id="rect3330"
|
||||
width="64"
|
||||
height="64"
|
||||
@@ -715,7 +715,7 @@
|
||||
height="64"
|
||||
width="63.461262"
|
||||
id="rect3720"
|
||||
style="font-size:18px;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1.06666672" />
|
||||
style="font-size:18px;fill:#eeeeec;fill-opacity:1;stroke:none;stroke-width:1.06666672" />
|
||||
<path
|
||||
id="path3724"
|
||||
d="m 854.35172,271.52738 c 21.26539,0 42.53077,0 63.79615,0"
|
||||
@@ -985,7 +985,7 @@
|
||||
<g
|
||||
id="g7167"
|
||||
transform="translate(74.666662,16.594076)"
|
||||
style="fill:#babdb6;fill-opacity:1">
|
||||
style="fill:#eeeeec;fill-opacity:1">
|
||||
<rect
|
||||
ry="4.7797003"
|
||||
y="296.53333"
|
||||
@@ -993,9 +993,9 @@
|
||||
height="64"
|
||||
width="63.461262"
|
||||
id="rect7169"
|
||||
style="font-size:18px;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1.06666672" />
|
||||
style="font-size:18px;fill:#eeeeec;fill-opacity:1;stroke:none;stroke-width:1.06666672" />
|
||||
<rect
|
||||
style="font-size:18px;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
style="font-size:18px;fill:#eeeeec;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
id="rect7171"
|
||||
width="63.461262"
|
||||
height="32"
|
||||
@@ -1005,7 +1005,7 @@
|
||||
<path
|
||||
id="path7173"
|
||||
d="m 640.14582,329.06667 c 21.0911,0 42.18218,0 63.27327,0"
|
||||
style="font-size:18px;fill:#babdb6;fill-opacity:1;stroke:#000000;stroke-width:1.16182172px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-size:18px;fill:#eeeeec;fill-opacity:1;stroke:#000000;stroke-width:1.16182172px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<text
|
||||
@@ -1303,7 +1303,7 @@
|
||||
height="64"
|
||||
width="63.461262"
|
||||
id="rect3980"
|
||||
style="font-size:18px;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1.06666672" />
|
||||
style="font-size:18px;fill:#eeeeec;fill-opacity:1;stroke:none;stroke-width:1.06666672" />
|
||||
<path
|
||||
id="path3982"
|
||||
d="m 11.247578,121.66071 c 21.091093,0 42.182176,0 63.273269,0"
|
||||
@@ -2475,21 +2475,21 @@
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">prev</tspan></text>
|
||||
<text
|
||||
id="text9514-60-8"
|
||||
y="357.28558"
|
||||
y="355.28558"
|
||||
x="588.79791"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12.80000019px;line-height:0%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
xml:space="preserve"><tspan
|
||||
y="357.28558"
|
||||
y="355.28558"
|
||||
x="588.79791"
|
||||
sodipodi:role="line"
|
||||
id="tspan5524"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">save</tspan><tspan
|
||||
y="364.96558"
|
||||
y="362.96558"
|
||||
x="588.79791"
|
||||
sodipodi:role="line"
|
||||
id="tspan5530"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">quick-</tspan><tspan
|
||||
y="372.64557"
|
||||
y="370.64557"
|
||||
x="588.79791"
|
||||
sodipodi:role="line"
|
||||
id="tspan5532"
|
||||
@@ -3039,6 +3039,8 @@
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara3792">;I - hint images in new tab</flowPara><flowPara
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara6096">;t - hint inputs</flowPara><flowPara
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara3794">;o - put hinted URL in cmd. line</flowPara><flowPara
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara3796">;O - like <flowSpan
|
||||
@@ -3188,7 +3190,9 @@
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara4148"><Ctrl-P> - prev. history item</flowPara><flowPara
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara3935-9"><Ctrl-N> - next history item</flowPara></flowRoot> <rect
|
||||
id="flowPara3935-9"><Ctrl-N> - next history item</flowPara><flowPara
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara6189"><Ctrl-D> - delete current item</flowPara></flowRoot> <rect
|
||||
style="font-size:18px;fill:#eeeeec;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
id="rect3764-9"
|
||||
width="64"
|
||||
@@ -3440,7 +3444,8 @@
|
||||
<flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot5691-4-9-3-6-6"
|
||||
style="font-style:normal;font-weight:normal;font-size:12.80000019px;line-height:0.01%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"><flowRegion
|
||||
style="font-style:normal;font-weight:normal;font-size:12.80000019px;line-height:0.01%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
transform="translate(0,10)"><flowRegion
|
||||
id="flowRegion5693-9-1-7-3-8"
|
||||
style="font-family:sans-serif;stroke-width:1.06666672"><rect
|
||||
id="rect5695-9-8-7-7-6"
|
||||
@@ -3498,5 +3503,162 @@
|
||||
sodipodi:role="line"
|
||||
id="tspan4112"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">mode</tspan></text>
|
||||
<text
|
||||
id="text10564-5"
|
||||
y="274.2934"
|
||||
x="873.4303"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12.80000019px;line-height:0%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
xml:space="preserve"><tspan
|
||||
y="274.2934"
|
||||
x="873.4303"
|
||||
sodipodi:role="line"
|
||||
id="tspan10566-6"
|
||||
style="font-size:9.60000038px;line-height:0.89999998;stroke-width:1.06666672"> </tspan><tspan
|
||||
id="tspan10570-91"
|
||||
y="282.1763"
|
||||
x="873.4303"
|
||||
sodipodi:role="line"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">jump to</tspan><tspan
|
||||
y="289.85632"
|
||||
x="873.4303"
|
||||
sodipodi:role="line"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672"
|
||||
id="tspan6066">scroll</tspan><tspan
|
||||
y="297.53632"
|
||||
x="873.4303"
|
||||
sodipodi:role="line"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672"
|
||||
id="tspan6068">mark</tspan></text>
|
||||
<text
|
||||
id="text10564-2"
|
||||
y="362.50635"
|
||||
x="731.82947"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12.80000019px;line-height:0%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
xml:space="preserve"><tspan
|
||||
id="tspan10568-0"
|
||||
y="362.50635"
|
||||
x="731.82947"
|
||||
sodipodi:role="line"
|
||||
style="font-size:9.60000038px;line-height:0.89999998;stroke-width:1.06666672">repeat</tspan><tspan
|
||||
id="tspan10570-93"
|
||||
y="370.38925"
|
||||
x="731.82947"
|
||||
sodipodi:role="line"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">cmd</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12.80000019px;line-height:0%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
x="183.06667"
|
||||
y="97.639633"
|
||||
id="text7245-1-6"><tspan
|
||||
sodipodi:role="line"
|
||||
x="183.06667"
|
||||
y="97.639633"
|
||||
id="tspan7366-3-0"
|
||||
style="font-size:9.60000038px;line-height:0.89999998;stroke-width:1.06666672"> </tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="183.06667"
|
||||
y="105.52255"
|
||||
id="tspan7249-4-6"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">run</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="183.06667"
|
||||
y="113.20255"
|
||||
id="tspan5293-2"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">macro</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12.80000019px;line-height:0%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
x="117.44301"
|
||||
y="203.05061"
|
||||
id="text7245-1-61"><tspan
|
||||
sodipodi:role="line"
|
||||
x="117.44301"
|
||||
y="203.05061"
|
||||
id="tspan7366-3-8"
|
||||
style="font-size:9.60000038px;line-height:0.89999998;stroke-width:1.06666672"> </tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="117.44301"
|
||||
y="210.93353"
|
||||
id="tspan5293-9"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">record</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="117.44301"
|
||||
y="218.61353"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672"
|
||||
id="tspan6136">macro</tspan></text>
|
||||
<text
|
||||
id="text10564-5-2"
|
||||
y="125.17836"
|
||||
x="37.344757"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12.80000019px;line-height:0%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
xml:space="preserve"><tspan
|
||||
y="125.17836"
|
||||
x="37.344757"
|
||||
sodipodi:role="line"
|
||||
id="tspan10566-6-0"
|
||||
style="font-size:9.60000038px;line-height:0.89999998;stroke-width:1.06666672"> </tspan><tspan
|
||||
id="tspan10570-91-2"
|
||||
y="133.06128"
|
||||
x="37.344757"
|
||||
sodipodi:role="line"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">set</tspan><tspan
|
||||
y="140.74127"
|
||||
x="37.344757"
|
||||
sodipodi:role="line"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672"
|
||||
id="tspan6066-3">scroll</tspan><tspan
|
||||
y="148.42128"
|
||||
x="37.344757"
|
||||
sodipodi:role="line"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672"
|
||||
id="tspan6068-7">mark</tspan></text>
|
||||
<text
|
||||
id="text9514-60-8-5"
|
||||
y="323.89648"
|
||||
x="590.26257"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12.80000019px;line-height:0%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
xml:space="preserve"><tspan
|
||||
y="323.89648"
|
||||
x="590.26257"
|
||||
sodipodi:role="line"
|
||||
id="tspan5524-9"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">save</tspan><tspan
|
||||
y="331.57648"
|
||||
x="590.26257"
|
||||
sodipodi:role="line"
|
||||
id="tspan5530-2"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">book-</tspan><tspan
|
||||
y="339.25647"
|
||||
x="590.26257"
|
||||
sodipodi:role="line"
|
||||
id="tspan5532-2"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">mark</tspan></text>
|
||||
<text
|
||||
id="text10564-5-2-8"
|
||||
y="200.40416"
|
||||
x="21.280243"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12.80000019px;line-height:0%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
xml:space="preserve"><tspan
|
||||
y="200.40416"
|
||||
x="21.280243"
|
||||
sodipodi:role="line"
|
||||
id="tspan10566-6-0-9"
|
||||
style="font-size:9.60000038px;line-height:0.89999998;stroke-width:1.06666672"> </tspan><tspan
|
||||
id="tspan10570-91-2-7"
|
||||
y="208.28708"
|
||||
x="21.280243"
|
||||
sodipodi:role="line"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">cycle</tspan><tspan
|
||||
y="215.96707"
|
||||
x="21.280243"
|
||||
sodipodi:role="line"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672"
|
||||
id="tspan6068-7-6">completion</tspan><tspan
|
||||
y="223.64708"
|
||||
x="21.280243"
|
||||
sodipodi:role="line"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672"
|
||||
id="tspan6220">items</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 170 KiB After Width: | Height: | Size: 178 KiB |
43
misc/qutebrowser.appdata.xml
Normal file
43
misc/qutebrowser.appdata.xml
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright 2017 suve <veg@svgames.pl> -->
|
||||
<component type="desktop">
|
||||
<id>org.qutebrowser.qutebrowser</id>
|
||||
<metadata_license>CC-BY-SA-3.0</metadata_license>
|
||||
<project_license>GPL-3.0</project_license>
|
||||
<name>qutebrowser</name>
|
||||
<summary>A keyboard-driven web browser</summary>
|
||||
<description>
|
||||
<p>
|
||||
qutebrowser is a keyboard-focused browser with a minimal GUI.
|
||||
It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl,
|
||||
and is based on Python and PyQt5.
|
||||
</p>
|
||||
</description>
|
||||
<categories>
|
||||
<category>Network</category>
|
||||
<category>WebBrowser</category>
|
||||
</categories>
|
||||
<provides>
|
||||
<binary>qutebrowser</binary>
|
||||
</provides>
|
||||
<launchable type="desktop-id">qutebrowser.desktop</launchable>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/main.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/downloads.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/completion.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/hints.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<url type="homepage">https://www.qutebrowser.org</url>
|
||||
<url type="faq">https://qutebrowser.org/doc/faq.html</url>
|
||||
<url type="help">https://qutebrowser.org/doc/help/</url>
|
||||
<url type="bugtracker">https://github.com/qutebrowser/qutebrowser/issues/</url>
|
||||
<url type="donation">https://github.com/qutebrowser/qutebrowser#donating</url>
|
||||
</component>
|
||||
@@ -7,5 +7,5 @@ Categories=Network;WebBrowser;
|
||||
Exec=qutebrowser %u
|
||||
Terminal=false
|
||||
StartupNotify=false
|
||||
MimeType=text/html;text/xml;application/xhtml+xml;application/xml;application/rdf+xml;image/gif;image/jpeg;image/png;x-scheme-handler/http;x-scheme-handler/https;
|
||||
MimeType=text/html;text/xml;application/xhtml+xml;application/xml;application/rdf+xml;image/gif;image/jpeg;image/png;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/qute;
|
||||
Keywords=Browser
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
This directory contains various `requirements` files which are used by `tox` to
|
||||
have reproducable tests with pinned versions.
|
||||
have reproducible tests with pinned versions.
|
||||
|
||||
The files are generated based on unpinned requirements in `*.txt-raw` files.
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
check-manifest==0.35
|
||||
check-manifest==0.36
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
certifi==2017.7.27.1
|
||||
certifi==2017.11.5
|
||||
chardet==3.0.4
|
||||
codecov==2.0.9
|
||||
coverage==4.4.1
|
||||
codecov==2.0.13
|
||||
coverage==4.4.2
|
||||
idna==2.6
|
||||
requests==2.18.4
|
||||
urllib3==1.22
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
flake8==2.6.2 # rq.filter: < 3.0.0
|
||||
attrs==17.4.0
|
||||
flake8==3.5.0
|
||||
flake8-bugbear==17.12.0
|
||||
flake8-builtins==1.0.post0
|
||||
flake8-comprehensions==1.4.1
|
||||
flake8-copyright==0.2.0
|
||||
flake8-debugger==1.4.0 # rq.filter: != 2.0.0
|
||||
flake8-deprecated==1.2.1
|
||||
flake8-docstrings==1.0.3 # rq.filter: < 1.1.0
|
||||
flake8-future-import==0.4.3
|
||||
flake8-debugger==3.0.0
|
||||
flake8-deprecated==1.3
|
||||
flake8-docstrings==1.3.0
|
||||
flake8-future-import==0.4.4
|
||||
flake8-mock==0.3
|
||||
flake8-pep3101==1.0 # rq.filter: < 1.1
|
||||
flake8-polyfill==1.0.1
|
||||
flake8-putty==0.4.0
|
||||
flake8-per-file-ignores==0.4
|
||||
flake8-polyfill==1.0.2
|
||||
flake8-string-format==0.2.3
|
||||
flake8-tidy-imports==1.1.0
|
||||
flake8-tuple==0.2.13
|
||||
mccabe==0.6.1
|
||||
packaging==16.8
|
||||
pep8-naming==0.4.1
|
||||
pep8-naming==0.5.0
|
||||
pycodestyle==2.3.1
|
||||
pydocstyle==1.1.1 # rq.filter: < 2.0.0
|
||||
pydocstyle==2.1.1
|
||||
pyflakes==1.6.0
|
||||
pyparsing==2.2.0
|
||||
six==1.11.0
|
||||
snowballstemmer==1.2.1
|
||||
|
||||
@@ -1,29 +1,17 @@
|
||||
flake8<3.0.0
|
||||
flake8
|
||||
flake8-bugbear
|
||||
flake8-builtins
|
||||
flake8-comprehensions
|
||||
flake8-copyright
|
||||
flake8-debugger!=2.0.0
|
||||
flake8-debugger
|
||||
flake8-deprecated
|
||||
flake8-docstrings<1.1.0
|
||||
flake8-docstrings
|
||||
flake8-future-import
|
||||
flake8-mock
|
||||
flake8-pep3101<1.1
|
||||
flake8-putty
|
||||
flake8-per-file-ignores
|
||||
flake8-string-format
|
||||
flake8-tidy-imports
|
||||
flake8-tuple
|
||||
pep8-naming
|
||||
pydocstyle<2.0.0
|
||||
pydocstyle
|
||||
pyflakes
|
||||
|
||||
# Pinned to 2.0.0 otherwise
|
||||
pycodestyle==2.3.1
|
||||
# Pinned to 0.5.3 otherwise
|
||||
mccabe==0.6.1
|
||||
|
||||
# Waiting until flake8-putty updated
|
||||
#@ filter: flake8 < 3.0.0
|
||||
#@ filter: pydocstyle < 2.0.0
|
||||
#@ filter: flake8-docstrings < 1.1.0
|
||||
#@ filter: flake8-pep3101 < 1.1
|
||||
|
||||
# https://github.com/JBKahn/flake8-debugger/issues/5
|
||||
#@ filter: flake8-debugger != 2.0.0
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
appdirs==1.4.3
|
||||
packaging==16.8
|
||||
pyparsing==2.2.0
|
||||
setuptools==36.5.0
|
||||
setuptools==38.4.0
|
||||
six==1.11.0
|
||||
wheel==0.30.0
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
altgraph==0.14
|
||||
altgraph==0.15
|
||||
future==0.16.0
|
||||
macholib==1.8
|
||||
pefile==2017.9.3
|
||||
macholib==1.9
|
||||
pefile==2017.11.5
|
||||
-e git+https://github.com/pyinstaller/pyinstaller.git@develop#egg=PyInstaller
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
-e git+https://github.com/PyCQA/astroid.git#egg=astroid
|
||||
certifi==2017.7.27.1
|
||||
certifi==2017.11.5
|
||||
chardet==3.0.4
|
||||
github3.py==0.9.6
|
||||
idna==2.6
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
astroid==1.5.3
|
||||
certifi==2017.7.27.1
|
||||
astroid==1.6.0
|
||||
certifi==2017.11.5
|
||||
chardet==3.0.4
|
||||
github3.py==0.9.6
|
||||
idna==2.6
|
||||
isort==4.2.15
|
||||
lazy-object-proxy==1.3.1
|
||||
mccabe==0.6.1
|
||||
pylint==1.7.4
|
||||
pylint==1.8.1
|
||||
./scripts/dev/pylint_checkers
|
||||
requests==2.18.4
|
||||
six==1.11.0
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.9
|
||||
sip==4.19.3
|
||||
PyQt5==5.10.1
|
||||
sip==4.19.8
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
docutils==0.14
|
||||
pyroma==2.2
|
||||
pyroma==2.3
|
||||
|
||||
@@ -12,12 +12,6 @@ git+https://github.com/jenisys/parse_type.git
|
||||
hg+https://bitbucket.org/pytest-dev/py
|
||||
git+https://github.com/pytest-dev/pytest.git@features
|
||||
git+https://github.com/pytest-dev/pytest-bdd.git
|
||||
|
||||
# This is broken at the moment because logfail tries to access
|
||||
# LogCaptureHandler
|
||||
# git+https://github.com/eisensheng/pytest-catchlog.git
|
||||
pytest-catchlog==1.2.2
|
||||
|
||||
git+https://github.com/pytest-dev/pytest-cov.git
|
||||
git+https://github.com/pytest-dev/pytest-faulthandler.git
|
||||
git+https://github.com/pytest-dev/pytest-instafail.git
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
attrs==17.2.0
|
||||
attrs==17.4.0
|
||||
beautifulsoup4==4.6.0
|
||||
cheroot==5.8.3
|
||||
cheroot==6.0.0
|
||||
click==6.7
|
||||
# colorama==0.3.9
|
||||
coverage==4.4.1
|
||||
coverage==4.4.2
|
||||
EasyProcess==0.2.3
|
||||
fields==5.0.0
|
||||
Flask==0.12.2
|
||||
glob2==0.6
|
||||
hunter==2.0.1
|
||||
hypothesis==3.32.0
|
||||
hunter==2.0.2
|
||||
hypothesis==3.44.16
|
||||
itsdangerous==0.24
|
||||
# Jinja2==2.9.6
|
||||
# Jinja2==2.10
|
||||
Mako==1.0.7
|
||||
# MarkupSafe==1.0
|
||||
parse==1.8.2
|
||||
parse-type==0.4.2
|
||||
py==1.4.34
|
||||
pluggy==0.6.0
|
||||
py==1.5.2
|
||||
py-cpuinfo==3.3.0
|
||||
pytest==3.2.3
|
||||
pytest-bdd==2.18.2
|
||||
pytest==3.3.1 # rq.filter: != 3.3.2
|
||||
pytest-bdd==2.19.0
|
||||
pytest-benchmark==3.1.1
|
||||
pytest-catchlog==1.2.2
|
||||
pytest-cov==2.5.1
|
||||
pytest-faulthandler==1.3.1
|
||||
pytest-instafail==0.3.0
|
||||
pytest-mock==1.6.3
|
||||
pytest-qt==2.2.1
|
||||
pytest-qt==2.3.1
|
||||
pytest-repeat==0.4.1
|
||||
pytest-rerunfailures==3.1
|
||||
pytest-travis-fold==1.2.0
|
||||
pytest-rerunfailures==4.0
|
||||
pytest-travis-fold==1.3.0
|
||||
pytest-xvfb==1.0.0
|
||||
PyVirtualDisplay==0.2.1
|
||||
six==1.11.0
|
||||
vulture==0.26
|
||||
Werkzeug==0.12.2
|
||||
Werkzeug==0.14.1
|
||||
|
||||
@@ -4,10 +4,9 @@ coverage
|
||||
Flask
|
||||
hunter
|
||||
hypothesis
|
||||
pytest
|
||||
pytest==3.3.1
|
||||
pytest-bdd
|
||||
pytest-benchmark
|
||||
pytest-catchlog
|
||||
pytest-cov
|
||||
pytest-faulthandler
|
||||
pytest-instafail
|
||||
@@ -20,3 +19,4 @@ pytest-xvfb
|
||||
vulture
|
||||
|
||||
#@ ignore: Jinja2, MarkupSafe, colorama
|
||||
#@ filter: pytest != 3.3.2
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
pluggy==0.5.2
|
||||
py==1.4.34
|
||||
pluggy==0.6.0
|
||||
py==1.5.2
|
||||
six==1.11.0
|
||||
tox==2.9.1
|
||||
virtualenv==15.1.0
|
||||
|
||||
@@ -133,18 +133,24 @@ echo "jseval -q $(printjs)" >> "$QUTE_FIFO"
|
||||
|
||||
tmpdir=$(mktemp -d)
|
||||
file_to_cast=${tmpdir}/qutecast
|
||||
program_=$(command -v castnow)
|
||||
|
||||
if [[ "${program_}" == "" ]]; then
|
||||
msg error "castnow can't be found..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# kill any running instance of castnow
|
||||
pkill -f /usr/bin/castnow
|
||||
pkill -f "${program_}"
|
||||
|
||||
# start youtube download in stream mode (-o -) into temporary file
|
||||
youtube-dl -qo - "$1" > ${file_to_cast} &
|
||||
youtube-dl -qo - "$1" > "${file_to_cast}" &
|
||||
ytdl_pid=$!
|
||||
|
||||
msg info "Casting $1" >> "$QUTE_FIFO"
|
||||
# start castnow in stream mode to cast on ChromeCast
|
||||
tail -F "${file_to_cast}" | castnow -
|
||||
tail -F "${file_to_cast}" | ${program_} -
|
||||
|
||||
# cleanup remaining background process and file on disk
|
||||
kill ${ytdl_pid}
|
||||
rm -rf ${tmpdir}
|
||||
rm -rf "${tmpdir}"
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
[ -z "$QUTE_URL" ] && QUTE_URL='http://google.com'
|
||||
|
||||
url=$(echo "$QUTE_URL" | cat - "$QUTE_CONFIG_DIR/quickmarks" "$QUTE_DATA_DIR/history" | dmenu -l 15 -p qutebrowser)
|
||||
url=$(echo "$url" | sed -E 's/[^ ]+ +//g' | egrep "https?:" || echo "$url")
|
||||
url=$(echo "$url" | sed -E 's/[^ ]+ +//g' | grep -E "https?:" || echo "$url")
|
||||
|
||||
[ -z "${url// }" ] && exit
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/bin/sh
|
||||
set -euo pipefail
|
||||
#
|
||||
# Behavior:
|
||||
# Userscript for qutebrowser which will take the raw JSON text of the current
|
||||
@@ -19,29 +20,23 @@
|
||||
#
|
||||
# Bryan Gilbert, 2017
|
||||
|
||||
# do not run pygmentize on files larger than this amount of bytes
|
||||
MAX_SIZE_PRETTIFY=10485760 # 10 MB
|
||||
# default style to monokai if none is provided
|
||||
STYLE=${1:-monokai}
|
||||
# format json using jq
|
||||
FORMATTED_JSON="$(cat "$QUTE_TEXT" | jq '.')"
|
||||
|
||||
# if jq command failed or formatted json is empty, assume failure and terminate
|
||||
if [ $? -ne 0 ] || [ -z "$FORMATTED_JSON" ]; then
|
||||
echo "Invalid json, aborting..."
|
||||
exit 1
|
||||
TEMP_FILE="$(mktemp)"
|
||||
jq . "$QUTE_TEXT" >"$TEMP_FILE"
|
||||
|
||||
# try GNU stat first and then OSX stat if the former fails
|
||||
FILE_SIZE=$(
|
||||
stat --printf="%s" "$TEMP_FILE" 2>/dev/null ||
|
||||
stat -f%z "$TEMP_FILE" 2>/dev/null
|
||||
)
|
||||
if [ "$FILE_SIZE" -lt "$MAX_SIZE_PRETTIFY" ]; then
|
||||
pygmentize -l json -f html -O full,style="$STYLE" <"$TEMP_FILE" >"${TEMP_FILE}_"
|
||||
mv -f "${TEMP_FILE}_" "$TEMP_FILE"
|
||||
fi
|
||||
|
||||
# calculate the filesize of the json document
|
||||
FILE_SIZE=$(ls -s --block-size=1048576 "$QUTE_TEXT" | cut -d' ' -f1)
|
||||
|
||||
# use pygments to pretty-up the json (syntax highlight) if file is less than 10MB
|
||||
if [ "$FILE_SIZE" -lt "10" ]; then
|
||||
FORMATTED_JSON="$(echo "$FORMATTED_JSON" | pygmentize -l json -f html -O full,style=$STYLE)"
|
||||
fi
|
||||
|
||||
# create a temp file and write the formatted json to that file
|
||||
TEMP_FILE="$(mktemp --suffix '.html')"
|
||||
echo "$FORMATTED_JSON" > $TEMP_FILE
|
||||
|
||||
|
||||
# send the command to qutebrowser to open the new file containing the formatted json
|
||||
echo "open -t file://$TEMP_FILE" >> "$QUTE_FIFO"
|
||||
|
||||
@@ -76,6 +76,7 @@ crop-first-column() {
|
||||
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
|
||||
# shellcheck disable=SC2010
|
||||
ls -Q --quoting-style escape -h -o -1 -A -t "${DOWNLOAD_DIR}/" \
|
||||
| grep '^[-]' \
|
||||
| cut -d' ' -f3- \
|
||||
@@ -91,10 +92,10 @@ if [ "${#entries[@]}" -eq 0 ] ; then
|
||||
die "Download directory »${DOWNLOAD_DIR}« empty"
|
||||
fi
|
||||
|
||||
line=$(printf "%s\n" "${entries[@]}" \
|
||||
line=$(printf '%s\n' "${entries[@]}" \
|
||||
| crop-first-column 55 \
|
||||
| column -s $'\t' -t \
|
||||
| $ROFI_CMD "${rofi_default_args[@]}" $ROFI_ARGS) || true
|
||||
| $ROFI_CMD "${rofi_default_args[@]}" "$ROFI_ARGS") || true
|
||||
if [ -z "$line" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -64,7 +64,7 @@ die() {
|
||||
javascript_escape() {
|
||||
# print the first argument in an escaped way, such that it can safely
|
||||
# be used within javascripts double quotes
|
||||
sed "s,[\\\'\"],\\\&,g" <<< "$1"
|
||||
sed "s,[\\\\'\"],\\\\&,g" <<< "$1"
|
||||
}
|
||||
|
||||
# ======================================================= #
|
||||
@@ -178,7 +178,7 @@ choose_entry_menu() {
|
||||
if [ "$nr" -eq 1 ] && ! ((menu_if_one_entry)) ; then
|
||||
file="${files[0]}"
|
||||
else
|
||||
file=$( printf "%s\n" "${files[@]}" | "${MENU_COMMAND[@]}" )
|
||||
file=$( printf '%s\n' "${files[@]}" | "${MENU_COMMAND[@]}" )
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -236,7 +236,7 @@ pass_backend() {
|
||||
if ((match_line)) ; then
|
||||
# add entries with matching URL-tag
|
||||
while read -r -d "" passfile ; do
|
||||
if $GPG "${GPG_OPTS}" -d "$passfile" \
|
||||
if $GPG "${GPG_OPTS[@]}" -d "$passfile" \
|
||||
| grep --max-count=1 -iE "${match_line_pattern}${url}" > /dev/null
|
||||
then
|
||||
passfile="${passfile#$PREFIX}"
|
||||
@@ -269,7 +269,7 @@ pass_backend() {
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done < <($GPG "${GPG_OPTS}" -d "$path" )
|
||||
done < <($GPG "${GPG_OPTS[@]}" -d "$path" )
|
||||
}
|
||||
}
|
||||
# =======================================================
|
||||
@@ -283,8 +283,8 @@ secret_backend() {
|
||||
query_entries() {
|
||||
local domain="$1"
|
||||
while read -r line ; do
|
||||
if [[ "$line" =~ "attribute.username = " ]] ; then
|
||||
files+=("$domain ${line#${BASH_REMATCH[0]}}")
|
||||
if [[ "$line" == "attribute.username = "* ]] ; then
|
||||
files+=("$domain ${line:21}")
|
||||
fi
|
||||
done < <( secret-tool search --unlock --all domain "$domain" 2>&1 )
|
||||
}
|
||||
@@ -303,6 +303,7 @@ pass_backend
|
||||
QUTE_CONFIG_DIR=${QUTE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/qutebrowser/}
|
||||
PWFILL_CONFIG=${PWFILL_CONFIG:-${QUTE_CONFIG_DIR}/password_fill_rc}
|
||||
if [ -f "$PWFILL_CONFIG" ] ; then
|
||||
# shellcheck source=/dev/null
|
||||
source "$PWFILL_CONFIG"
|
||||
fi
|
||||
init
|
||||
@@ -311,7 +312,7 @@ simplify_url "$QUTE_URL"
|
||||
query_entries "${simple_url}"
|
||||
no_entries_found
|
||||
# remove duplicates
|
||||
mapfile -t files < <(printf "%s\n" "${files[@]}" | sort | uniq )
|
||||
mapfile -t files < <(printf '%s\n' "${files[@]}" | sort | uniq )
|
||||
choose_entry
|
||||
if [ -z "$file" ] ; then
|
||||
# choose_entry didn't want any of these entries
|
||||
|
||||
176
misc/userscripts/qute-pass
Executable file
176
misc/userscripts/qute-pass
Executable file
@@ -0,0 +1,176 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright 2017 Chris Braun (cryzed) <cryzed@googlemail.com>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
"""
|
||||
Insert login information using pass and a dmenu-compatible application (e.g. dmenu, rofi -dmenu, ...). A short
|
||||
demonstration can be seen here: https://i.imgur.com/KN3XuZP.gif.
|
||||
"""
|
||||
|
||||
USAGE = """The domain of the site has to appear as a segment in the pass path, for example: "github.com/cryzed" or
|
||||
"websites/github.com". How the username and password are determined is freely configurable using the CLI arguments. The
|
||||
login information is inserted by emulating key events using qutebrowser's fake-key command in this manner:
|
||||
[USERNAME]<Tab>[PASSWORD], which is compatible with almost all login forms."""
|
||||
|
||||
EPILOG = """Dependencies: tldextract (Python 3 module), pass.
|
||||
For issues and feedback please use: https://github.com/cryzed/qutebrowser-userscripts.
|
||||
|
||||
WARNING: The login details are viewable as plaintext in qutebrowser's debug log (qute://log) and might be shared if
|
||||
you decide to submit a crash report!"""
|
||||
|
||||
import argparse
|
||||
import enum
|
||||
import fnmatch
|
||||
import functools
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import tldextract
|
||||
|
||||
argument_parser = argparse.ArgumentParser(description=__doc__, usage=USAGE, epilog=EPILOG)
|
||||
argument_parser.add_argument('url', nargs='?', default=os.getenv('QUTE_URL'))
|
||||
argument_parser.add_argument('--password-store', '-p', default=os.path.expanduser('~/.password-store'),
|
||||
help='Path to your pass password-store')
|
||||
argument_parser.add_argument('--username-pattern', '-u', default=r'.*/(.+)',
|
||||
help='Regular expression that matches the username')
|
||||
argument_parser.add_argument('--username-target', '-U', choices=['path', 'secret'], default='path',
|
||||
help='The target for the username regular expression')
|
||||
argument_parser.add_argument('--password-pattern', '-P', default=r'(.*)',
|
||||
help='Regular expression that matches the password')
|
||||
argument_parser.add_argument('--dmenu-invocation', '-d', default='rofi -dmenu',
|
||||
help='Invocation used to execute a dmenu-provider')
|
||||
argument_parser.add_argument('--no-insert-mode', '-n', dest='insert_mode', action='store_false',
|
||||
help="Don't automatically enter insert mode")
|
||||
argument_parser.add_argument('--io-encoding', '-i', default='UTF-8',
|
||||
help='Encoding used to communicate with subprocesses')
|
||||
argument_parser.add_argument('--merge-candidates', '-m', action='store_true',
|
||||
help='Merge pass candidates for fully-qualified and registered domain name')
|
||||
group = argument_parser.add_mutually_exclusive_group()
|
||||
group.add_argument('--username-only', '-e', action='store_true', help='Only insert username')
|
||||
group.add_argument('--password-only', '-w', action='store_true', help='Only insert password')
|
||||
|
||||
stderr = functools.partial(print, file=sys.stderr)
|
||||
|
||||
|
||||
class ExitCodes(enum.IntEnum):
|
||||
SUCCESS = 0
|
||||
FAILURE = 1
|
||||
# 1 is automatically used if Python throws an exception
|
||||
NO_PASS_CANDIDATES = 2
|
||||
COULD_NOT_MATCH_USERNAME = 3
|
||||
COULD_NOT_MATCH_PASSWORD = 4
|
||||
|
||||
|
||||
def qute_command(command):
|
||||
with open(os.environ['QUTE_FIFO'], 'w') as fifo:
|
||||
fifo.write(command + '\n')
|
||||
fifo.flush()
|
||||
|
||||
|
||||
def find_pass_candidates(domain, password_store_path):
|
||||
candidates = []
|
||||
for path, directories, file_names in os.walk(password_store_path):
|
||||
if directories or domain not in path.split(os.path.sep):
|
||||
continue
|
||||
|
||||
# Strip password store path prefix to get the relative pass path
|
||||
pass_path = path[len(password_store_path) + 1:]
|
||||
secrets = fnmatch.filter(file_names, '*.gpg')
|
||||
candidates.extend(os.path.join(pass_path, os.path.splitext(secret)[0]) for secret in secrets)
|
||||
return candidates
|
||||
|
||||
|
||||
def pass_(path, encoding):
|
||||
process = subprocess.run(['pass', path], stdout=subprocess.PIPE)
|
||||
return process.stdout.decode(encoding).strip()
|
||||
|
||||
|
||||
def dmenu(items, invocation, encoding):
|
||||
command = shlex.split(invocation)
|
||||
process = subprocess.run(command, input='\n'.join(items).encode(encoding), stdout=subprocess.PIPE)
|
||||
return process.stdout.decode(encoding).strip()
|
||||
|
||||
|
||||
def main(arguments):
|
||||
if not arguments.url:
|
||||
argument_parser.print_help()
|
||||
return ExitCodes.FAILURE
|
||||
|
||||
extract_result = tldextract.extract(arguments.url)
|
||||
|
||||
# Expand potential ~ in paths, since this script won't be called from a shell that does it for us
|
||||
password_store_path = os.path.expanduser(arguments.password_store)
|
||||
|
||||
# Try to find candidates using targets in the following order: fully-qualified domain name (includes subdomains),
|
||||
# the registered domain name and finally: the IPv4 address if that's what the URL represents
|
||||
candidates = set()
|
||||
for target in filter(None, [extract_result.fqdn, extract_result.registered_domain, extract_result.ipv4]):
|
||||
target_candidates = find_pass_candidates(target, password_store_path)
|
||||
if not target_candidates:
|
||||
continue
|
||||
|
||||
candidates.update(target_candidates)
|
||||
if not arguments.merge_candidates:
|
||||
break
|
||||
else:
|
||||
if not candidates:
|
||||
stderr('No pass candidates for URL {!r} found!'.format(arguments.url))
|
||||
return ExitCodes.NO_PASS_CANDIDATES
|
||||
|
||||
selection = candidates.pop() if len(candidates) == 1 else dmenu(sorted(candidates), arguments.dmenu_invocation,
|
||||
arguments.io_encoding)
|
||||
# Nothing was selected, simply return
|
||||
if not selection:
|
||||
return ExitCodes.SUCCESS
|
||||
|
||||
secret = pass_(selection, arguments.io_encoding)
|
||||
|
||||
# Match username
|
||||
target = selection if arguments.username_target == 'path' else secret
|
||||
match = re.match(arguments.username_pattern, target)
|
||||
if not match:
|
||||
stderr('Failed to match username pattern on {}!'.format(arguments.username_target))
|
||||
return ExitCodes.COULD_NOT_MATCH_USERNAME
|
||||
username = match.group(1)
|
||||
|
||||
# Match password
|
||||
match = re.match(arguments.password_pattern, secret)
|
||||
if not match:
|
||||
stderr('Failed to match password pattern on secret!')
|
||||
return ExitCodes.COULD_NOT_MATCH_PASSWORD
|
||||
password = match.group(1)
|
||||
|
||||
insert_mode = ';; enter-mode insert' if arguments.insert_mode else ''
|
||||
if arguments.username_only:
|
||||
qute_command('fake-key {}{}'.format(username, insert_mode))
|
||||
elif arguments.password_only:
|
||||
qute_command('fake-key {}{}'.format(password, insert_mode))
|
||||
else:
|
||||
# Enter username and password using fake-key and <Tab> (which seems to work almost universally), then switch
|
||||
# back into insert-mode, so the form can be directly submitted by hitting enter afterwards
|
||||
qute_command('fake-key {} ;; fake-key <Tab> ;; fake-key {}{}'.format(username, password, insert_mode))
|
||||
|
||||
return ExitCodes.SUCCESS
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
arguments = argument_parser.parse_args()
|
||||
sys.exit(main(arguments))
|
||||
@@ -35,17 +35,12 @@ get_selection() {
|
||||
|
||||
# Main
|
||||
# https://github.com/halfwit/dotfiles/blob/master/.config/dmenu/font
|
||||
if [[ -s $confdir/dmenu/font ]]; then
|
||||
read -r font < "$confdir"/dmenu/font
|
||||
fi
|
||||
[[ -s $confdir/dmenu/font ]] && read -r font < "$confdir"/dmenu/font
|
||||
|
||||
if [[ $font ]]; then
|
||||
opts+=(-fn "$font")
|
||||
fi
|
||||
[[ $font ]] && opts+=(-fn "$font")
|
||||
|
||||
if [[ -s $optsfile ]]; then
|
||||
source "$optsfile"
|
||||
fi
|
||||
# shellcheck source=/dev/null
|
||||
[[ -s $optsfile ]] && source "$optsfile"
|
||||
|
||||
url=$(get_selection)
|
||||
url=${url/*http/http}
|
||||
|
||||
@@ -32,7 +32,7 @@ add_feed () {
|
||||
if grep -Fq "$1" "feeds"; then
|
||||
notice "$1 is saved already."
|
||||
else
|
||||
printf "%s\n" "$1" >> "feeds"
|
||||
printf '%s\n' "$1" >> "feeds"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ notice () {
|
||||
|
||||
# Update a database of a feed and open new URLs
|
||||
read_items () {
|
||||
cd read_urls
|
||||
cd read_urls || return 1
|
||||
feed_file="$(echo "$1" | tr -d /)"
|
||||
feed_temp_file="$(mktemp "$feed_file.tmp.XXXXXXXXXX")"
|
||||
feed_new_items="$(mktemp "$feed_file.new.XXXXXXXXXX")"
|
||||
@@ -75,7 +75,7 @@ read_items () {
|
||||
cat "$feed_new_items" >> "$feed_file"
|
||||
sort -o "$feed_file" "$feed_file"
|
||||
rm "$feed_temp_file" "$feed_new_items"
|
||||
fi | while read item; do
|
||||
fi | while read -r item; do
|
||||
echo "open -t $item" > "$QUTE_FIFO"
|
||||
done
|
||||
}
|
||||
@@ -85,7 +85,7 @@ if [ ! -d "$config_dir/read_urls" ]; then
|
||||
mkdir -p "$config_dir/read_urls"
|
||||
fi
|
||||
|
||||
cd "$config_dir"
|
||||
cd "$config_dir" || exit 1
|
||||
|
||||
if [ $# != 0 ]; then
|
||||
for arg in "$@"; do
|
||||
@@ -115,7 +115,7 @@ if < /dev/null grep --help 2>&1 | grep -q -- -a; then
|
||||
text_only="-a"
|
||||
fi
|
||||
|
||||
while read feed_url; do
|
||||
while read -r feed_url; do
|
||||
read_items "$feed_url" &
|
||||
done < "$config_dir/feeds"
|
||||
|
||||
|
||||
@@ -25,12 +25,10 @@
|
||||
[[ $QUTE_MODE == 'hints' ]] && title=$QUTE_SELECTED_TEXT || title=$QUTE_TITLE
|
||||
|
||||
# try to add the task and grab the output
|
||||
msg="$(task add $title $@ 2>&1)"
|
||||
|
||||
if [[ $? == 0 ]]; then
|
||||
if msg="$(task add "$title" "$*" 2>&1)"; then
|
||||
# annotate the new task with the url, send the output back to the browser
|
||||
task +LATEST annotate "$QUTE_URL"
|
||||
echo "message-info '$msg'" >> $QUTE_FIFO
|
||||
echo "message-info '$msg'" >> "$QUTE_FIFO"
|
||||
else
|
||||
echo "message-error '$msg'" >> $QUTE_FIFO
|
||||
echo "message-error '$msg'" >> "$QUTE_FIFO"
|
||||
fi
|
||||
|
||||
@@ -50,7 +50,7 @@ msg() {
|
||||
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 --ytdl-raw-options=yes-playlist=}
|
||||
video_command=( "$MPV_COMMAND" $MPV_FLAGS )
|
||||
IFS=" " read -r -a video_command <<< "$MPV_COMMAND $MPV_FLAGS"
|
||||
|
||||
js() {
|
||||
cat <<EOF
|
||||
|
||||
@@ -26,6 +26,7 @@ markers =
|
||||
no_invalid_lines: Don't fail on unparseable lines in end2end tests
|
||||
issue2478: Tests which are broken on Windows with QtWebEngine, https://github.com/qutebrowser/qutebrowser/issues/2478
|
||||
fake_os: Fake utils.is_* to a fake operating system
|
||||
unicode_locale: Tests which need an unicode locale to work
|
||||
qt_log_level_fail = WARNING
|
||||
qt_log_ignore =
|
||||
^SpellCheck: .*
|
||||
@@ -50,9 +51,12 @@ qt_log_ignore =
|
||||
^Error when parsing the netrc file
|
||||
^Image of format '' blocked because it is not considered safe. If you are sure it is safe to do so, you can white-list the format by setting the environment variable QTWEBKIT_IMAGEFORMAT_WHITELIST=
|
||||
^QPainter::end: Painter ended with \d+ saved states
|
||||
^QSslSocket: cannot resolve *
|
||||
^QSslSocket: cannot resolve .*
|
||||
^QSslSocket: cannot call unresolved function .*
|
||||
^Incompatible version of OpenSSL
|
||||
^QQuickWidget::invalidateRenderControl could not make context current
|
||||
^libpng warning: iCCP: known incorrect sRGB profile
|
||||
^inotify_add_watch(".*") failed: "No space left on device"
|
||||
^inotify_add_watch\(".*"\) failed: "No space left on device"
|
||||
^QSettings::value: Empty key passed
|
||||
^Icon theme ".*" not found
|
||||
xfail_strict = true
|
||||
|
||||
@@ -26,7 +26,7 @@ __copyright__ = "Copyright 2014-2017 Florian Bruhin (The Compiler)"
|
||||
__license__ = "GPL"
|
||||
__maintainer__ = __author__
|
||||
__email__ = "mail@qutebrowser.org"
|
||||
__version_info__ = (1, 0, 1)
|
||||
__version_info__ = (1, 1, 2)
|
||||
__version__ = '.'.join(str(e) for e in __version_info__)
|
||||
__description__ = "A keyboard-driven, vim-like browser based on PyQt5."
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ import tokenize
|
||||
from PyQt5.QtWidgets import QApplication, QWidget
|
||||
from PyQt5.QtGui import QDesktopServices, QPixmap, QIcon, QWindow
|
||||
from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QTimer, QUrl,
|
||||
QObject, QEvent, pyqtSignal)
|
||||
QObject, QEvent, pyqtSignal, Qt)
|
||||
try:
|
||||
import hunter
|
||||
except ImportError:
|
||||
@@ -64,7 +64,7 @@ from qutebrowser.completion.models import miscmodels
|
||||
from qutebrowser.commands import cmdutils, runners, cmdexc
|
||||
from qutebrowser.config import config, websettings, configfiles, configinit
|
||||
from qutebrowser.browser import (urlmarks, adblock, history, browsertab,
|
||||
downloads)
|
||||
downloads, greasemonkey)
|
||||
from qutebrowser.browser.network import proxy
|
||||
from qutebrowser.browser.webkit import cookies, cache
|
||||
from qutebrowser.browser.webkit.network import networkmanager
|
||||
@@ -417,7 +417,6 @@ def _init_modules(args, crash_handler):
|
||||
args: The argparse namespace.
|
||||
crash_handler: The CrashHandler instance.
|
||||
"""
|
||||
# pylint: disable=too-many-statements
|
||||
log.init.debug("Initializing save manager...")
|
||||
save_manager = savemanager.SaveManager(qApp)
|
||||
objreg.register('save-manager', save_manager)
|
||||
@@ -492,6 +491,9 @@ def _init_modules(args, crash_handler):
|
||||
diskcache = cache.DiskCache(standarddir.cache(), parent=qApp)
|
||||
objreg.register('cache', diskcache)
|
||||
|
||||
log.init.debug("Initializing Greasemonkey...")
|
||||
greasemonkey.init()
|
||||
|
||||
log.init.debug("Misc initialization...")
|
||||
macros.init()
|
||||
# Init backend-specific stuff
|
||||
@@ -562,8 +564,8 @@ class Quitter:
|
||||
cwd = os.path.abspath(os.path.dirname(sys.executable))
|
||||
else:
|
||||
args = [sys.executable, '-m', 'qutebrowser']
|
||||
cwd = os.path.join(os.path.abspath(os.path.dirname(
|
||||
qutebrowser.__file__)), '..')
|
||||
cwd = os.path.join(
|
||||
os.path.abspath(os.path.dirname(qutebrowser.__file__)), '..')
|
||||
if not os.path.isdir(cwd):
|
||||
# Probably running from a python egg. Let's fallback to
|
||||
# cwd=None and see if that works out.
|
||||
@@ -821,6 +823,7 @@ class Application(QApplication):
|
||||
|
||||
self.launch_time = datetime.datetime.now()
|
||||
self.focusObjectChanged.connect(self.on_focus_object_changed)
|
||||
self.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
|
||||
|
||||
@pyqtSlot(QObject)
|
||||
def on_focus_object_changed(self, obj):
|
||||
@@ -869,10 +872,6 @@ class EventFilter(QObject):
|
||||
super().__init__(parent)
|
||||
self._activated = True
|
||||
self._handlers = {
|
||||
QEvent.MouseButtonDblClick: self._handle_mouse_event,
|
||||
QEvent.MouseButtonPress: self._handle_mouse_event,
|
||||
QEvent.MouseButtonRelease: self._handle_mouse_event,
|
||||
QEvent.MouseMove: self._handle_mouse_event,
|
||||
QEvent.KeyPress: self._handle_key_event,
|
||||
QEvent.KeyRelease: self._handle_key_event,
|
||||
}
|
||||
@@ -897,19 +896,6 @@ class EventFilter(QObject):
|
||||
# No window available yet, or not a MainWindow
|
||||
return False
|
||||
|
||||
def _handle_mouse_event(self, _event):
|
||||
"""Handle a mouse event.
|
||||
|
||||
Args:
|
||||
_event: The QEvent which is about to be delivered.
|
||||
|
||||
Return:
|
||||
True if the event should be filtered, False if it's passed through.
|
||||
"""
|
||||
# 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.
|
||||
|
||||
|
||||
@@ -19,8 +19,10 @@
|
||||
|
||||
"""Base class for a wrapper over QWebView/QWebEngineView."""
|
||||
|
||||
import enum
|
||||
import itertools
|
||||
|
||||
import sip
|
||||
import attr
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt
|
||||
from PyQt5.QtGui import QIcon
|
||||
@@ -74,7 +76,7 @@ class UnsupportedOperationError(WebTabError):
|
||||
"""Raised when an operation is not supported with the given backend."""
|
||||
|
||||
|
||||
TerminationStatus = usertypes.enum('TerminationStatus', [
|
||||
TerminationStatus = enum.Enum('TerminationStatus', [
|
||||
'normal',
|
||||
'abnormal', # non-zero exit status
|
||||
'crashed', # e.g. segfault
|
||||
@@ -485,6 +487,7 @@ class AbstractHistory:
|
||||
raise NotImplementedError
|
||||
|
||||
def back(self, count=1):
|
||||
"""Go back in the tab's history."""
|
||||
idx = self.current_idx() - count
|
||||
if idx >= 0:
|
||||
self._go_to_item(self._item_at(idx))
|
||||
@@ -493,6 +496,7 @@ class AbstractHistory:
|
||||
raise WebTabError("At beginning of history.")
|
||||
|
||||
def forward(self, count=1):
|
||||
"""Go forward in the tab's history."""
|
||||
idx = self.current_idx() + count
|
||||
if idx < len(self):
|
||||
self._go_to_item(self._item_at(idx))
|
||||
@@ -702,8 +706,8 @@ class AbstractTab(QWidget):
|
||||
# This only gives us some mild protection against re-using events, but
|
||||
# it's certainly better than a segfault.
|
||||
if getattr(evt, 'posted', False):
|
||||
raise AssertionError("Can't re-use an event which was already "
|
||||
"posted!")
|
||||
raise utils.Unreachable("Can't re-use an event which was already "
|
||||
"posted!")
|
||||
recipient = self.event_target()
|
||||
evt.posted = True
|
||||
QApplication.postEvent(recipient, evt)
|
||||
@@ -745,6 +749,10 @@ class AbstractTab(QWidget):
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _on_load_finished(self, ok):
|
||||
if sip.isdeleted(self._widget):
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/3498
|
||||
return
|
||||
|
||||
sess_manager = objreg.get('session-manager')
|
||||
sess_manager.save_autosave()
|
||||
|
||||
@@ -863,3 +871,6 @@ class AbstractTab(QWidget):
|
||||
except (AttributeError, RuntimeError) as exc:
|
||||
url = '<{}>'.format(exc.__class__.__name__)
|
||||
return utils.get_repr(self, tab_id=self.tab_id, url=url)
|
||||
|
||||
def is_deleted(self):
|
||||
return sip.isdeleted(self._widget)
|
||||
|
||||
@@ -39,7 +39,7 @@ from qutebrowser.browser import (urlmarks, browsertab, inspector, navigate,
|
||||
webelem, downloads)
|
||||
from qutebrowser.keyinput import modeman
|
||||
from qutebrowser.utils import (message, usertypes, log, qtutils, urlutils,
|
||||
objreg, utils, debug)
|
||||
objreg, utils, debug, standarddir)
|
||||
from qutebrowser.utils.usertypes import KeyMode
|
||||
from qutebrowser.misc import editor, guiprocess
|
||||
from qutebrowser.completion.models import urlmodel, miscmodels
|
||||
@@ -353,6 +353,10 @@ class CommandDispatcher:
|
||||
Return:
|
||||
A list of URLs that can be opened.
|
||||
"""
|
||||
if isinstance(url, QUrl):
|
||||
yield url
|
||||
return
|
||||
|
||||
force_search = False
|
||||
urllist = [u for u in url.split('\n') if u.strip()]
|
||||
if (len(urllist) > 1 and not urlutils.is_url(urllist[0]) and
|
||||
@@ -514,14 +518,58 @@ class CommandDispatcher:
|
||||
return newtab
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('index', completion=miscmodels.other_buffer)
|
||||
def tab_take(self, index):
|
||||
"""Take a tab from another window.
|
||||
|
||||
Args:
|
||||
index: The [win_id/]index of the tab to take. Or a substring
|
||||
in which case the closest match will be taken.
|
||||
"""
|
||||
tabbed_browser, tab = self._resolve_buffer_index(index)
|
||||
|
||||
if tabbed_browser is self._tabbed_browser:
|
||||
raise cmdexc.CommandError("Can't take a tab from the same window")
|
||||
|
||||
self._open(tab.url(), tab=True)
|
||||
tabbed_browser.close_tab(tab, add_undo=False)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('win_id', completion=miscmodels.window)
|
||||
def tab_give(self, win_id: int = None):
|
||||
"""Give the current tab to a new or existing window if win_id given.
|
||||
|
||||
If no win_id is given, the tab will get detached into a new window.
|
||||
|
||||
Args:
|
||||
win_id: The window ID of the window to give the current tab to.
|
||||
"""
|
||||
if win_id == self._win_id:
|
||||
raise cmdexc.CommandError("Can't give a tab to the same window")
|
||||
|
||||
if win_id is None:
|
||||
if self._count() < 2:
|
||||
raise cmdexc.CommandError("Cannot detach from a window with "
|
||||
"only one tab")
|
||||
|
||||
tabbed_browser = self._new_tabbed_browser(
|
||||
private=self._tabbed_browser.private)
|
||||
else:
|
||||
if win_id not in objreg.window_registry:
|
||||
raise cmdexc.CommandError(
|
||||
"There's no window with id {}!".format(win_id))
|
||||
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
|
||||
tabbed_browser.tabopen(self._current_url())
|
||||
self._tabbed_browser.close_tab(self._current_widget(), add_undo=False)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
deprecated='Use :tab-give instead!')
|
||||
def tab_detach(self):
|
||||
"""Detach the current tab to its own window."""
|
||||
if self._count() < 2:
|
||||
raise cmdexc.CommandError("Cannot detach one tab.")
|
||||
url = self._current_url()
|
||||
self._open(url, window=True)
|
||||
cur_widget = self._current_widget()
|
||||
self._tabbed_browser.close_tab(cur_widget, add_undo=False)
|
||||
"""Deprecated way to detach a tab."""
|
||||
self.tab_give()
|
||||
|
||||
def _back_forward(self, tab, bg, window, count, forward):
|
||||
"""Helper function for :back/:forward."""
|
||||
@@ -625,12 +673,11 @@ class CommandDispatcher:
|
||||
self._open(new_url, tab, bg, window, related=True)
|
||||
else: # pragma: no cover
|
||||
raise ValueError("Got called with invalid value {} for "
|
||||
"`where'.".format(where))
|
||||
"`where'.".format(where))
|
||||
except navigate.Error as e:
|
||||
raise cmdexc.CommandError(e)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
scope='window')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
def scroll_px(self, dx: int, dy: int, count=1):
|
||||
"""Scroll the current tab by 'count * dx/dy' pixels.
|
||||
@@ -646,8 +693,7 @@ class CommandDispatcher:
|
||||
cmdutils.check_overflow(dy, 'int')
|
||||
self._current_widget().scroller.delta(dx, dy)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
scope='window')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
def scroll(self, direction: typing.Union[str, int], count=1):
|
||||
"""Scroll the current tab in the given direction.
|
||||
@@ -684,8 +730,7 @@ class CommandDispatcher:
|
||||
else:
|
||||
func(count=count)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
scope='window')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
@cmdutils.argument('horizontal', flag='x')
|
||||
def scroll_to_perc(self, perc: float = None, horizontal=False, count=None):
|
||||
@@ -716,8 +761,7 @@ class CommandDispatcher:
|
||||
|
||||
self._current_widget().scroller.to_perc(x, y)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
scope='window')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
@cmdutils.argument('top_navigate', metavar='ACTION',
|
||||
choices=('prev', 'decrement'))
|
||||
@@ -766,7 +810,7 @@ class CommandDispatcher:
|
||||
flags |= QUrl.FullyEncoded
|
||||
url = QUrl(self._current_url())
|
||||
url_query = QUrlQuery()
|
||||
url_query_str = url.query()
|
||||
url_query_str = urlutils.query_string(url)
|
||||
if '&' not in url_query_str and ';' in url_query_str:
|
||||
url_query.setQueryDelimiters('=', ';')
|
||||
url_query.setQuery(url_query_str)
|
||||
@@ -920,13 +964,15 @@ class CommandDispatcher:
|
||||
prev=prev, next_=next_, force=True))
|
||||
return
|
||||
|
||||
first_tab = True
|
||||
for i, tab in enumerate(self._tabbed_browser.widgets()):
|
||||
if _to_close(i):
|
||||
self._tabbed_browser.close_tab(tab)
|
||||
self._tabbed_browser.close_tab(tab, new_undo=first_tab)
|
||||
first_tab = False
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def undo(self):
|
||||
"""Re-open a closed tab."""
|
||||
"""Re-open the last closed tab or tabs."""
|
||||
try:
|
||||
self._tabbed_browser.undo()
|
||||
except IndexError:
|
||||
@@ -972,77 +1018,27 @@ class CommandDispatcher:
|
||||
else:
|
||||
raise cmdexc.CommandError("Last tab")
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
deprecated="Use :open {clipboard}")
|
||||
def paste(self, sel=False, tab=False, bg=False, window=False):
|
||||
"""Open a page from the clipboard.
|
||||
|
||||
If the pasted text contains newlines, each line gets opened in its own
|
||||
tab.
|
||||
def _resolve_buffer_index(self, index):
|
||||
"""Resolve a buffer index to the tabbedbrowser and tab.
|
||||
|
||||
Args:
|
||||
sel: Use the primary selection instead of the clipboard.
|
||||
tab: Open in a new tab.
|
||||
bg: Open in a background tab.
|
||||
window: Open in new window.
|
||||
"""
|
||||
force_search = False
|
||||
if not utils.supports_selection():
|
||||
sel = False
|
||||
try:
|
||||
text = utils.get_clipboard(selection=sel)
|
||||
except utils.ClipboardError as e:
|
||||
raise cmdexc.CommandError(e)
|
||||
text_urls = [u for u in text.split('\n') if u.strip()]
|
||||
if (len(text_urls) > 1 and not urlutils.is_url(text_urls[0]) and
|
||||
urlutils.get_path_if_valid(
|
||||
text_urls[0], check_exists=True) is None):
|
||||
force_search = True
|
||||
text_urls = [text]
|
||||
for i, text_url in enumerate(text_urls):
|
||||
if not window and i > 0:
|
||||
tab = False
|
||||
bg = True
|
||||
try:
|
||||
url = urlutils.fuzzy_url(text_url, force_search=force_search)
|
||||
except urlutils.InvalidUrlError as e:
|
||||
raise cmdexc.CommandError(e)
|
||||
self._open(url, tab, bg, window)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('index', completion=miscmodels.buffer)
|
||||
@cmdutils.argument('count', count=True)
|
||||
def buffer(self, index=None, count=None):
|
||||
"""Select tab by index or url/title best match.
|
||||
|
||||
Focuses window if necessary when index is given. If both index and
|
||||
count are given, use count.
|
||||
|
||||
Args:
|
||||
index: The [win_id/]index of the tab to focus. Or a substring
|
||||
index: The [win_id/]index of the tab to be selected. Or a substring
|
||||
in which case the closest match will be focused.
|
||||
count: The tab index to focus, starting with 1.
|
||||
"""
|
||||
if count is not None:
|
||||
index_parts = [count]
|
||||
elif index is None:
|
||||
raise cmdexc.CommandError("buffer: Either a count or the argument "
|
||||
"index must be specified.")
|
||||
else:
|
||||
index_parts = index.split('/', 1)
|
||||
index_parts = index.split('/', 1)
|
||||
|
||||
try:
|
||||
for part in index_parts:
|
||||
int(part)
|
||||
except ValueError:
|
||||
model = miscmodels.buffer()
|
||||
model.set_pattern(index)
|
||||
if model.count() > 0:
|
||||
index = model.data(model.first_item())
|
||||
index_parts = index.split('/', 1)
|
||||
else:
|
||||
raise cmdexc.CommandError(
|
||||
"No matching tab for: {}".format(index))
|
||||
try:
|
||||
for part in index_parts:
|
||||
int(part)
|
||||
except ValueError:
|
||||
model = miscmodels.buffer()
|
||||
model.set_pattern(index)
|
||||
if model.count() > 0:
|
||||
index = model.data(model.first_item())
|
||||
index_parts = index.split('/', 1)
|
||||
else:
|
||||
raise cmdexc.CommandError(
|
||||
"No matching tab for: {}".format(index))
|
||||
|
||||
if len(index_parts) == 2:
|
||||
win_id = int(index_parts[0])
|
||||
@@ -1066,10 +1062,36 @@ class CommandDispatcher:
|
||||
raise cmdexc.CommandError(
|
||||
"There's no tab with index {}!".format(idx))
|
||||
|
||||
window = objreg.window_registry[win_id]
|
||||
return (tabbed_browser, tabbed_browser.widget(idx-1))
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
maxsplit=0)
|
||||
@cmdutils.argument('index', completion=miscmodels.buffer)
|
||||
@cmdutils.argument('count', count=True)
|
||||
def buffer(self, index=None, count=None):
|
||||
"""Select tab by index or url/title best match.
|
||||
|
||||
Focuses window if necessary when index is given. If both index and
|
||||
count are given, use count.
|
||||
|
||||
Args:
|
||||
index: The [win_id/]index of the tab to focus. Or a substring
|
||||
in which case the closest match will be focused.
|
||||
count: The tab index to focus, starting with 1.
|
||||
"""
|
||||
if count is None and index is None:
|
||||
raise cmdexc.CommandError("buffer: Either a count or the argument "
|
||||
"index must be specified.")
|
||||
|
||||
if count is not None:
|
||||
index = str(count)
|
||||
|
||||
tabbed_browser, tab = self._resolve_buffer_index(index)
|
||||
|
||||
window = tabbed_browser.window()
|
||||
window.activateWindow()
|
||||
window.raise_()
|
||||
tabbed_browser.setCurrentIndex(idx-1)
|
||||
tabbed_browser.setCurrentWidget(tab)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('index', choices=['last'])
|
||||
@@ -1155,7 +1177,8 @@ class CommandDispatcher:
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
maxsplit=0, no_replace_variables=True)
|
||||
def spawn(self, cmdline, userscript=False, verbose=False, detach=False):
|
||||
def spawn(self, cmdline, userscript=False, verbose=False,
|
||||
output=False, detach=False):
|
||||
"""Spawn a command in a shell.
|
||||
|
||||
Args:
|
||||
@@ -1166,6 +1189,7 @@ class CommandDispatcher:
|
||||
(or `$XDG_DATA_DIR`)
|
||||
- `/usr/share/qutebrowser/userscripts`
|
||||
verbose: Show notifications when the command started/exited.
|
||||
output: Whether the output should be shown in a new tab.
|
||||
detach: Whether the command should be detached from qutebrowser.
|
||||
cmdline: The commandline to execute.
|
||||
"""
|
||||
@@ -1192,10 +1216,15 @@ class CommandDispatcher:
|
||||
else:
|
||||
proc.start(cmd, args)
|
||||
|
||||
if output:
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window='last-focused')
|
||||
tabbed_browser.openurl(QUrl('qute://spawn-output'), newtab=True)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def home(self):
|
||||
"""Open main startpage in current tab."""
|
||||
self._current_widget().openurl(config.val.url.start_pages[0])
|
||||
self.openurl(config.val.url.start_pages[0])
|
||||
|
||||
def _run_userscript(self, cmd, *args, verbose=False):
|
||||
"""Run a userscript given as argument.
|
||||
@@ -1362,8 +1391,7 @@ class CommandDispatcher:
|
||||
except KeyError:
|
||||
raise cmdexc.CommandError("Bookmark '{}' not found!".format(url))
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
scope='window')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def follow_selected(self, *, tab=False):
|
||||
"""Follow the selected text.
|
||||
|
||||
@@ -1397,27 +1425,14 @@ class CommandDispatcher:
|
||||
raise cmdexc.CommandError(e)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('dest_old', hide=True)
|
||||
def download(self, url=None, dest_old=None, *, mhtml_=False, dest=None):
|
||||
def download(self, url=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(":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
|
||||
|
||||
# FIXME:qtwebengine do this with the QtWebEngine download manager?
|
||||
download_manager = objreg.get('qtnetwork-download-manager',
|
||||
scope='window', window=self._win_id)
|
||||
@@ -1467,8 +1482,6 @@ class CommandDispatcher:
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def view_source(self):
|
||||
"""Show the source of the current page in a new tab."""
|
||||
# pylint: disable=no-member
|
||||
# WORKAROUND for https://bitbucket.org/logilab/pylint/issue/491/
|
||||
tab = self._current_widget()
|
||||
if tab.data.viewing_source:
|
||||
raise cmdexc.CommandError("Already viewing source!")
|
||||
@@ -1481,10 +1494,13 @@ class CommandDispatcher:
|
||||
|
||||
def show_source_cb(source):
|
||||
"""Show source as soon as it's ready."""
|
||||
# WORKAROUND for https://github.com/PyCQA/pylint/issues/491
|
||||
# pylint: disable=no-member
|
||||
lexer = pygments.lexers.HtmlLexer()
|
||||
formatter = pygments.formatters.HtmlFormatter(
|
||||
full=True, linenos='table',
|
||||
title='Source for {}'.format(current_url.toDisplayString()))
|
||||
# pylint: enable=no-member
|
||||
highlighted = pygments.highlight(source, lexer, formatter)
|
||||
|
||||
new_tab = self._tabbed_browser.tabopen()
|
||||
@@ -1506,6 +1522,7 @@ class CommandDispatcher:
|
||||
dest = os.path.expanduser(dest)
|
||||
|
||||
def callback(data):
|
||||
"""Write the data to disk."""
|
||||
try:
|
||||
with open(dest, 'w', encoding='utf-8') as f:
|
||||
f.write(data)
|
||||
@@ -1594,13 +1611,14 @@ class CommandDispatcher:
|
||||
return
|
||||
assert isinstance(text, str), text
|
||||
|
||||
caret_position = elem.caret_position()
|
||||
|
||||
ed = editor.ExternalEditor(self._tabbed_browser)
|
||||
ed.editing_finished.connect(functools.partial(
|
||||
self.on_editing_finished, elem))
|
||||
ed.edit(text)
|
||||
ed.edit(text, caret_position)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
scope='window')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def open_editor(self):
|
||||
"""Open an external editor with the currently selected form field.
|
||||
|
||||
@@ -1621,16 +1639,12 @@ class CommandDispatcher:
|
||||
"""
|
||||
try:
|
||||
elem.set_value(text)
|
||||
except webelem.OrphanedError as e:
|
||||
message.error('Edited element vanished')
|
||||
except webelem.Error as e:
|
||||
raise cmdexc.CommandError(str(e))
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher',
|
||||
deprecated="Use :insert-text {primary}",
|
||||
modes=[KeyMode.insert], hide=True, scope='window',
|
||||
backend=usertypes.Backend.QtWebKit)
|
||||
def paste_primary(self):
|
||||
"""Paste the primary selection at cursor position."""
|
||||
self.insert_text(utils.get_clipboard(selection=True, fallback=True))
|
||||
mainwindow.raise_window(objreg.last_focused_window(), alert=False)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', maxsplit=0,
|
||||
scope='window')
|
||||
@@ -1654,8 +1668,7 @@ class CommandDispatcher:
|
||||
|
||||
tab.elements.find_focused(_insert_text_cb)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
hide=True)
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('filter_', choices=['id'])
|
||||
def click_element(self, filter_: str, value, *,
|
||||
target: usertypes.ClickTarget =
|
||||
@@ -1726,7 +1739,8 @@ class CommandDispatcher:
|
||||
elif going_up and tab.scroller.pos_px().y() > old_scroll_pos.y():
|
||||
message.info("Search hit TOP, continuing at BOTTOM")
|
||||
else:
|
||||
message.warning("Text '{}' not found on page!".format(text))
|
||||
message.warning("Text '{}' not found on page!".format(text),
|
||||
replace=True)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
maxsplit=0)
|
||||
@@ -1746,7 +1760,7 @@ class CommandDispatcher:
|
||||
return
|
||||
|
||||
options = {
|
||||
'ignore_case': config.val.ignore_case,
|
||||
'ignore_case': config.val.search.ignore_case,
|
||||
'reverse': reverse,
|
||||
}
|
||||
|
||||
@@ -1760,8 +1774,7 @@ class CommandDispatcher:
|
||||
|
||||
tab.search.search(text, **options)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
scope='window')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
def search_next(self, count=1):
|
||||
"""Continue the search to the ([count]th) next term.
|
||||
@@ -1795,8 +1808,7 @@ class CommandDispatcher:
|
||||
tab.search.next_result()
|
||||
tab.search.next_result(result_cb=cb)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
scope='window')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
def search_prev(self, count=1):
|
||||
"""Continue the search to the ([count]th) previous term.
|
||||
@@ -1830,8 +1842,8 @@ class CommandDispatcher:
|
||||
tab.search.prev_result()
|
||||
tab.search.prev_result(result_cb=cb)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
modes=[KeyMode.caret], scope='window')
|
||||
@cmdutils.register(instance='command-dispatcher', modes=[KeyMode.caret],
|
||||
scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
def move_to_next_line(self, count=1):
|
||||
"""Move the cursor or selection to the next line.
|
||||
@@ -1841,8 +1853,8 @@ class CommandDispatcher:
|
||||
"""
|
||||
self._current_widget().caret.move_to_next_line(count)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
modes=[KeyMode.caret], scope='window')
|
||||
@cmdutils.register(instance='command-dispatcher', modes=[KeyMode.caret],
|
||||
scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
def move_to_prev_line(self, count=1):
|
||||
"""Move the cursor or selection to the prev line.
|
||||
@@ -1852,8 +1864,8 @@ class CommandDispatcher:
|
||||
"""
|
||||
self._current_widget().caret.move_to_prev_line(count)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
modes=[KeyMode.caret], scope='window')
|
||||
@cmdutils.register(instance='command-dispatcher', modes=[KeyMode.caret],
|
||||
scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
def move_to_next_char(self, count=1):
|
||||
"""Move the cursor or selection to the next char.
|
||||
@@ -1863,8 +1875,8 @@ class CommandDispatcher:
|
||||
"""
|
||||
self._current_widget().caret.move_to_next_char(count)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
modes=[KeyMode.caret], scope='window')
|
||||
@cmdutils.register(instance='command-dispatcher', modes=[KeyMode.caret],
|
||||
scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
def move_to_prev_char(self, count=1):
|
||||
"""Move the cursor or selection to the previous char.
|
||||
@@ -1874,8 +1886,8 @@ class CommandDispatcher:
|
||||
"""
|
||||
self._current_widget().caret.move_to_prev_char(count)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
modes=[KeyMode.caret], scope='window')
|
||||
@cmdutils.register(instance='command-dispatcher', modes=[KeyMode.caret],
|
||||
scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
def move_to_end_of_word(self, count=1):
|
||||
"""Move the cursor or selection to the end of the word.
|
||||
@@ -1885,8 +1897,8 @@ class CommandDispatcher:
|
||||
"""
|
||||
self._current_widget().caret.move_to_end_of_word(count)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
modes=[KeyMode.caret], scope='window')
|
||||
@cmdutils.register(instance='command-dispatcher', modes=[KeyMode.caret],
|
||||
scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
def move_to_next_word(self, count=1):
|
||||
"""Move the cursor or selection to the next word.
|
||||
@@ -1896,8 +1908,8 @@ class CommandDispatcher:
|
||||
"""
|
||||
self._current_widget().caret.move_to_next_word(count)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
modes=[KeyMode.caret], scope='window')
|
||||
@cmdutils.register(instance='command-dispatcher', modes=[KeyMode.caret],
|
||||
scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
def move_to_prev_word(self, count=1):
|
||||
"""Move the cursor or selection to the previous word.
|
||||
@@ -1907,20 +1919,20 @@ class CommandDispatcher:
|
||||
"""
|
||||
self._current_widget().caret.move_to_prev_word(count)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
modes=[KeyMode.caret], scope='window')
|
||||
@cmdutils.register(instance='command-dispatcher', modes=[KeyMode.caret],
|
||||
scope='window')
|
||||
def move_to_start_of_line(self):
|
||||
"""Move the cursor or selection to the start of the line."""
|
||||
self._current_widget().caret.move_to_start_of_line()
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
modes=[KeyMode.caret], scope='window')
|
||||
@cmdutils.register(instance='command-dispatcher', modes=[KeyMode.caret],
|
||||
scope='window')
|
||||
def move_to_end_of_line(self):
|
||||
"""Move the cursor or selection to the end of line."""
|
||||
self._current_widget().caret.move_to_end_of_line()
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
modes=[KeyMode.caret], scope='window')
|
||||
@cmdutils.register(instance='command-dispatcher', modes=[KeyMode.caret],
|
||||
scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
def move_to_start_of_next_block(self, count=1):
|
||||
"""Move the cursor or selection to the start of next block.
|
||||
@@ -1930,8 +1942,8 @@ class CommandDispatcher:
|
||||
"""
|
||||
self._current_widget().caret.move_to_start_of_next_block(count)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
modes=[KeyMode.caret], scope='window')
|
||||
@cmdutils.register(instance='command-dispatcher', modes=[KeyMode.caret],
|
||||
scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
def move_to_start_of_prev_block(self, count=1):
|
||||
"""Move the cursor or selection to the start of previous block.
|
||||
@@ -1941,8 +1953,8 @@ class CommandDispatcher:
|
||||
"""
|
||||
self._current_widget().caret.move_to_start_of_prev_block(count)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
modes=[KeyMode.caret], scope='window')
|
||||
@cmdutils.register(instance='command-dispatcher', modes=[KeyMode.caret],
|
||||
scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
def move_to_end_of_next_block(self, count=1):
|
||||
"""Move the cursor or selection to the end of next block.
|
||||
@@ -1952,8 +1964,8 @@ class CommandDispatcher:
|
||||
"""
|
||||
self._current_widget().caret.move_to_end_of_next_block(count)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
modes=[KeyMode.caret], scope='window')
|
||||
@cmdutils.register(instance='command-dispatcher', modes=[KeyMode.caret],
|
||||
scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
def move_to_end_of_prev_block(self, count=1):
|
||||
"""Move the cursor or selection to the end of previous block.
|
||||
@@ -1963,26 +1975,26 @@ class CommandDispatcher:
|
||||
"""
|
||||
self._current_widget().caret.move_to_end_of_prev_block(count)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
modes=[KeyMode.caret], scope='window')
|
||||
@cmdutils.register(instance='command-dispatcher', modes=[KeyMode.caret],
|
||||
scope='window')
|
||||
def move_to_start_of_document(self):
|
||||
"""Move the cursor or selection to the start of the document."""
|
||||
self._current_widget().caret.move_to_start_of_document()
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
modes=[KeyMode.caret], scope='window')
|
||||
@cmdutils.register(instance='command-dispatcher', modes=[KeyMode.caret],
|
||||
scope='window')
|
||||
def move_to_end_of_document(self):
|
||||
"""Move the cursor or selection to the end of the document."""
|
||||
self._current_widget().caret.move_to_end_of_document()
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
modes=[KeyMode.caret], scope='window')
|
||||
@cmdutils.register(instance='command-dispatcher', modes=[KeyMode.caret],
|
||||
scope='window')
|
||||
def toggle_selection(self):
|
||||
"""Toggle caret selection mode."""
|
||||
self._current_widget().caret.toggle_selection()
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
modes=[KeyMode.caret], scope='window')
|
||||
@cmdutils.register(instance='command-dispatcher', modes=[KeyMode.caret],
|
||||
scope='window')
|
||||
def drop_selection(self):
|
||||
"""Drop selection and keep selection mode enabled."""
|
||||
self._current_widget().caret.drop_selection()
|
||||
@@ -2017,6 +2029,9 @@ class CommandDispatcher:
|
||||
Args:
|
||||
js_code: The string/file to evaluate.
|
||||
file: Interpret js-code as a path to a file.
|
||||
If the path is relative, the file is searched in a js/ subdir
|
||||
in qutebrowser's data dir, e.g.
|
||||
`~/.local/share/qutebrowser/js`.
|
||||
quiet: Don't show resulting JS object.
|
||||
world: Ignored on QtWebKit. On QtWebEngine, a world ID or name to
|
||||
run the snippet in.
|
||||
@@ -2028,6 +2043,7 @@ class CommandDispatcher:
|
||||
jseval_cb = None
|
||||
else:
|
||||
def jseval_cb(out):
|
||||
"""Show the data returned from JS."""
|
||||
if out is None:
|
||||
# Getting the actual error (if any) seems to be difficult.
|
||||
# The error does end up in
|
||||
@@ -2046,6 +2062,9 @@ class CommandDispatcher:
|
||||
|
||||
if file:
|
||||
path = os.path.expanduser(js_code)
|
||||
if not os.path.isabs(path):
|
||||
path = os.path.join(standarddir.data(), 'js', path)
|
||||
|
||||
try:
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
js_code = f.read()
|
||||
@@ -2096,7 +2115,8 @@ class CommandDispatcher:
|
||||
self._current_widget().clear_ssl_errors()
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def edit_url(self, url=None, bg=False, tab=False, window=False):
|
||||
def edit_url(self, url=None, bg=False, tab=False, window=False,
|
||||
private=False, related=False):
|
||||
"""Navigate to a url formed in an external editor.
|
||||
|
||||
The editor which should be launched can be configured via the
|
||||
@@ -2107,6 +2127,9 @@ class CommandDispatcher:
|
||||
bg: Open in a new background tab.
|
||||
tab: Open in a new tab.
|
||||
window: Open in a new window.
|
||||
private: Open a new window in private browsing mode.
|
||||
related: If opening a new tab, position the tab as related to the
|
||||
current one (like clicking on a link).
|
||||
"""
|
||||
cmdutils.check_exclusive((tab, bg, window), 'tbw')
|
||||
|
||||
@@ -2117,12 +2140,11 @@ class CommandDispatcher:
|
||||
# Passthrough for openurl args (e.g. -t, -b, -w)
|
||||
ed.editing_finished.connect(functools.partial(
|
||||
self._open_if_changed, old_url=old_url, bg=bg, tab=tab,
|
||||
window=window))
|
||||
window=window, private=private, related=related))
|
||||
|
||||
ed.edit(url or old_url)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
hide=True)
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def set_mark(self, key):
|
||||
"""Set a mark at the current scroll position in the current tab.
|
||||
|
||||
@@ -2131,8 +2153,7 @@ class CommandDispatcher:
|
||||
"""
|
||||
self._tabbed_browser.set_mark(key)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
hide=True)
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def jump_mark(self, key):
|
||||
"""Jump to the mark named by `key`.
|
||||
|
||||
@@ -2142,7 +2163,7 @@ class CommandDispatcher:
|
||||
self._tabbed_browser.jump_mark(key)
|
||||
|
||||
def _open_if_changed(self, url=None, old_url=None, bg=False, tab=False,
|
||||
window=False):
|
||||
window=False, private=False, related=False):
|
||||
"""Open a URL unless it's already open in the tab.
|
||||
|
||||
Args:
|
||||
@@ -2151,9 +2172,13 @@ class CommandDispatcher:
|
||||
bg: Open in a new background tab.
|
||||
tab: Open in a new tab.
|
||||
window: Open in a new window.
|
||||
private: Open a new window in private browsing mode.
|
||||
related: If opening a new tab, position the tab as related to the
|
||||
current one (like clicking on a link).
|
||||
"""
|
||||
if bg or tab or window or url != old_url:
|
||||
self.openurl(url=url, bg=bg, tab=tab, window=window)
|
||||
if bg or tab or window or private or related or url != old_url:
|
||||
self.openurl(url=url, bg=bg, tab=tab, window=window,
|
||||
private=private, related=related)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def fullscreen(self, leave=False):
|
||||
|
||||
@@ -27,6 +27,7 @@ import collections
|
||||
import functools
|
||||
import pathlib
|
||||
import tempfile
|
||||
import enum
|
||||
|
||||
import sip
|
||||
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex,
|
||||
@@ -38,8 +39,7 @@ from qutebrowser.utils import (usertypes, standarddir, utils, message, log,
|
||||
qtutils)
|
||||
|
||||
|
||||
ModelRole = usertypes.enum('ModelRole', ['item'], start=Qt.UserRole,
|
||||
is_int=True)
|
||||
ModelRole = enum.IntEnum('ModelRole', ['item'], start=Qt.UserRole)
|
||||
|
||||
|
||||
# Remember the last used directory
|
||||
@@ -103,6 +103,8 @@ def immediate_download_path(prompt_download_directory=None):
|
||||
if not prompt_download_directory:
|
||||
return download_dir()
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _path_suggestion(filename):
|
||||
"""Get the suggested file path.
|
||||
@@ -137,7 +139,8 @@ def create_full_filename(basename, filename):
|
||||
encoding = sys.getfilesystemencoding()
|
||||
filename = utils.force_encoding(filename, encoding)
|
||||
basename = utils.force_encoding(basename, encoding)
|
||||
if os.path.isabs(filename) and os.path.isdir(filename):
|
||||
if os.path.isabs(filename) and (os.path.isdir(filename) or
|
||||
filename.endswith(os.sep)):
|
||||
# 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)
|
||||
@@ -179,7 +182,7 @@ def transform_path(path):
|
||||
path = utils.expand_windows_drive(path)
|
||||
# Drive dependent working directories are not supported, e.g.
|
||||
# E:filename is invalid
|
||||
if re.match(r'[A-Z]:[^\\]', path, re.IGNORECASE):
|
||||
if re.search(r'^[A-Z]:[^\\]', path, re.IGNORECASE):
|
||||
return None
|
||||
# Paths like COM1, ...
|
||||
# See https://github.com/qutebrowser/qutebrowser/issues/82
|
||||
@@ -604,6 +607,11 @@ class AbstractDownloadItem(QObject):
|
||||
"""Ask a confirmation question for the download."""
|
||||
raise NotImplementedError
|
||||
|
||||
def _ask_create_parent_question(self, title, msg,
|
||||
force_overwrite, remember_directory):
|
||||
"""Ask a confirmation question for the parent directory."""
|
||||
raise NotImplementedError
|
||||
|
||||
def _set_fileobj(self, fileobj, *, autoclose=True):
|
||||
"""Set a file object to save the download to.
|
||||
|
||||
@@ -630,7 +638,6 @@ class AbstractDownloadItem(QObject):
|
||||
remember_directory: If True, remember the directory for future
|
||||
downloads.
|
||||
"""
|
||||
global last_used_directory
|
||||
filename = os.path.expanduser(filename)
|
||||
self._ensure_can_set_filename(filename)
|
||||
|
||||
@@ -657,11 +664,41 @@ class AbstractDownloadItem(QObject):
|
||||
self._filename = create_full_filename(self.basename,
|
||||
os.path.expanduser('~'))
|
||||
|
||||
dirname = os.path.dirname(self._filename)
|
||||
if not os.path.exists(dirname):
|
||||
txt = ("<b>{}</b> does not exist. Create it?".
|
||||
format(html.escape(
|
||||
os.path.join(dirname, ""))))
|
||||
self._ask_create_parent_question("Create directory?", txt,
|
||||
force_overwrite,
|
||||
remember_directory)
|
||||
else:
|
||||
self._after_create_parent_question(force_overwrite,
|
||||
remember_directory)
|
||||
|
||||
def _after_create_parent_question(self,
|
||||
force_overwrite, remember_directory):
|
||||
"""After asking about parent directory.
|
||||
|
||||
Args:
|
||||
force_overwrite: Force overwriting existing files.
|
||||
remember_directory: If True, remember the directory for future
|
||||
downloads.
|
||||
"""
|
||||
global last_used_directory
|
||||
|
||||
try:
|
||||
os.makedirs(os.path.dirname(self._filename))
|
||||
except FileExistsError:
|
||||
pass
|
||||
except OSError as e:
|
||||
self._die(e.strerror)
|
||||
|
||||
self.basename = os.path.basename(self._filename)
|
||||
if remember_directory:
|
||||
last_used_directory = os.path.dirname(self._filename)
|
||||
|
||||
log.downloads.debug("Setting filename to {}".format(filename))
|
||||
log.downloads.debug("Setting filename to {}".format(self._filename))
|
||||
if force_overwrite:
|
||||
self._after_set_filename()
|
||||
elif os.path.isfile(self._filename):
|
||||
@@ -955,7 +992,7 @@ class DownloadModel(QAbstractListModel):
|
||||
if not count:
|
||||
count = len(self)
|
||||
raise cmdexc.CommandError("Download {} is already done!"
|
||||
.format(count))
|
||||
.format(count))
|
||||
download.cancel()
|
||||
|
||||
@cmdutils.register(instance='download-model', scope='window')
|
||||
|
||||
224
qutebrowser/browser/greasemonkey.py
Normal file
224
qutebrowser/browser/greasemonkey.py
Normal file
@@ -0,0 +1,224 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Load, parse and make available Greasemonkey scripts."""
|
||||
|
||||
import re
|
||||
import os
|
||||
import json
|
||||
import fnmatch
|
||||
import functools
|
||||
import glob
|
||||
|
||||
import attr
|
||||
from PyQt5.QtCore import pyqtSignal, QObject, QUrl
|
||||
|
||||
from qutebrowser.utils import log, standarddir, jinja, objreg
|
||||
from qutebrowser.commands import cmdutils
|
||||
|
||||
|
||||
def _scripts_dir():
|
||||
"""Get the directory of the scripts."""
|
||||
return os.path.join(standarddir.data(), 'greasemonkey')
|
||||
|
||||
|
||||
class GreasemonkeyScript:
|
||||
|
||||
"""Container class for userscripts, parses metadata blocks."""
|
||||
|
||||
def __init__(self, properties, code):
|
||||
self._code = code
|
||||
self.includes = []
|
||||
self.excludes = []
|
||||
self.description = None
|
||||
self.name = None
|
||||
self.namespace = None
|
||||
self.run_at = None
|
||||
self.script_meta = None
|
||||
self.runs_on_sub_frames = True
|
||||
for name, value in properties:
|
||||
if name == 'name':
|
||||
self.name = value
|
||||
elif name == 'namespace':
|
||||
self.namespace = value
|
||||
elif name == 'description':
|
||||
self.description = value
|
||||
elif name in ['include', 'match']:
|
||||
self.includes.append(value)
|
||||
elif name in ['exclude', 'exclude_match']:
|
||||
self.excludes.append(value)
|
||||
elif name == 'run-at':
|
||||
self.run_at = value
|
||||
elif name == 'noframes':
|
||||
self.runs_on_sub_frames = False
|
||||
|
||||
HEADER_REGEX = r'// ==UserScript==|\n+// ==/UserScript==\n'
|
||||
PROPS_REGEX = r'// @(?P<prop>[^\s]+)\s*(?P<val>.*)'
|
||||
|
||||
@classmethod
|
||||
def parse(cls, source):
|
||||
"""GreasemonkeyScript factory.
|
||||
|
||||
Takes a userscript source and returns a GreasemonkeyScript.
|
||||
Parses the Greasemonkey metadata block, if present, to fill out
|
||||
attributes.
|
||||
"""
|
||||
matches = re.split(cls.HEADER_REGEX, source, maxsplit=2)
|
||||
try:
|
||||
_head, props, _code = matches
|
||||
except ValueError:
|
||||
props = ""
|
||||
script = cls(re.findall(cls.PROPS_REGEX, props), source)
|
||||
script.script_meta = props
|
||||
if not props:
|
||||
script.includes = ['*']
|
||||
return script
|
||||
|
||||
def code(self):
|
||||
"""Return the processed JavaScript code of this script.
|
||||
|
||||
Adorns the source code with GM_* methods for Greasemonkey
|
||||
compatibility and wraps it in an IFFE to hide it within a
|
||||
lexical scope. Note that this means line numbers in your
|
||||
browser's debugger/inspector will not match up to the line
|
||||
numbers in the source script directly.
|
||||
"""
|
||||
return jinja.js_environment.get_template(
|
||||
'greasemonkey_wrapper.js').render(
|
||||
scriptName="/".join([self.namespace or '', self.name]),
|
||||
scriptInfo=self._meta_json(),
|
||||
scriptMeta=self.script_meta,
|
||||
scriptSource=self._code)
|
||||
|
||||
def _meta_json(self):
|
||||
return json.dumps({
|
||||
'name': self.name,
|
||||
'description': self.description,
|
||||
'matches': self.includes,
|
||||
'includes': self.includes,
|
||||
'excludes': self.excludes,
|
||||
'run-at': self.run_at,
|
||||
})
|
||||
|
||||
|
||||
@attr.s
|
||||
class MatchingScripts(object):
|
||||
|
||||
"""All userscripts registered to run on a particular url."""
|
||||
|
||||
url = attr.ib()
|
||||
start = attr.ib(default=attr.Factory(list))
|
||||
end = attr.ib(default=attr.Factory(list))
|
||||
idle = attr.ib(default=attr.Factory(list))
|
||||
|
||||
|
||||
class GreasemonkeyManager(QObject):
|
||||
|
||||
"""Manager of userscripts and a Greasemonkey compatible environment.
|
||||
|
||||
Signals:
|
||||
scripts_reloaded: Emitted when scripts are reloaded from disk.
|
||||
Any cached or already-injected scripts should be
|
||||
considered obselete.
|
||||
"""
|
||||
|
||||
scripts_reloaded = pyqtSignal()
|
||||
# https://wiki.greasespot.net/Include_and_exclude_rules#Greaseable_schemes
|
||||
# Limit the schemes scripts can run on due to unreasonable levels of
|
||||
# exploitability
|
||||
greaseable_schemes = ['http', 'https', 'ftp', 'file']
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.load_scripts()
|
||||
|
||||
@cmdutils.register(name='greasemonkey-reload',
|
||||
instance='greasemonkey')
|
||||
def load_scripts(self):
|
||||
"""Re-read Greasemonkey scripts from disk.
|
||||
|
||||
The scripts are read from a 'greasemonkey' subdirectory in
|
||||
qutebrowser's data directory (see `:version`).
|
||||
"""
|
||||
self._run_start = []
|
||||
self._run_end = []
|
||||
self._run_idle = []
|
||||
|
||||
scripts_dir = os.path.abspath(_scripts_dir())
|
||||
log.greasemonkey.debug("Reading scripts from: {}".format(scripts_dir))
|
||||
for script_filename in glob.glob(os.path.join(scripts_dir, '*.js')):
|
||||
if not os.path.isfile(script_filename):
|
||||
continue
|
||||
script_path = os.path.join(scripts_dir, script_filename)
|
||||
with open(script_path, encoding='utf-8') as script_file:
|
||||
script = GreasemonkeyScript.parse(script_file.read())
|
||||
if not script.name:
|
||||
script.name = script_filename
|
||||
|
||||
if script.run_at == 'document-start':
|
||||
self._run_start.append(script)
|
||||
elif script.run_at == 'document-end':
|
||||
self._run_end.append(script)
|
||||
elif script.run_at == 'document-idle':
|
||||
self._run_idle.append(script)
|
||||
else:
|
||||
log.greasemonkey.warning("Script {} has invalid run-at "
|
||||
"defined, defaulting to "
|
||||
"document-end"
|
||||
.format(script_path))
|
||||
# Default as per
|
||||
# https://wiki.greasespot.net/Metadata_Block#.40run-at
|
||||
self._run_end.append(script)
|
||||
log.greasemonkey.debug("Loaded script: {}".format(script.name))
|
||||
self.scripts_reloaded.emit()
|
||||
|
||||
def scripts_for(self, url):
|
||||
"""Fetch scripts that are registered to run for url.
|
||||
|
||||
returns a tuple of lists of scripts meant to run at (document-start,
|
||||
document-end, document-idle)
|
||||
"""
|
||||
if url.scheme() not in self.greaseable_schemes:
|
||||
return MatchingScripts(url, [], [], [])
|
||||
match = functools.partial(fnmatch.fnmatch,
|
||||
url.toString(QUrl.FullyEncoded))
|
||||
tester = (lambda script:
|
||||
any(match(pat) for pat in script.includes) and
|
||||
not any(match(pat) for pat in script.excludes))
|
||||
return MatchingScripts(
|
||||
url,
|
||||
[script for script in self._run_start if tester(script)],
|
||||
[script for script in self._run_end if tester(script)],
|
||||
[script for script in self._run_idle if tester(script)]
|
||||
)
|
||||
|
||||
def all_scripts(self):
|
||||
"""Return all scripts found in the configured script directory."""
|
||||
return self._run_start + self._run_end + self._run_idle
|
||||
|
||||
|
||||
def init():
|
||||
"""Initialize Greasemonkey support."""
|
||||
gm_manager = GreasemonkeyManager()
|
||||
objreg.register('greasemonkey', gm_manager)
|
||||
|
||||
try:
|
||||
os.mkdir(_scripts_dir())
|
||||
except FileExistsError:
|
||||
pass
|
||||
@@ -24,6 +24,7 @@ import functools
|
||||
import math
|
||||
import re
|
||||
import html
|
||||
import enum
|
||||
from string import ascii_lowercase
|
||||
|
||||
import attr
|
||||
@@ -37,10 +38,9 @@ from qutebrowser.commands import userscripts, cmdexc, cmdutils, runners
|
||||
from qutebrowser.utils import usertypes, log, qtutils, message, objreg, utils
|
||||
|
||||
|
||||
Target = usertypes.enum('Target', ['normal', 'current', 'tab', 'tab_fg',
|
||||
'tab_bg', 'window', 'yank', 'yank_primary',
|
||||
'run', 'fill', 'hover', 'download',
|
||||
'userscript', 'spawn'])
|
||||
Target = enum.Enum('Target', ['normal', 'current', 'tab', 'tab_fg', 'tab_bg',
|
||||
'window', 'yank', 'yank_primary', 'run', 'fill',
|
||||
'hover', 'download', 'userscript', 'spawn'])
|
||||
|
||||
|
||||
class HintingError(Exception):
|
||||
@@ -390,7 +390,6 @@ class HintManager(QObject):
|
||||
|
||||
def _cleanup(self):
|
||||
"""Clean up after hinting."""
|
||||
# pylint: disable=not-an-iterable
|
||||
for label in self._context.all_labels:
|
||||
label.cleanup()
|
||||
|
||||
@@ -445,8 +444,17 @@ class HintManager(QObject):
|
||||
# Short hints are the number of hints we can possibly show which are
|
||||
# (needed - 1) digits in length.
|
||||
if needed > min_chars:
|
||||
short_count = math.floor((len(chars) ** needed - len(elems)) /
|
||||
total_space = len(chars) ** needed
|
||||
# Calculate short_count naively, by finding the avaiable space and
|
||||
# dividing by the number of spots we would loose by adding a
|
||||
# short element
|
||||
short_count = math.floor((total_space - len(elems)) /
|
||||
len(chars))
|
||||
# Check if we double counted above to warrant another short_count
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/3242
|
||||
if total_space - (short_count * len(chars) +
|
||||
(len(elems) - short_count)) >= len(chars) - 1:
|
||||
short_count += 1
|
||||
else:
|
||||
short_count = 0
|
||||
|
||||
@@ -611,13 +619,16 @@ class HintManager(QObject):
|
||||
@cmdutils.register(instance='hintmanager', scope='tab', name='hint',
|
||||
star_args_optional=True, maxsplit=2)
|
||||
@cmdutils.argument('win_id', win_id=True)
|
||||
def start(self, rapid=False, group=webelem.Group.all, target=Target.normal,
|
||||
*args, win_id, mode=None, add_history=False):
|
||||
def start(self, # pylint: disable=keyword-arg-before-vararg
|
||||
group=webelem.Group.all, target=Target.normal,
|
||||
*args, win_id, mode=None, add_history=False, rapid=False):
|
||||
"""Start hinting.
|
||||
|
||||
Args:
|
||||
rapid: Whether to do rapid hinting. This is only possible with
|
||||
targets `tab` (with `tabs.background_tabs=true`), `tab-bg`,
|
||||
rapid: Whether to do rapid hinting. With rapid hinting, the hint
|
||||
mode isn't left after a hint is followed, so you can easily
|
||||
open multiple links. This is only possible with targets
|
||||
`tab` (with `tabs.background_tabs=true`), `tab-bg`,
|
||||
`window`, `run`, `hover`, `userscript` and `spawn`.
|
||||
add_history: Whether to add the spawned or yanked link to the
|
||||
browsing history.
|
||||
@@ -797,7 +808,6 @@ class HintManager(QObject):
|
||||
log.hints.debug("Filtering hints on {!r}".format(filterstr))
|
||||
|
||||
visible = []
|
||||
# pylint: disable=not-an-iterable
|
||||
for label in self._context.all_labels:
|
||||
try:
|
||||
if self._filter_matches(filterstr, str(label.elem)):
|
||||
@@ -898,7 +908,7 @@ class HintManager(QObject):
|
||||
except HintingError as e:
|
||||
message.error(str(e))
|
||||
|
||||
@cmdutils.register(instance='hintmanager', scope='tab', hide=True,
|
||||
@cmdutils.register(instance='hintmanager', scope='tab',
|
||||
modes=[usertypes.KeyMode.hint])
|
||||
def follow_hint(self, keystring=None):
|
||||
"""Follow a hint.
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
import os
|
||||
import time
|
||||
import contextlib
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QUrl, QTimer
|
||||
|
||||
@@ -31,7 +32,7 @@ from qutebrowser.misc import objects, sql
|
||||
|
||||
|
||||
# increment to indicate that HistoryCompletion must be regenerated
|
||||
_USER_VERSION = 1
|
||||
_USER_VERSION = 2
|
||||
|
||||
|
||||
class CompletionHistory(sql.SqlTable):
|
||||
@@ -87,11 +88,22 @@ class WebHistory(sql.SqlTable):
|
||||
def __contains__(self, url):
|
||||
return self._contains_query.run(val=url).value()
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _handle_sql_errors(self):
|
||||
try:
|
||||
yield
|
||||
except sql.SqlError as e:
|
||||
if e.environmental:
|
||||
message.error("Failed to write history: {}".format(e.text()))
|
||||
else:
|
||||
raise
|
||||
|
||||
def _rebuild_completion(self):
|
||||
data = {'url': [], 'title': [], 'last_atime': []}
|
||||
# select the latest entry for each url
|
||||
q = sql.Query('SELECT url, title, max(atime) AS atime FROM History '
|
||||
'WHERE NOT redirect GROUP BY url ORDER BY atime asc')
|
||||
'WHERE NOT redirect and url NOT LIKE "qute://back%" '
|
||||
'GROUP BY url ORDER BY atime asc')
|
||||
for entry in q.run():
|
||||
data['url'].append(self._format_completion_url(QUrl(entry.url)))
|
||||
data['title'].append(entry.title)
|
||||
@@ -138,12 +150,13 @@ class WebHistory(sql.SqlTable):
|
||||
if force:
|
||||
self._do_clear()
|
||||
else:
|
||||
message.confirm_async(self._do_clear, title="Clear all browsing "
|
||||
"history?")
|
||||
message.confirm_async(yes_action=self._do_clear,
|
||||
title="Clear all browsing history?")
|
||||
|
||||
def _do_clear(self):
|
||||
self.delete_all()
|
||||
self.completion.delete_all()
|
||||
with self._handle_sql_errors():
|
||||
self.delete_all()
|
||||
self.completion.delete_all()
|
||||
|
||||
def delete_url(self, url):
|
||||
"""Remove all history entries with the given url.
|
||||
@@ -159,7 +172,9 @@ class WebHistory(sql.SqlTable):
|
||||
@pyqtSlot(QUrl, QUrl, str)
|
||||
def add_from_tab(self, url, requested_url, title):
|
||||
"""Add a new history entry as slot, called from a BrowserTab."""
|
||||
if url.scheme() == 'data' or requested_url.scheme() == 'data':
|
||||
if any(url.scheme() == 'data' or
|
||||
(url.scheme(), url.host()) == ('qute', 'back')
|
||||
for url in (url, requested_url)):
|
||||
return
|
||||
if url.isEmpty():
|
||||
# things set via setHtml
|
||||
@@ -191,7 +206,7 @@ class WebHistory(sql.SqlTable):
|
||||
|
||||
atime = int(atime) if (atime is not None) else int(time.time())
|
||||
|
||||
try:
|
||||
with self._handle_sql_errors():
|
||||
self.insert({'url': self._format_url(url),
|
||||
'title': title,
|
||||
'atime': atime,
|
||||
@@ -202,11 +217,6 @@ class WebHistory(sql.SqlTable):
|
||||
'title': title,
|
||||
'last_atime': atime
|
||||
}, replace=True)
|
||||
except sql.SqlError as e:
|
||||
if e.environmental:
|
||||
message.error("Failed to write history: {}".format(e.text()))
|
||||
else:
|
||||
raise
|
||||
|
||||
def _parse_entry(self, line):
|
||||
"""Parse a history line like '12345 http://example.com title'."""
|
||||
@@ -261,6 +271,7 @@ class WebHistory(sql.SqlTable):
|
||||
return
|
||||
|
||||
def action():
|
||||
"""Actually run the import."""
|
||||
with debug.log_time(log.init, 'Import old history file to sqlite'):
|
||||
try:
|
||||
self._read(path)
|
||||
@@ -333,7 +344,7 @@ class WebHistory(sql.SqlTable):
|
||||
f.write('\n'.join(lines))
|
||||
message.info("Dumped history to {}".format(dest))
|
||||
except OSError as e:
|
||||
raise cmdexc.CommandError('Could not write history: {}', e)
|
||||
raise cmdexc.CommandError('Could not write history: {}'.format(e))
|
||||
|
||||
|
||||
def init(parent=None):
|
||||
|
||||
@@ -94,6 +94,7 @@ class AbstractWebInspector(QWidget):
|
||||
raise NotImplementedError
|
||||
|
||||
def toggle(self, page):
|
||||
"""Show/hide the inspector."""
|
||||
if self._widget.isVisible():
|
||||
self.hide()
|
||||
else:
|
||||
|
||||
@@ -19,15 +19,13 @@
|
||||
|
||||
"""Mouse handling for a browser tab."""
|
||||
|
||||
from PyQt5.QtCore import QObject, QEvent, Qt, QTimer
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import message, log, usertypes
|
||||
from qutebrowser.keyinput import modeman
|
||||
|
||||
|
||||
from PyQt5.QtCore import QObject, QEvent, Qt, QTimer
|
||||
|
||||
|
||||
class ChildEventFilter(QObject):
|
||||
|
||||
"""An event filter re-adding MouseEventFilter on ChildEvent.
|
||||
|
||||
@@ -64,6 +64,7 @@ def path_up(url, count):
|
||||
raise Error("Can't go up!")
|
||||
for _i in range(0, min(count, path.count('/'))):
|
||||
path = posixpath.join(path, posixpath.pardir)
|
||||
path = posixpath.normpath(path)
|
||||
url.setPath(path)
|
||||
return url
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ def _js_slot(*args):
|
||||
def _decorator(method):
|
||||
@functools.wraps(method)
|
||||
def new_method(self, *args, **kwargs):
|
||||
"""Call the underlying function."""
|
||||
try:
|
||||
return method(self, *args, **kwargs)
|
||||
except:
|
||||
|
||||
@@ -82,7 +82,7 @@ def fix_urls(asset):
|
||||
('viewer.css', 'qute://pdfjs/web/viewer.css'),
|
||||
('compatibility.js', 'qute://pdfjs/web/compatibility.js'),
|
||||
('locale/locale.properties',
|
||||
'qute://pdfjs/web/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'),
|
||||
|
||||
@@ -203,8 +203,19 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
||||
no_action=no_action, cancel_action=no_action,
|
||||
abort_on=[self.cancelled, self.error])
|
||||
|
||||
def _ask_create_parent_question(self, title, msg,
|
||||
force_overwrite, remember_directory):
|
||||
no_action = functools.partial(self.cancel, remove_data=False)
|
||||
message.confirm_async(title=title, text=msg,
|
||||
yes_action=(lambda:
|
||||
self._after_create_parent_question(
|
||||
force_overwrite,
|
||||
remember_directory)),
|
||||
no_action=no_action, cancel_action=no_action,
|
||||
abort_on=[self.cancelled, self.error])
|
||||
|
||||
def _set_fileobj(self, fileobj, *, autoclose=True):
|
||||
""""Set the file object to write the download to.
|
||||
"""Set the file object to write the download to.
|
||||
|
||||
Args:
|
||||
fileobj: A file-like object.
|
||||
@@ -292,8 +303,7 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
||||
"""Handle QNetworkReply errors."""
|
||||
if code == QNetworkReply.OperationCanceledError:
|
||||
return
|
||||
else:
|
||||
self._die(self._reply.errorString())
|
||||
self._die(self._reply.errorString())
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_read_timer_timeout(self):
|
||||
@@ -388,7 +398,7 @@ class DownloadManager(downloads.AbstractDownloadManager):
|
||||
"""
|
||||
if not url.isValid():
|
||||
urlutils.invalid_url_error(url, "start download")
|
||||
return
|
||||
return None
|
||||
req = QNetworkRequest(url)
|
||||
if user_agent is not None:
|
||||
req.setHeader(QNetworkRequest.UserAgentHeader, user_agent)
|
||||
|
||||
@@ -27,20 +27,22 @@ Module attributes:
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import urllib.parse
|
||||
import textwrap
|
||||
import pkg_resources
|
||||
import mimetypes
|
||||
import urllib
|
||||
|
||||
import pkg_resources
|
||||
from PyQt5.QtCore import QUrlQuery, QUrl
|
||||
|
||||
import qutebrowser
|
||||
from qutebrowser.config import config, configdata, configexc, configdiff
|
||||
from qutebrowser.utils import (version, utils, jinja, log, message, docutils,
|
||||
objreg)
|
||||
objreg, urlutils)
|
||||
from qutebrowser.misc import objects
|
||||
|
||||
|
||||
pyeval_output = ":pyeval was never called"
|
||||
spawn_output = ":spawn was never called"
|
||||
|
||||
|
||||
_HANDLERS = {}
|
||||
@@ -91,7 +93,7 @@ class Redirect(Exception):
|
||||
self.url = url
|
||||
|
||||
|
||||
class add_handler: # pylint: disable=invalid-name
|
||||
class add_handler: # noqa: N801,N806 pylint: disable=invalid-name
|
||||
|
||||
"""Decorator to register a qute://* URL handler.
|
||||
|
||||
@@ -111,6 +113,7 @@ class add_handler: # pylint: disable=invalid-name
|
||||
return function
|
||||
|
||||
def wrapper(self, *args, **kwargs):
|
||||
"""Call the underlying function."""
|
||||
if self._backend is not None and objects.backend != self._backend:
|
||||
return self.wrong_backend_handler(*args, **kwargs)
|
||||
else:
|
||||
@@ -135,16 +138,30 @@ def data_for_url(url):
|
||||
Return:
|
||||
A (mimetype, data) tuple.
|
||||
"""
|
||||
norm_url = url.adjusted(QUrl.NormalizePathSegments |
|
||||
QUrl.StripTrailingSlash)
|
||||
if norm_url != url:
|
||||
raise Redirect(norm_url)
|
||||
|
||||
path = url.path()
|
||||
host = url.host()
|
||||
query = urlutils.query_string(url)
|
||||
# A url like "qute:foo" is split as "scheme:path", not "scheme:host".
|
||||
log.misc.debug("url: {}, path: {}, host {}".format(
|
||||
url.toDisplayString(), path, host))
|
||||
if path and not host:
|
||||
if not path or not host:
|
||||
new_url = QUrl()
|
||||
new_url.setScheme('qute')
|
||||
new_url.setHost(path)
|
||||
# When path is absent, e.g. qute://help (with no trailing slash)
|
||||
if host:
|
||||
new_url.setHost(host)
|
||||
# When host is absent, e.g. qute:help
|
||||
else:
|
||||
new_url.setHost(path)
|
||||
|
||||
new_url.setPath('/')
|
||||
if query:
|
||||
new_url.setQuery(query)
|
||||
if new_url.host(): # path was a valid host
|
||||
raise Redirect(new_url)
|
||||
|
||||
@@ -253,6 +270,13 @@ def qute_pyeval(_url):
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
@add_handler('spawn-output')
|
||||
def qute_spawn_output(_url):
|
||||
"""Handler for qute://spawn-output."""
|
||||
html = jinja.render('pre.html', title='spawn output', content=spawn_output)
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
@add_handler('version')
|
||||
@add_handler('verizon')
|
||||
def qute_version(_url):
|
||||
@@ -274,9 +298,8 @@ def qute_plainlog(url):
|
||||
if log.ram_handler is None:
|
||||
text = "Log output was disabled."
|
||||
else:
|
||||
try:
|
||||
level = urllib.parse.parse_qs(url.query())['level'][0]
|
||||
except KeyError:
|
||||
level = QUrlQuery(url).queryItemValue('level')
|
||||
if not level:
|
||||
level = 'vdebug'
|
||||
text = log.ram_handler.dump_log(html=False, level=level)
|
||||
html = jinja.render('pre.html', title='log', content=text)
|
||||
@@ -294,9 +317,8 @@ def qute_log(url):
|
||||
if log.ram_handler is None:
|
||||
html_log = None
|
||||
else:
|
||||
try:
|
||||
level = urllib.parse.parse_qs(url.query())['level'][0]
|
||||
except KeyError:
|
||||
level = QUrlQuery(url).queryItemValue('level')
|
||||
if not level:
|
||||
level = 'vdebug'
|
||||
html_log = log.ram_handler.dump_log(html=True, level=level)
|
||||
|
||||
@@ -307,7 +329,7 @@ def qute_log(url):
|
||||
@add_handler('gpl')
|
||||
def qute_gpl(_url):
|
||||
"""Handler for qute://gpl. Return HTML content as string."""
|
||||
return 'text/html', utils.read_file('html/LICENSE.html')
|
||||
return 'text/html', utils.read_file('html/license.html')
|
||||
|
||||
|
||||
@add_handler('help')
|
||||
@@ -323,8 +345,14 @@ def qute_help(url):
|
||||
"scripts/asciidoc2html.py.")
|
||||
|
||||
path = 'html/doc/{}'.format(urlpath)
|
||||
if urlpath.endswith('.png'):
|
||||
return 'image/png', utils.read_file(path, binary=True)
|
||||
if not urlpath.endswith('.html'):
|
||||
try:
|
||||
bdata = utils.read_file(path, binary=True)
|
||||
except OSError as e:
|
||||
raise QuteSchemeOSError(e)
|
||||
mimetype, _encoding = mimetypes.guess_type(urlpath)
|
||||
assert mimetype is not None, url
|
||||
return mimetype, bdata
|
||||
|
||||
try:
|
||||
data = utils.read_file(path)
|
||||
@@ -407,6 +435,18 @@ def qute_settings(url):
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
@add_handler('back')
|
||||
def qute_back(url):
|
||||
"""Handler for qute://back.
|
||||
|
||||
Simple page to free ram / lazy load a site, goes back on focusing the tab.
|
||||
"""
|
||||
html = jinja.render(
|
||||
'back.html',
|
||||
title='Suspended: ' + urllib.parse.unquote(url.fragment()))
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
@add_handler('configdiff')
|
||||
def qute_configdiff(url):
|
||||
"""Handler for qute://configdiff."""
|
||||
@@ -415,7 +455,7 @@ def qute_configdiff(url):
|
||||
return 'text/html', configdiff.get_diff()
|
||||
except OSError as e:
|
||||
error = (b'Failed to read old config: ' +
|
||||
str(e.strerror).encode('utf-8'))
|
||||
str(e.strerror).encode('utf-8'))
|
||||
return 'text/plain', error
|
||||
else:
|
||||
data = config.instance.dump_userconfig().encode('utf-8')
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
import html
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import usertypes, message, log, objreg, jinja
|
||||
from qutebrowser.utils import usertypes, message, log, objreg, jinja, utils
|
||||
from qutebrowser.mainwindow import mainwindow
|
||||
|
||||
|
||||
@@ -182,7 +182,7 @@ def ignore_certificate_errors(url, errors, abort_on):
|
||||
return False
|
||||
else:
|
||||
raise ValueError("Invalid ssl_strict value {!r}".format(ssl_strict))
|
||||
raise AssertionError("Not reached")
|
||||
raise utils.Unreachable
|
||||
|
||||
|
||||
def feature_permission(url, option, msg, yes_action, no_action, abort_on):
|
||||
|
||||
@@ -26,8 +26,8 @@ to a file on shutdown, so it makes sense to keep them as strings here.
|
||||
"""
|
||||
|
||||
import os
|
||||
import html
|
||||
import os.path
|
||||
import html
|
||||
import functools
|
||||
import collections
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ Module attributes:
|
||||
SELECTORS: CSS selectors for different groups of elements.
|
||||
"""
|
||||
|
||||
import enum
|
||||
import collections.abc
|
||||
|
||||
from PyQt5.QtCore import QUrl, Qt, QEvent, QTimer
|
||||
@@ -35,7 +36,7 @@ from qutebrowser.mainwindow import mainwindow
|
||||
from qutebrowser.utils import log, usertypes, utils, qtutils, objreg
|
||||
|
||||
|
||||
Group = usertypes.enum('Group', ['all', 'links', 'images', 'url', 'inputs'])
|
||||
Group = enum.Enum('Group', ['all', 'links', 'images', 'url', 'inputs'])
|
||||
|
||||
|
||||
SELECTORS = {
|
||||
@@ -59,6 +60,13 @@ class Error(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class OrphanedError(Error):
|
||||
|
||||
"""Raised when a webelement's parent has vanished."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class AbstractWebElement(collections.abc.MutableMapping):
|
||||
|
||||
"""A wrapper around QtWebKit/QtWebEngine web element.
|
||||
@@ -220,7 +228,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
||||
}
|
||||
relevant_classes = classes[self.tag_name()]
|
||||
for klass in self.classes():
|
||||
if any([klass.strip().startswith(e) for e in relevant_classes]):
|
||||
if any(klass.strip().startswith(e) for e in relevant_classes):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@@ -21,8 +21,20 @@
|
||||
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
|
||||
from PyQt5.QtCore import QLibraryInfo
|
||||
from qutebrowser.utils import log
|
||||
|
||||
|
||||
def version(filename):
|
||||
"""Extract the version number from the dictionary file name."""
|
||||
version_re = re.compile(r".+-(?P<version>[0-9]+-[0-9]+?)\.bdic")
|
||||
match = version_re.fullmatch(filename)
|
||||
if match is None:
|
||||
raise ValueError('the given dictionary file name is malformed: {}'
|
||||
.format(filename))
|
||||
return tuple(int(n) for n in match.group('version').split('-'))
|
||||
|
||||
|
||||
def dictionary_dir():
|
||||
@@ -31,16 +43,23 @@ def dictionary_dir():
|
||||
return os.path.join(datapath, 'qtwebengine_dictionaries')
|
||||
|
||||
|
||||
def installed_file(code):
|
||||
"""Return the installed dictionary for the given code.
|
||||
|
||||
Return the filename of the installed dictionary or None
|
||||
if the dictionary is not installed.
|
||||
"""
|
||||
def local_files(code):
|
||||
"""Return all installed dictionaries for the given code."""
|
||||
pathname = os.path.join(dictionary_dir(), '{}*.bdic'.format(code))
|
||||
matching_dicts = glob.glob(pathname)
|
||||
if matching_dicts:
|
||||
with_extension = os.path.basename(matching_dicts[0])
|
||||
return os.path.splitext(with_extension)[0]
|
||||
else:
|
||||
return None
|
||||
files = []
|
||||
for matching_dict in sorted(matching_dicts, key=version, reverse=True):
|
||||
filename = os.path.basename(matching_dict)
|
||||
log.config.debug('Found file for dict {}: {}'.format(code, filename))
|
||||
files.append(filename)
|
||||
return files
|
||||
|
||||
|
||||
def local_filename(code):
|
||||
"""Return the newest installed dictionary for the given code.
|
||||
|
||||
Return the filename of the installed dictionary with the highest version
|
||||
number or None if the dictionary is not installed.
|
||||
"""
|
||||
all_installed = local_files(code)
|
||||
return os.path.splitext(all_installed[0])[0] if all_installed else None
|
||||
|
||||
@@ -133,6 +133,22 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
||||
self.error.connect(question.abort)
|
||||
message.global_bridge.ask(question, blocking=True)
|
||||
|
||||
def _ask_create_parent_question(self, title, msg,
|
||||
force_overwrite, remember_directory):
|
||||
no_action = functools.partial(self.cancel, remove_data=False)
|
||||
question = usertypes.Question()
|
||||
question.title = title
|
||||
question.text = msg
|
||||
question.mode = usertypes.PromptMode.yesno
|
||||
question.answered_yes.connect(lambda:
|
||||
self._after_create_parent_question(
|
||||
force_overwrite, remember_directory))
|
||||
question.answered_no.connect(no_action)
|
||||
question.cancelled.connect(no_action)
|
||||
self.cancelled.connect(question.abort)
|
||||
self.error.connect(question.abort)
|
||||
message.global_bridge.ask(question, blocking=True)
|
||||
|
||||
def _after_set_filename(self):
|
||||
self._qt_item.setPath(self._filename)
|
||||
self._qt_item.accept()
|
||||
|
||||
@@ -47,6 +47,7 @@ class WebEngineElement(webelem.AbstractWebElement):
|
||||
'class_name': str,
|
||||
'rects': list,
|
||||
'attributes': dict,
|
||||
'caret_position': (int, type(None)),
|
||||
}
|
||||
assert set(js_dict.keys()).issubset(js_dict_types.keys())
|
||||
for name, typ in js_dict_types.items():
|
||||
@@ -99,6 +100,8 @@ class WebEngineElement(webelem.AbstractWebElement):
|
||||
|
||||
def _js_call(self, name, *args, callback=None):
|
||||
"""Wrapper to run stuff from webelem.js."""
|
||||
if self._tab.is_deleted():
|
||||
raise webelem.OrphanedError("Tab containing element vanished")
|
||||
js_code = javascript.assemble('webelem', name, self._id, *args)
|
||||
self._tab.run_js_async(js_code, callback=callback)
|
||||
|
||||
@@ -132,6 +135,13 @@ class WebEngineElement(webelem.AbstractWebElement):
|
||||
def set_value(self, value):
|
||||
self._js_call('set_value', value)
|
||||
|
||||
def caret_position(self):
|
||||
"""Get the text caret position for the current element.
|
||||
|
||||
If the element is not a text element, None is returned.
|
||||
"""
|
||||
return self._js_dict.get('caret_position', None)
|
||||
|
||||
def insert_text(self, text):
|
||||
if not self.is_editable(strict=True):
|
||||
raise webelem.Error("Element is not editable!")
|
||||
@@ -201,11 +211,11 @@ class WebEngineElement(webelem.AbstractWebElement):
|
||||
def _click_js(self, _click_target):
|
||||
# FIXME:qtwebengine Have a proper API for this
|
||||
# pylint: disable=protected-access
|
||||
settings = self._tab._widget.settings()
|
||||
view = self._tab._widget
|
||||
# pylint: enable=protected-access
|
||||
attribute = QWebEngineSettings.JavascriptCanOpenWindows
|
||||
could_open_windows = settings.testAttribute(attribute)
|
||||
settings.setAttribute(attribute, True)
|
||||
could_open_windows = view.settings().testAttribute(attribute)
|
||||
view.settings().setAttribute(attribute, True)
|
||||
|
||||
# Get QtWebEngine do apply the settings
|
||||
# (it does so with a 0ms QTimer...)
|
||||
@@ -216,6 +226,12 @@ class WebEngineElement(webelem.AbstractWebElement):
|
||||
QEventLoop.ExcludeUserInputEvents)
|
||||
|
||||
def reset_setting(_arg):
|
||||
settings.setAttribute(attribute, could_open_windows)
|
||||
"""Set the JavascriptCanOpenWindows setting to its old value."""
|
||||
try:
|
||||
view.settings().setAttribute(attribute, could_open_windows)
|
||||
except RuntimeError:
|
||||
# Happens if this callback gets called during QWebEnginePage
|
||||
# destruction, i.e. if the tab was closed in the meantime.
|
||||
pass
|
||||
|
||||
self._js_call('click', callback=reset_setting)
|
||||
|
||||
@@ -29,6 +29,7 @@ Module attributes:
|
||||
|
||||
import os
|
||||
|
||||
import sip
|
||||
from PyQt5.QtGui import QFont
|
||||
from PyQt5.QtWebEngineWidgets import (QWebEngineSettings, QWebEngineProfile,
|
||||
QWebEngineScript)
|
||||
@@ -36,7 +37,8 @@ from PyQt5.QtWebEngineWidgets import (QWebEngineSettings, QWebEngineProfile,
|
||||
from qutebrowser.browser import shared
|
||||
from qutebrowser.browser.webengine import spell
|
||||
from qutebrowser.config import config, websettings
|
||||
from qutebrowser.utils import utils, standarddir, javascript, qtutils, message
|
||||
from qutebrowser.utils import (utils, standarddir, javascript, qtutils,
|
||||
message, log, objreg)
|
||||
|
||||
# The default QWebEngineProfile
|
||||
default_profile = None
|
||||
@@ -92,9 +94,10 @@ class DefaultProfileSetter(websettings.Base):
|
||||
|
||||
"""A setting set on the QWebEngineProfile."""
|
||||
|
||||
def __init__(self, setter, default=websettings.UNSET):
|
||||
def __init__(self, setter, converter=None, default=websettings.UNSET):
|
||||
super().__init__(default)
|
||||
self._setter = setter
|
||||
self._converter = converter
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, setter=self._setter, constructor=True)
|
||||
@@ -103,7 +106,11 @@ class DefaultProfileSetter(websettings.Base):
|
||||
if settings is not None:
|
||||
raise ValueError("'settings' may not be set with "
|
||||
"DefaultProfileSetters!")
|
||||
|
||||
setter = getattr(default_profile, self._setter)
|
||||
if self._converter is not None:
|
||||
value = self._converter(value)
|
||||
|
||||
setter(value)
|
||||
|
||||
|
||||
@@ -133,51 +140,63 @@ class DictionaryLanguageSetter(DefaultProfileSetter):
|
||||
super().__init__('setSpellCheckLanguages', default=[])
|
||||
|
||||
def _find_installed(self, code):
|
||||
installed_file = spell.installed_file(code)
|
||||
if not installed_file:
|
||||
local_filename = spell.local_filename(code)
|
||||
if not local_filename:
|
||||
message.warning(
|
||||
"Language {} is not installed - see scripts/install_dict.py "
|
||||
"Language {} is not installed - see scripts/dictcli.py "
|
||||
"in qutebrowser's sources".format(code))
|
||||
return installed_file
|
||||
return local_filename
|
||||
|
||||
def _set(self, value, settings=None):
|
||||
if settings is not None:
|
||||
raise ValueError("'settings' may not be set with "
|
||||
"DictionaryLanguageSetter!")
|
||||
filenames = [self._find_installed(code) for code in value]
|
||||
log.config.debug("Found dicts: {}".format(filenames))
|
||||
super()._set([f for f in filenames if f], settings)
|
||||
|
||||
|
||||
def _init_stylesheet(profile):
|
||||
"""Initialize custom stylesheets.
|
||||
|
||||
Mostly inspired by QupZilla:
|
||||
Partially inspired by QupZilla:
|
||||
https://github.com/QupZilla/qupzilla/blob/v2.0/src/lib/app/mainapplication.cpp#L1063-L1101
|
||||
https://github.com/QupZilla/qupzilla/blob/v2.0/src/lib/tools/scripts.cpp#L119-L132
|
||||
"""
|
||||
old_script = profile.scripts().findScript('_qute_stylesheet')
|
||||
if not old_script.isNull():
|
||||
profile.scripts().remove(old_script)
|
||||
|
||||
css = shared.get_user_stylesheet()
|
||||
source = """
|
||||
(function() {{
|
||||
var css = document.createElement('style');
|
||||
css.setAttribute('type', 'text/css');
|
||||
css.appendChild(document.createTextNode('{}'));
|
||||
document.getElementsByTagName('head')[0].appendChild(css);
|
||||
}})()
|
||||
""".format(javascript.string_escape(css))
|
||||
source = '\n'.join([
|
||||
'"use strict";',
|
||||
'window._qutebrowser = window._qutebrowser || {};',
|
||||
utils.read_file('javascript/stylesheet.js'),
|
||||
javascript.assemble('stylesheet', 'set_css', css),
|
||||
])
|
||||
|
||||
script = QWebEngineScript()
|
||||
script.setName('_qute_stylesheet')
|
||||
script.setInjectionPoint(QWebEngineScript.DocumentReady)
|
||||
script.setInjectionPoint(QWebEngineScript.DocumentCreation)
|
||||
script.setWorldId(QWebEngineScript.ApplicationWorld)
|
||||
script.setRunsOnSubFrames(True)
|
||||
script.setSourceCode(source)
|
||||
profile.scripts().insert(script)
|
||||
|
||||
|
||||
def _update_stylesheet():
|
||||
"""Update the custom stylesheet in existing tabs."""
|
||||
css = shared.get_user_stylesheet()
|
||||
code = javascript.assemble('stylesheet', 'set_css', css)
|
||||
for win_id, window in objreg.window_registry.items():
|
||||
# We could be in the middle of destroying a window here
|
||||
if sip.isdeleted(window):
|
||||
continue
|
||||
tab_registry = objreg.get('tab-registry', scope='window',
|
||||
window=win_id)
|
||||
for tab in tab_registry.values():
|
||||
tab.run_js_async(code)
|
||||
|
||||
|
||||
def _set_http_headers(profile):
|
||||
"""Set the user agent and accept-language for the given profile.
|
||||
|
||||
@@ -197,6 +216,7 @@ def _update_settings(option):
|
||||
if option in ['scrolling.bar', 'content.user_stylesheets']:
|
||||
_init_stylesheet(default_profile)
|
||||
_init_stylesheet(private_profile)
|
||||
_update_stylesheet()
|
||||
elif option in ['content.headers.user_agent',
|
||||
'content.headers.accept_language']:
|
||||
_set_http_headers(default_profile)
|
||||
@@ -224,6 +244,43 @@ def _init_profiles():
|
||||
private_profile.setSpellCheckEnabled(True)
|
||||
|
||||
|
||||
def inject_userscripts():
|
||||
"""Register user JavaScript files with the global profiles."""
|
||||
# The Greasemonkey metadata block support in QtWebEngine only starts at
|
||||
# Qt 5.8. With 5.7.1, we need to inject the scripts ourselves in response
|
||||
# to urlChanged.
|
||||
if not qtutils.version_check('5.8'):
|
||||
return
|
||||
|
||||
# Since we are inserting scripts into profile.scripts they won't
|
||||
# just get replaced by new gm scripts like if we were injecting them
|
||||
# ourselves so we need to remove all gm scripts, while not removing
|
||||
# any other stuff that might have been added. Like the one for
|
||||
# stylesheets.
|
||||
greasemonkey = objreg.get('greasemonkey')
|
||||
for profile in [default_profile, private_profile]:
|
||||
scripts = profile.scripts()
|
||||
for script in scripts.toList():
|
||||
if script.name().startswith("GM-"):
|
||||
log.greasemonkey.debug('Removing script: {}'
|
||||
.format(script.name()))
|
||||
removed = scripts.remove(script)
|
||||
assert removed, script.name()
|
||||
|
||||
# Then add the new scripts.
|
||||
for script in greasemonkey.all_scripts():
|
||||
# @run-at (and @include/@exclude/@match) is parsed by
|
||||
# QWebEngineScript.
|
||||
new_script = QWebEngineScript()
|
||||
new_script.setWorldId(QWebEngineScript.MainWorld)
|
||||
new_script.setSourceCode(script.code())
|
||||
new_script.setName("GM-{}".format(script.name))
|
||||
new_script.setRunsOnSubFrames(script.runs_on_sub_frames)
|
||||
log.greasemonkey.debug('adding script: {}'
|
||||
.format(new_script.name()))
|
||||
scripts.insert(new_script)
|
||||
|
||||
|
||||
def init(args):
|
||||
"""Initialize the global QWebSettings."""
|
||||
if args.enable_webengine_inspector:
|
||||
@@ -281,7 +338,9 @@ MAPPINGS = {
|
||||
Attribute(QWebEngineSettings.LocalStorageEnabled),
|
||||
'content.cache.size':
|
||||
# 0: automatically managed by QtWebEngine
|
||||
DefaultProfileSetter('setHttpCacheMaximumSize', default=0),
|
||||
DefaultProfileSetter('setHttpCacheMaximumSize', default=0,
|
||||
converter=lambda val:
|
||||
qtutils.check_overflow(val, 'int', fatal=False)),
|
||||
'content.xss_auditing':
|
||||
Attribute(QWebEngineSettings.XSSAuditingEnabled),
|
||||
'content.default_encoding':
|
||||
|
||||
@@ -24,7 +24,8 @@ import functools
|
||||
import html as html_utils
|
||||
|
||||
import sip
|
||||
from PyQt5.QtCore import pyqtSlot, Qt, QEvent, QPoint, QUrl, QTimer
|
||||
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QEvent, QPoint, QPointF,
|
||||
QUrl, QTimer)
|
||||
from PyQt5.QtGui import QKeyEvent
|
||||
from PyQt5.QtNetwork import QAuthenticator
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
@@ -69,6 +70,10 @@ def init():
|
||||
download_manager.install(webenginesettings.private_profile)
|
||||
objreg.register('webengine-download-manager', download_manager)
|
||||
|
||||
greasemonkey = objreg.get('greasemonkey')
|
||||
greasemonkey.scripts_reloaded.connect(webenginesettings.inject_userscripts)
|
||||
webenginesettings.inject_userscripts()
|
||||
|
||||
|
||||
# Mapping worlds from usertypes.JsWorld to QWebEngineScript world IDs.
|
||||
_JS_WORLD_MAP = {
|
||||
@@ -121,18 +126,35 @@ class WebEnginePrinting(browsertab.AbstractPrinting):
|
||||
|
||||
class WebEngineSearch(browsertab.AbstractSearch):
|
||||
|
||||
"""QtWebEngine implementations related to searching on the page."""
|
||||
"""QtWebEngine implementations related to searching on the page.
|
||||
|
||||
Attributes:
|
||||
_flags: The QWebEnginePage.FindFlags of the last search.
|
||||
_pending_searches: How many searches have been started but not called
|
||||
back yet.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._flags = QWebEnginePage.FindFlags(0)
|
||||
self._pending_searches = 0
|
||||
|
||||
def _find(self, text, flags, callback, caller):
|
||||
"""Call findText on the widget."""
|
||||
self.search_displayed = True
|
||||
self._pending_searches += 1
|
||||
|
||||
def wrapped_callback(found):
|
||||
"""Wrap the callback to do debug logging."""
|
||||
self._pending_searches -= 1
|
||||
if self._pending_searches > 0:
|
||||
# See https://github.com/qutebrowser/qutebrowser/issues/2442
|
||||
# and https://github.com/qt/qtwebengine/blob/5.10/src/core/web_contents_adapter.cpp#L924-L934
|
||||
log.webview.debug("Ignoring cancelled search callback with "
|
||||
"{} pending searches".format(
|
||||
self._pending_searches))
|
||||
return
|
||||
|
||||
found_text = 'found' if found else "didn't find"
|
||||
if flags:
|
||||
flag_text = 'with flags {}'.format(debug.qflags_key(
|
||||
@@ -293,6 +315,7 @@ class WebEngineScroller(browsertab.AbstractScroller):
|
||||
|
||||
def __init__(self, tab, parent=None):
|
||||
super().__init__(tab, parent)
|
||||
self._args = objreg.get('args')
|
||||
self._pos_perc = (0, 0)
|
||||
self._pos_px = QPoint()
|
||||
self._at_bottom = False
|
||||
@@ -307,39 +330,42 @@ class WebEngineScroller(browsertab.AbstractScroller):
|
||||
for _ in range(min(count, 5000)):
|
||||
self._tab.key_press(key, modifier)
|
||||
|
||||
@pyqtSlot()
|
||||
def _update_pos(self):
|
||||
@pyqtSlot(QPointF)
|
||||
def _update_pos(self, pos):
|
||||
"""Update the scroll position attributes when it changed."""
|
||||
def update_pos_cb(jsret):
|
||||
"""Callback after getting scroll position via JS."""
|
||||
if jsret is None:
|
||||
# This can happen when the callback would get called after
|
||||
# shutting down a tab
|
||||
return
|
||||
log.webview.vdebug(jsret)
|
||||
assert isinstance(jsret, dict), jsret
|
||||
self._pos_px = QPoint(jsret['px']['x'], jsret['px']['y'])
|
||||
self._pos_px = pos.toPoint()
|
||||
contents_size = self._widget.page().contentsSize()
|
||||
|
||||
dx = jsret['scroll']['width'] - jsret['inner']['width']
|
||||
if dx == 0:
|
||||
perc_x = 0
|
||||
else:
|
||||
perc_x = min(100, round(100 / dx * jsret['px']['x']))
|
||||
scrollable_x = contents_size.width() - self._widget.width()
|
||||
if scrollable_x == 0:
|
||||
perc_x = 0
|
||||
else:
|
||||
try:
|
||||
perc_x = min(100, round(100 / scrollable_x * pos.x()))
|
||||
except ValueError:
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/3219
|
||||
log.misc.debug("Got ValueError!")
|
||||
log.misc.debug("contents_size.width(): {}".format(
|
||||
contents_size.width()))
|
||||
log.misc.debug("self._widget.width(): {}".format(
|
||||
self._widget.width()))
|
||||
log.misc.debug("scrollable_x: {}".format(scrollable_x))
|
||||
log.misc.debug("pos.x(): {}".format(pos.x()))
|
||||
raise
|
||||
|
||||
dy = jsret['scroll']['height'] - jsret['inner']['height']
|
||||
if dy == 0:
|
||||
perc_y = 0
|
||||
else:
|
||||
perc_y = min(100, round(100 / dy * jsret['px']['y']))
|
||||
scrollable_y = contents_size.height() - self._widget.height()
|
||||
if scrollable_y == 0:
|
||||
perc_y = 0
|
||||
else:
|
||||
perc_y = min(100, round(100 / scrollable_y * pos.y()))
|
||||
|
||||
self._at_bottom = math.ceil(jsret['px']['y']) >= dy
|
||||
self._at_bottom = math.ceil(pos.y()) >= scrollable_y
|
||||
|
||||
if (self._pos_perc != (perc_x, perc_y) or
|
||||
'no-scroll-filtering' in self._args.debug_flags):
|
||||
self._pos_perc = perc_x, perc_y
|
||||
|
||||
self.perc_changed.emit(*self._pos_perc)
|
||||
|
||||
js_code = javascript.assemble('scroll', 'pos')
|
||||
self._tab.run_js_async(js_code, update_pos_cb)
|
||||
|
||||
def pos_px(self):
|
||||
return self._pos_px
|
||||
|
||||
@@ -514,7 +540,15 @@ class WebEngineElements(browsertab.AbstractElements):
|
||||
|
||||
class WebEngineTab(browsertab.AbstractTab):
|
||||
|
||||
"""A QtWebEngine tab in the browser."""
|
||||
"""A QtWebEngine tab in the browser.
|
||||
|
||||
Signals:
|
||||
_load_finished_fake:
|
||||
Used in place of unreliable loadFinished
|
||||
"""
|
||||
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-65223
|
||||
_load_finished_fake = pyqtSignal(bool)
|
||||
|
||||
def __init__(self, *, win_id, mode_manager, private, parent=None):
|
||||
super().__init__(win_id=win_id, mode_manager=mode_manager,
|
||||
@@ -540,7 +574,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
def _init_js(self):
|
||||
js_code = '\n'.join([
|
||||
'"use strict";',
|
||||
'window._qutebrowser = {};',
|
||||
'window._qutebrowser = window._qutebrowser || {};',
|
||||
utils.read_file('javascript/scroll.js'),
|
||||
utils.read_file('javascript/webelem.js'),
|
||||
])
|
||||
@@ -563,6 +597,9 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
|
||||
@pyqtSlot()
|
||||
def _restore_zoom(self):
|
||||
if sip.isdeleted(self._widget):
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/3498
|
||||
return
|
||||
if self._saved_zoom is None:
|
||||
return
|
||||
self.zoom.set_factor(self._saved_zoom)
|
||||
@@ -693,6 +730,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
try:
|
||||
# pylint: disable=no-member, useless-suppression
|
||||
sip.assign(authenticator, QAuthenticator())
|
||||
# pylint: enable=no-member, useless-suppression
|
||||
except AttributeError:
|
||||
url_string = url.toDisplayString()
|
||||
error_page = jinja.render(
|
||||
@@ -712,6 +750,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
try:
|
||||
# pylint: disable=no-member, useless-suppression
|
||||
sip.assign(authenticator, QAuthenticator())
|
||||
# pylint: enable=no-member, useless-suppression
|
||||
except AttributeError:
|
||||
# WORKAROUND for
|
||||
# https://www.riverbankcomputing.com/pipermail/pyqt/2016-December/038400.html
|
||||
@@ -766,6 +805,24 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
}
|
||||
self.renderer_process_terminated.emit(status_map[status], exitcode)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def _on_load_progress_workaround(self, perc):
|
||||
"""Use loadProgress(100) to emit loadFinished(True).
|
||||
|
||||
See https://bugreports.qt.io/browse/QTBUG-65223
|
||||
"""
|
||||
if perc == 100 and self.load_status() != usertypes.LoadStatus.error:
|
||||
self._load_finished_fake.emit(True)
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _on_load_finished_workaround(self, ok):
|
||||
"""Use only loadFinished(False).
|
||||
|
||||
See https://bugreports.qt.io/browse/QTBUG-65223
|
||||
"""
|
||||
if not ok:
|
||||
self._load_finished_fake.emit(False)
|
||||
|
||||
def _connect_signals(self):
|
||||
view = self._widget
|
||||
page = view.page()
|
||||
@@ -774,9 +831,6 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
page.linkHovered.connect(self.link_hovered)
|
||||
page.loadProgress.connect(self._on_load_progress)
|
||||
page.loadStarted.connect(self._on_load_started)
|
||||
page.loadFinished.connect(self._on_history_trigger)
|
||||
page.loadFinished.connect(self._restore_zoom)
|
||||
page.loadFinished.connect(self._on_load_finished)
|
||||
page.certificate_error.connect(self._on_ssl_errors)
|
||||
page.authenticationRequired.connect(self._on_authentication_required)
|
||||
page.proxyAuthenticationRequired.connect(
|
||||
@@ -789,6 +843,19 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
view.renderProcessTerminated.connect(
|
||||
self._on_render_process_terminated)
|
||||
view.iconChanged.connect(self.icon_changed)
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-65223
|
||||
if qtutils.version_check('5.10', compiled=False):
|
||||
page.loadProgress.connect(self._on_load_progress_workaround)
|
||||
self._load_finished_fake.connect(self._on_history_trigger)
|
||||
self._load_finished_fake.connect(self._restore_zoom)
|
||||
self._load_finished_fake.connect(self._on_load_finished)
|
||||
page.loadFinished.connect(self._on_load_finished_workaround)
|
||||
else:
|
||||
# for older Qt versions which break with the above
|
||||
page.loadProgress.connect(self._on_load_progress)
|
||||
page.loadFinished.connect(self._on_history_trigger)
|
||||
page.loadFinished.connect(self._restore_zoom)
|
||||
page.loadFinished.connect(self._on_load_finished)
|
||||
|
||||
def event_target(self):
|
||||
return self._widget.focusProxy()
|
||||
|
||||
@@ -23,12 +23,14 @@ import functools
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, PYQT_VERSION
|
||||
from PyQt5.QtGui import QPalette
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
|
||||
from PyQt5.QtWebEngineWidgets import (QWebEngineView, QWebEnginePage,
|
||||
QWebEngineScript)
|
||||
|
||||
from qutebrowser.browser import shared
|
||||
from qutebrowser.browser.webengine import certificateerror, webenginesettings
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import log, debug, usertypes, jinja, urlutils, message
|
||||
from qutebrowser.utils import (log, debug, usertypes, jinja, urlutils, message,
|
||||
objreg, qtutils)
|
||||
|
||||
|
||||
class WebEngineView(QWebEngineView):
|
||||
@@ -135,6 +137,7 @@ class WebEnginePage(QWebEnginePage):
|
||||
self._theme_color = theme_color
|
||||
self._set_bg_color()
|
||||
config.instance.changed.connect(self._set_bg_color)
|
||||
self.urlChanged.connect(self._inject_userjs)
|
||||
|
||||
@config.change_filter('colors.webpage.bg')
|
||||
def _set_bg_color(self):
|
||||
@@ -300,3 +303,43 @@ class WebEnginePage(QWebEnginePage):
|
||||
message.error(msg)
|
||||
return False
|
||||
return True
|
||||
|
||||
@pyqtSlot('QUrl')
|
||||
def _inject_userjs(self, url):
|
||||
"""Inject userscripts registered for `url` into the current page."""
|
||||
if qtutils.version_check('5.8'):
|
||||
# Handled in webenginetab with the builtin Greasemonkey
|
||||
# support.
|
||||
return
|
||||
|
||||
# Using QWebEnginePage.scripts() to hold the user scripts means
|
||||
# we don't have to worry ourselves about where to inject the
|
||||
# page but also means scripts hang around for the tab lifecycle.
|
||||
# So clear them here.
|
||||
scripts = self.scripts()
|
||||
for script in scripts.toList():
|
||||
if script.name().startswith("GM-"):
|
||||
log.greasemonkey.debug("Removing script: {}"
|
||||
.format(script.name()))
|
||||
removed = scripts.remove(script)
|
||||
assert removed, script.name()
|
||||
|
||||
def _add_script(script, injection_point):
|
||||
new_script = QWebEngineScript()
|
||||
new_script.setInjectionPoint(injection_point)
|
||||
new_script.setWorldId(QWebEngineScript.MainWorld)
|
||||
new_script.setSourceCode(script.code())
|
||||
new_script.setName("GM-{}".format(script.name))
|
||||
new_script.setRunsOnSubFrames(script.runs_on_sub_frames)
|
||||
log.greasemonkey.debug("Adding script: {}"
|
||||
.format(new_script.name()))
|
||||
scripts.insert(new_script)
|
||||
|
||||
greasemonkey = objreg.get('greasemonkey')
|
||||
matching_scripts = greasemonkey.scripts_for(url)
|
||||
for script in matching_scripts.start:
|
||||
_add_script(script, QWebEngineScript.DocumentCreation)
|
||||
for script in matching_scripts.end:
|
||||
_add_script(script, QWebEngineScript.DocumentReady)
|
||||
for script in matching_scripts.idle:
|
||||
_add_script(script, QWebEngineScript.Deferred)
|
||||
|
||||
@@ -22,11 +22,11 @@
|
||||
|
||||
import os.path
|
||||
|
||||
from PyQt5.QtNetwork import QNetworkRequest
|
||||
|
||||
from qutebrowser.utils import log
|
||||
from qutebrowser.browser.webkit import rfc6266
|
||||
|
||||
from PyQt5.QtNetwork import QNetworkRequest
|
||||
|
||||
|
||||
def parse_content_disposition(reply):
|
||||
"""Parse a content_disposition header.
|
||||
@@ -57,9 +57,7 @@ def parse_content_disposition(reply):
|
||||
is_inline = content_disposition.is_inline()
|
||||
# Then try to get filename from url
|
||||
if not filename:
|
||||
path = reply.url().path()
|
||||
if path is not None:
|
||||
filename = path.rstrip('/')
|
||||
filename = reply.url().path().rstrip('/')
|
||||
# If that fails as well, use a fallback
|
||||
if not filename:
|
||||
filename = 'qutebrowser-download'
|
||||
|
||||
@@ -502,8 +502,8 @@ class _Downloader:
|
||||
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)
|
||||
items = {(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)
|
||||
|
||||
@@ -127,7 +127,11 @@ class FileSchemeHandler(schemehandler.SchemeHandler):
|
||||
A QNetworkReply for directories, None for files.
|
||||
"""
|
||||
path = request.url().toLocalFile()
|
||||
if os.path.isdir(path):
|
||||
data = dirbrowser_html(path)
|
||||
return networkreply.FixedDataNetworkReply(
|
||||
request, data, 'text/html', self.parent())
|
||||
try:
|
||||
if os.path.isdir(path):
|
||||
data = dirbrowser_html(path)
|
||||
return networkreply.FixedDataNetworkReply(
|
||||
request, data, 'text/html', self.parent())
|
||||
return None
|
||||
except UnicodeEncodeError:
|
||||
return None
|
||||
|
||||
@@ -206,7 +206,7 @@ class NetworkManager(QNetworkAccessManager):
|
||||
|
||||
# No @pyqtSlot here, see
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/2213
|
||||
def on_ssl_errors(self, reply, errors): # pragma: no mccabe
|
||||
def on_ssl_errors(self, reply, errors): # noqa: C901 pragma: no mccabe
|
||||
"""Decide if SSL errors should be ignored or not.
|
||||
|
||||
This slot is called on SSL/TLS errors by the self.sslErrors signal.
|
||||
|
||||
@@ -34,7 +34,7 @@ class FixedDataNetworkReply(QNetworkReply):
|
||||
|
||||
"""QNetworkReply subclass for fixed data."""
|
||||
|
||||
def __init__(self, request, fileData, mimeType, # flake8: disable=N803
|
||||
def __init__(self, request, fileData, mimeType, # noqa: N803
|
||||
parent=None):
|
||||
"""Constructor.
|
||||
|
||||
|
||||
@@ -270,6 +270,7 @@ class _ContentDisposition:
|
||||
elif 'filename' in self.assocs:
|
||||
# XXX Reject non-ascii (parsed via qdtext) here?
|
||||
return self.assocs['filename']
|
||||
return None
|
||||
|
||||
def is_inline(self):
|
||||
"""Return if the file should be handled inline.
|
||||
|
||||
@@ -118,6 +118,8 @@ class WebKitElement(webelem.AbstractWebElement):
|
||||
|
||||
def set_value(self, value):
|
||||
self._check_vanished()
|
||||
if self._tab.is_deleted():
|
||||
raise webelem.OrphanedError("Tab containing element vanished")
|
||||
if self.is_content_editable():
|
||||
log.webelem.debug("Filling {!r} via set_text.".format(self))
|
||||
self._elem.setPlainText(value)
|
||||
@@ -126,6 +128,14 @@ class WebKitElement(webelem.AbstractWebElement):
|
||||
value = javascript.string_escape(value)
|
||||
self._elem.evaluateJavaScript("this.value='{}'".format(value))
|
||||
|
||||
def caret_position(self):
|
||||
"""Get the text caret position for the current element."""
|
||||
self._check_vanished()
|
||||
pos = self._elem.evaluateJavaScript('this.selectionStart')
|
||||
if pos is None:
|
||||
return 0
|
||||
return int(pos)
|
||||
|
||||
def insert_text(self, text):
|
||||
self._check_vanished()
|
||||
if not self.is_editable(strict=True):
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
"""Wrapper over our (QtWebKit) WebView."""
|
||||
|
||||
import re
|
||||
import functools
|
||||
import xml.etree.ElementTree
|
||||
|
||||
@@ -422,12 +423,13 @@ class WebKitScroller(browsertab.AbstractScroller):
|
||||
else:
|
||||
for val, orientation in [(x, Qt.Horizontal), (y, Qt.Vertical)]:
|
||||
if val is not None:
|
||||
val = qtutils.check_overflow(val, 'int', fatal=False)
|
||||
frame = self._widget.page().mainFrame()
|
||||
m = frame.scrollBarMaximum(orientation)
|
||||
if m == 0:
|
||||
maximum = frame.scrollBarMaximum(orientation)
|
||||
if maximum == 0:
|
||||
continue
|
||||
frame.setScrollBarValue(orientation, int(m * val / 100))
|
||||
pos = int(maximum * val / 100)
|
||||
pos = qtutils.check_overflow(pos, 'int', fatal=False)
|
||||
frame.setScrollBarValue(orientation, pos)
|
||||
|
||||
def _key_press(self, key, count=1, getter_name=None, direction=None):
|
||||
frame = self._widget.page().mainFrame()
|
||||
@@ -540,10 +542,15 @@ class WebKitElements(browsertab.AbstractElements):
|
||||
|
||||
def find_id(self, elem_id, callback):
|
||||
def find_id_cb(elems):
|
||||
"""Call the real callback with the found elements."""
|
||||
if not elems:
|
||||
callback(None)
|
||||
else:
|
||||
callback(elems[0])
|
||||
|
||||
# Escape non-alphanumeric characters in the selector
|
||||
# https://www.w3.org/TR/CSS2/syndata.html#value-def-identifier
|
||||
elem_id = re.sub(r'[^a-zA-Z0-9_-]', r'\\\g<0>', elem_id)
|
||||
self.find_css('#' + elem_id, find_id_cb)
|
||||
|
||||
def find_focused(self, callback):
|
||||
|
||||
@@ -86,6 +86,21 @@ class BrowserPage(QWebPage):
|
||||
self.on_save_frame_state_requested)
|
||||
self.restoreFrameStateRequested.connect(
|
||||
self.on_restore_frame_state_requested)
|
||||
self.loadFinished.connect(
|
||||
functools.partial(self._inject_userjs, self.mainFrame()))
|
||||
self.frameCreated.connect(self._connect_userjs_signals)
|
||||
|
||||
@pyqtSlot('QWebFrame*')
|
||||
def _connect_userjs_signals(self, frame):
|
||||
"""Connect userjs related signals to `frame`.
|
||||
|
||||
Connect the signals used as triggers for injecting user
|
||||
JavaScripts into the passed QWebFrame.
|
||||
"""
|
||||
log.greasemonkey.debug("Connecting to frame {} ({})"
|
||||
.format(frame, frame.url().toDisplayString()))
|
||||
frame.loadFinished.connect(
|
||||
functools.partial(self._inject_userjs, frame))
|
||||
|
||||
def javaScriptPrompt(self, frame, js_msg, default):
|
||||
"""Override javaScriptPrompt to use qutebrowser prompts."""
|
||||
@@ -283,6 +298,38 @@ class BrowserPage(QWebPage):
|
||||
else:
|
||||
self.error_occurred = False
|
||||
|
||||
def _inject_userjs(self, frame):
|
||||
"""Inject user JavaScripts into the page.
|
||||
|
||||
Args:
|
||||
frame: The QWebFrame to inject the user scripts into.
|
||||
"""
|
||||
url = frame.url()
|
||||
if url.isEmpty():
|
||||
url = frame.requestedUrl()
|
||||
|
||||
log.greasemonkey.debug("_inject_userjs called for {} ({})"
|
||||
.format(frame, url.toDisplayString()))
|
||||
|
||||
greasemonkey = objreg.get('greasemonkey')
|
||||
scripts = greasemonkey.scripts_for(url)
|
||||
# QtWebKit has trouble providing us with signals representing
|
||||
# page load progress at reasonable times, so we just load all
|
||||
# scripts on the same event.
|
||||
toload = scripts.start + scripts.end + scripts.idle
|
||||
|
||||
if url.isEmpty():
|
||||
# This happens during normal usage like with view source but may
|
||||
# also indicate a bug.
|
||||
log.greasemonkey.debug("Not running scripts for frame with no "
|
||||
"url: {}".format(frame))
|
||||
assert not toload, toload
|
||||
|
||||
for script in toload:
|
||||
if frame is self.mainFrame() or script.runs_on_sub_frames:
|
||||
log.webview.debug('Running GM script: {}'.format(script.name))
|
||||
frame.evaluateJavaScript(script.code())
|
||||
|
||||
@pyqtSlot('QWebFrame*', 'QWebPage::Feature')
|
||||
def _on_feature_permission_requested(self, frame, feature):
|
||||
"""Ask the user for approval for geolocation/notifications."""
|
||||
|
||||
@@ -61,7 +61,7 @@ def check_exclusive(flags, names):
|
||||
argstr))
|
||||
|
||||
|
||||
class register: # pylint: disable=invalid-name
|
||||
class register: # noqa: N801,N806 pylint: disable=invalid-name
|
||||
|
||||
"""Decorator to register a new command handler.
|
||||
|
||||
@@ -114,7 +114,7 @@ class register: # pylint: disable=invalid-name
|
||||
return func
|
||||
|
||||
|
||||
class argument: # pylint: disable=invalid-name
|
||||
class argument: # noqa: N801,N806 pylint: disable=invalid-name
|
||||
|
||||
"""Decorator to customize an argument for @cmdutils.register.
|
||||
|
||||
|
||||
@@ -58,7 +58,6 @@ class Command:
|
||||
name: The main name of the command.
|
||||
maxsplit: The maximum amount of splits to do for the commandline, or
|
||||
None.
|
||||
hide: Whether to hide the arguments or not.
|
||||
deprecated: False, or a string to describe why a command is deprecated.
|
||||
desc: The description of the command.
|
||||
handler: The handler function to call.
|
||||
@@ -69,41 +68,37 @@ class Command:
|
||||
backend: Which backend the command works with (or None if it works with
|
||||
both)
|
||||
no_replace_variables: Don't replace variables like {url}
|
||||
modes: The modes the command can be executed in.
|
||||
_qute_args: The saved data from @cmdutils.argument
|
||||
_modes: The modes the command can be executed in.
|
||||
_count: The count set for the command.
|
||||
_instance: The object to bind 'self' to.
|
||||
_scope: The scope to get _instance for in the object registry.
|
||||
"""
|
||||
|
||||
def __init__(self, *, handler, name, instance=None, maxsplit=None,
|
||||
hide=False, modes=None, not_modes=None, debug=False,
|
||||
deprecated=False, no_cmd_split=False,
|
||||
star_args_optional=False, scope='global', backend=None,
|
||||
no_replace_variables=False):
|
||||
# I really don't know how to solve this in a better way, I tried.
|
||||
# pylint: disable=too-many-locals
|
||||
modes=None, not_modes=None, debug=False, deprecated=False,
|
||||
no_cmd_split=False, star_args_optional=False, scope='global',
|
||||
backend=None, no_replace_variables=False):
|
||||
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:
|
||||
for m in modes:
|
||||
if not isinstance(m, usertypes.KeyMode):
|
||||
raise TypeError("Mode {} is no KeyMode member!".format(m))
|
||||
self._modes = set(modes)
|
||||
self.modes = set(modes)
|
||||
elif not_modes is not None:
|
||||
for m in not_modes:
|
||||
if not isinstance(m, usertypes.KeyMode):
|
||||
raise TypeError("Mode {} is no KeyMode member!".format(m))
|
||||
self._modes = set(usertypes.KeyMode).difference(not_modes)
|
||||
self.modes = set(usertypes.KeyMode).difference(not_modes)
|
||||
else:
|
||||
self._modes = set(usertypes.KeyMode)
|
||||
self.modes = set(usertypes.KeyMode)
|
||||
if scope != 'global' and instance is None:
|
||||
raise ValueError("Setting scope without setting instance makes "
|
||||
"no sense!")
|
||||
|
||||
self.name = name
|
||||
self.maxsplit = maxsplit
|
||||
self.hide = hide
|
||||
self.deprecated = deprecated
|
||||
self._instance = instance
|
||||
self._scope = scope
|
||||
@@ -195,6 +190,7 @@ class Command:
|
||||
return True
|
||||
elif arg_info.win_id:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _inspect_func(self):
|
||||
"""Inspect the function to get useful informations from it.
|
||||
@@ -398,10 +394,8 @@ class Command:
|
||||
if isinstance(typ, tuple):
|
||||
raise TypeError("{}: Legacy tuple type annotation!".format(
|
||||
self.name))
|
||||
elif type(typ) is type(typing.Union): # flake8: disable=E721
|
||||
elif getattr(typ, '__origin__', None) is typing.Union:
|
||||
# this is... slightly evil, I know
|
||||
# We also can't use isinstance here because typing.Union doesn't
|
||||
# support that.
|
||||
# pylint: disable=no-member,useless-suppression
|
||||
try:
|
||||
types = list(typ.__args__)
|
||||
@@ -510,8 +504,8 @@ class Command:
|
||||
Args:
|
||||
mode: The usertypes.KeyMode to check.
|
||||
"""
|
||||
if mode not in self._modes:
|
||||
mode_names = '/'.join(sorted(m.name for m in self._modes))
|
||||
if mode not in self.modes:
|
||||
mode_names = '/'.join(sorted(m.name for m in self.modes))
|
||||
raise cmdexc.PrerequisitesError(
|
||||
"{}: This command is only allowed in {} mode, not {}.".format(
|
||||
self.name, mode_names, mode.name))
|
||||
|
||||
@@ -95,7 +95,6 @@ class CommandParser:
|
||||
"""Parse qutebrowser commandline commands.
|
||||
|
||||
Attributes:
|
||||
|
||||
_partial_match: Whether to allow partial command matches.
|
||||
"""
|
||||
|
||||
@@ -127,7 +126,7 @@ class CommandParser:
|
||||
new_cmd += ' '
|
||||
return new_cmd
|
||||
|
||||
def _parse_all_gen(self, text, aliases=True, *args, **kwargs):
|
||||
def _parse_all_gen(self, text, *args, aliases=True, **kwargs):
|
||||
"""Split a command on ;; and parse all parts.
|
||||
|
||||
If the first command in the commandline is a non-split one, it only
|
||||
@@ -214,12 +213,12 @@ class CommandParser:
|
||||
Return:
|
||||
cmdstr modified to the matching completion or unmodified
|
||||
"""
|
||||
matches = []
|
||||
for valid_command in cmdutils.cmd_dict:
|
||||
if valid_command.find(cmdstr) == 0:
|
||||
matches.append(valid_command)
|
||||
matches = [cmd for cmd in sorted(cmdutils.cmd_dict, key=len)
|
||||
if cmdstr in cmd]
|
||||
if len(matches) == 1:
|
||||
cmdstr = matches[0]
|
||||
elif len(matches) > 1 and config.val.completion.use_best_match:
|
||||
cmdstr = matches[0]
|
||||
return cmdstr
|
||||
|
||||
def _split_args(self, cmd, argstr, keep):
|
||||
|
||||
@@ -56,6 +56,7 @@ class _QtFIFOReader(QObject):
|
||||
# can add O_NONBLOCK.
|
||||
# pylint: disable=no-member,useless-suppression
|
||||
fd = os.open(filepath, os.O_RDWR | os.O_NONBLOCK)
|
||||
# pylint: enable=no-member,useless-suppression
|
||||
self._fifo = os.fdopen(fd, 'r')
|
||||
self._notifier = QSocketNotifier(fd, QSocketNotifier.Read, self)
|
||||
self._notifier.activated.connect(self.read_line)
|
||||
@@ -247,6 +248,7 @@ class _POSIXUserscriptRunner(_BaseUserscriptRunner):
|
||||
dir=standarddir.runtime())
|
||||
# pylint: disable=no-member,useless-suppression
|
||||
os.mkfifo(self._filepath)
|
||||
# pylint: enable=no-member,useless-suppression
|
||||
except OSError as e:
|
||||
message.error("Error while creating FIFO: {}".format(e))
|
||||
return
|
||||
|
||||
@@ -35,6 +35,7 @@ class CompletionInfo:
|
||||
|
||||
config = attr.ib()
|
||||
keyconf = attr.ib()
|
||||
win_id = attr.ib()
|
||||
|
||||
|
||||
class Completer(QObject):
|
||||
@@ -43,7 +44,7 @@ class Completer(QObject):
|
||||
|
||||
Attributes:
|
||||
_cmd: The statusbar Command object this completer belongs to.
|
||||
_ignore_change: Whether to ignore the next completion update.
|
||||
_win_id: The id of the window that owns this object.
|
||||
_timer: The timer used to trigger the completion update.
|
||||
_last_cursor_pos: The old cursor position so we avoid double completion
|
||||
updates.
|
||||
@@ -51,10 +52,10 @@ class Completer(QObject):
|
||||
_last_completion_func: The completion function used for the last text.
|
||||
"""
|
||||
|
||||
def __init__(self, cmd, parent=None):
|
||||
def __init__(self, *, cmd, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
self._cmd = cmd
|
||||
self._ignore_change = False
|
||||
self._win_id = win_id
|
||||
self._timer = QTimer()
|
||||
self._timer.setSingleShot(True)
|
||||
self._timer.setInterval(0)
|
||||
@@ -86,8 +87,6 @@ class Completer(QObject):
|
||||
# cursor on a flag or after an explicit split (--)
|
||||
return None
|
||||
log.completion.debug("Before removing flags: {}".format(before_cursor))
|
||||
before_cursor = [x for x in before_cursor if not x.startswith('-')]
|
||||
log.completion.debug("After removing flags: {}".format(before_cursor))
|
||||
if not before_cursor:
|
||||
# '|' or 'set|'
|
||||
log.completion.debug('Starting command completion')
|
||||
@@ -98,6 +97,9 @@ class Completer(QObject):
|
||||
log.completion.debug("No completion for unknown command: {}"
|
||||
.format(before_cursor[0]))
|
||||
return None
|
||||
|
||||
before_cursor = [x for x in before_cursor if not x.startswith('-')]
|
||||
log.completion.debug("After removing flags: {}".format(before_cursor))
|
||||
argpos = len(before_cursor) - 1
|
||||
try:
|
||||
func = cmd.get_pos_arg_info(argpos).completion
|
||||
@@ -133,9 +135,7 @@ class Completer(QObject):
|
||||
return [], '', []
|
||||
parser = runners.CommandParser()
|
||||
result = parser.parse(text, fallback=True, keep=True)
|
||||
# pylint: disable=not-an-iterable
|
||||
parts = [x for x in result.cmdline if x]
|
||||
# pylint: enable=not-an-iterable
|
||||
pos = self._cmd.cursorPosition() - len(self._cmd.prefix())
|
||||
pos = min(pos, len(text)) # Qt treats 2-byte UTF-16 chars as 2 chars
|
||||
log.completion.debug('partitioning {} around position {}'.format(parts,
|
||||
@@ -154,8 +154,7 @@ class Completer(QObject):
|
||||
"partitioned: {} '{}' {}".format(prefix, center, postfix))
|
||||
return prefix, center, postfix
|
||||
|
||||
# We should always return above
|
||||
assert False, parts
|
||||
raise utils.Unreachable("Not all parts consumed: {}".format(parts))
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_selection_changed(self, text):
|
||||
@@ -178,16 +177,16 @@ class Completer(QObject):
|
||||
text = self._quote(text)
|
||||
model = self._model()
|
||||
if model.count() == 1 and config.val.completion.quick:
|
||||
# If we only have one item, we want to apply it immediately
|
||||
# and go on to the next part.
|
||||
self._change_completed_part(text, before, after, immediate=True)
|
||||
# If we only have one item, we want to apply it immediately and go
|
||||
# on to the next part, unless we are quick-completing the part
|
||||
# after maxsplit, so that we don't keep offering completions
|
||||
# (see issue #1519)
|
||||
if maxsplit is not None and maxsplit < len(before):
|
||||
# If we are quick-completing the part after maxsplit, don't
|
||||
# keep offering completions (see issue #1519)
|
||||
self._ignore_change = True
|
||||
self._change_completed_part(text, before, after)
|
||||
else:
|
||||
self._change_completed_part(text, before, after,
|
||||
immediate=True)
|
||||
else:
|
||||
log.completion.debug("Will ignore next completion update.")
|
||||
self._ignore_change = True
|
||||
self._change_completed_part(text, before, after)
|
||||
|
||||
@pyqtSlot()
|
||||
@@ -196,26 +195,31 @@ class Completer(QObject):
|
||||
|
||||
For performance reasons we don't want to block here, instead we do this
|
||||
in the background.
|
||||
|
||||
We delay the update only if we've already input some text and ignore
|
||||
updates if the text is shorter than completion.min_chars (unless we're
|
||||
hitting backspace in which case updates won't be ignored).
|
||||
"""
|
||||
if (self._cmd.cursorPosition() == self._last_cursor_pos and
|
||||
self._cmd.text() == self._last_text):
|
||||
_cmd, _sep, rest = self._cmd.text().partition(' ')
|
||||
input_length = len(rest)
|
||||
if (0 < input_length < config.val.completion.min_chars and
|
||||
self._cmd.cursorPosition() > self._last_cursor_pos):
|
||||
log.completion.debug("Ignoring update because the length of "
|
||||
"the text is less than completion.min_chars.")
|
||||
elif (self._cmd.cursorPosition() == self._last_cursor_pos and
|
||||
self._cmd.text() == self._last_text):
|
||||
log.completion.debug("Ignoring update because there were no "
|
||||
"changes.")
|
||||
else:
|
||||
log.completion.debug("Scheduling completion update.")
|
||||
self._timer.start()
|
||||
start_delay = config.val.completion.delay if self._last_text else 0
|
||||
self._timer.start(start_delay)
|
||||
self._last_cursor_pos = self._cmd.cursorPosition()
|
||||
self._last_text = self._cmd.text()
|
||||
|
||||
@pyqtSlot()
|
||||
def _update_completion(self):
|
||||
"""Check if completions are available and activate them."""
|
||||
if self._ignore_change:
|
||||
log.completion.debug("Ignoring completion update because "
|
||||
"ignore_change is True.")
|
||||
self._ignore_change = False
|
||||
return
|
||||
|
||||
completion = self.parent()
|
||||
|
||||
if self._cmd.prefix() != ':':
|
||||
@@ -244,10 +248,11 @@ class Completer(QObject):
|
||||
if func != self._last_completion_func:
|
||||
self._last_completion_func = func
|
||||
args = (x for x in before_cursor[1:] if not x.startswith('-'))
|
||||
with debug.log_time(log.completion,
|
||||
'Starting {} completion'.format(func.__name__)):
|
||||
with debug.log_time(log.completion, 'Starting {} completion'
|
||||
.format(func.__name__)):
|
||||
info = CompletionInfo(config=config.instance,
|
||||
keyconf=config.key_instance)
|
||||
keyconf=config.key_instance,
|
||||
win_id=self._win_id)
|
||||
model = func(*args, info=info)
|
||||
with debug.log_time(log.completion, 'Set completion model'):
|
||||
completion.set_model(model)
|
||||
@@ -273,7 +278,20 @@ class Completer(QObject):
|
||||
# pad with a space if quick-completing the last entry
|
||||
text += ' '
|
||||
log.completion.debug("setting text = '{}', pos = {}".format(text, pos))
|
||||
|
||||
# generally, we don't want to let self._cmd emit cursorPositionChanged,
|
||||
# because that'll schedule a completion update. That happens when
|
||||
# tabbing through the completions, and we want to change the command
|
||||
# text but we also want to keep the original completion list for the
|
||||
# command the user manually entered. The exception is when we're
|
||||
# immediately completing, in which case we *do* want to update the
|
||||
# completion view so that we can start completing the next part
|
||||
if not immediate:
|
||||
self._cmd.blockSignals(True)
|
||||
|
||||
self._cmd.setText(text)
|
||||
self._cmd.setCursorPosition(pos)
|
||||
self._cmd.setFocus()
|
||||
|
||||
self._cmd.blockSignals(False)
|
||||
self._cmd.show_cmd.emit()
|
||||
|
||||
@@ -138,10 +138,10 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
||||
|
||||
self._painter.translate(text_rect.left(), text_rect.top())
|
||||
self._get_textdoc(index)
|
||||
self._draw_textdoc(text_rect)
|
||||
self._draw_textdoc(text_rect, index.column())
|
||||
self._painter.restore()
|
||||
|
||||
def _draw_textdoc(self, rect):
|
||||
def _draw_textdoc(self, rect, col):
|
||||
"""Draw the QTextDocument of an item.
|
||||
|
||||
Args:
|
||||
@@ -156,7 +156,9 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
||||
elif not self._opt.state & QStyle.State_Enabled:
|
||||
color = config.val.colors.completion.category.fg
|
||||
else:
|
||||
color = config.val.colors.completion.fg
|
||||
colors = config.val.colors.completion.fg
|
||||
# if multiple colors are set, use different colors per column
|
||||
color = colors[col % len(colors)]
|
||||
self._painter.setPen(color)
|
||||
|
||||
ctx = QAbstractTextDocumentLayout.PaintContext()
|
||||
@@ -202,7 +204,8 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
||||
if index.column() in columns_to_filter and pattern:
|
||||
repl = r'<span class="highlight">\g<0></span>'
|
||||
text = re.sub(re.escape(pattern).replace(r'\ ', r'|'),
|
||||
repl, self._opt.text, flags=re.IGNORECASE)
|
||||
repl, html.escape(self._opt.text),
|
||||
flags=re.IGNORECASE)
|
||||
self._doc.setHtml(text)
|
||||
else:
|
||||
self._doc.setPlainText(self._opt.text)
|
||||
|
||||
@@ -23,12 +23,12 @@ Defines a CompletionView which uses CompletionFiterModel and CompletionModel
|
||||
subclasses to provide completions.
|
||||
"""
|
||||
|
||||
from PyQt5.QtWidgets import QStyle, QTreeView, QSizePolicy, QStyleFactory
|
||||
from PyQt5.QtWidgets import QTreeView, QSizePolicy, QStyleFactory
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QItemSelectionModel, QSize
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.completion import completiondelegate
|
||||
from qutebrowser.utils import utils, usertypes, debug, log
|
||||
from qutebrowser.utils import utils, usertypes, debug, log, objreg
|
||||
from qutebrowser.commands import cmdexc, cmdutils
|
||||
|
||||
|
||||
@@ -152,12 +152,12 @@ class CompletionView(QTreeView):
|
||||
column_widths = self.model().column_widths
|
||||
pixel_widths = [(width * perc // 100) for perc in column_widths]
|
||||
|
||||
if self.verticalScrollBar().isVisible():
|
||||
delta = self.style().pixelMetric(QStyle.PM_ScrollBarExtent) + 5
|
||||
if pixel_widths[-1] > delta:
|
||||
pixel_widths[-1] -= delta
|
||||
else:
|
||||
pixel_widths[-2] -= delta
|
||||
delta = self.verticalScrollBar().sizeHint().width()
|
||||
if pixel_widths[-1] > delta:
|
||||
pixel_widths[-1] -= delta
|
||||
else:
|
||||
pixel_widths[-2] -= delta
|
||||
|
||||
for i, w in enumerate(pixel_widths):
|
||||
assert w >= 0, i
|
||||
self.setColumnWidth(i, w)
|
||||
@@ -180,6 +180,7 @@ class CompletionView(QTreeView):
|
||||
return self.model().last_item()
|
||||
else:
|
||||
return self.model().first_item()
|
||||
|
||||
while True:
|
||||
idx = self.indexAbove(idx) if upwards else self.indexBelow(idx)
|
||||
# wrap around if we arrived at beginning/end
|
||||
@@ -193,6 +194,8 @@ class CompletionView(QTreeView):
|
||||
# Item is a real item, not a category header -> success
|
||||
return idx
|
||||
|
||||
raise utils.Unreachable
|
||||
|
||||
def _next_category_idx(self, upwards):
|
||||
"""Get the index of the previous/next category.
|
||||
|
||||
@@ -222,30 +225,46 @@ class CompletionView(QTreeView):
|
||||
self.scrollTo(idx)
|
||||
return idx.child(0, 0)
|
||||
|
||||
@cmdutils.register(instance='completion', hide=True,
|
||||
raise utils.Unreachable
|
||||
|
||||
@cmdutils.register(instance='completion',
|
||||
modes=[usertypes.KeyMode.command], scope='window')
|
||||
@cmdutils.argument('which', choices=['next', 'prev', 'next-category',
|
||||
'prev-category'])
|
||||
def completion_item_focus(self, which):
|
||||
@cmdutils.argument('history', flag='H')
|
||||
def completion_item_focus(self, which, history=False):
|
||||
"""Shift the focus of the completion menu to another item.
|
||||
|
||||
Args:
|
||||
which: 'next', 'prev', 'next-category', or 'prev-category'.
|
||||
history: Navigate through command history if no text was typed.
|
||||
"""
|
||||
if history:
|
||||
status = objreg.get('status-command', scope='window',
|
||||
window=self._win_id)
|
||||
if (status.text() == ':' or status.history.is_browsing() or
|
||||
not self._active):
|
||||
if which == 'next':
|
||||
status.command_history_next()
|
||||
return
|
||||
elif which == 'prev':
|
||||
status.command_history_prev()
|
||||
return
|
||||
else:
|
||||
raise cmdexc.CommandError("Can't combine --history with "
|
||||
"{}!".format(which))
|
||||
|
||||
if not self._active:
|
||||
return
|
||||
selmodel = self.selectionModel()
|
||||
|
||||
if which == 'next':
|
||||
idx = self._next_idx(upwards=False)
|
||||
elif which == 'prev':
|
||||
idx = self._next_idx(upwards=True)
|
||||
elif which == 'next-category':
|
||||
idx = self._next_category_idx(upwards=False)
|
||||
elif which == 'prev-category':
|
||||
idx = self._next_category_idx(upwards=True)
|
||||
else: # pragma: no cover
|
||||
raise ValueError("Invalid 'which' value {!r}".format(which))
|
||||
selmodel = self.selectionModel()
|
||||
indices = {
|
||||
'next': self._next_idx(upwards=False),
|
||||
'prev': self._next_idx(upwards=True),
|
||||
'next-category': self._next_category_idx(upwards=False),
|
||||
'prev-category': self._next_category_idx(upwards=True),
|
||||
}
|
||||
idx = indices[which]
|
||||
|
||||
if not idx.isValid():
|
||||
return
|
||||
@@ -369,7 +388,7 @@ class CompletionView(QTreeView):
|
||||
scrollbar.setValue(scrollbar.minimum())
|
||||
super().showEvent(e)
|
||||
|
||||
@cmdutils.register(instance='completion', hide=True,
|
||||
@cmdutils.register(instance='completion',
|
||||
modes=[usertypes.KeyMode.command], scope='window')
|
||||
def completion_item_del(self):
|
||||
"""Delete the current completion item."""
|
||||
@@ -377,3 +396,21 @@ class CompletionView(QTreeView):
|
||||
if not index.isValid():
|
||||
raise cmdexc.CommandError("No item selected!")
|
||||
self.model().delete_cur_item(index)
|
||||
|
||||
@cmdutils.register(instance='completion',
|
||||
modes=[usertypes.KeyMode.command], scope='window')
|
||||
def completion_item_yank(self, sel=False):
|
||||
"""Yank the current completion item into the clipboard.
|
||||
|
||||
Args:
|
||||
sel: Use the primary selection instead of the clipboard.
|
||||
"""
|
||||
status = objreg.get('status-command', scope='window',
|
||||
window=self._win_id)
|
||||
text = status.selectedText()
|
||||
if not text:
|
||||
index = self.currentIndex()
|
||||
if not index.isValid():
|
||||
raise cmdexc.CommandError("No item selected!")
|
||||
text = self.model().data(index)
|
||||
utils.set_clipboard(text, selection=sel)
|
||||
|
||||
@@ -60,8 +60,6 @@ class CompletionModel(QAbstractItemModel):
|
||||
def add_category(self, cat):
|
||||
"""Add a completion category to the model."""
|
||||
self._categories.append(cat)
|
||||
cat.layoutAboutToBeChanged.connect(self.layoutAboutToBeChanged)
|
||||
cat.layoutChanged.connect(self.layoutChanged)
|
||||
|
||||
def data(self, index, role=Qt.DisplayRole):
|
||||
"""Return the item data for index.
|
||||
@@ -179,8 +177,13 @@ class CompletionModel(QAbstractItemModel):
|
||||
pattern: The filter pattern to set.
|
||||
"""
|
||||
log.completion.debug("Setting completion pattern '{}'".format(pattern))
|
||||
# WORKAROUND:
|
||||
# layoutChanged is broken in PyQt 5.7.1, so we must use metaObject
|
||||
# https://www.riverbankcomputing.com/pipermail/pyqt/2017-January/038483.html
|
||||
self.metaObject().invokeMethod(self, "layoutAboutToBeChanged")
|
||||
for cat in self._categories:
|
||||
cat.set_pattern(pattern)
|
||||
self.metaObject().invokeMethod(self, "layoutChanged")
|
||||
|
||||
def first_item(self):
|
||||
"""Return the index of the first child (non-category) in the model."""
|
||||
|
||||
@@ -29,7 +29,7 @@ def option(*, info):
|
||||
model = completionmodel.CompletionModel(column_widths=(20, 70, 10))
|
||||
options = ((opt.name, opt.description, info.config.get_str(opt.name))
|
||||
for opt in configdata.DATA.values())
|
||||
model.add_category(listcategory.ListCategory("Options", sorted(options)))
|
||||
model.add_category(listcategory.ListCategory("Options", options))
|
||||
return model
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ def customized_option(*, info):
|
||||
options = ((opt.name, opt.description, info.config.get_str(opt.name))
|
||||
for opt, _value in info.config)
|
||||
model.add_category(listcategory.ListCategory("Customized options",
|
||||
sorted(options)))
|
||||
options))
|
||||
return model
|
||||
|
||||
|
||||
@@ -60,14 +60,14 @@ def value(optname, *_values, info):
|
||||
|
||||
opt = info.config.get_opt(optname)
|
||||
default = opt.typ.to_str(opt.default)
|
||||
cur_cat = listcategory.ListCategory("Current/Default",
|
||||
cur_cat = listcategory.ListCategory(
|
||||
"Current/Default",
|
||||
[(current, "Current value"), (default, "Default value")])
|
||||
model.add_category(cur_cat)
|
||||
|
||||
vals = opt.typ.complete()
|
||||
if vals is not None:
|
||||
model.add_category(listcategory.ListCategory("Completions",
|
||||
sorted(vals)))
|
||||
model.add_category(listcategory.ListCategory("Completions", vals))
|
||||
return model
|
||||
|
||||
|
||||
@@ -78,17 +78,26 @@ def bind(key, *, info):
|
||||
key: the key being bound.
|
||||
"""
|
||||
model = completionmodel.CompletionModel(column_widths=(20, 60, 20))
|
||||
cmd_text = info.keyconf.get_command(key, 'normal')
|
||||
data = []
|
||||
|
||||
cmd_text = info.keyconf.get_command(key, 'normal')
|
||||
if cmd_text:
|
||||
parser = runners.CommandParser()
|
||||
try:
|
||||
cmd = parser.parse(cmd_text).cmd
|
||||
except cmdexc.NoSuchCommandError:
|
||||
data = [(cmd_text, 'Invalid command!', key)]
|
||||
data.append((cmd_text, '(Current) Invalid command!', key))
|
||||
else:
|
||||
data = [(cmd_text, cmd.desc, key)]
|
||||
model.add_category(listcategory.ListCategory("Current", data))
|
||||
data.append((cmd_text, '(Current) {}'.format(cmd.desc), key))
|
||||
|
||||
cmd_text = info.keyconf.get_command(key, 'normal', default=True)
|
||||
if cmd_text:
|
||||
parser = runners.CommandParser()
|
||||
cmd = parser.parse(cmd_text).cmd
|
||||
data.append((cmd_text, '(Default) {}'.format(cmd.desc), key))
|
||||
|
||||
if data:
|
||||
model.add_category(listcategory.ListCategory("Current/Default", data))
|
||||
|
||||
cmdlist = util.get_cmd_completions(info, include_hidden=True,
|
||||
include_aliases=True)
|
||||
|
||||
@@ -19,8 +19,6 @@
|
||||
|
||||
"""A completion category that queries the SQL History store."""
|
||||
|
||||
import re
|
||||
|
||||
from PyQt5.QtSql import QSqlQueryModel
|
||||
|
||||
from qutebrowser.misc import sql
|
||||
@@ -36,21 +34,7 @@ class HistoryCategory(QSqlQueryModel):
|
||||
"""Create a new History completion category."""
|
||||
super().__init__(parent=parent)
|
||||
self.name = "History"
|
||||
|
||||
# replace ' in timestamp-format to avoid breaking the query
|
||||
timestamp_format = config.val.completion.timestamp_format
|
||||
timefmt = ("strftime('{}', last_atime, 'unixepoch', 'localtime')"
|
||||
.format(timestamp_format.replace("'", "`")))
|
||||
|
||||
self._query = sql.Query(' '.join([
|
||||
"SELECT url, title, {}".format(timefmt),
|
||||
"FROM CompletionHistory",
|
||||
# the incoming pattern will have literal % and _ escaped with '\'
|
||||
# we need to tell sql to treat '\' as an escape character
|
||||
"WHERE (url LIKE :pat escape '\\' or title LIKE :pat escape '\\')",
|
||||
self._atime_expr(),
|
||||
"ORDER BY last_atime DESC",
|
||||
]), forward_only=False)
|
||||
self._query = None
|
||||
|
||||
# advertise that this model filters by URL and title
|
||||
self.columns_to_filter = [0, 1]
|
||||
@@ -86,11 +70,36 @@ class HistoryCategory(QSqlQueryModel):
|
||||
# escape to treat a user input % or _ as a literal, not a wildcard
|
||||
pattern = pattern.replace('%', '\\%')
|
||||
pattern = pattern.replace('_', '\\_')
|
||||
# treat spaces as wildcards to match any of the typed words
|
||||
pattern = re.sub(r' +', '%', pattern)
|
||||
pattern = '%{}%'.format(pattern)
|
||||
words = ['%{}%'.format(w) for w in pattern.split(' ')]
|
||||
|
||||
# build a where clause to match all of the words in any order
|
||||
# given the search term "a b", the WHERE clause would be:
|
||||
# ((url || title) LIKE '%a%') AND ((url || title) LIKE '%b%')
|
||||
where_clause = ' AND '.join(
|
||||
"(url || title) LIKE :{} escape '\\'".format(i)
|
||||
for i in range(len(words)))
|
||||
|
||||
# replace ' in timestamp-format to avoid breaking the query
|
||||
timestamp_format = config.val.completion.timestamp_format
|
||||
timefmt = ("strftime('{}', last_atime, 'unixepoch', 'localtime')"
|
||||
.format(timestamp_format.replace("'", "`")))
|
||||
|
||||
if not self._query or len(words) != len(self._query.boundValues()):
|
||||
# if the number of words changed, we need to generate a new query
|
||||
# otherwise, we can reuse the prepared query for performance
|
||||
self._query = sql.Query(' '.join([
|
||||
"SELECT url, title, {}".format(timefmt),
|
||||
"FROM CompletionHistory",
|
||||
# the incoming pattern will have literal % and _ escaped
|
||||
# we need to tell sql to treat '\' as an escape character
|
||||
'WHERE ({})'.format(where_clause),
|
||||
self._atime_expr(),
|
||||
"ORDER BY last_atime DESC",
|
||||
]), forward_only=False)
|
||||
|
||||
with debug.log_time('sql', 'Running completion query'):
|
||||
self._query.run(pat=pattern)
|
||||
self._query.run(**{
|
||||
str(i): w for i, w in enumerate(words)})
|
||||
self.setQuery(self._query)
|
||||
|
||||
def removeRows(self, row, _count, _parent=None):
|
||||
|
||||
@@ -31,7 +31,7 @@ class ListCategory(QSortFilterProxyModel):
|
||||
|
||||
"""Expose a list of items as a category for the CompletionModel."""
|
||||
|
||||
def __init__(self, name, items, delete_func=None, parent=None):
|
||||
def __init__(self, name, items, sort=True, delete_func=None, parent=None):
|
||||
super().__init__(parent)
|
||||
self.name = name
|
||||
self.srcmodel = QStandardItemModel(parent=self)
|
||||
@@ -43,6 +43,7 @@ class ListCategory(QSortFilterProxyModel):
|
||||
self.srcmodel.appendRow([QStandardItem(x) for x in item])
|
||||
self.setSourceModel(self.srcmodel)
|
||||
self.delete_func = delete_func
|
||||
self._sort = sort
|
||||
|
||||
def set_pattern(self, val):
|
||||
"""Setter for pattern.
|
||||
@@ -60,19 +61,33 @@ class ListCategory(QSortFilterProxyModel):
|
||||
sortcol = 0
|
||||
self.sort(sortcol)
|
||||
|
||||
def lessThan(self, _lindex, rindex):
|
||||
def lessThan(self, lindex, rindex):
|
||||
"""Custom sorting implementation.
|
||||
|
||||
Prefers all items which start with self._pattern. Other than that, keep
|
||||
items in their original order.
|
||||
Prefers all items which start with self._pattern. Other than that, uses
|
||||
normal Python string sorting.
|
||||
|
||||
Args:
|
||||
_lindex: The QModelIndex of the left item (*left* < right)
|
||||
lindex: The QModelIndex of the left item (*left* < right)
|
||||
rindex: The QModelIndex of the right item (left < *right*)
|
||||
|
||||
Return:
|
||||
True if left < right, else False
|
||||
"""
|
||||
qtutils.ensure_valid(lindex)
|
||||
qtutils.ensure_valid(rindex)
|
||||
|
||||
left = self.srcmodel.data(lindex)
|
||||
right = self.srcmodel.data(rindex)
|
||||
return not right.startswith(self._pattern)
|
||||
|
||||
leftstart = left.startswith(self._pattern)
|
||||
rightstart = right.startswith(self._pattern)
|
||||
|
||||
if leftstart and not rightstart:
|
||||
return True
|
||||
elif rightstart and not leftstart:
|
||||
return False
|
||||
elif self._sort:
|
||||
return left < right
|
||||
else:
|
||||
return False
|
||||
|
||||
@@ -43,7 +43,7 @@ def helptopic(*, info):
|
||||
for opt in configdata.DATA.values())
|
||||
|
||||
model.add_category(listcategory.ListCategory("Commands", cmdlist))
|
||||
model.add_category(listcategory.ListCategory("Settings", sorted(settings)))
|
||||
model.add_category(listcategory.ListCategory("Settings", settings))
|
||||
return model
|
||||
|
||||
|
||||
@@ -59,7 +59,8 @@ def quickmark(*, info=None): # pylint: disable=unused-argument
|
||||
model = completionmodel.CompletionModel(column_widths=(30, 70, 0))
|
||||
marks = objreg.get('quickmark-manager').marks.items()
|
||||
model.add_category(listcategory.ListCategory('Quickmarks', marks,
|
||||
delete_func=delete))
|
||||
delete_func=delete,
|
||||
sort=False))
|
||||
return model
|
||||
|
||||
|
||||
@@ -75,7 +76,8 @@ def bookmark(*, info=None): # pylint: disable=unused-argument
|
||||
model = completionmodel.CompletionModel(column_widths=(30, 70, 0))
|
||||
marks = objreg.get('bookmark-manager').marks.items()
|
||||
model.add_category(listcategory.ListCategory('Bookmarks', marks,
|
||||
delete_func=delete))
|
||||
delete_func=delete,
|
||||
sort=False))
|
||||
return model
|
||||
|
||||
|
||||
@@ -92,10 +94,11 @@ def session(*, info=None): # pylint: disable=unused-argument
|
||||
return model
|
||||
|
||||
|
||||
def buffer(*, info=None): # pylint: disable=unused-argument
|
||||
"""A model to complete on open tabs across all windows.
|
||||
def _buffer(skip_win_id=None):
|
||||
"""Helper to get the completion model for buffer/other_buffer.
|
||||
|
||||
Used for switching the buffer command.
|
||||
Args:
|
||||
skip_win_id: The id of the window to skip, or None to include all.
|
||||
"""
|
||||
def delete_buffer(data):
|
||||
"""Close the selected tab."""
|
||||
@@ -107,6 +110,8 @@ def buffer(*, info=None): # pylint: disable=unused-argument
|
||||
model = completionmodel.CompletionModel(column_widths=(6, 40, 54))
|
||||
|
||||
for win_id in objreg.window_registry:
|
||||
if skip_win_id and win_id == skip_win_id:
|
||||
continue
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
if tabbed_browser.shutting_down:
|
||||
@@ -118,7 +123,44 @@ def buffer(*, info=None): # pylint: disable=unused-argument
|
||||
tab.url().toDisplayString(),
|
||||
tabbed_browser.page_title(idx)))
|
||||
cat = listcategory.ListCategory("{}".format(win_id), tabs,
|
||||
delete_func=delete_buffer)
|
||||
delete_func=delete_buffer)
|
||||
model.add_category(cat)
|
||||
|
||||
return model
|
||||
|
||||
|
||||
def buffer(*, info=None): # pylint: disable=unused-argument
|
||||
"""A model to complete on open tabs across all windows.
|
||||
|
||||
Used for switching the buffer command.
|
||||
"""
|
||||
return _buffer()
|
||||
|
||||
|
||||
def other_buffer(*, info):
|
||||
"""A model to complete on open tabs across all windows except the current.
|
||||
|
||||
Used for the tab-take command.
|
||||
"""
|
||||
return _buffer(skip_win_id=info.win_id)
|
||||
|
||||
|
||||
def window(*, info):
|
||||
"""A model to complete on all open windows."""
|
||||
model = completionmodel.CompletionModel(column_widths=(6, 30, 64))
|
||||
|
||||
windows = []
|
||||
|
||||
for win_id in objreg.window_registry:
|
||||
if win_id == info.win_id:
|
||||
continue
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
tab_titles = (tab.title() for tab in tabbed_browser.widgets())
|
||||
windows.append(("{}".format(win_id),
|
||||
objreg.window_registry[win_id].windowTitle(),
|
||||
", ".join(tab_titles)))
|
||||
|
||||
model.add_category(listcategory.ListCategory("Windows", windows))
|
||||
|
||||
return model
|
||||
|
||||
@@ -56,14 +56,17 @@ def url(*, info):
|
||||
"""
|
||||
model = completionmodel.CompletionModel(column_widths=(40, 50, 10))
|
||||
|
||||
quickmarks = ((url, name) for (name, url)
|
||||
in objreg.get('quickmark-manager').marks.items())
|
||||
quickmarks = [(url, name) for (name, url)
|
||||
in objreg.get('quickmark-manager').marks.items()]
|
||||
bookmarks = objreg.get('bookmark-manager').marks.items()
|
||||
|
||||
model.add_category(listcategory.ListCategory(
|
||||
'Quickmarks', quickmarks, delete_func=_delete_quickmark))
|
||||
model.add_category(listcategory.ListCategory(
|
||||
'Bookmarks', bookmarks, delete_func=_delete_bookmark))
|
||||
if quickmarks:
|
||||
model.add_category(listcategory.ListCategory(
|
||||
'Quickmarks', quickmarks, delete_func=_delete_quickmark,
|
||||
sort=False))
|
||||
if bookmarks:
|
||||
model.add_category(listcategory.ListCategory(
|
||||
'Bookmarks', bookmarks, delete_func=_delete_bookmark, sort=False))
|
||||
|
||||
if info.config.get('completion.web_history_max_items') != 0:
|
||||
hist_cat = histcategory.HistoryCategory(delete_func=_delete_history)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user