Compare commits
1308 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55a88ceea6 | ||
|
|
d4bf04d2c8 | ||
|
|
cb527913dc | ||
|
|
ddfa82345c | ||
|
|
45c75d5e04 | ||
|
|
9404c61f10 | ||
|
|
01d2654c23 | ||
|
|
bad349aacf | ||
|
|
5dacf1431f | ||
|
|
27c46f20c0 | ||
|
|
97a14c14b3 | ||
|
|
0195f717c3 | ||
|
|
b3f395453b | ||
|
|
d411ec1eba | ||
|
|
db8fa5fdb6 | ||
|
|
2872ae5641 | ||
|
|
cbe0ff94a1 | ||
|
|
19a0a026dc | ||
|
|
2bf9a81451 | ||
|
|
35d5038ab1 | ||
|
|
bf1d6acb06 | ||
|
|
07b1b3fbd4 | ||
|
|
8539d092df | ||
|
|
dfe2f9e38c | ||
|
|
4f870f902c | ||
|
|
e9f9449237 | ||
|
|
e6ffcfc673 | ||
|
|
5905b27299 | ||
|
|
5dca8cc8e5 | ||
|
|
80f46192b5 | ||
|
|
20f935e1f1 | ||
|
|
c87d369725 | ||
|
|
f035d4f362 | ||
|
|
a21a60c5ca | ||
|
|
cbe9ff7435 | ||
|
|
96c0cde8b8 | ||
|
|
ba92f32e9f | ||
|
|
64443a3eed | ||
|
|
1b5f1aaebf | ||
|
|
4bca180a44 | ||
|
|
f5e7388bfe | ||
|
|
4827e98b4e | ||
|
|
5f7ce74e66 | ||
|
|
d78c184b6a | ||
|
|
1a8de3b504 | ||
|
|
35335d954b | ||
|
|
9c080538ba | ||
|
|
e9a50f5f9f | ||
|
|
bdc82bc633 | ||
|
|
59a1609dd8 | ||
|
|
2a3369e2fe | ||
|
|
0cd5d4300b | ||
|
|
abbd69f604 | ||
|
|
fadc8f1e0b | ||
|
|
085304a1de | ||
|
|
ba1a7a8de8 | ||
|
|
211de6d664 | ||
|
|
0afbcc0faa | ||
|
|
4c1273ba0a | ||
|
|
e1f5da3eff | ||
|
|
e537826ff5 | ||
|
|
40185385cf | ||
|
|
14da05f7b1 | ||
|
|
00f0e519a9 | ||
|
|
552d041422 | ||
|
|
5301a42495 | ||
|
|
6b7cecc840 | ||
|
|
c9c827aa3e | ||
|
|
8a6ea6a375 | ||
|
|
a6e363e779 | ||
|
|
db8a84cf82 | ||
|
|
c25bbcc0cb | ||
|
|
756dca8a0e | ||
|
|
191ee67403 | ||
|
|
513773e81d | ||
|
|
d6d13770a3 | ||
|
|
b5777299fd | ||
|
|
b608259751 | ||
|
|
6498273b31 | ||
|
|
83aa936276 | ||
|
|
e95260092c | ||
|
|
0e64511d63 | ||
|
|
f63b95c298 | ||
|
|
220d1be500 | ||
|
|
36ef68c698 | ||
|
|
83fdb68fc2 | ||
|
|
8c1e95787d | ||
|
|
6c3f90146f | ||
|
|
13b7647443 | ||
|
|
1d1faf8d25 | ||
|
|
6c300f41dd | ||
|
|
1d7af2e74b | ||
|
|
8ce69e1e57 | ||
|
|
277daa334d | ||
|
|
e63d11527d | ||
|
|
51ea56375e | ||
|
|
8d34d4d4f5 | ||
|
|
f1b53cdbdc | ||
|
|
00666feaf6 | ||
|
|
be3727a599 | ||
|
|
0d8edd54fb | ||
|
|
4bac2f3e44 | ||
|
|
54e5176f28 | ||
|
|
cb357b326d | ||
|
|
1d50c2c39a | ||
|
|
e1d358f4c1 | ||
|
|
acf3945efc | ||
|
|
a7955505be | ||
|
|
2651688ef4 | ||
|
|
fd9a5fa334 | ||
|
|
2c9003fd4b | ||
|
|
5f69247033 | ||
|
|
9503f23abc | ||
|
|
d8e9d10c00 | ||
|
|
30aab8ef79 | ||
|
|
618586f8b0 | ||
|
|
953d028bd6 | ||
|
|
581b09b08f | ||
|
|
c63d16e2ea | ||
|
|
012f79b244 | ||
|
|
712e4a975a | ||
|
|
3cb93b22ae | ||
|
|
ade0e1bd0b | ||
|
|
e0c76fcb4f | ||
|
|
231b0522ca | ||
|
|
8dc34cf78a | ||
|
|
91c6847e59 | ||
|
|
0f1444125f | ||
|
|
5350b948ea | ||
|
|
763d035ee3 | ||
|
|
2f33f93a98 | ||
|
|
d3d644d6a5 | ||
|
|
09f2b06081 | ||
|
|
6d9f04355c | ||
|
|
c5d695b59e | ||
|
|
e20ad95666 | ||
|
|
6a486058c5 | ||
|
|
bae49c9366 | ||
|
|
9257538dcf | ||
|
|
36857d9250 | ||
|
|
6fa001481d | ||
|
|
7fc5e42cca | ||
|
|
f18b730f24 | ||
|
|
f0c0fea281 | ||
|
|
96ff0c61ef | ||
|
|
3b689166f8 | ||
|
|
da4402e98c | ||
|
|
38270de120 | ||
|
|
cc871389c9 | ||
|
|
6037d44d0e | ||
|
|
969b8f3200 | ||
|
|
37fa7a0d3e | ||
|
|
208b4d1cbc | ||
|
|
42550cd2e6 | ||
|
|
9bba3ddf0d | ||
|
|
ae4d5153b9 | ||
|
|
cdf0cf56fa | ||
|
|
7cbb2b079f | ||
|
|
8c660d1bf4 | ||
|
|
4a9e22163b | ||
|
|
5d787c84ea | ||
|
|
932e7a9ab9 | ||
|
|
68481bc989 | ||
|
|
feaccb3083 | ||
|
|
7f28097f55 | ||
|
|
d70bdb5552 | ||
|
|
16d369d98c | ||
|
|
6c2958b646 | ||
|
|
22adcfba75 | ||
|
|
3907d1e032 | ||
|
|
22088d9f7b | ||
|
|
727580d1f4 | ||
|
|
555930791f | ||
|
|
c8c6199369 | ||
|
|
8506e1f4f2 | ||
|
|
8edaad51c3 | ||
|
|
0695cfccfc | ||
|
|
999d70ae40 | ||
|
|
586c6e810f | ||
|
|
9f10fa105c | ||
|
|
2f9d1875cd | ||
|
|
9383452ab9 | ||
|
|
ed8c3f4aa2 | ||
|
|
3772084cbf | ||
|
|
31f49afdb2 | ||
|
|
1603b15cfd | ||
|
|
b06a38ce7e | ||
|
|
ad867a3b90 | ||
|
|
58bef6ba97 | ||
|
|
368e9a5cf9 | ||
|
|
4ed60efa80 | ||
|
|
f533e3b751 | ||
|
|
81993a70a2 | ||
|
|
64e0313090 | ||
|
|
8ae0bd2797 | ||
|
|
138ce60c1d | ||
|
|
5af8a95c82 | ||
|
|
eacdbe132e | ||
|
|
85532248fa | ||
|
|
506b1cdbc1 | ||
|
|
9c1b604cb1 | ||
|
|
14dacbaa92 | ||
|
|
0df4569502 | ||
|
|
fbf9817dcb | ||
|
|
a8fc561707 | ||
|
|
32d529b54e | ||
|
|
a576fae893 | ||
|
|
a273baf8a0 | ||
|
|
0fbd914432 | ||
|
|
ab6bfe61b8 | ||
|
|
1b88fec7f0 | ||
|
|
e0ff95d62a | ||
|
|
59d5c0f8e8 | ||
|
|
ba06323696 | ||
|
|
1a381bf0a5 | ||
|
|
dca962ca03 | ||
|
|
0b5af757ec | ||
|
|
322d97c3fa | ||
|
|
5c181a23ab | ||
|
|
02bcec37f4 | ||
|
|
4e57b79e91 | ||
|
|
9d963d55f5 | ||
|
|
4b9bbaa04d | ||
|
|
45db0eaccb | ||
|
|
6496442503 | ||
|
|
6c25e96621 | ||
|
|
3be0a78819 | ||
|
|
865fc2e0de | ||
|
|
45c6ffe991 | ||
|
|
6770a474c4 | ||
|
|
35beb84e85 | ||
|
|
dfa65a0bfe | ||
|
|
f077f52997 | ||
|
|
c77cff3fcb | ||
|
|
b906c862bb | ||
|
|
ce0622e38a | ||
|
|
e5958e6061 | ||
|
|
defcf5394a | ||
|
|
fa902c5d82 | ||
|
|
093f34183c | ||
|
|
ca4a997559 | ||
|
|
bb8d41cedc | ||
|
|
e1f3829383 | ||
|
|
fac322058e | ||
|
|
6573888dc6 | ||
|
|
fef1a65247 | ||
|
|
9607f3de59 | ||
|
|
c694bff902 | ||
|
|
cc84c1722d | ||
|
|
59c6555537 | ||
|
|
2dfcf9c506 | ||
|
|
b879f5e648 | ||
|
|
6af879887f | ||
|
|
8e000dfe54 | ||
|
|
1704438777 | ||
|
|
6b5d34c7fb | ||
|
|
474bf8ad06 | ||
|
|
e32d311d8e | ||
|
|
277beae399 | ||
|
|
b96ba8e41f | ||
|
|
dba631102a | ||
|
|
ff6df0c8ca | ||
|
|
e7dba338b5 | ||
|
|
5a60630450 | ||
|
|
e766cf5ed1 | ||
|
|
6e226c6885 | ||
|
|
38449e3e2b | ||
|
|
38038df703 | ||
|
|
1086e31f28 | ||
|
|
6aed6bca93 | ||
|
|
5a080207ff | ||
|
|
930bc9c998 | ||
|
|
54eb23eab1 | ||
|
|
4d4eee15d6 | ||
|
|
414dc29493 | ||
|
|
2a1f628e4e | ||
|
|
d23d53de1c | ||
|
|
9ecc0d8ef7 | ||
|
|
3605b1b510 | ||
|
|
c7c198b949 | ||
|
|
ad2bb45446 | ||
|
|
78bddaefe6 | ||
|
|
07862ce52e | ||
|
|
8408d6ed9b | ||
|
|
d7273283ce | ||
|
|
40f0f75ad5 | ||
|
|
fb9fca2477 | ||
|
|
cb68e4b6b2 | ||
|
|
1784dc777d | ||
|
|
888a1b8c57 | ||
|
|
e2e9bbacce | ||
|
|
b8389e4496 | ||
|
|
e8ceeceac8 | ||
|
|
459bbc3a6f | ||
|
|
4e46c34e5a | ||
|
|
e27c54a5c1 | ||
|
|
5be44756e3 | ||
|
|
b840b8066b | ||
|
|
1e2015be65 | ||
|
|
7f4cba8bc2 | ||
|
|
d5a1fff637 | ||
|
|
d1a4a028cd | ||
|
|
69d19e49df | ||
|
|
501764d1cb | ||
|
|
d9a3268405 | ||
|
|
43ab27634f | ||
|
|
7f8ae531aa | ||
|
|
9b22480b07 | ||
|
|
ebf378a945 | ||
|
|
1dbd156c2f | ||
|
|
276b244466 | ||
|
|
10016ae240 | ||
|
|
43ce10efc3 | ||
|
|
4e22b4666d | ||
|
|
7ddde334da | ||
|
|
333c0d848b | ||
|
|
0332dce458 | ||
|
|
a2952e13a8 | ||
|
|
c652b0f96c | ||
|
|
f4017eb5b6 | ||
|
|
cd9fe57d84 | ||
|
|
3f18a5ada7 | ||
|
|
c74236dd96 | ||
|
|
599a5b9648 | ||
|
|
2150154350 | ||
|
|
1c76a51c1e | ||
|
|
396f82d474 | ||
|
|
64b783d9c0 | ||
|
|
f97f427100 | ||
|
|
32b2b3dfd9 | ||
|
|
b1ddb9a6df | ||
|
|
691cd2d09b | ||
|
|
3e0d49a4b3 | ||
|
|
f821fb793a | ||
|
|
9e620ce6e9 | ||
|
|
2f7cbfa1ee | ||
|
|
7cad8f41f2 | ||
|
|
132b1f705e | ||
|
|
329cfa5756 | ||
|
|
cf229cb9c8 | ||
|
|
e61e6b124e | ||
|
|
95592770a7 | ||
|
|
fac0e44a7e | ||
|
|
82433e04ad | ||
|
|
d923ab9ae5 | ||
|
|
c2197102a3 | ||
|
|
cb57525f69 | ||
|
|
a559477028 | ||
|
|
e0e7d4ca67 | ||
|
|
53b1ffe953 | ||
|
|
9a6de48efa | ||
|
|
fb33985f07 | ||
|
|
192c063743 | ||
|
|
106edc953a | ||
|
|
d5c2f2855a | ||
|
|
9913550688 | ||
|
|
e49aa35c75 | ||
|
|
da57d21f0c | ||
|
|
b46f116075 | ||
|
|
9d360f80cf | ||
|
|
6892705e18 | ||
|
|
6132a3d7ca | ||
|
|
7f03b0d0d5 | ||
|
|
ccba76f757 | ||
|
|
f5c15b6ce8 | ||
|
|
6a997851eb | ||
|
|
5cd00f699e | ||
|
|
f9440b8026 | ||
|
|
1d964ccdaf | ||
|
|
2a4f10f0f5 | ||
|
|
46cfd5353d | ||
|
|
ef1c83862b | ||
|
|
e4594bd688 | ||
|
|
9ddc59e8e5 | ||
|
|
e50ab3f72c | ||
|
|
285b534384 | ||
|
|
31bd4d7ffe | ||
|
|
10c84dfb90 | ||
|
|
55cbb39127 | ||
|
|
50b19462f4 | ||
|
|
ba1cf06be6 | ||
|
|
a519d54e7d | ||
|
|
54ceb52eaf | ||
|
|
1d2d31d0f9 | ||
|
|
22be2bf7ab | ||
|
|
f74d93b4e9 | ||
|
|
5b8b6cfa99 | ||
|
|
a3456c41e4 | ||
|
|
dd4294de03 | ||
|
|
1de25c14e4 | ||
|
|
58a43d0851 | ||
|
|
3a5241b642 | ||
|
|
a530b0cc95 | ||
|
|
cc540bb166 | ||
|
|
83473b9c69 | ||
|
|
8e14d1b7e6 | ||
|
|
8db630d358 | ||
|
|
7b192d426e | ||
|
|
7226750363 | ||
|
|
0e743f0e09 | ||
|
|
1a1a6ebf79 | ||
|
|
bb073e1709 | ||
|
|
248afde21e | ||
|
|
e8ae672c93 | ||
|
|
7d1549aaeb | ||
|
|
a23492fe27 | ||
|
|
51afe14965 | ||
|
|
cfbe0f8cbc | ||
|
|
59236802c1 | ||
|
|
55a4eb18f2 | ||
|
|
40b26d7492 | ||
|
|
97a7cee878 | ||
|
|
e9b8288e4b | ||
|
|
13a8867e13 | ||
|
|
7bece81519 | ||
|
|
43ff79be0b | ||
|
|
8ae87bbde2 | ||
|
|
b1b6c462c1 | ||
|
|
61e183d9bb | ||
|
|
cd701e95fa | ||
|
|
5c0ebc1f94 | ||
|
|
f10d334c90 | ||
|
|
3772dc5930 | ||
|
|
3e70bf5af9 | ||
|
|
5298d14084 | ||
|
|
01da144a03 | ||
|
|
a17c4767d6 | ||
|
|
db807a1bbc | ||
|
|
cf07bfc5c5 | ||
|
|
505321c336 | ||
|
|
852baaa8c3 | ||
|
|
2b4304908a | ||
|
|
ee4d92364e | ||
|
|
9867200c38 | ||
|
|
8e2b2d113b | ||
|
|
c6ea0f8372 | ||
|
|
66e4c3286a | ||
|
|
f83696a670 | ||
|
|
fe05947b54 | ||
|
|
34042522f1 | ||
|
|
7b42e38dae | ||
|
|
1959a76393 | ||
|
|
2398a58526 | ||
|
|
b711d15617 | ||
|
|
73873c5bbd | ||
|
|
6191a81eff | ||
|
|
609ed6d261 | ||
|
|
4c616a5733 | ||
|
|
3e0ca5d94d | ||
|
|
70b8585e95 | ||
|
|
f40103cbba | ||
|
|
b35a808712 | ||
|
|
6f1b8bd1d9 | ||
|
|
21a10fcb17 | ||
|
|
e4d05e3fec | ||
|
|
884f73f349 | ||
|
|
62b30af12a | ||
|
|
36ca819cb3 | ||
|
|
975df02704 | ||
|
|
f85f4630ff | ||
|
|
f6d878f33f | ||
|
|
f29bafcdb2 | ||
|
|
7ed64efa08 | ||
|
|
d4709f7c44 | ||
|
|
b31db0d0d5 | ||
|
|
5aa653a54f | ||
|
|
df9726a152 | ||
|
|
813a7b2c3a | ||
|
|
476ca6d42f | ||
|
|
9354297276 | ||
|
|
9706dcbda5 | ||
|
|
a1fa40f067 | ||
|
|
a85e19a5e1 | ||
|
|
046a16f924 | ||
|
|
7c11d3ecd9 | ||
|
|
624c3a4c27 | ||
|
|
1aa918bb86 | ||
|
|
c5ceb6b880 | ||
|
|
8420f03f18 | ||
|
|
0ae1f5909d | ||
|
|
2afd7549bc | ||
|
|
800464d311 | ||
|
|
714c18db0e | ||
|
|
0aa85f5967 | ||
|
|
3179e8c7b9 | ||
|
|
337d57b940 | ||
|
|
eaa1bdcddb | ||
|
|
9face7567c | ||
|
|
c8f3743008 | ||
|
|
1bcc66b5b9 | ||
|
|
4f6e085be8 | ||
|
|
a7d5a98cc4 | ||
|
|
d901bee88e | ||
|
|
543bc3bcaa | ||
|
|
42039eee99 | ||
|
|
3be7299cb4 | ||
|
|
54c417391d | ||
|
|
2e8419db27 | ||
|
|
c9625cb311 | ||
|
|
3f8817cc2d | ||
|
|
745ef63451 | ||
|
|
a92ffd9770 | ||
|
|
413c7ec1ac | ||
|
|
d7d8d191c0 | ||
|
|
3d3391b55e | ||
|
|
fa4ea912c9 | ||
|
|
e87a782411 | ||
|
|
54214873f4 | ||
|
|
54dfc083f9 | ||
|
|
9f955f251a | ||
|
|
ee5dd7fad2 | ||
|
|
e0621c6eda | ||
|
|
7c39600508 | ||
|
|
f406e8d9ca | ||
|
|
25baf3b97e | ||
|
|
bb648b62f3 | ||
|
|
5efce10c2c | ||
|
|
4da9b8c495 | ||
|
|
c5c566aadc | ||
|
|
bbffda669a | ||
|
|
c031a7ab3d | ||
|
|
b8fb88f4c2 | ||
|
|
506ee571b1 | ||
|
|
490de32b49 | ||
|
|
5a11c96e56 | ||
|
|
b3734b151b | ||
|
|
cb806aefa3 | ||
|
|
ed6933a839 | ||
|
|
0a3a1b756d | ||
|
|
6618c3a6e8 | ||
|
|
12260e068a | ||
|
|
5cd14c941b | ||
|
|
0de7b2eb83 | ||
|
|
3dc67df180 | ||
|
|
d1e69a75dd | ||
|
|
cee51df4fb | ||
|
|
1fc9817cd4 | ||
|
|
bf9d401198 | ||
|
|
13f49738d7 | ||
|
|
8537e92d39 | ||
|
|
9d95dec5ea | ||
|
|
08b5fc8e3b | ||
|
|
718dd21573 | ||
|
|
70a9a7e5c8 | ||
|
|
231193f7a6 | ||
|
|
2f394d3c9f | ||
|
|
50aab7a802 | ||
|
|
f7d17c4c55 | ||
|
|
0498e042a0 | ||
|
|
e84c1fa82f | ||
|
|
2a9441dfbf | ||
|
|
8c4bc76de6 | ||
|
|
ad2598b475 | ||
|
|
a2f16dbecd | ||
|
|
2d500d4efa | ||
|
|
91f5e72f02 | ||
|
|
a1f91f799f | ||
|
|
1fe1cd45f5 | ||
|
|
b185e57406 | ||
|
|
56bbd73622 | ||
|
|
56b673ca05 | ||
|
|
5d50ec612d | ||
|
|
40882c4ce2 | ||
|
|
73ea316501 | ||
|
|
b04a233e8d | ||
|
|
f70c5968a9 | ||
|
|
07079664a6 | ||
|
|
eb4691adfc | ||
|
|
40ee89bddc | ||
|
|
a60e932454 | ||
|
|
bcb486379a | ||
|
|
fe8ffcc5c3 | ||
|
|
9bcd120dcc | ||
|
|
a4e215cee4 | ||
|
|
f6a0500bd3 | ||
|
|
9b8c21cace | ||
|
|
a65d70820a | ||
|
|
50d43b0678 | ||
|
|
2538fec0c5 | ||
|
|
c62e748b7b | ||
|
|
4794d1970b | ||
|
|
f320da07b4 | ||
|
|
836c8de87d | ||
|
|
37464c8e3a | ||
|
|
9e10f891ff | ||
|
|
9a8088586f | ||
|
|
816369f0ef | ||
|
|
e6864b6599 | ||
|
|
1491f20201 | ||
|
|
a11baeb84d | ||
|
|
b4f30f6df2 | ||
|
|
49a389542e | ||
|
|
eae276b539 | ||
|
|
1d66aacb36 | ||
|
|
a283a1bb65 | ||
|
|
2117824cf9 | ||
|
|
a8b0a42791 | ||
|
|
8fce08a927 | ||
|
|
adb552ee6e | ||
|
|
deb6cccff9 | ||
|
|
a6d14ad7dc | ||
|
|
607c64742c | ||
|
|
f0509d1c26 | ||
|
|
6e0f65c063 | ||
|
|
aec736439c | ||
|
|
63e0574411 | ||
|
|
7f30fe377c | ||
|
|
a5ecb75fcd | ||
|
|
3726502017 | ||
|
|
fc02216754 | ||
|
|
0d78c72018 | ||
|
|
3bfa01f0d0 | ||
|
|
1938520878 | ||
|
|
ee147bb327 | ||
|
|
2598fd8c5d | ||
|
|
69ea2cf327 | ||
|
|
6a292f9d56 | ||
|
|
79d3c49f26 | ||
|
|
25780eb2bc | ||
|
|
6b795e0093 | ||
|
|
61a03a7808 | ||
|
|
37ba256900 | ||
|
|
d2d55531e6 | ||
|
|
536c28a952 | ||
|
|
8e92848356 | ||
|
|
187facd5c7 | ||
|
|
b89caf0458 | ||
|
|
074cc1b723 | ||
|
|
df909ca75b | ||
|
|
f1fc078dc1 | ||
|
|
8a0b7b9441 | ||
|
|
7f2e8d8147 | ||
|
|
6c83016657 | ||
|
|
67e3de06c7 | ||
|
|
86a9487fb2 | ||
|
|
dc61e8ecdf | ||
|
|
8151a73d64 | ||
|
|
0313982ac8 | ||
|
|
d35b47c9d8 | ||
|
|
111846a909 | ||
|
|
722137ab29 | ||
|
|
b5a6583559 | ||
|
|
90c49b3fe7 | ||
|
|
0286e9ddf2 | ||
|
|
5f45b9b40e | ||
|
|
8c6133e29d | ||
|
|
7073c33dce | ||
|
|
c607537319 | ||
|
|
173688c748 | ||
|
|
7b1f3e36de | ||
|
|
bda5ac9bbf | ||
|
|
1581a68082 | ||
|
|
d0d27e7fb1 | ||
|
|
4f49e58d52 | ||
|
|
29cc8ed272 | ||
|
|
c00d35ea73 | ||
|
|
f5ee01ab6a | ||
|
|
5ebbe80cfe | ||
|
|
843f14042b | ||
|
|
085d1e9c10 | ||
|
|
1941071f87 | ||
|
|
dd8b5fc638 | ||
|
|
2957c4e55f | ||
|
|
6ef53c814c | ||
|
|
ba04822388 | ||
|
|
5ea420b49b | ||
|
|
3a2d64ba46 | ||
|
|
5f4ecd7efc | ||
|
|
a20f017c7a | ||
|
|
b7a296c81f | ||
|
|
81b260998d | ||
|
|
3179599c31 | ||
|
|
9da802eadf | ||
|
|
e47e22ba28 | ||
|
|
d77ecc8218 | ||
|
|
af5872bc83 | ||
|
|
fbb2a175ff | ||
|
|
6dbae7fe64 | ||
|
|
111390db0f | ||
|
|
d288325f64 | ||
|
|
c0f6588339 | ||
|
|
e844962645 | ||
|
|
71b71dbc58 | ||
|
|
6e025c1bb0 | ||
|
|
49b858e359 | ||
|
|
6b99ad95d3 | ||
|
|
0611dc0cb4 | ||
|
|
edc0512102 | ||
|
|
a329ce41b5 | ||
|
|
bcba14a029 | ||
|
|
dd25205623 | ||
|
|
931d9cc372 | ||
|
|
240feaf547 | ||
|
|
e4db036382 | ||
|
|
2e4704aaa7 | ||
|
|
92a520fa8c | ||
|
|
633026e8b3 | ||
|
|
67f3396ced | ||
|
|
28c62a7f03 | ||
|
|
26b47bcb6e | ||
|
|
57bf36156b | ||
|
|
695769d1b4 | ||
|
|
ba92ea9fb4 | ||
|
|
8e34b54cd7 | ||
|
|
1ab7bb83cc | ||
|
|
cee82a3c7b | ||
|
|
c6cb6ccd07 | ||
|
|
8f63bb1edc | ||
|
|
2b07b3db2b | ||
|
|
629f6a6876 | ||
|
|
e4f776448e | ||
|
|
a942613d7f | ||
|
|
32fa1ff1e9 | ||
|
|
210bc0fd6b | ||
|
|
1929883485 | ||
|
|
79e7eb6495 | ||
|
|
5ecda25fdb | ||
|
|
792a01ba6d | ||
|
|
3de0b15073 | ||
|
|
79c088d3a4 | ||
|
|
cb2dbc1e0a | ||
|
|
05dba38190 | ||
|
|
c4d7cc79b5 | ||
|
|
38664f9a0a | ||
|
|
734acd628e | ||
|
|
b70f56e87f | ||
|
|
f8312e9502 | ||
|
|
96ed6668e5 | ||
|
|
c49e5f84d9 | ||
|
|
920fae02c1 | ||
|
|
c1b8830831 | ||
|
|
137eec8745 | ||
|
|
f09423efe5 | ||
|
|
b7fe13434b | ||
|
|
99559b24e3 | ||
|
|
cd27363126 | ||
|
|
07b2fde2de | ||
|
|
df3ba278e9 | ||
|
|
837ee5c626 | ||
|
|
2ad4cdd729 | ||
|
|
ff9efe22ae | ||
|
|
630e9ebd66 | ||
|
|
e402e37f12 | ||
|
|
56b4989f44 | ||
|
|
7d10e47046 | ||
|
|
a08fd0fcb1 | ||
|
|
353f86488a | ||
|
|
00be9e3c7f | ||
|
|
b61691684e | ||
|
|
bc21904fef | ||
|
|
a00548ec4d | ||
|
|
27dfc72012 | ||
|
|
e943f0063e | ||
|
|
f9dc31e464 | ||
|
|
6873991e2b | ||
|
|
118a7942a5 | ||
|
|
544094ba72 | ||
|
|
6660297871 | ||
|
|
de0b50eaf7 | ||
|
|
5bea9c7794 | ||
|
|
fba25338be | ||
|
|
33a9c8cce6 | ||
|
|
f1d4f693bb | ||
|
|
03a0bfdddd | ||
|
|
7e36310e8a | ||
|
|
1175543ce1 | ||
|
|
a4e644c285 | ||
|
|
0eb347186c | ||
|
|
57167a5cde | ||
|
|
5939bc990a | ||
|
|
8dbb61e9e3 | ||
|
|
fafa063bcd | ||
|
|
a3834d043b | ||
|
|
a26fc89f49 | ||
|
|
0f85898137 | ||
|
|
3756d9d76b | ||
|
|
4d356e5320 | ||
|
|
19d8411c15 | ||
|
|
d9f0e21ea4 | ||
|
|
a976e9011d | ||
|
|
72de0fcfcb | ||
|
|
db2f60b0ef | ||
|
|
bdfea0fa6f | ||
|
|
4a7fe25f66 | ||
|
|
f45acaa9c8 | ||
|
|
c32d452786 | ||
|
|
ee1707c4d4 | ||
|
|
cbf9da0b7e | ||
|
|
5c367e7ab2 | ||
|
|
8745f80d90 | ||
|
|
9898c1ba4b | ||
|
|
71ee64a974 | ||
|
|
7dfca60893 | ||
|
|
1aed2470e5 | ||
|
|
ea459a1eca | ||
|
|
53620ecce4 | ||
|
|
ba8083c539 | ||
|
|
9307cf86fa | ||
|
|
182d067ff8 | ||
|
|
3dfa36fad1 | ||
|
|
c0426d3482 | ||
|
|
6f930be08e | ||
|
|
1e58c87380 | ||
|
|
882dc75536 | ||
|
|
a91e6c3405 | ||
|
|
f93b92cca8 | ||
|
|
237362663a | ||
|
|
ac8fb03b80 | ||
|
|
7da6908850 | ||
|
|
1cb23f1193 | ||
|
|
196f4a67b2 | ||
|
|
1f4012cc1e | ||
|
|
a631c971d9 | ||
|
|
135fb042da | ||
|
|
9574549798 | ||
|
|
deaa5f363a | ||
|
|
045831f3c7 | ||
|
|
cb0bd2c52d | ||
|
|
c015e9cc5d | ||
|
|
03c70f0421 | ||
|
|
cf2f81aae1 | ||
|
|
9c83ea4717 | ||
|
|
34eddc92ff | ||
|
|
44270b37b9 | ||
|
|
57caf80e5d | ||
|
|
9da52c5d86 | ||
|
|
9e6b84e31e | ||
|
|
6ab49fdf1d | ||
|
|
3c1b05c81e | ||
|
|
e81dcccace | ||
|
|
5fb6cb713b | ||
|
|
bf074d14de | ||
|
|
c6ed4fe4f9 | ||
|
|
0e8175b8eb | ||
|
|
bce28fe526 | ||
|
|
28a2482cf7 | ||
|
|
b3b2f69673 | ||
|
|
b3a9e09d6c | ||
|
|
d895ad183d | ||
|
|
7ffe6a2c78 | ||
|
|
211a586173 | ||
|
|
bb567a61b6 | ||
|
|
fd4bc29beb | ||
|
|
ead71db41a | ||
|
|
38c00e53cd | ||
|
|
6c0ceeac7f | ||
|
|
84c2289aa5 | ||
|
|
cd063c74d9 | ||
|
|
6a2163d36f | ||
|
|
cfb169b5f0 | ||
|
|
9e7f2e470f | ||
|
|
915cd5f016 | ||
|
|
cf4ac1a5b7 | ||
|
|
fcf5158258 | ||
|
|
b81474d2fd | ||
|
|
c9fd182dba | ||
|
|
f9f8900fe9 | ||
|
|
ad615941a2 | ||
|
|
0de0bbfa71 | ||
|
|
515e82262d | ||
|
|
a572b0f34d | ||
|
|
f80fd2a27c | ||
|
|
f7dbd3c283 | ||
|
|
215503ba59 | ||
|
|
af6d833c50 | ||
|
|
5098aa388b | ||
|
|
6d9e5dc931 | ||
|
|
d4da82805f | ||
|
|
6a8d2ac826 | ||
|
|
82d194cf2e | ||
|
|
3bfafb5e50 | ||
|
|
d179450c29 | ||
|
|
1dd5f06a4f | ||
|
|
cee0aa3adc | ||
|
|
338d62204e | ||
|
|
66168a5b49 | ||
|
|
911e59b0f4 | ||
|
|
94951d92a1 | ||
|
|
57e4d4978b | ||
|
|
3c9de92d58 | ||
|
|
6b4e0ad2bc | ||
|
|
a4833fcc46 | ||
|
|
f15dbecc73 | ||
|
|
dc4472470e | ||
|
|
81f5b7115f | ||
|
|
361251bf53 | ||
|
|
20db65e430 | ||
|
|
eaecfe5882 | ||
|
|
725d4a44f0 | ||
|
|
c424a745d8 | ||
|
|
3cbe419cee | ||
|
|
8f03a36862 | ||
|
|
7ecdd6c1c5 | ||
|
|
d96403fe93 | ||
|
|
2df9508e44 | ||
|
|
defe140d98 | ||
|
|
28410b8533 | ||
|
|
378914b327 | ||
|
|
7ea7a2f3fd | ||
|
|
023bf82638 | ||
|
|
45b1285402 | ||
|
|
0cdd3ff82f | ||
|
|
770c879410 | ||
|
|
cff61fa0bc | ||
|
|
88b878098d | ||
|
|
f98b8a240e | ||
|
|
f92ccd4893 | ||
|
|
397ca47efb | ||
|
|
f71e678d80 | ||
|
|
acf85eb96b | ||
|
|
79c11d6008 | ||
|
|
65585b313d | ||
|
|
9ac2dbcc80 | ||
|
|
fa0f4e1101 | ||
|
|
f00e91e85e | ||
|
|
05f4f2e742 | ||
|
|
ea2b9f5596 | ||
|
|
0528a800f2 | ||
|
|
8933b4c5da | ||
|
|
56ec5719a2 | ||
|
|
91cd6c6288 | ||
|
|
9db4a8cb43 | ||
|
|
9dfe4429d7 | ||
|
|
b42265212b | ||
|
|
4bebfd8d5f | ||
|
|
d5cd0b19b0 | ||
|
|
22b0f2fd24 | ||
|
|
1663280f53 | ||
|
|
7dd5e4b2e6 | ||
|
|
ff05560047 | ||
|
|
78d7ac311f | ||
|
|
4562a3574b | ||
|
|
afb3b496e8 | ||
|
|
a36f5bafc1 | ||
|
|
be94098597 | ||
|
|
5ada3606d8 | ||
|
|
040be60697 | ||
|
|
202b8445f6 | ||
|
|
f546cbe934 | ||
|
|
d7036fe8a8 | ||
|
|
8712fc6fd3 | ||
|
|
d641652a92 | ||
|
|
c214acd899 | ||
|
|
f8a88ae042 | ||
|
|
54adf3898a | ||
|
|
e72b0fc89d | ||
|
|
1a492e9f4a | ||
|
|
556f49d367 | ||
|
|
215fd2f055 | ||
|
|
009ed3584d | ||
|
|
2b9b54cf6b | ||
|
|
4495e721d8 | ||
|
|
e259293f83 | ||
|
|
2c3981e57e | ||
|
|
252c5396f3 | ||
|
|
07d0ea6a54 | ||
|
|
353c10aee7 | ||
|
|
3edebce833 | ||
|
|
31b999ea59 | ||
|
|
725ffef5f3 | ||
|
|
5aac991446 | ||
|
|
978013e750 | ||
|
|
a8c7e8ba05 | ||
|
|
28670f8e48 | ||
|
|
81d6406e14 | ||
|
|
a5c8a52dd5 | ||
|
|
0dc95aceed | ||
|
|
a2f62238f1 | ||
|
|
df1685905e | ||
|
|
50602cbf26 | ||
|
|
5c08c6c930 | ||
|
|
034d727a2c | ||
|
|
25ab3b30c2 | ||
|
|
94ac2ca56c | ||
|
|
065f82f485 | ||
|
|
ac78039171 | ||
|
|
ac64ea287a | ||
|
|
441b3a4df4 | ||
|
|
9d8b76e497 | ||
|
|
da0a2b8578 | ||
|
|
2ba637891a | ||
|
|
bc526cf0ce | ||
|
|
7ee222af88 | ||
|
|
c141c33b32 | ||
|
|
9cbacf3264 | ||
|
|
0115285a84 | ||
|
|
127db2fe42 | ||
|
|
cbf6e4287f | ||
|
|
e7ba56cb2c | ||
|
|
0ed0a6db57 | ||
|
|
67cb6a9802 | ||
|
|
70f6d0e305 | ||
|
|
78434a330c | ||
|
|
fda4fd4888 | ||
|
|
ad0a961a5f | ||
|
|
45e7e35233 | ||
|
|
9bd438618a | ||
|
|
2a40401398 | ||
|
|
2577b2c5e3 | ||
|
|
ad919fc972 | ||
|
|
23d30d4fc0 | ||
|
|
f434f955c2 | ||
|
|
ba1bc29a97 | ||
|
|
290d27a064 | ||
|
|
a6c629899e | ||
|
|
d4cbd4ace4 | ||
|
|
d9f1c4595e | ||
|
|
ed5bea6e3f | ||
|
|
3c2d568a2e | ||
|
|
d7f1ebedbf | ||
|
|
44e4816cbb | ||
|
|
5081e4f201 | ||
|
|
5e4675b34a | ||
|
|
340a62869d | ||
|
|
ceca99a99c | ||
|
|
e752f87876 | ||
|
|
75798bebb0 | ||
|
|
383968d948 | ||
|
|
0d062b28bf | ||
|
|
e894ad4ab0 | ||
|
|
6aafaca329 | ||
|
|
ae6cc543ed | ||
|
|
bd9b45bb96 | ||
|
|
089e1ee91b | ||
|
|
88fb5bbd82 | ||
|
|
ab1b80967f | ||
|
|
198040b2e2 | ||
|
|
f27978e268 | ||
|
|
3470e9bf5d | ||
|
|
471755d370 | ||
|
|
3aa7f771c1 | ||
|
|
d0904a9f67 | ||
|
|
c2a2845ee7 | ||
|
|
85bee4a7d2 | ||
|
|
bc8176ff21 | ||
|
|
785de9fb99 | ||
|
|
b5eac744b5 | ||
|
|
46d0fee11b | ||
|
|
30f1970850 | ||
|
|
aa75262fe4 | ||
|
|
22f096088b | ||
|
|
f5d2c48bbb | ||
|
|
056edcfed3 | ||
|
|
67afc06d79 | ||
|
|
e4278a69ac | ||
|
|
82102279bc | ||
|
|
624c6777ff | ||
|
|
94b200835a | ||
|
|
dc74a55b84 | ||
|
|
72d4421ac8 | ||
|
|
3392ccc58b | ||
|
|
ecba175b16 | ||
|
|
4c2f65819b | ||
|
|
5414744439 | ||
|
|
d69c6d0c66 | ||
|
|
1cbb4ece4b | ||
|
|
41655e7852 | ||
|
|
8ea3d92697 | ||
|
|
0c1f480fc1 | ||
|
|
ffc29ee043 | ||
|
|
fede64ba7a | ||
|
|
18eb133811 | ||
|
|
0857a45b0a | ||
|
|
a7c3bb0d55 | ||
|
|
3a6bcb3dd0 | ||
|
|
36a5614c61 | ||
|
|
a1ed81f790 | ||
|
|
6733e92b50 | ||
|
|
001312ca82 | ||
|
|
4e729bb9ec | ||
|
|
dfee857466 | ||
|
|
05dc94ccc4 | ||
|
|
7416164aca | ||
|
|
71f2e8c577 | ||
|
|
ffd1a91467 | ||
|
|
61ba92ae18 | ||
|
|
cdbd64a30d | ||
|
|
51a29468be | ||
|
|
41565fcfd4 | ||
|
|
7ddce62cd6 | ||
|
|
51474724e5 | ||
|
|
cc0e66fe7b | ||
|
|
6a451b37d7 | ||
|
|
52f15c84a6 | ||
|
|
7e7fbf106b | ||
|
|
63bdee8b55 | ||
|
|
ce7597b3f6 | ||
|
|
d751539a25 | ||
|
|
cc90cc6843 | ||
|
|
3cee9cdcd7 | ||
|
|
45ce7efc71 | ||
|
|
7eacea1057 | ||
|
|
1e1335aa5e | ||
|
|
25c79bec67 | ||
|
|
a34df34208 | ||
|
|
f2dbff92f4 | ||
|
|
629038632c | ||
|
|
3b53ec1cb6 | ||
|
|
e828f5b812 | ||
|
|
c25022f549 | ||
|
|
1022b7ea32 | ||
|
|
4b4acc5f5a | ||
|
|
c8c9536beb | ||
|
|
8c1b5f0581 | ||
|
|
1f508d9d8f | ||
|
|
b9aa5d0e4e | ||
|
|
fcc0b3e8c0 | ||
|
|
269e9d69e0 | ||
|
|
500ad8b00f | ||
|
|
e6275ab561 | ||
|
|
298553d48d | ||
|
|
61fe40f4a1 | ||
|
|
e2b0fdf8aa | ||
|
|
a3d4822b9f | ||
|
|
26bf588fad | ||
|
|
231b7303f5 | ||
|
|
af134eb861 | ||
|
|
129ee33ffb | ||
|
|
f1d81d86aa | ||
|
|
aa6f229e6b | ||
|
|
1a6511c7a8 | ||
|
|
1ed8df8903 | ||
|
|
3e3685b68b | ||
|
|
5ab2c89a37 | ||
|
|
b5110b07f0 | ||
|
|
3009e5eebe | ||
|
|
8de0445661 | ||
|
|
616aad84d8 | ||
|
|
5ec47da127 | ||
|
|
921d02e4d3 | ||
|
|
7e52eb7b0e | ||
|
|
c2e75bf2fd | ||
|
|
938946c48b | ||
|
|
8b9b750f8f | ||
|
|
c856f6d97b | ||
|
|
52f6ea2525 | ||
|
|
f965805099 | ||
|
|
836395cdb1 | ||
|
|
00c8d8da34 | ||
|
|
2f26490536 | ||
|
|
69337ed264 | ||
|
|
2fbadc46d2 | ||
|
|
9cedaa60bc | ||
|
|
e4a054d34e | ||
|
|
596dee69d6 | ||
|
|
22880926b1 | ||
|
|
c1f5e77fc6 | ||
|
|
1e489325c4 | ||
|
|
fd07c571e5 | ||
|
|
262b028ee9 | ||
|
|
c007f592b3 | ||
|
|
0d5a33ef2a | ||
|
|
d132b6ed71 | ||
|
|
9c0c174534 | ||
|
|
302961a86a | ||
|
|
f136f78802 | ||
|
|
a98a6ac0c8 | ||
|
|
4d1dbe11e8 | ||
|
|
6ac940fa32 | ||
|
|
f06880c6e2 | ||
|
|
0a09758be1 | ||
|
|
5e2be8a44a | ||
|
|
8a5b48d374 | ||
|
|
5ec94f96fd | ||
|
|
92d5f6c41d | ||
|
|
24caaea54d | ||
|
|
130be2aedc | ||
|
|
62a849c2db | ||
|
|
736dd77a6e | ||
|
|
8a7610206e | ||
|
|
78c93e1225 | ||
|
|
e8dac08a35 | ||
|
|
6d1775fcd6 | ||
|
|
46161c3af0 | ||
|
|
a24d7f6686 | ||
|
|
866f4653c7 | ||
|
|
cb67a911fa | ||
|
|
6080830a8b | ||
|
|
b722cc1dec | ||
|
|
63cb88a0f4 | ||
|
|
0f585eda4f | ||
|
|
038dcff4ba | ||
|
|
f838eb1bdc | ||
|
|
da875755d1 | ||
|
|
c1776bbf9d | ||
|
|
29ce1b3811 | ||
|
|
4296a61b9a | ||
|
|
9f94f28181 | ||
|
|
051d2665f3 | ||
|
|
891a6bcf14 | ||
|
|
c7a18a8b8d | ||
|
|
4e87773d89 | ||
|
|
e436f48164 | ||
|
|
f4f52ee204 | ||
|
|
1fe1813431 | ||
|
|
cf23f42b99 | ||
|
|
679e001a48 | ||
|
|
61a1709141 | ||
|
|
fa39b82b3c | ||
|
|
a6a9ad72f9 | ||
|
|
22b7b21d5a | ||
|
|
862f8d3188 | ||
|
|
18cd8ba0b6 | ||
|
|
3a4ef09f58 | ||
|
|
9b0395db08 | ||
|
|
6fc61d12fc | ||
|
|
feed9c8936 | ||
|
|
6ce52f39ae | ||
|
|
57d96a4512 | ||
|
|
c64b7d00e6 | ||
|
|
7f27603772 | ||
|
|
389e1b0178 | ||
|
|
f8325cbbc1 | ||
|
|
309b6ba32c | ||
|
|
ea0b3eee05 | ||
|
|
478a719f77 | ||
|
|
5b827cf86a | ||
|
|
6a0fc5afd2 | ||
|
|
565ba23f8c | ||
|
|
42243d3d97 | ||
|
|
a01c76db54 | ||
|
|
c297f047d2 | ||
|
|
2cd02be7b1 | ||
|
|
8fb6f45bec | ||
|
|
cf89ffa971 | ||
|
|
2c501f7fb7 | ||
|
|
39b561a182 | ||
|
|
b1b521e0c2 | ||
|
|
e67da51662 | ||
|
|
20000088de | ||
|
|
87643040a4 | ||
|
|
e201a42383 | ||
|
|
9b25b7ee5d | ||
|
|
119c33ac32 | ||
|
|
882da71397 | ||
|
|
eb61269068 | ||
|
|
0aa0478327 | ||
|
|
71191f10a2 | ||
|
|
9d4888a772 | ||
|
|
784d9bb043 | ||
|
|
a8ed9f1c2f | ||
|
|
231bbe7c2b | ||
|
|
44080b8ad4 | ||
|
|
e661fb7446 | ||
|
|
f110cf4d53 | ||
|
|
024386d189 | ||
|
|
6412c88277 | ||
|
|
3e63b62d6e | ||
|
|
8ff45331df | ||
|
|
80647b062a | ||
|
|
de5be0dc5a | ||
|
|
b47c3b6a60 | ||
|
|
2eea115b3a | ||
|
|
7d04f155c8 | ||
|
|
4296eed429 | ||
|
|
93e0bfa410 | ||
|
|
5dce6fa494 | ||
|
|
f95dff4d9e | ||
|
|
ce3c555712 | ||
|
|
1d54688b0b | ||
|
|
80619c88b3 | ||
|
|
e3a33ca427 | ||
|
|
921211bbaa | ||
|
|
99c9b2d396 | ||
|
|
c4c5723a61 | ||
|
|
56f3b3a027 | ||
|
|
3c676f9562 | ||
|
|
a050cb94f6 | ||
|
|
4968590075 | ||
|
|
c6645d47ba | ||
|
|
1474e38eec | ||
|
|
6a8b1d51fa | ||
|
|
6cc2095221 | ||
|
|
fc5fd6096a | ||
|
|
788babbb61 | ||
|
|
be38e181a8 | ||
|
|
d658378af3 | ||
|
|
21757a96a3 | ||
|
|
490250f5be | ||
|
|
be07107b1c | ||
|
|
6a04c4b3e8 | ||
|
|
02fb1a037c | ||
|
|
ffd044b52b | ||
|
|
ea9217a61f | ||
|
|
0e650ad719 | ||
|
|
d89898ef7d | ||
|
|
fe80878788 | ||
|
|
9f27a9a5d7 | ||
|
|
3f6f03e325 | ||
|
|
df995c02a3 | ||
|
|
b70d5ba901 | ||
|
|
52d7d1df0c | ||
|
|
839d49a8ac | ||
|
|
5bd047b70b | ||
|
|
b381148e06 | ||
|
|
c3155afc21 | ||
|
|
a774647c26 | ||
|
|
d4f2a70f83 | ||
|
|
93f8984987 | ||
|
|
3005374ada | ||
|
|
acea0d3c67 | ||
|
|
6e1ea89ca1 | ||
|
|
93d81d96ce | ||
|
|
9477a2eeb2 | ||
|
|
f43f78c40f | ||
|
|
3b30b42211 | ||
|
|
b36cf0572d | ||
|
|
08bb3f4f19 |
@@ -7,7 +7,7 @@ environment:
|
||||
PYTHONUNBUFFERED: 1
|
||||
PYTHON: C:\Python36\python.exe
|
||||
matrix:
|
||||
- TESTENV: py36-pyqt58
|
||||
- TESTENV: py36-pyqt59
|
||||
- TESTENV: pylint
|
||||
|
||||
install:
|
||||
|
||||
5
.flake8
5
.flake8
@@ -11,6 +11,7 @@ exclude = .*,__pycache__,resources.py
|
||||
# (for pytest's __tracebackhide__)
|
||||
# F401: Unused import
|
||||
# N802: function name should be lowercase
|
||||
# N806: variable in function should be lowercase
|
||||
# P101: format string does contain unindexed parameters
|
||||
# P102: docstring does contain unindexed parameters
|
||||
# P103: other string does contain unindexed parameters
|
||||
@@ -38,10 +39,12 @@ putty-ignore =
|
||||
/# pragma: no mccabe/ : +C901
|
||||
tests/*/test_*.py : +D100,D101,D401
|
||||
tests/conftest.py : +F403
|
||||
tests/unit/browser/webkit/test_history.py : +N806
|
||||
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
|
||||
copyright-check = True
|
||||
copyright-regexp = # Copyright [\d-]+ .*
|
||||
copyright-min-file-size = 110
|
||||
|
||||
8
.github/CODEOWNERS
vendored
Normal file
8
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
qutebrowser/browser/history.py @rcorre
|
||||
qutebrowser/completion/* @rcorre
|
||||
qutebrowser/misc/sql.py @rcorre
|
||||
tests/end2end/features/completion.feature @rcorre
|
||||
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
|
||||
9
.github/CONTRIBUTING.asciidoc
vendored
Normal file
9
.github/CONTRIBUTING.asciidoc
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
- Before you start to work on something, please leave a comment on the relevant
|
||||
issue (or open one). This makes sure there is no duplicate work done.
|
||||
|
||||
- Either run the testsuite locally, or keep an eye on Travis CI / AppVeyor
|
||||
after pushing changes.
|
||||
|
||||
See the full contribution docs for details:
|
||||
|
||||
include::../doc/contributing.asciidoc[]
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -15,11 +15,8 @@ __pycache__
|
||||
/qutebrowser/3rdparty
|
||||
/doc/*.html
|
||||
/README.html
|
||||
/CHANGELOG.html
|
||||
/CONTRIBUTING.html
|
||||
/FAQ.html
|
||||
/INSTALL.html
|
||||
/qutebrowser/html/doc/
|
||||
/qutebrowser/html/*.html
|
||||
/.venv*
|
||||
/.coverage
|
||||
/htmlcov
|
||||
|
||||
@@ -30,18 +30,24 @@ disable=no-self-use,
|
||||
broad-except,
|
||||
bare-except,
|
||||
eval-used,
|
||||
exec-used,
|
||||
ungrouped-imports,
|
||||
suppressed-message,
|
||||
too-many-return-statements,
|
||||
duplicate-code,
|
||||
wrong-import-position,
|
||||
no-else-return
|
||||
no-else-return,
|
||||
# https://github.com/PyCQA/pylint/issues/1698
|
||||
unsupported-membership-test,
|
||||
unsupported-assignment-operation,
|
||||
unsubscriptable-object
|
||||
|
||||
[BASIC]
|
||||
function-rgx=[a-z_][a-z0-9_]{2,50}$
|
||||
const-rgx=[A-Za-z_][A-Za-z0-9_]{0,30}$
|
||||
method-rgx=[a-z_][A-Za-z0-9_]{1,50}$
|
||||
attr-rgx=[a-z_][a-z0-9_]{0,30}$
|
||||
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{1,30}|(__.*__))$
|
||||
argument-rgx=[a-z_][a-z0-9_]{0,30}$
|
||||
variable-rgx=[a-z_][a-z0-9_]{0,30}$
|
||||
docstring-min-length=3
|
||||
@@ -63,6 +69,7 @@ valid-metaclass-classmethod-first-arg=cls
|
||||
|
||||
[TYPECHECK]
|
||||
ignored-modules=PyQt5,PyQt5.QtWebKit
|
||||
ignored-classes=_CountingAttr
|
||||
|
||||
[IMPORTS]
|
||||
# WORKAROUND
|
||||
|
||||
48
.travis.yml
48
.travis.yml
@@ -1,15 +1,11 @@
|
||||
sudo: required
|
||||
sudo: false
|
||||
dist: trusty
|
||||
language: generic
|
||||
language: python
|
||||
group: edge
|
||||
python: 3.6
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
env: TESTENV=py34-cov
|
||||
- os: linux
|
||||
env: DOCKER=debian-jessie
|
||||
services: docker
|
||||
- os: linux
|
||||
env: DOCKER=archlinux
|
||||
services: docker
|
||||
@@ -17,35 +13,32 @@ matrix:
|
||||
env: DOCKER=archlinux-webengine QUTE_BDD_WEBENGINE=true
|
||||
services: docker
|
||||
- os: linux
|
||||
env: DOCKER=ubuntu-xenial
|
||||
services: docker
|
||||
- os: linux
|
||||
language: python
|
||||
python: 3.6
|
||||
env: TESTENV=py36-pyqt571
|
||||
- os: linux
|
||||
language: python
|
||||
python: 3.5
|
||||
env: TESTENV=py35-pyqt58
|
||||
- os: linux
|
||||
language: python
|
||||
python: 3.6
|
||||
env: TESTENV=py36-pyqt58
|
||||
- os: linux
|
||||
python: 3.5
|
||||
env: TESTENV=py35-pyqt59
|
||||
- os: linux
|
||||
env: TESTENV=py36-pyqt59-cov
|
||||
- os: osx
|
||||
env: TESTENV=py36 OSX=elcapitan
|
||||
osx_image: xcode7.3
|
||||
env: TESTENV=py36 OSX=sierra
|
||||
osx_image: xcode8.3
|
||||
language: generic
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/2013
|
||||
# - os: osx
|
||||
# env: TESTENV=py35 OSX=yosemite
|
||||
# osx_image: xcode6.4
|
||||
- os: linux
|
||||
env: TESTENV=pylint PYTHON=python3.6
|
||||
language: python
|
||||
python: 3.6
|
||||
- os: linux
|
||||
env: TESTENV=flake8
|
||||
- os: linux
|
||||
env: TESTENV=docs
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- asciidoc
|
||||
- os: linux
|
||||
env: TESTENV=vulture
|
||||
- os: linux
|
||||
@@ -56,10 +49,9 @@ matrix:
|
||||
env: TESTENV=check-manifest
|
||||
- os: linux
|
||||
env: TESTENV=eslint
|
||||
allow_failures:
|
||||
- os: osx
|
||||
env: TESTENV=py36 OSX=elcapitan
|
||||
osx_image: xcode7.3
|
||||
language: node_js
|
||||
python: null
|
||||
node_js: node
|
||||
fast_finish: true
|
||||
|
||||
cache:
|
||||
@@ -67,10 +59,6 @@ cache:
|
||||
- $HOME/.cache/pip
|
||||
- $HOME/build/qutebrowser/qutebrowser/.cache
|
||||
|
||||
before_install:
|
||||
# We need to do this so we pick up the system-wide python properly
|
||||
- 'export PATH="/usr/bin:$PATH"'
|
||||
|
||||
install:
|
||||
- bash scripts/dev/ci/travis_install.sh
|
||||
- ulimit -c unlimited
|
||||
|
||||
393
INSTALL.asciidoc
393
INSTALL.asciidoc
@@ -1,393 +0,0 @@
|
||||
Installing qutebrowser
|
||||
======================
|
||||
|
||||
toc::[]
|
||||
|
||||
On Debian / Ubuntu
|
||||
------------------
|
||||
|
||||
qutebrowser should run on these systems:
|
||||
|
||||
* Debian jessie or newer
|
||||
* Ubuntu Trusty (14.04 LTS) or newer
|
||||
* Any other distribution based on these (e.g. Linux Mint 17+)
|
||||
|
||||
Unfortunately there is no Debian package in the official repos yet, but installing qutebrowser is
|
||||
still relatively easy!
|
||||
|
||||
You can use packages that are built for every release or build it yourself from git.
|
||||
|
||||
On Ubuntu 16.04 and 16.10 it's recommended to <<tox,install qutebrowser via tox>>
|
||||
instead in order to be able to use the new QtWebEngine backend. Newer versions
|
||||
have a QtWebEngine package in the repositories.
|
||||
|
||||
Using the packages
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Install the dependencies via apt-get:
|
||||
|
||||
----
|
||||
# apt-get install python3-lxml python-tox python3-pyqt5 python3-pyqt5.qtwebkit python3-pyqt5.qtquick python3-sip python3-jinja2 python3-pygments python3-yaml
|
||||
----
|
||||
|
||||
On Debian Stretch or Ubuntu 17.04 or later, it's also recommended to use the
|
||||
newer QtWebEngine backend.
|
||||
|
||||
To do so, install `python3-pyqt5.qtwebengine` and `python3-pyqt5.qtopengl`, then
|
||||
start qutebrowser with `--backend webengine`.
|
||||
|
||||
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].
|
||||
|
||||
Install the packages:
|
||||
|
||||
----
|
||||
# dpkg -i python3-pypeg2_*_all.deb
|
||||
# dpkg -i qutebrowser_*_all.deb
|
||||
----
|
||||
|
||||
Build it from git
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Install the dependencies via apt-get:
|
||||
|
||||
----
|
||||
# apt-get install python3-pyqt5 python3-pyqt5.qtwebkit python3-pyqt5.qtquick python-tox python3-sip python3-dev
|
||||
----
|
||||
|
||||
On Debian Stretch or Ubuntu 17.04 or later, it's also recommended to install
|
||||
`python3-pyqt5.qtwebengine` and start qutebrowser with `--backend webengine` in
|
||||
order to use the new backend.
|
||||
|
||||
To generate the documentation for the `:help` command, when using the git
|
||||
repository (rather than a release):
|
||||
|
||||
----
|
||||
# apt-get install asciidoc source-highlight
|
||||
$ python3 scripts/asciidoc2html.py
|
||||
----
|
||||
|
||||
If video or sound don't seem to work, try installing the gstreamer plugins:
|
||||
|
||||
----
|
||||
# apt-get install gstreamer1.0-plugins-{bad,base,good,ugly}
|
||||
----
|
||||
|
||||
Then <<tox,install qutebrowser via tox>>.
|
||||
|
||||
On Fedora
|
||||
---------
|
||||
|
||||
qutebrowser is available in the official repositories for Fedora 22 and newer.
|
||||
|
||||
----
|
||||
# dnf install qutebrowser
|
||||
----
|
||||
|
||||
It's also recommended to install `qt5-qtwebengine` and start with `--backend
|
||||
webengine` to use the new backend.
|
||||
|
||||
On Archlinux
|
||||
------------
|
||||
|
||||
qutebrowser is available in the official [community] repository.
|
||||
|
||||
----
|
||||
# pacman -S qutebrowser
|
||||
----
|
||||
|
||||
Archlinux packages an updated `qt5-webkit` package by default. If you want to
|
||||
use the QtWebEngine backend instead, install `qt5-webengine` and start with
|
||||
`--backend webengine`.
|
||||
|
||||
There is also a -git version available in the AUR:
|
||||
https://aur.archlinux.org/packages/qutebrowser-git/[qutebrowser-git].
|
||||
|
||||
You can install it using `makepkg` like this:
|
||||
|
||||
----
|
||||
$ git clone https://aur.archlinux.org/qutebrowser-git.git
|
||||
$ cd qutebrowser-git
|
||||
$ makepkg -si
|
||||
$ cd ..
|
||||
$ rm -r qutebrowser-git
|
||||
----
|
||||
|
||||
or you could use an AUR helper, e.g. `yaourt -S qutebrowser-git`.
|
||||
|
||||
If video or sound don't seem to work, try installing the gstreamer plugins:
|
||||
|
||||
----
|
||||
# pacman -S gst-plugins-{base,good,bad,ugly} gst-libav
|
||||
----
|
||||
|
||||
On Gentoo
|
||||
---------
|
||||
|
||||
A version of qutebrowser is available in the main repository and can be installed with:
|
||||
|
||||
----
|
||||
# emerge -av qutebrowser
|
||||
----
|
||||
|
||||
However it is suggested to install the Live version (-9999) to take advantage
|
||||
of the newest features introduced.
|
||||
|
||||
First of all you need to edit your package.accept_keywords file to accept the live
|
||||
version:
|
||||
|
||||
----
|
||||
# nano /etc/portage/package.accept_keywords
|
||||
----
|
||||
|
||||
And add the following line to it:
|
||||
|
||||
=www-client/qutebrowser-9999 **
|
||||
|
||||
Save the file and then install qutebrowser via
|
||||
|
||||
----
|
||||
# emerge -av qutebrowser
|
||||
----
|
||||
|
||||
Or rebuild your system if you already installed it.
|
||||
|
||||
To update to the last Live version, remember to do
|
||||
|
||||
----
|
||||
# emerge -uDNav @live-rebuild @world
|
||||
----
|
||||
|
||||
To include qutebrowser among the updates.
|
||||
|
||||
Make sure you have `python3_4` in your `PYTHON_TARGETS`
|
||||
(`/etc/portage/make.conf`) and rebuild your system (`emerge -uDNav @world`) if
|
||||
necessary.
|
||||
|
||||
It's also recommended to install QtWebKit-NG via
|
||||
https://gist.github.com/annulen/309569fb61e5d64a703c055c1e726f71[this ebuild],
|
||||
or install Qt >= 5.7.1 with QtWebEngine in order to use an up-to-date backend.
|
||||
|
||||
If video or sound don't seem to work, try installing the gstreamer plugins:
|
||||
|
||||
----
|
||||
# emerge -av gst-plugins-{base,good,bad,ugly,libav}
|
||||
----
|
||||
|
||||
|
||||
On Void Linux
|
||||
-------------
|
||||
|
||||
qutebrowser is available in the official repositories and can be installed
|
||||
with:
|
||||
|
||||
----
|
||||
# xbps-install qutebrowser
|
||||
----
|
||||
|
||||
It's currently recommended to install `python3-PyQt5-webengine` and
|
||||
`python3-PyQt5-opengl`, then start with `--backend webengine` to use the new
|
||||
backend.
|
||||
|
||||
On NixOS
|
||||
--------
|
||||
|
||||
Nixpkgs collection contains `pkgs.qutebrowser` since June 2015. You can install
|
||||
it with:
|
||||
|
||||
----
|
||||
$ nix-env -i qutebrowser
|
||||
----
|
||||
|
||||
It's recommended to install `qt5.qtwebengine` and start with
|
||||
`--backend webengine` to use the new backend.
|
||||
|
||||
On openSUSE
|
||||
-----------
|
||||
|
||||
There are prebuilt RPMs available for Tumbleweed and Leap 42.1:
|
||||
|
||||
http://software.opensuse.org/download.html?project=home%3Aarpraher&package=qutebrowser[One Click Install]
|
||||
|
||||
Or add the repo manually:
|
||||
|
||||
----
|
||||
# zypper addrepo http://download.opensuse.org/repositories/home:arpraher/openSUSE_Tumbleweed/home:arpraher.repo
|
||||
# zypper refresh
|
||||
# zypper install qutebrowser
|
||||
----
|
||||
|
||||
On OpenBSD
|
||||
----------
|
||||
|
||||
qutebrowser is in http://cvsweb.openbsd.org/cgi-bin/cvsweb/ports/www/qutebrowser/[OpenBSD ports].
|
||||
|
||||
Install the package:
|
||||
|
||||
----
|
||||
# pkg_add qutebrowser
|
||||
----
|
||||
|
||||
Or alternatively, use the ports system :
|
||||
|
||||
----
|
||||
# cd /usr/ports/www/qutebrowser
|
||||
# make install
|
||||
----
|
||||
|
||||
On Windows
|
||||
----------
|
||||
|
||||
There are different ways to install qutebrowser on Windows:
|
||||
|
||||
Prebuilt binaries
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Prebuilt standalone packages and installers
|
||||
https://github.com/qutebrowser/qutebrowser/releases[are built] for every
|
||||
release.
|
||||
|
||||
https://chocolatey.org/packages/qutebrowser[Chocolatey package]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* PackageManagement PowerShell module
|
||||
----
|
||||
PS C:\> Install-Package qutebrowser
|
||||
----
|
||||
* Chocolatey's client
|
||||
----
|
||||
C:\> choco install qutebrowser
|
||||
----
|
||||
|
||||
Manual install
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* Use the installer from http://www.python.org/downloads[python.org] to get
|
||||
Python 3 (be sure to install pip).
|
||||
* Use the installer from
|
||||
http://www.riverbankcomputing.com/software/pyqt/download5[Riverbank computing]
|
||||
to get Qt and PyQt5.
|
||||
* Install https://testrun.org/tox/latest/index.html[tox] via
|
||||
https://pip.pypa.io/en/latest/[pip]:
|
||||
|
||||
----
|
||||
$ pip install tox
|
||||
----
|
||||
|
||||
Then <<tox,install qutebrowser via tox>>.
|
||||
|
||||
On OS X
|
||||
-------
|
||||
|
||||
Prebuilt binary
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
The easiest way to install qutebrowser on OS X is to use the prebuilt `.app`
|
||||
files from the
|
||||
https://github.com/qutebrowser/qutebrowser/releases[release page].
|
||||
|
||||
This binary is also available through the
|
||||
https://caskroom.github.io/[Homebrew Cask] package manager:
|
||||
|
||||
----
|
||||
$ brew cask install qutebrowser
|
||||
----
|
||||
|
||||
Manual Install
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Alternatively, you can install the dependencies via a package manager (like
|
||||
http://brew.sh/[Homebrew] or https://www.macports.org/[MacPorts]) and run
|
||||
qutebrowser from source.
|
||||
|
||||
==== Homebrew
|
||||
|
||||
----
|
||||
$ brew install qt5
|
||||
$ pip3 install qutebrowser
|
||||
----
|
||||
|
||||
Homebrew's builds of Qt and PyQt no longer include QtWebKit - if you need
|
||||
QtWebKit support, it is necessary to build from source. The build takes several
|
||||
hours on an average laptop.
|
||||
|
||||
----
|
||||
$ brew install qt5 --with-qtwebkit
|
||||
$ brew install -s pyqt5
|
||||
$ pip3 install qutebrowser
|
||||
----
|
||||
|
||||
Packagers
|
||||
---------
|
||||
|
||||
There are example .desktop and icon files provided. They would go in the
|
||||
standard location for your distro (`/usr/share/applications` and
|
||||
`/usr/share/pixmaps` for example).
|
||||
|
||||
The normal `setup.py install` doesn't install these files, so you'll have to do
|
||||
it as part of the packaging process.
|
||||
|
||||
[[tox]]
|
||||
Installing qutebrowser with tox
|
||||
-------------------------------
|
||||
|
||||
First of all, clone the repository using http://git-scm.org/[git] and switch
|
||||
into the repository folder:
|
||||
|
||||
----
|
||||
$ git clone https://github.com/qutebrowser/qutebrowser.git
|
||||
$ cd qutebrowser
|
||||
----
|
||||
|
||||
|
||||
Then run tox inside the qutebrowser repository to set up a
|
||||
https://docs.python.org/3/library/venv.html[virtual environment]:
|
||||
|
||||
----
|
||||
$ tox -e mkvenv-pypi
|
||||
----
|
||||
|
||||
If your distribution uses OpenSSL 1.1 (like Debian Stretch or Archlinux), you'll
|
||||
need to set `LD_LIBRARY_PATH` to the OpenSSL 1.0 directory
|
||||
(`export LD_LIBRARY_PATH=/usr/lib/openssl-1.0` on Archlinux) before starting
|
||||
qutebrowser.
|
||||
|
||||
Alternatively, you can use `tox -e mkvenv` (without `-pypi`) to symlink your
|
||||
local Qt install instead of installing PyQt in the virtualenv. However, unless
|
||||
you have QtWebKit-NG or QtWebEngine available, qutebrowser will use the legacy
|
||||
QtWebKit backend.
|
||||
|
||||
On Windows, run `tox -e 'mkvenv-win' instead, however make sure that ONLY
|
||||
Python3 is in your PATH before running tox.
|
||||
|
||||
This installs all needed Python dependencies in a `.venv` subfolder.
|
||||
|
||||
You can then 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 "$@"
|
||||
----
|
||||
|
||||
If you are developing on qutebrowser, you may want to redirect it to a local
|
||||
config:
|
||||
|
||||
----
|
||||
#!/bin/bash
|
||||
~/path/to/qutebrowser/.venv/bin/python3 -m qutebrowser -c .qutebrowser-local "$@"
|
||||
----
|
||||
|
||||
Updating
|
||||
~~~~~~~~
|
||||
|
||||
When you updated your local copy of the code (e.g. by pulling the git repo, or
|
||||
extracting a new version), the virtualenv should automatically use the updated
|
||||
code. However, if dependencies got added, this won't be reflected in the
|
||||
virtualenv. Thus it's recommended to run the following command to recreate the
|
||||
virtualenv:
|
||||
|
||||
----
|
||||
$ tox -r -e mkvenv-pypi
|
||||
----
|
||||
@@ -8,15 +8,16 @@ graft icons
|
||||
graft doc/img
|
||||
graft misc/apparmor
|
||||
graft misc/userscripts
|
||||
recursive-include scripts *.py
|
||||
recursive-include scripts *.py *.sh
|
||||
include qutebrowser/utils/testfile
|
||||
include qutebrowser/git-commit-id
|
||||
include COPYING doc/* README.asciidoc CONTRIBUTING.asciidoc FAQ.asciidoc INSTALL.asciidoc CHANGELOG.asciidoc
|
||||
include qutebrowser.desktop
|
||||
include LICENSE doc/* README.asciidoc
|
||||
include misc/qutebrowser.desktop
|
||||
include requirements.txt
|
||||
include tox.ini
|
||||
include qutebrowser.py
|
||||
include misc/cheatsheet.svg
|
||||
include qutebrowser/config/configdata.yml
|
||||
|
||||
prune www
|
||||
prune scripts/dev
|
||||
@@ -26,6 +27,7 @@ exclude scripts/asciidoc2html.py
|
||||
exclude doc/notes
|
||||
recursive-exclude doc *.asciidoc
|
||||
include doc/qutebrowser.1.asciidoc
|
||||
include doc/changelog.asciidoc
|
||||
prune tests
|
||||
prune qutebrowser/3rdparty
|
||||
prune misc/requirements
|
||||
@@ -36,7 +38,6 @@ exclude qutebrowser/javascript/.eslintrc.yaml
|
||||
exclude qutebrowser/javascript/.eslintignore
|
||||
exclude doc/help
|
||||
exclude .*
|
||||
exclude codecov.yml
|
||||
exclude misc/appveyor_install.py
|
||||
exclude misc/qutebrowser.spec
|
||||
exclude misc/qutebrowser.nsi
|
||||
|
||||
312
README.asciidoc
312
README.asciidoc
@@ -9,9 +9,8 @@ qutebrowser
|
||||
// QUTE_WEB_HIDE
|
||||
image:icons/qutebrowser-64x64.png[qutebrowser logo] *A keyboard-driven, vim-like browser based on PyQt5 and Qt.*
|
||||
|
||||
image:https://img.shields.io/pypi/l/qutebrowser.svg?style=flat["license badge",link="https://github.com/qutebrowser/qutebrowser/blob/master/COPYING"]
|
||||
image:https://img.shields.io/pypi/l/qutebrowser.svg?style=flat["license badge",link="https://github.com/qutebrowser/qutebrowser/blob/master/LICENSE"]
|
||||
image:https://img.shields.io/pypi/v/qutebrowser.svg?style=flat["version badge",link="https://pypi.python.org/pypi/qutebrowser/"]
|
||||
image:https://requires.io/github/qutebrowser/qutebrowser/requirements.svg?branch=master["requirements badge",link="https://requires.io/github/qutebrowser/qutebrowser/requirements/?branch=master"]
|
||||
image:https://travis-ci.org/qutebrowser/qutebrowser.svg?branch=master["Build Status", link="https://travis-ci.org/qutebrowser/qutebrowser"]
|
||||
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"]
|
||||
@@ -36,11 +35,8 @@ Downloads
|
||||
---------
|
||||
|
||||
See the https://github.com/qutebrowser/qutebrowser/releases[github releases
|
||||
page] for available downloads (currently a source archive, and standalone
|
||||
packages as well as MSI installers for Windows).
|
||||
|
||||
See link:INSTALL.asciidoc[INSTALL] for detailed instructions on how to get
|
||||
qutebrowser running for various platforms.
|
||||
page] for available downloads and the link:doc/install.asciidoc[INSTALL] file for
|
||||
detailed instructions on how to get qutebrowser running on various platforms.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
@@ -48,14 +44,15 @@ Documentation
|
||||
In addition to the topics mentioned in this README, the following documents are
|
||||
available:
|
||||
|
||||
* A https://qutebrowser.org/img/cheatsheet-big.png[key binding cheatsheet]: +
|
||||
* https://qutebrowser.org/img/cheatsheet-big.png[Key binding cheatsheet]: +
|
||||
image:https://qutebrowser.org/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="https://qutebrowser.org/img/cheatsheet-big.png"]
|
||||
* link:doc/quickstart.asciidoc[Quick start guide]
|
||||
* A https://www.shortcutfoo.com/app/dojos/qutebrowser[free training course] to remember those key bindings.
|
||||
* link:FAQ.asciidoc[Frequently asked questions]
|
||||
* link:CONTRIBUTING.asciidoc[Contributing to qutebrowser]
|
||||
* link:INSTALL.asciidoc[INSTALL]
|
||||
* link:CHANGELOG.asciidoc[Change Log]
|
||||
* https://www.shortcutfoo.com/app/dojos/qutebrowser[Free training course] to remember those key bindings
|
||||
* link:doc/faq.asciidoc[Frequently asked questions]
|
||||
* link:doc/help/configuring.asciidoc[Configuring qutebrowser]
|
||||
* link:doc/contributing.asciidoc[Contributing to qutebrowser]
|
||||
* link:doc/install.asciidoc[Installing qutebrowser]
|
||||
* link:doc/changelog.asciidoc[Change Log]
|
||||
* link:doc/stacktrace.asciidoc[Reporting segfaults]
|
||||
* link:doc/userscripts.asciidoc[How to write userscripts]
|
||||
|
||||
@@ -70,15 +67,18 @@ message to the
|
||||
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at
|
||||
mailto:qutebrowser@lists.qutebrowser.org[].
|
||||
|
||||
There's also a https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[announce-only mailinglist]
|
||||
There's also an https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[announce-only mailinglist]
|
||||
at mailto:qutebrowser-announce@lists.qutebrowser.org[] (the announcements also
|
||||
get sent to the general qutebrowser@ list).
|
||||
|
||||
If you're a reddit user, there's a
|
||||
https://www.reddit.com/r/qutebrowser/[/r/qutebrowser] subreddit there.
|
||||
|
||||
Contributions / Bugs
|
||||
--------------------
|
||||
|
||||
You want to contribute to qutebrowser? Awesome! Please read
|
||||
link:CONTRIBUTING.asciidoc[the contribution guidelines] for details and
|
||||
link:doc/contributing.asciidoc[the contribution guidelines] for details and
|
||||
useful hints.
|
||||
|
||||
If you found a bug or have a feature request, you can report it in several
|
||||
@@ -98,30 +98,36 @@ Requirements
|
||||
|
||||
The following software and libraries are required to run qutebrowser:
|
||||
|
||||
* http://www.python.org/[Python] 3.4 or newer (3.5 recommended)
|
||||
* http://qt.io/[Qt] 5.2.0 or newer (5.9.0 recommended)
|
||||
* QtWebKit (old or link:https://github.com/annulen/webkit/wiki[reloaded]/NG) or QtWebEngine
|
||||
* http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.2.0 or newer
|
||||
(5.8.1 recommended) for Python 3
|
||||
* http://www.python.org/[Python] 3.5 or newer (3.6 recommended)
|
||||
* http://qt.io/[Qt] 5.7.1 or newer with the following modules:
|
||||
- QtCore / qtbase
|
||||
- QtQuick (part of qtbase in some distributions)
|
||||
- QtSQL (part of qtbase in some distributions)
|
||||
- QtOpenGL
|
||||
- QtWebEngine, or
|
||||
- QtWebKit - only the
|
||||
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
|
||||
* https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools]
|
||||
* http://fdik.org/pyPEG/[pyPEG2]
|
||||
* http://jinja.pocoo.org/[jinja2]
|
||||
* http://pygments.org/[pygments]
|
||||
* http://pyyaml.org/wiki/PyYAML[PyYAML]
|
||||
* http://pyopengl.sourceforge.net/[PyOpenGL] when using QtWebEngine
|
||||
* http://www.attrs.org/[attrs]
|
||||
|
||||
The following libraries are optional and provide a better user experience:
|
||||
The following libraries are optional:
|
||||
|
||||
* http://cthedot.de/cssutils/[cssutils]
|
||||
* http://cthedot.de/cssutils/[cssutils] (for an improved `:download --mhtml`
|
||||
with QtWebKit).
|
||||
* On Windows, https://pypi.python.org/pypi/colorama/[colorama] for colored log
|
||||
output.
|
||||
* http://asciidoc.org/[asciidoc] to generate the documentation for the `:help`
|
||||
command, when using the git repository (rather than a release).
|
||||
|
||||
To generate the documentation for the `:help` command, when using the git
|
||||
repository (rather than a release), http://asciidoc.org/[asciidoc] is needed.
|
||||
|
||||
On Windows, https://pypi.python.org/pypi/colorama/[colorama] is needed to
|
||||
display colored log output.
|
||||
|
||||
See link:INSTALL.asciidoc[INSTALL] for directions on how to install qutebrowser
|
||||
and its dependencies.
|
||||
See link:doc/install.asciidoc[the documentation] for directions on how to
|
||||
install qutebrowser and its dependencies.
|
||||
|
||||
Donating
|
||||
--------
|
||||
@@ -142,218 +148,62 @@ get in touch!
|
||||
Authors
|
||||
-------
|
||||
|
||||
Contributors, sorted by the number of commits in descending order:
|
||||
qutebrowser's primary author is Florian Bruhin (The Compiler), but qutebrowser
|
||||
wouldn't be what it is without the help of
|
||||
https://github.com/qutebrowser/qutebrowser/graphs/contributors[hundreds of contributors]!
|
||||
|
||||
// QUTE_AUTHORS_START
|
||||
* Florian Bruhin
|
||||
* Daniel Schadt
|
||||
* Ryan Roden-Corrent
|
||||
* Jan Verbeek
|
||||
* Jakub Klinkovský
|
||||
* Antoni Boucher
|
||||
* Lamar Pavel
|
||||
* Marshall Lochbaum
|
||||
* Bruno Oliveira
|
||||
* thuck
|
||||
* Martin Tournoij
|
||||
* Imran Sobir
|
||||
* Alexander Cogneau
|
||||
* Felix Van der Jeugt
|
||||
* Daniel Karbach
|
||||
* Kevin Velghe
|
||||
* Raphael Pierzina
|
||||
* Joel Torstensson
|
||||
* Patric Schmitz
|
||||
* Tarcisio Fedrizzi
|
||||
* Jay Kamat
|
||||
* Claude
|
||||
* Philipp Hansch
|
||||
* Fritz Reichwald
|
||||
* Corentin Julé
|
||||
* meles5
|
||||
* Panagiotis Ktistakis
|
||||
* Artur Shaik
|
||||
* Nathan Isom
|
||||
* Thorsten Wißmann
|
||||
* Austin Anderson
|
||||
* Jimmy
|
||||
* Niklas Haas
|
||||
* Maciej Wołczyk
|
||||
* Clayton Craft
|
||||
* sandrosc
|
||||
* Alexey "Averrin" Nabrodov
|
||||
* pkill9
|
||||
* nanjekyejoannah
|
||||
* avk
|
||||
* ZDarian
|
||||
* Milan Svoboda
|
||||
* John ShaggyTwoDope Jenkins
|
||||
* Peter Vilim
|
||||
* Jacob Sword
|
||||
* knaggita
|
||||
* Oliver Caldwell
|
||||
* Nikolay Amiantov
|
||||
* Marius
|
||||
* Julian Weigt
|
||||
* Tomasz Kramkowski
|
||||
* Sebastian Frysztak
|
||||
* Julie Engel
|
||||
* Jonas Schürmann
|
||||
* error800
|
||||
* Michael Hoang
|
||||
* Liam BEGUIN
|
||||
* Daniel Fiser
|
||||
* skinnay
|
||||
* Zach-Button
|
||||
* Samuel Walladge
|
||||
* Peter Rice
|
||||
* Ismail S
|
||||
* Halfwit
|
||||
* David Vogt
|
||||
* Claire Cavanaugh
|
||||
* rikn00
|
||||
* kanikaa1234
|
||||
* haitaka
|
||||
* Nick Ginther
|
||||
* Michał Góral
|
||||
* Michael Ilsaas
|
||||
* Martin Zimmermann
|
||||
* Link
|
||||
* Jussi Timperi
|
||||
* Cosmin Popescu
|
||||
* Brian Jackson
|
||||
* sbinix
|
||||
* rsteube
|
||||
* neeasade
|
||||
* jnphilipp
|
||||
* Yannis Rohloff
|
||||
* Tobias Patzl
|
||||
* Stefan Tatschner
|
||||
* Samuel Loury
|
||||
* Peter Michely
|
||||
* Panashe M. Fundira
|
||||
* Lucas Hoffmann
|
||||
* Larry Hynes
|
||||
* Kirill A. Shutemov
|
||||
* Johannes Altmanninger
|
||||
* Jeremy Kaplan
|
||||
* Ismail
|
||||
* Iordanis Grigoriou
|
||||
* Edgar Hipp
|
||||
* Daryl Finlay
|
||||
* arza
|
||||
* adam
|
||||
* Samir Benmendil
|
||||
* Regina Hug
|
||||
* Penaz
|
||||
* Matthias Lisin
|
||||
* Mathias Fussenegger
|
||||
* Marcelo Santos
|
||||
* Marcel Schilling
|
||||
* Joel Bradshaw
|
||||
* Jean-Louis Fuchs
|
||||
* Franz Fellner
|
||||
* Eric Drechsel
|
||||
* zwarag
|
||||
* xd1le
|
||||
* rmortens
|
||||
* oniondreams
|
||||
* issue
|
||||
* haxwithaxe
|
||||
* evan
|
||||
* dylan araps
|
||||
* caveman
|
||||
* addictedtoflames
|
||||
* Xitian9
|
||||
* Vasilij Schneidermann
|
||||
* Tomas Orsava
|
||||
* Tom Janson
|
||||
* Tobias Werth
|
||||
* Tim Harder
|
||||
* Thiago Barroso Perrotta
|
||||
* Steve Peak
|
||||
* Sorokin Alexei
|
||||
* Simon Désaulniers
|
||||
* Rok Mandeljc
|
||||
* Noah Huesser
|
||||
* Moez Bouhlel
|
||||
* MikeinRealLife
|
||||
* Lazlow Carmichael
|
||||
* Kevin Wang
|
||||
* Ján Kobezda
|
||||
* Johannes Martinsson
|
||||
* Jean-Christophe Petkovich
|
||||
* Helen Sherwood-Taylor
|
||||
* HalosGhost
|
||||
* Gregor Pohl
|
||||
* Eivind Uggedal
|
||||
* Dietrich Daroch
|
||||
* Derek Sivers
|
||||
* Daniel Lu
|
||||
* Daniel Jakots
|
||||
* Arseniy Seroka
|
||||
* Anton Grensjö
|
||||
* Andy Balaam
|
||||
* Andreas Fischer
|
||||
* Amos Bird
|
||||
* Akselmo
|
||||
// QUTE_AUTHORS_END
|
||||
|
||||
The following people have contributed graphics:
|
||||
Additionally, the following people have contributed graphics:
|
||||
|
||||
* Jad/link:http://yelostudio.com[yelo] (new icon)
|
||||
* WOFall (original icon)
|
||||
* regines (key binding cheatsheet)
|
||||
|
||||
Thanks / Similar projects
|
||||
-------------------------
|
||||
Also, thanks to everyone who contributed to one of qutebrowser's
|
||||
link:doc/backers.asciidoc[crowdfunding campaigns]!
|
||||
|
||||
Many projects with a similar goal as qutebrowser exist:
|
||||
|
||||
* http://portix.bitbucket.org/dwb/[dwb] (C, GTK+ with WebKit1, currently
|
||||
http://www.reddit.com/r/linux/comments/2huqbc/dwb_abandoned/[unmaintained] -
|
||||
main inspiration for qutebrowser)
|
||||
* https://github.com/fanglingsu/vimb[vimb] (C, GTK+ with WebKit1, active)
|
||||
* http://sourceforge.net/p/vimprobable/wiki/Home/[vimprobable] (C, GTK+ with
|
||||
WebKit1, dead)
|
||||
* http://surf.suckless.org/[surf] (C, GTK+ with WebKit1, active)
|
||||
* https://mason-larobina.github.io/luakit/[luakit] (C/Lua, GTK+ with
|
||||
WebKit1, not very active)
|
||||
* http://pwmt.org/projects/jumanji/[jumanji] (C, GTK+ with WebKit1, not very
|
||||
active)
|
||||
* http://www.uzbl.org/[uzbl] (C, GTK+ with WebKit1/WebKit2, active)
|
||||
* http://conkeror.org/[conkeror] (Javascript, Emacs-like, XULRunner/Gecko,
|
||||
active)
|
||||
* https://github.com/AeroNotix/lispkit[lispkit] (quite new, lisp, GTK+ with
|
||||
WebKit, active)
|
||||
* http://www.vimperator.org/[Vimperator] (Firefox addon)
|
||||
* http://5digits.org/pentadactyl/[Pentadactyl] (Firefox addon)
|
||||
* https://github.com/akhodakivskiy/VimFx[VimFx] (Firefox addon)
|
||||
* https://github.com/1995eaton/chromium-vim[cVim] (Chrome/Chromium addon)
|
||||
* http://vimium.github.io/[vimium] (Chrome/Chromium addon)
|
||||
* https://chrome.google.com/webstore/detail/vichrome/gghkfhpblkcmlkmpcpgaajbbiikbhpdi?hl=en[ViChrome] (Chrome/Chromium addon)
|
||||
* https://github.com/jinzhu/vrome[Vrome] (Chrome/Chromium addon)
|
||||
Similar projects
|
||||
----------------
|
||||
|
||||
Many projects with a similar goal as qutebrowser exist.
|
||||
Most of them were inspirations for qutebrowser in some way, thanks for that!
|
||||
|
||||
Thanks as well to the following projects and people for helping me with
|
||||
problems and helpful hints:
|
||||
Active
|
||||
~~~~~~
|
||||
|
||||
* http://eric-ide.python-projects.org/[eric5] / Detlev Offenbach
|
||||
* https://code.google.com/p/devicenzo/[devicenzo]
|
||||
* portix
|
||||
* seir
|
||||
* nitroxleecher
|
||||
* https://fanglingsu.github.io/vimb/[vimb] (C, GTK+ with WebKit2)
|
||||
* https://luakit.github.io/luakit/[luakit] (C/Lua, GTK+ with WebKit2)
|
||||
* http://surf.suckless.org/[surf] (C, GTK+ with WebKit1/WebKit2)
|
||||
* http://www.uzbl.org/[uzbl] (C, GTK+ with WebKit1/WebKit2)
|
||||
* Chrome/Chromium addons:
|
||||
https://github.com/1995eaton/chromium-vim[cVim],
|
||||
http://vimium.github.io/[Vimium],
|
||||
https://github.com/brookhong/Surfingkeys[Surfingkeys],
|
||||
https://key.saka.io/[Saka Key]
|
||||
* 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
|
||||
on a https://bugzilla.mozilla.org/show_bug.cgi?id=1215061[better API] for
|
||||
keyboard integration in Firefox).
|
||||
|
||||
Also, thanks to:
|
||||
Inactive
|
||||
~~~~~~~~
|
||||
|
||||
* Everyone contributing to the link:doc/backers.asciidoc[crowdfunding].
|
||||
* Everyone who had the patience to test qutebrowser before v0.1.
|
||||
* Everyone triaging/fixing my bugs in the
|
||||
https://bugreports.qt.io/secure/Dashboard.jspa[Qt bugtracker]
|
||||
* Everyone answering my questions on http://stackoverflow.com/[Stack Overflow]
|
||||
and in IRC.
|
||||
* All the projects which were a great help while developing qutebrowser.
|
||||
* https://bitbucket.org/portix/dwb[dwb] (C, GTK+ with WebKit1,
|
||||
https://bitbucket.org/portix/dwb/pull-requests/22/several-cleanups-to-increase-portability/diff[unmaintained] -
|
||||
main inspiration for qutebrowser)
|
||||
* http://sourceforge.net/p/vimprobable/wiki/Home/[vimprobable] (C, GTK+ with
|
||||
WebKit1)
|
||||
* http://pwmt.org/projects/jumanji/[jumanji] (C, GTK+ with WebKit1)
|
||||
* http://conkeror.org/[conkeror] (Javascript, Emacs-like, XULRunner/Gecko)
|
||||
* Firefox addons (not based on WebExtensions or no recent activity):
|
||||
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]
|
||||
|
||||
License
|
||||
-------
|
||||
@@ -369,7 +219,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
|
||||
|
||||
pdf.js
|
||||
------
|
||||
|
||||
@@ -4,7 +4,8 @@ Change Log
|
||||
// http://keepachangelog.com/
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to http://semver.org/[Semantic Versioning].
|
||||
This project adheres to http://semver.org/[Semantic Versioning], though minor
|
||||
breaking changes (such as renamed commands) can happen in minor releases.
|
||||
|
||||
// tags:
|
||||
// `Added` for new features.
|
||||
@@ -14,8 +15,137 @@ This project adheres to http://semver.org/[Semantic Versioning].
|
||||
// `Fixed` for any bug fixes.
|
||||
// `Security` to invite users to upgrade in case of vulnerabilities.
|
||||
|
||||
v0.11.0 (unreleased)
|
||||
--------------------
|
||||
v1.0.2
|
||||
------
|
||||
|
||||
Fixes
|
||||
~~~~~
|
||||
|
||||
- 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 starting after customizing `fonts.tabs` or `fonts.debug_console`.
|
||||
- Fixed starting with old PyQt versions compiled against newer Qt versions.
|
||||
- Fixed check for PyQt version to correctly enforce 5.7 (not 5.2).
|
||||
|
||||
v1.0.0
|
||||
------
|
||||
|
||||
Major changes
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
- Dependency changes:
|
||||
* Support for legacy QtWebKit (before 5.212 which is
|
||||
https://github.com/annulen/webkit/wiki[distributed independently from Qt])
|
||||
is dropped.
|
||||
* Support for Python 3.4 is dropped.
|
||||
* Support for Qt before 5.7.1 and PyQt before 5.7 is dropped.
|
||||
* New dependency on the QtSql module and Qt sqlite support.
|
||||
* New dependency on the http://www.attrs.org/[attrs] project (packaged as
|
||||
`python-attr` in some distributions).
|
||||
* The depedency on PyOpenGL (when using QtWebEngine) got removed. Note
|
||||
that PyQt5.QtOpenGL is still a dependency.
|
||||
* PyQt5.QtOpenGL is now always required, even with QtWebKit.
|
||||
- The QtWebEngine backend is now used by default. Note this means that
|
||||
QtWebEngine now should be a required dependency, and QtWebKit (if new enough)
|
||||
should be changed to an optional dependency.
|
||||
- Completely rewritten configuration system which ignores the old config file.
|
||||
See link:qute://help/configuring.html[] for details.
|
||||
- Various documentation files got moved to the doc/ subfolder;
|
||||
`qutebrowser.desktop` got moved to misc/.
|
||||
- `:set` now doesn't support toggling/cycling values anymore, that functionality
|
||||
got moved to `:config-cycle`.
|
||||
- New completion engine based on sqlite, which allows to complete
|
||||
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.
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- QtWebEngine: Spell checking support, see the `spellcheck.languages` setting.
|
||||
- New `qt.args` setting to pass additional arguments to Qt/Chromium.
|
||||
- New `backend` setting to select the backend to use.
|
||||
Together with the previous setting, this should make most wrapper scripts
|
||||
unnecessary.
|
||||
- qutebrowser can now be set as the default browser on macOS.
|
||||
- New config commands:
|
||||
* `:config-cycle` to cycle an option between multiple values.
|
||||
* `:config-unset` to remove a configured option.
|
||||
* `:config-clear` to remove all configured options.
|
||||
* `:config-source` to (re-)read a `config.py` file.
|
||||
* `:config-edit` to open the `config.py` file in an editor.
|
||||
* `:config-write-py` to write a `config.py` template file.
|
||||
- New `:version` command which opens `qute://version`.
|
||||
- New back/forward indicator in the statusbar.
|
||||
- New `bindings.key_mappings` setting to map keys to other keys.
|
||||
- QtWebEngine: Support for proxy authentication.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- Using `:download` now uses the page's title as filename.
|
||||
- Using `:back` or `:forward` with a count now skips intermediate pages.
|
||||
- When there are multiple messages shown, the timeout is increased.
|
||||
- `:search` now only clears the search if one was displayed before, so pressing
|
||||
`<Escape>` doesn't un-focus inputs anymore.
|
||||
- Pinned tabs now adjust to their text's width, so the `tabs.width.pinned`
|
||||
setting got removed.
|
||||
- `:set-cmd-text` now has a `--run-on-count` argument to run the underlying
|
||||
command directly if a count was given.
|
||||
- `:scroll-perc` got renamed to `:scroll-to-perc`.
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- Migrating QtWebEngine data written by versions before 2016-11-15 (before
|
||||
v0.9.0) is now not supported anymore.
|
||||
- Upgrading qutebrowser with a version older than v0.4.0 still running now won't
|
||||
work properly anymore.
|
||||
- The `--harfbuzz` and `--relaxed-config` commandline arguments got dropped.
|
||||
|
||||
Fixes
|
||||
~~~~~
|
||||
|
||||
- Exiting fullscreen via `:fullscreen` or buttons on a page now
|
||||
restores the correct previous window state (maximized/fullscreen).
|
||||
- When `input.insert_mode.auto_load` is set, background tabs now don't enter
|
||||
insert mode anymore.
|
||||
- The keybinding help widget now works correctly when using keybindings with a
|
||||
count.
|
||||
- The `window.hide_wayland_decoration` setting now works correctly again.
|
||||
|
||||
v0.11.1
|
||||
-------
|
||||
|
||||
Fixes
|
||||
~~~~~
|
||||
|
||||
- Fixed empty space being shown after tabs in the tabbar in some cases.
|
||||
- Fixed `:restart` in private browsing mode.
|
||||
- Fixed printing on macOS.
|
||||
- Closing a pinned tab via mouse now also prompts for confirmation.
|
||||
- The "try again" button on error pages works correctly again.
|
||||
- :spawn -u -d is now disallowed.
|
||||
- :spawn -d shows error messages correctly now.
|
||||
|
||||
v0.11.0
|
||||
-------
|
||||
|
||||
New dependencies
|
||||
~~~~~~~~~~~~~~~~
|
||||
@@ -28,7 +158,10 @@ New dependencies
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- New `-p` flag for `:open` to open a private window.
|
||||
- Private browsing is now implemented for QtWebEngine, *and changed its
|
||||
behavior*: The `general -> private-browsing` setting now only applies to newly
|
||||
opened windows, and you can use the `-p` flag to `:open` to open a private
|
||||
window.
|
||||
- New "pinned tabs" feature, with a new `:tab-pin` command (bound
|
||||
to `<Ctrl-p>` by default).
|
||||
- (QtWebEngine) Implemented `:follow-selected`.
|
||||
@@ -45,6 +178,8 @@ Added
|
||||
customize statusbar colors for private windows.
|
||||
- New `{private}` field displaying `[Private Mode]` for
|
||||
`ui -> window-title-format` and `tabs -> title-format`.
|
||||
- (QtWebEngine) Proxy support with Qt 5.7.1 (already was supported for 5.8 and
|
||||
newer)
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
@@ -52,62 +187,51 @@ Changed
|
||||
- To prevent elaborate phishing attacks, the Punycode version (`xn--*`) is now
|
||||
shown in addition to the decoded version for international domain names
|
||||
(IDN).
|
||||
- Private browsing is now implemented for QtWebEngine, and changed it's
|
||||
behavior: The `general -> private-browsing` setting now only applies to newly
|
||||
opened windows, and you can use the `-p` flag to `:open` to open a private
|
||||
window.
|
||||
- Starting with legacy QtWebKit now shows a warning message.
|
||||
*With the next release, support for it will be removed.*
|
||||
- The Windows releases are redone from scratch, which means:
|
||||
* They now use the new QtWebEngine backend
|
||||
* The bundled Qt is updated from 5.5 to 5.9
|
||||
* The bundled Python is updated from 3.4 to 3.6
|
||||
* They are now generated with PyInstaller instead of cx_Freeze
|
||||
* The installer is now generated using NSIS instead of being a MSI
|
||||
- Improved `qute://history` page (with lazy loading)
|
||||
- Starting with legacy QtWebKit now shows a warning message once.
|
||||
- Crash reports are not public anymore.
|
||||
- Paths like `C:` are now treated as absolute paths on Windows for downloads,
|
||||
and invalid paths are handled properly.
|
||||
- PAC on QtWebKit now supports SOCKS5 as type.
|
||||
- Comments in the config file are now before the individual options instead of
|
||||
being before sections.
|
||||
- Comments in the config file are now placed before the individual options
|
||||
instead of being before sections.
|
||||
- Messages are now hidden when clicked.
|
||||
- stdin is now closed immediately for processes spawned from qutebrowser.
|
||||
- When `ui -> message-timeout` is set to 0, messages are now never cleared.
|
||||
- Middle/right-clicking the blank parts of the tab bar (when vertical) now
|
||||
closes the current tab.
|
||||
- (QtWebEngine) With Qt 5.9, `content -> cookies-store` can now be set without
|
||||
a restart.
|
||||
- (QtWebEngine) With Qt 5.9, better error messages are now shown for failed
|
||||
downloads.
|
||||
- The adblocker now also blocks non-GET requests (e.g. POST).
|
||||
- `javascript:` links can now be hinted.
|
||||
- `:view-source`, `:tab-clone` and `:navigate --tab` now don't open the tab as
|
||||
"explicit" anymore, i.e. (with the default settings) open it next to the
|
||||
active tab.
|
||||
- (QtWebEngine) The underlying Chromium version is now shown in the version
|
||||
info.
|
||||
- `qute:*` pages now use `qute://*` instead (e.g. `qute://version` instead of
|
||||
`qute:version`), but the old versions are automatically redirected.
|
||||
- The Windows releases are redone from scratch, which means:
|
||||
- They now use the new QtWebEngine backend
|
||||
- The bundled Qt is updated from 5.5 to 5.9
|
||||
- The bundled Python is updated from 3.4 to 3.6
|
||||
- They are now generated with PyInstaller instead of cx_Freeze
|
||||
- The installer is now generated using NSIS instead of being a MSI
|
||||
- Texts in prompts are now selectable.
|
||||
- Renderer process crashes now show an error page.
|
||||
- (QtWebKit) storage -> offline-web-application-storage` got renamed to `...-cache`
|
||||
- The default level for `:messages` is now `info`, not `error`
|
||||
- Trying to focus the currently focused tab with `:tab-focus` now focuses the
|
||||
last viewed tab.
|
||||
- (QtWebEngine) With Qt 5.9, `content -> cookies-store` can now be set without
|
||||
a restart.
|
||||
- (QtWebEngine) With Qt 5.9, better error messages are now shown for failed
|
||||
downloads.
|
||||
- (QtWebEngine) The underlying Chromium version is now shown in the version
|
||||
info.
|
||||
- (QtWebKit) Renderer process crashes now show an error page on Qt 5.9 or newer.
|
||||
- (QtWebKit) storage -> offline-web-application-storage` got renamed to `...-cache`
|
||||
- (QtWebKit) PAC now supports SOCKS5 as type.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- The macOS .dmg is now built against Qt 5.9 which fixes various
|
||||
important issues (such as not being able to type dead keys).
|
||||
- (QtWebEngine) Added a workaround for a black screen with some setups
|
||||
(the workaround requires PyOpenGL to be installed, but it's optional)
|
||||
- (QtWebEngine) Starting with Nouveau graphics now shows an error message
|
||||
instead of crashing in Qt. This adds a new dependency on `PyQt5.QtOpenGL`.
|
||||
- (QtWebEngine) Retrying downloads now shows an error instead of crashing.
|
||||
- (QtWebEngine) Cloning a view-source tab now doesn't crash anymore.
|
||||
- (QtWebKit) The HTTP cache is disabled on Qt 5.7.1 and 5.8 now as it leads to
|
||||
frequent crashes due to a Qt bug.
|
||||
- Fixed crash with `:download` on PyQt 5.9.
|
||||
- Cloning a page without history doesn't crash anymore.
|
||||
- When a download results in a HTTP error, it now shows the error correctly
|
||||
@@ -117,7 +241,6 @@ Fixed
|
||||
- Fixed crash when unbinding an unbound key in the key config.
|
||||
- Fixed crash when using `:debug-log-filter` when `--filter` wasn't given on startup.
|
||||
- Fixed crash with some invalid setting values.
|
||||
- (QtWebKit) Fixed Crash when a PAC file returns an invalid value.
|
||||
- Continuing a search after clearing it now works correctly.
|
||||
- The tabbar and completion should now be more consistently and correctly
|
||||
styled with various system styles.
|
||||
@@ -125,18 +248,27 @@ Fixed
|
||||
- The validation for colors in stylesheets is now less strict,
|
||||
allowing for all valid Qt values.
|
||||
- `data:` URLs now aren't added to the history anymore.
|
||||
- (QtWebEngine) `window.navigator.userAgent` is now set correctly when
|
||||
customizing the user agent.
|
||||
- Accidentally starting with Python 2 now shows a proper error message again.
|
||||
- (QtWebEngine) HTML fullscreen is now tracked for each tab separately, which
|
||||
means it's not possible anymore to accidentally get stuck in fullscreen state
|
||||
by closing a tab with a fullscreen video.
|
||||
- For some people, running some userscripts crashed - this should now be fixed.
|
||||
- Various other rare crashes should now be fixed.
|
||||
- The settings documentation was truncated with v0.10.1 which should now be
|
||||
fixed.
|
||||
- Scrolling to an anchor in a background tab now works correctly, and javascript
|
||||
gets the correct window size for background tabs.
|
||||
- (QtWebEngine) Added a workaround for a black screen with some setups
|
||||
- (QtWebEngine) Starting with Nouveau graphics now shows an error message
|
||||
instead of crashing in Qt.
|
||||
- (QtWebEngine) Retrying downloads now shows an error instead of crashing.
|
||||
- (QtWebEngine) Cloning a view-source tab now doesn't crash anymore.
|
||||
- (QtWebEngine) `window.navigator.userAgent` is now set correctly when
|
||||
customizing the user agent.
|
||||
- (QtWebEngine) HTML fullscreen is now tracked for each tab separately, which
|
||||
means it's not possible anymore to accidentally get stuck in fullscreen state
|
||||
by closing a tab with a fullscreen video.
|
||||
- (QtWebEngine) `:scroll-page` with `--bottom-navigate` now works correctly.
|
||||
- (QtWebKit) The HTTP cache is disabled on Qt 5.7.1 and 5.8 now as it leads to
|
||||
frequent crashes due to a Qt bug.
|
||||
- (QtWebKit) Fixed Crash when a PAC file returns an invalid value.
|
||||
|
||||
v0.10.1
|
||||
-------
|
||||
@@ -181,7 +313,7 @@ Added
|
||||
- Open tabs are now auto-saved on each successful load and restored in case of a crash
|
||||
- `:jseval` now has a `--file` flag so you can pass a javascript file
|
||||
- `:session-save` now has a `--only-active-window` flag to only save the active window
|
||||
- OS X builds are back, and built with QtWebEngine
|
||||
- macOS builds are back, and built with QtWebEngine
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
@@ -201,13 +333,13 @@ Removed
|
||||
~~~~~~~
|
||||
|
||||
- (QtWebKit) Various rarely customized settings were removed:
|
||||
- `ui -> css-media-type` (defaults to desktop)
|
||||
- `general -> site-specific-quirks` (now always turned on)
|
||||
- `storage -> offline-storage-default-quota` (defaults to 5MB)
|
||||
- `storage -> offline-web-application-cache-quota` (defaults to no quota)
|
||||
- `storage -> object-cache-capacities` (default depends on disk space)
|
||||
- `content -> css-regions` (now always turned off)
|
||||
- `storage -> offline-storage-database` (merged into `storage -> local-storage`)
|
||||
* `ui -> css-media-type` (defaults to desktop)
|
||||
* `general -> site-specific-quirks` (now always turned on)
|
||||
* `storage -> offline-storage-default-quota` (defaults to 5MB)
|
||||
* `storage -> offline-web-application-cache-quota` (defaults to no quota)
|
||||
* `storage -> object-cache-capacities` (default depends on disk space)
|
||||
* `content -> css-regions` (now always turned off)
|
||||
* `storage -> offline-storage-database` (merged into `storage -> local-storage`)
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
@@ -483,7 +615,7 @@ Fixed
|
||||
- Fix crash when pressing enter without a command
|
||||
- Adjust error message to point out QtWebEngine is unsupported with the OS
|
||||
X .app currently.
|
||||
- Hide Harfbuzz warning with the OS X .app
|
||||
- Hide Harfbuzz warning with the macOS .app
|
||||
|
||||
v0.8.0
|
||||
------
|
||||
@@ -846,7 +978,7 @@ Fixed
|
||||
- Fixed scrolling to the very left/right with `:scroll-perc`.
|
||||
- Using an external editor should now work correctly with some funny chars
|
||||
(U+2028/U+2029/BOM).
|
||||
- Movements in caret mode now should work correctly on OS X and Windows.
|
||||
- Movements in caret mode now should work correctly on macOS and Windows.
|
||||
- Fixed upgrade from earlier config versions.
|
||||
- Fixed crash when killing a running userscript.
|
||||
- Fixed characters being passed through when shifted with
|
||||
@@ -921,7 +1053,7 @@ Changed
|
||||
- The completion widget doesn't show a border anymore.
|
||||
- The tabbar doesn't display ugly arrows anymore if there isn't enough space
|
||||
for all tabs.
|
||||
- Some insignificant Qt warnings which were printed on OS X are now hidden.
|
||||
- Some insignificant Qt warnings which were printed on macOS are now hidden.
|
||||
- Better support for Qt 5.5 and Python 3.5.
|
||||
|
||||
Fixed
|
||||
@@ -1032,7 +1164,7 @@ Fixed
|
||||
- Fixed AssertionError when closing many windows quickly.
|
||||
- Various fixes for deprecated key bindings and auto-migrations.
|
||||
- Workaround for qutebrowser not starting when there are NUL-bytes in the history (because of a currently unknown bug).
|
||||
- Fixed handling of keybindings containing Ctrl/Meta on OS X.
|
||||
- Fixed handling of keybindings containing Ctrl/Meta on macOS.
|
||||
- Fixed crash when downloading a URL without filename (e.g. magnet links) via "Save as...".
|
||||
- Fixed exception when starting qutebrowser with `:set` as argument.
|
||||
- Fixed horrible completion performance when the `shrink` option was set.
|
||||
@@ -1130,7 +1262,7 @@ Changed
|
||||
- Add a `:search` command in addition to `/foo` so it's more visible and can be used from scripts.
|
||||
- Various improvements to documentation, logging, and the crash reporter.
|
||||
- Expand `~` to the users home directory with `:run-userscript`.
|
||||
- Improve the userscript runner on Linux/OS X by using `QSocketNotifier`.
|
||||
- Improve the userscript runner on Linux/macOS by using `QSocketNotifier`.
|
||||
- Add luakit-like `gt`/`gT` keybindings to cycle through tabs.
|
||||
- Show default value for config values in the completion.
|
||||
- Clone tab icon, tab text and zoom level when cloning tabs.
|
||||
@@ -1150,7 +1282,7 @@ Changed
|
||||
* `init_venv.py` and `run_checks.py` have been replaced by http://tox.readthedocs.org/[tox]. Install tox and run `tox -e mkvenv` instead.
|
||||
* The tests now use http://pytest.org/[pytest]
|
||||
* Many new tests added
|
||||
* Mac Mini buildbot to run the tests on OS X.
|
||||
* Mac Mini buildbot to run the tests on macOS.
|
||||
* Coverage recording via http://nedbatchelder.com/code/coverage/[coverage.py].
|
||||
* New `--pdb-postmortem argument` to drop into the pdb debugger on exceptions.
|
||||
* Use https://github.com/ionelmc/python-hunter[hunter] for line tracing instead of a selfmade solution.
|
||||
@@ -1286,7 +1418,7 @@ Fixed
|
||||
|
||||
* Fix rare exception when a key is pressed shortly after opening a window
|
||||
* Fix exception with certain invalid URLs like `http:foo:0`
|
||||
* Work around Qt bug which renders checkboxes on OS X unusable
|
||||
* Work around Qt bug which renders checkboxes on macOS unusable
|
||||
* Fix exception when a local files can't be read in `:adblock-update`
|
||||
* Hide 2 more Qt warnings.
|
||||
* Add `!important` to hint CSS so websites don't override the hint look
|
||||
@@ -1322,7 +1454,7 @@ Changes
|
||||
* Set zoom to default instead of 100% with `:zoom`/`=`.
|
||||
* Adjust page zoom if default zoom changed.
|
||||
* Force tabs to be focused on `:undo`.
|
||||
* Replace manual installation instructions on OS X with homebrew/macports.
|
||||
* Replace manual installation instructions on macOS with homebrew/macports.
|
||||
* Allow min-/maximizing of print preview on Windows.
|
||||
* Various documentation improvements.
|
||||
* Various other small improvements and cleanups.
|
||||
@@ -39,8 +39,7 @@ pointers:
|
||||
|
||||
* https://github.com/qutebrowser/qutebrowser/labels/easy[Issues which should
|
||||
be easy to solve]
|
||||
* https://github.com/qutebrowser/qutebrowser/labels/not%20code[Issues which
|
||||
require little/no coding]
|
||||
* https://github.com/qutebrowser/qutebrowser/labels/component%3A%20docs[Documentation issues which require little/no coding]
|
||||
|
||||
If you prefer C++ or Javascript to Python, see the relevant issues which involve
|
||||
work in those languages:
|
||||
@@ -98,25 +97,24 @@ unittests and several linters/checkers.
|
||||
Currently, the following tox environments are available:
|
||||
|
||||
* Tests using https://www.pytest.org[pytest]:
|
||||
- `py34`: Run pytest for python-3.4.
|
||||
- `py35`: Run pytest for python-3.5.
|
||||
- `py34-cov`: Run pytest for python-3.4 with code coverage report.
|
||||
- `py35-cov`: Run pytest for python-3.5 with code coverage report.
|
||||
- `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]
|
||||
https://pypi.python.org/pypi/mccabe[mccabe].
|
||||
* `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]
|
||||
https://github.com/PyCQA/pydocstyle[pydocstyle].
|
||||
* `pyroma`: Check packaging practices with
|
||||
https://pypi.python.org/pypi/pyroma/[pyroma]
|
||||
https://pypi.python.org/pypi/pyroma/[pyroma].
|
||||
* `eslint`: Run http://eslint.org/[ESLint] javascript checker.
|
||||
* `check-manifest`: Check MANIFEST.in completeness with
|
||||
https://github.com/mgedmin/check-manifest[check-manifest]
|
||||
https://github.com/mgedmin/check-manifest[check-manifest].
|
||||
* `mkvenv`: Bootstrap a virtualenv for testing.
|
||||
* `misc`: Run `scripts/misc_checks.py` to check for:
|
||||
- untracked git files
|
||||
@@ -133,7 +131,7 @@ techniques are useful to handle these:
|
||||
|
||||
* Use `_foo` for unused parameters, with `foo` being a descriptive name. Using
|
||||
`_` is discouraged.
|
||||
* If you think you have a good reason to suppress a message, then add the
|
||||
* If you think you have a good reason to suppress a message, then add the
|
||||
following comment:
|
||||
+
|
||||
----
|
||||
@@ -181,7 +179,7 @@ In the _scripts/_ subfolder there's a `run_profile.py` which profiles the code
|
||||
and shows a graphical representation of what takes how much time.
|
||||
|
||||
It uses the built-in Python
|
||||
https://docs.python.org/3.4/library/profile.html[cProfile] module and can show
|
||||
https://docs.python.org/3.6/library/profile.html[cProfile] module and can show
|
||||
the output in four different ways:
|
||||
|
||||
* Raw profile file (`--profile-tool=none`)
|
||||
@@ -292,7 +290,7 @@ There are some exceptions to that:
|
||||
|
||||
* `QThread` is used instead of Python threads because it provides signals and
|
||||
slots.
|
||||
* `QProcess` is used instead of Python's `subprocess`
|
||||
* `QProcess` is used instead of Python's `subprocess`.
|
||||
* `QUrl` is used instead of storing URLs as string, see the
|
||||
<<handling-urls,handling URLs>> section for details.
|
||||
|
||||
@@ -313,7 +311,7 @@ carefully be checked.
|
||||
* Methods of Qt objects have certain maximum values based on their
|
||||
underlying C++ types.
|
||||
+
|
||||
To avoid passing too large of a numeric parameter to a Qt function, all
|
||||
To avoid passing too large of a numeric parameter to a Qt function, all
|
||||
numbers should be range-checked using `qutebrowser.qtutils.check_overflow`,
|
||||
or by other means (e.g. by setting a maximum value for a config object).
|
||||
|
||||
@@ -327,7 +325,7 @@ dictionaries which map object names to the actual long-living objects.
|
||||
There are currently these object registries, also called 'scopes':
|
||||
|
||||
* The `global` scope, with objects which are used globally (`config`,
|
||||
`cookie-jar`, etc.)
|
||||
`cookie-jar`, etc.).
|
||||
* The `tab` scope with objects which are per-tab (`hintmanager`, `webview`,
|
||||
etc.). Passing this scope to `objreg.get()` selects the object in the currently
|
||||
focused tab by default. A tab can be explicitly selected by passing
|
||||
@@ -436,14 +434,14 @@ def foo(bar: int, baz=True):
|
||||
----
|
||||
|
||||
Possible values:
|
||||
- A callable (`int`, `float`, etc.): Gets called to validate/convert the
|
||||
value.
|
||||
- A python enum type: All members of the enum are possible values.
|
||||
- A `typing.Union` of multiple types above: Any of these types are valid
|
||||
values, e.g., `typing.Union[str, int]`
|
||||
|
||||
- A callable (`int`, `float`, etc.): Gets called to validate/convert the value.
|
||||
- A python enum type: All members of the enum are possible values.
|
||||
- A `typing.Union` of multiple types above: Any of these types are valid
|
||||
values, e.g., `typing.Union[str, int]`.
|
||||
|
||||
You can customize how an argument is handled using the `@cmdutils.argument`
|
||||
decorator *after* `@cmdutils.register`. This can, for example, be used to
|
||||
decorator *after* `@cmdutils.register`. This can, for example, be used to
|
||||
customize the flag an argument should get:
|
||||
|
||||
[source,python]
|
||||
@@ -470,10 +468,11 @@ For `typing.Union` types, the given `choices` are only checked if other types
|
||||
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 `usertypes.Completion` member to use as completion.
|
||||
- `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.
|
||||
|
||||
The name of an argument will always be the parameter name, with any trailing
|
||||
@@ -535,12 +534,12 @@ ____
|
||||
Setting up a Windows Development Environment
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Install https://www.python.org/downloads/release/python-344/[Python 3.4]
|
||||
* Install https://sourceforge.net/projects/pyqt/files/PyQt5/PyQt-5.5.1/[PyQt 5.5]
|
||||
* Create a file at `C:\Windows\system32\python3.bat` with the following content:
|
||||
`@C:\Python34\python %*`
|
||||
This will make the Python 3.4 interpreter available as `python3`, which is used by various development scripts.
|
||||
* Install git from the https://git-scm.com/download/win[git-scm downloads page]
|
||||
* Install https://www.python.org/downloads/release/python-362/[Python 3.6].
|
||||
* Install PyQt via `pip install PyQt5`.
|
||||
* Create a file at `C:\Windows\system32\python3.bat` with the following content (adjust the path as necessary):
|
||||
`@C:\Python36\python %*`.
|
||||
This will make the Python 3.6 interpreter available as `python3`, which is used by various development scripts.
|
||||
* Install git from the https://git-scm.com/download/win[git-scm downloads page].
|
||||
Try not to enable `core.autocrlf`, since that will cause `flake8` to complain a lot. Use an editor that can deal with plain line feeds instead.
|
||||
* Clone your favourite qutebrowser repository.
|
||||
* To install tox, open an elevated cmd, enter your working directory and run `pip install -rmisc/requirements/requirements-tox.txt`.
|
||||
@@ -659,7 +658,7 @@ New Qt release
|
||||
https://bugreports.qt.io/issues/?jql=reporter%20%3D%20%22The%20Compiler%22%20ORDER%20BY%20fixVersion%20ASC[Qt bugtracker]
|
||||
and make sure all bugs marked as resolved are actually fixed.
|
||||
* Update own PKGBUILDs based on upstream Archlinux updates and rebuild.
|
||||
* Update recommended Qt version in `README`
|
||||
* Update recommended Qt version in `README`.
|
||||
* Grep for `WORKAROUND` in the code and test if fixed stuff works without the
|
||||
workaround.
|
||||
* Check relevant
|
||||
@@ -669,37 +668,32 @@ bugs] and check if they're fixed.
|
||||
New PyQt release
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
* See above
|
||||
* Install new PyQt in Windows VM (32- and 64-bit)
|
||||
* See above.
|
||||
* Install new PyQt in Windows VM (32- and 64-bit).
|
||||
* Download new installer and update PyQt installer path in `ci_install.py`.
|
||||
* Update `tox.ini`/`.travis.yml`/`.appveyor.yml` to test new versions
|
||||
* Update `tox.ini`/`.travis.yml`/`.appveyor.yml` to test new versions.
|
||||
|
||||
qutebrowser release
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Make sure there are no unstaged changes and the tests are green.
|
||||
* Run `x=... y=...` to set the respective shell variables
|
||||
* Run `x=... y=...` to set the respective shell variables.
|
||||
|
||||
* Add newest config to `tests/unit/config/old_configs` and update `test_upgrade_version`
|
||||
- `python -m qutebrowser --basedir conf :quit`
|
||||
- `sed '/^#/d' conf/config/qutebrowser.conf > tests/unit/config/old_configs/qutebrowser-v0.x.y.conf`
|
||||
- `rm -r conf`
|
||||
- commit
|
||||
* Adjust `__version_info__` in `qutebrowser/__init__.py`.
|
||||
* Update changelog (remove *(unreleased)*)
|
||||
* Run tests again
|
||||
* Commit
|
||||
* Update changelog (remove *(unreleased)*).
|
||||
* Run tests again.
|
||||
* Commit.
|
||||
|
||||
* Create annotated git tag (`git tag -s "v0.$x.$y" -m "Release v0.$x.$y"`)
|
||||
* `git push origin`; `git push origin v0.$x.$y`
|
||||
* Create annotated git tag (`git tag -s "v1.$x.$y" -m "Release v1.$x.$y"`).
|
||||
* `git push origin`; `git push origin v1.$x.$y`.
|
||||
* If committing on minor branch, cherry-pick release commit to master.
|
||||
* Create release on github
|
||||
* Create release on github.
|
||||
* Mark the milestone at https://github.com/qutebrowser/qutebrowser/milestones
|
||||
as closed.
|
||||
|
||||
* Linux: Run `python3 scripts/dev/build_release.py --upload v0.$x.$y`
|
||||
* Windows: Run `C:\Python34_x32\python scripts\dev\build_release.py --asciidoc C:\Python27\python C:\asciidoc-8.6.9\asciidoc.py --upload v0.X.Y` (replace X/Y by hand)
|
||||
* OS X: Run `python3 scripts/dev/build_release.py --upload v0.X.Y` (replace X/Y by hand)
|
||||
* On server: Run `python3 scripts/dev/download_release.py v0.X.Y` (replace X/Y by hand)
|
||||
* Update `qutebrowser-git` PKGBUILD if dependencies/install changed
|
||||
* Announce to qutebrowser and qutebrowser-announce mailinglist
|
||||
* 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).
|
||||
* 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.
|
||||
@@ -125,9 +125,9 @@ When using quickmark, you can give them all names, like
|
||||
without having to remember the exact website title or address.
|
||||
|
||||
How do I use spell checking?::
|
||||
Qutebrowser's support for spell checking is somewhat limited at the moment
|
||||
(see https://github.com/qutebrowser/qutebrowser/issues/700[#700]), but it
|
||||
can be done.
|
||||
Configuring spell checking in qutebrowser depends on the backend in use
|
||||
(see https://github.com/qutebrowser/qutebrowser/issues/700[#700] for
|
||||
a more detailed discussion).
|
||||
+
|
||||
For QtWebKit:
|
||||
|
||||
@@ -145,12 +145,11 @@ For QtWebKit:
|
||||
+
|
||||
For QtWebEngine:
|
||||
|
||||
. Not yet supported unfortunately :-( +
|
||||
Adding it shouldn't be too hard though, since QtWebEngine 5.8 added an API for
|
||||
this (see
|
||||
https://github.com/qutebrowser/qutebrowser/issues/700#issuecomment-290780706[this
|
||||
comment for a basic example]), so what are you waiting for and why aren't you
|
||||
hacking qutebrowser yet?
|
||||
. Make sure your versions of PyQt and Qt are 5.8 or higher.
|
||||
. Use `install_dict.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/`
|
||||
@@ -171,6 +170,20 @@ What's the difference between insert and passthrough mode?::
|
||||
be useful to rebind escape to something else in passthrough mode only, to be
|
||||
able to send an escape keypress to the website.
|
||||
|
||||
Why takes it longer to open an URL in qutebrowser than in chromium?::
|
||||
When opening an URL in an existing instance the normal qutebrowser
|
||||
Python script is started and a few PyQt libraries need to be
|
||||
loaded until it is detected that there is an instance running
|
||||
where the URL is then passed to. This takes some time.
|
||||
One workaround is to use this
|
||||
https://github.com/qutebrowser/qutebrowser/blob/master/scripts/open_url_in_instance.sh[script]
|
||||
and place it in your $PATH with the name "qutebrowser". This
|
||||
script passes the URL via an unix socket to qutebrowser (if its
|
||||
running already) using socat which is much faster and starts a new
|
||||
qutebrowser if it is not running already. Also check if you want
|
||||
to use webengine as backend in line 17 and change it to your
|
||||
needs.
|
||||
|
||||
== Troubleshooting
|
||||
|
||||
Configuration not saved after modifying config.::
|
||||
@@ -179,7 +192,7 @@ Configuration not saved after modifying config.::
|
||||
|
||||
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 allow-plugins true`
|
||||
to use the flash plugin. Using the command `:set content.plugins true`
|
||||
in qutebrowser will enable plugins. Packages for flash should
|
||||
be provided for your platform or it can be obtained from
|
||||
http://get.adobe.com/flashplayer/[Adobe].
|
||||
@@ -191,25 +204,17 @@ Experiencing freezing on sites like duckduckgo and youtube.::
|
||||
See https://github.com/qutebrowser/qutebrowser/issues/357[Issue #357]
|
||||
for more details.
|
||||
|
||||
Experiencing segfaults (crashes) on Debian systems.::
|
||||
For Debian it's highly recommended to install the `gstreamer0.10-plugins-base` package.
|
||||
This is a workaround for a bug in Qt, it has been fixed upstream in Qt 5.4
|
||||
More details can be found
|
||||
https://bugs.webkit.org/show_bug.cgi?id=119951[here].
|
||||
|
||||
Segfaults on Facebook, Medium, Amazon, ...::
|
||||
If you are on a Debian or Ubuntu based system, you might experience some crashes
|
||||
visiting these sites. This is caused by various bugs in Qt which have been
|
||||
fixed in Qt 5.4. However Debian and Ubuntu are slow to adopt or upgrade
|
||||
some packages. On Debian Jessie, it's recommended to use the experimental
|
||||
repos as described in https://github.com/qutebrowser/qutebrowser/blob/master/INSTALL.asciidoc#on-debian--ubuntu[INSTALL].
|
||||
When using QtWebEngine, qutebrowser reports "Render Process Crashed" and the console prints a traceback on Gentoo Linux or another Source-Based Distro::
|
||||
As stated in https://gcc.gnu.org/gcc-6/changes.html[GCC's Website] GCC 6 has introduced some optimizations that could break non-conforming codebases, like QtWebEngine. +
|
||||
As a workaround, you can disable the nullpointer check optimization by adding the -fno-delete-null-pointer-checks flag while compiling. +
|
||||
On gentoo, you just need to add it into your make.conf, like this: +
|
||||
|
||||
CFLAGS="... -fno-delete-null-pointer-checks"
|
||||
CXXFLAGS="... -fno-delete-null-pointer-checks"
|
||||
+
|
||||
Since Ubuntu Trusty (using Qt 5.2.1),
|
||||
https://bugreports.qt.io/browse/QTBUG-42417?jql=component%20%3D%20WebKit%20and%20resolution%20%3D%20Done%20and%20fixVersion%20in%20(5.3.0%2C%20%225.3.0%20Alpha%22%2C%20%225.3.0%20Beta1%22%2C%20%225.3.0%20RC1%22%2C%205.3.1%2C%205.3.2%2C%205.4.0%2C%20%225.4.0%20Alpha%22%2C%20%225.4.0%20Beta%22%2C%20%225.4.0%20RC%22)%20and%20priority%20in%20(%22P2%3A%20Important%22%2C%20%22P1%3A%20Critical%22%2C%20%22P0%3A%20Blocker%22)[over
|
||||
70 important bugs] have been fixed in QtWebKit. For Debian Jessie (using Qt 5.3.2)
|
||||
it's still
|
||||
https://bugreports.qt.io/browse/QTBUG-42417?jql=component%20%3D%20WebKit%20and%20resolution%20%3D%20Done%20and%20fixVersion%20in%20(5.4.0%2C%20%225.4.0%20Alpha%22%2C%20%225.4.0%20Beta%22%2C%20%225.4.0%20RC%22)%20and%20priority%20in%20(%22P2%3A%20Important%22%2C%20%22P1%3A%20Critical%22%2C%20%22P0%3A%20Blocker%22)[nearly
|
||||
20 important bugs].
|
||||
And then re-emerging qtwebengine with: +
|
||||
|
||||
emerge -1 qtwebengine
|
||||
|
||||
My issue is not listed.::
|
||||
If you experience any segfaults or crashes, you can report the issue in
|
||||
@@ -1,5 +1,5 @@
|
||||
// DO NOT EDIT THIS FILE DIRECTLY!
|
||||
// It is autogenerated from docstrings by running:
|
||||
// It is autogenerated by running:
|
||||
// $ python3 scripts/dev/src2asciidoc.py
|
||||
|
||||
= Commands
|
||||
@@ -31,6 +31,12 @@ It is possible to run or bind multiple commands by separating them with `;;`.
|
||||
|<<bookmark-load,bookmark-load>>|Load a bookmark.
|
||||
|<<buffer,buffer>>|Select tab by index or url/title best match.
|
||||
|<<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.
|
||||
|<<config-edit,config-edit>>|Open the config.py file in the editor.
|
||||
|<<config-source,config-source>>|Read a config.py file.
|
||||
|<<config-unset,config-unset>>|Unset an option.
|
||||
|<<config-write-py,config-write-py>>|Write the current configuration to a config.py file.
|
||||
|<<download,download>>|Download a given URL, or current page if no URL given.
|
||||
|<<download-cancel,download-cancel>>|Cancel the last/[count]th download.
|
||||
|<<download-clear,download-clear>>|Remove all finished downloads from the list.
|
||||
@@ -85,10 +91,10 @@ It is possible to run or bind multiple commands by separating them with `;;`.
|
||||
|<<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.
|
||||
|<<unbind,unbind>>|Unbind a keychain.
|
||||
|<<undo,undo>>|Re-open a closed tab (optionally skipping [count] closed tabs).
|
||||
|<<undo,undo>>|Re-open a closed tab.
|
||||
|<<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.
|
||||
|<<wq,wq>>|Save open pages and quit.
|
||||
|<<yank,yank>>|Yank something to the clipboard or primary selection.
|
||||
|<<zoom,zoom>>|Set the zoom level for the current tab.
|
||||
|<<zoom-in,zoom-in>>|Increase the zoom level for the current tab.
|
||||
@@ -116,7 +122,7 @@ How many pages to go back.
|
||||
|
||||
[[bind]]
|
||||
=== bind
|
||||
Syntax: +:bind [*--mode* 'mode'] [*--force*] 'key' ['command']+
|
||||
Syntax: +:bind [*--mode* 'mode'] [*--default*] 'key' ['command']+
|
||||
|
||||
Bind a key to a command.
|
||||
|
||||
@@ -126,9 +132,10 @@ Bind a key to a command.
|
||||
|
||||
|
||||
==== optional arguments
|
||||
* +*-m*+, +*--mode*+: A comma-separated list of modes to bind the key in (default: `normal`).
|
||||
* +*-m*+, +*--mode*+: A comma-separated list of modes to bind the key in (default: `normal`). See `:help bindings.commands` for the
|
||||
available modes.
|
||||
|
||||
* +*-f*+, +*--force*+: Rebind the key if it is already bound.
|
||||
* +*-d*+, +*--default*+: If given, restore a default binding.
|
||||
|
||||
==== note
|
||||
* This command does not split arguments after the last argument and handles quotes literally.
|
||||
@@ -184,20 +191,96 @@ Load a bookmark.
|
||||
|
||||
[[buffer]]
|
||||
=== buffer
|
||||
Syntax: +:buffer 'index'+
|
||||
Syntax: +:buffer ['index']+
|
||||
|
||||
Select tab by index or url/title best match.
|
||||
|
||||
Focuses window if necessary.
|
||||
Focuses window if necessary when index is given. If both index and count are given, use count.
|
||||
|
||||
==== positional arguments
|
||||
* +'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.
|
||||
|
||||
[[close]]
|
||||
=== close
|
||||
Close the current window.
|
||||
|
||||
[[config-clear]]
|
||||
=== config-clear
|
||||
Syntax: +:config-clear [*--save*]+
|
||||
|
||||
Set all settings back to their default.
|
||||
|
||||
==== optional arguments
|
||||
* +*-s*+, +*--save*+: If given, all configuration in autoconfig.yml is also removed.
|
||||
|
||||
|
||||
[[config-cycle]]
|
||||
=== config-cycle
|
||||
Syntax: +:config-cycle [*--temp*] [*--print*] 'option' ['values' ['values' ...]]+
|
||||
|
||||
Cycle an option between multiple values.
|
||||
|
||||
==== positional arguments
|
||||
* +'option'+: The name of the option.
|
||||
* +'values'+: The values to cycle through.
|
||||
|
||||
==== optional arguments
|
||||
* +*-t*+, +*--temp*+: Set value temporarily until qutebrowser is closed.
|
||||
* +*-p*+, +*--print*+: Print the value after setting.
|
||||
|
||||
[[config-edit]]
|
||||
=== config-edit
|
||||
Syntax: +:config-edit [*--no-source*]+
|
||||
|
||||
Open the config.py file in the editor.
|
||||
|
||||
==== optional arguments
|
||||
* +*-n*+, +*--no-source*+: Don't re-source the config file after editing.
|
||||
|
||||
[[config-source]]
|
||||
=== config-source
|
||||
Syntax: +:config-source [*--clear*] ['filename']+
|
||||
|
||||
Read a config.py file.
|
||||
|
||||
==== positional arguments
|
||||
* +'filename'+: The file to load. If not given, loads the default config.py.
|
||||
|
||||
|
||||
==== optional arguments
|
||||
* +*-c*+, +*--clear*+: Clear current settings first.
|
||||
|
||||
[[config-unset]]
|
||||
=== config-unset
|
||||
Syntax: +:config-unset [*--temp*] 'option'+
|
||||
|
||||
Unset an option.
|
||||
|
||||
This sets an option back to its default and removes it from autoconfig.yml.
|
||||
|
||||
==== positional arguments
|
||||
* +'option'+: The name of the option.
|
||||
|
||||
==== optional arguments
|
||||
* +*-t*+, +*--temp*+: Don't touch autoconfig.yml.
|
||||
|
||||
[[config-write-py]]
|
||||
=== config-write-py
|
||||
Syntax: +:config-write-py [*--force*] [*--defaults*] ['filename']+
|
||||
|
||||
Write the current configuration to a config.py file.
|
||||
|
||||
==== positional arguments
|
||||
* +'filename'+: The file to write to, or not given for the default config.py.
|
||||
|
||||
==== optional arguments
|
||||
* +*-f*+, +*--force*+: Force overwriting existing files.
|
||||
* +*-d*+, +*--defaults*+: Write the defaults instead of values configured via :set.
|
||||
|
||||
[[download]]
|
||||
=== download
|
||||
Syntax: +:download [*--mhtml*] [*--dest* 'dest'] ['url'] ['dest-old']+
|
||||
@@ -281,7 +364,7 @@ Syntax: +:edit-url [*--bg*] [*--tab*] [*--window*] ['url']+
|
||||
|
||||
Navigate to a url formed in an external editor.
|
||||
|
||||
The editor which should be launched can be configured via the `general -> editor` config option.
|
||||
The editor which should be launched can be configured via the `editor.command` config option.
|
||||
|
||||
==== positional arguments
|
||||
* +'url'+: URL to edit; defaults to the current page url.
|
||||
@@ -338,7 +421,7 @@ Show help about a command or setting.
|
||||
* +'topic'+: The topic to show help for.
|
||||
|
||||
- :__command__ for commands.
|
||||
- __section__\->__option__ for settings.
|
||||
- __section__.__option__ for settings.
|
||||
|
||||
|
||||
==== optional arguments
|
||||
@@ -368,7 +451,7 @@ Start hinting.
|
||||
- `normal`: Open the link.
|
||||
- `current`: Open the link in the current tab.
|
||||
- `tab`: Open the link in a new tab (honoring the
|
||||
background-tabs setting).
|
||||
`tabs.background_tabs` setting).
|
||||
- `tab-fg`: Open the link in a new foreground tab.
|
||||
- `tab-bg`: Open the link in a new background tab.
|
||||
- `window`: Open the link in a new window.
|
||||
@@ -402,13 +485,13 @@ Start hinting.
|
||||
|
||||
|
||||
==== optional arguments
|
||||
* +*-r*+, +*--rapid*+: Whether to do rapid hinting. This is only possible with targets `tab` (with background-tabs=true), `tab-bg`,
|
||||
* +*-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.
|
||||
- `letter`: Use the chars in the hints->chars settings.
|
||||
- `letter`: Use the chars in the hints.chars setting.
|
||||
- `word`: Use hint words based on the html elements and the
|
||||
extra words.
|
||||
|
||||
@@ -545,7 +628,7 @@ For `increment` and `decrement`, the number to change the URL by. For `up`, the
|
||||
|
||||
[[open]]
|
||||
=== open
|
||||
Syntax: +:open [*--implicit*] [*--bg*] [*--tab*] [*--window*] [*--secure*] [*--private*]
|
||||
Syntax: +:open [*--related*] [*--bg*] [*--tab*] [*--window*] [*--secure*] [*--private*]
|
||||
['url']+
|
||||
|
||||
Open a URL in the current/[count]th tab.
|
||||
@@ -556,7 +639,7 @@ If the URL contains newlines, each line gets opened in its own tab.
|
||||
* +'url'+: The URL to open.
|
||||
|
||||
==== optional arguments
|
||||
* +*-i*+, +*--implicit*+: If opening a new tab, treat the tab as implicit (like clicking on a link).
|
||||
* +*-r*+, +*--related*+: If opening a new tab, position the tab as related to the current one (like clicking on a link).
|
||||
|
||||
* +*-b*+, +*--bg*+: Open in a new background tab.
|
||||
* +*-t*+, +*--tab*+: Open in a new tab.
|
||||
@@ -632,8 +715,17 @@ Save the current page as a quickmark.
|
||||
|
||||
[[quit]]
|
||||
=== quit
|
||||
Syntax: +:quit [*--save*] ['session']+
|
||||
|
||||
Quit qutebrowser.
|
||||
|
||||
==== positional arguments
|
||||
* +'session'+: The name of the session to save.
|
||||
|
||||
==== optional arguments
|
||||
* +*-s*+, +*--save*+: When given, save the open windows even if auto_save.session is turned off.
|
||||
|
||||
|
||||
[[record-macro]]
|
||||
=== record-macro
|
||||
Syntax: +:record-macro ['register']+
|
||||
@@ -752,7 +844,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 general -> 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
|
||||
@@ -764,24 +856,23 @@ Save a session.
|
||||
|
||||
[[set]]
|
||||
=== set
|
||||
Syntax: +:set [*--temp*] [*--print*] ['section'] ['option'] ['values' ['values' ...]]+
|
||||
Syntax: +:set [*--temp*] [*--print*] ['option'] ['value']+
|
||||
|
||||
Set an option.
|
||||
|
||||
If the option name ends with '?', the value of the option is shown instead. If the option name ends with '!' and it is a boolean value, toggle it.
|
||||
If the option name ends with '?', the value of the option is shown instead.
|
||||
|
||||
==== positional arguments
|
||||
* +'section'+: The section where the option is in.
|
||||
* +'option'+: The name of the option.
|
||||
* +'values'+: The value to set, or the values to cycle through.
|
||||
* +'value'+: The value to set.
|
||||
|
||||
==== optional arguments
|
||||
* +*-t*+, +*--temp*+: Set value temporarily.
|
||||
* +*-t*+, +*--temp*+: Set value temporarily until qutebrowser is closed.
|
||||
* +*-p*+, +*--print*+: Print the value after setting.
|
||||
|
||||
[[set-cmd-text]]
|
||||
=== set-cmd-text
|
||||
Syntax: +:set-cmd-text [*--space*] [*--append*] 'text'+
|
||||
Syntax: +:set-cmd-text [*--space*] [*--append*] [*--run-on-count*] 'text'+
|
||||
|
||||
Preset the statusbar to some text.
|
||||
|
||||
@@ -791,6 +882,11 @@ Preset the statusbar to some text.
|
||||
==== optional arguments
|
||||
* +*-s*+, +*--space*+: If given, a space is added to the end.
|
||||
* +*-a*+, +*--append*+: If given, the text is appended to the current text.
|
||||
* +*-r*+, +*--run-on-count*+: If given with a count, the command is run with the given count rather than setting the command text.
|
||||
|
||||
|
||||
==== count
|
||||
The count if given.
|
||||
|
||||
==== note
|
||||
* This command does not split arguments after the last argument and handles quotes literally.
|
||||
@@ -843,7 +939,7 @@ Close the current/[count]th tab.
|
||||
==== optional arguments
|
||||
* +*-p*+, +*--prev*+: Force selecting the tab before the current tab.
|
||||
* +*-n*+, +*--next*+: Force selecting the tab after the current tab.
|
||||
* +*-o*+, +*--opposite*+: Force selecting the tab in the opposite direction of what's configured in 'tabs->select-on-remove'.
|
||||
* +*-o*+, +*--opposite*+: Force selecting the tab in the opposite direction of what's configured in 'tabs.select_on_remove'.
|
||||
|
||||
* +*-f*+, +*--force*+: Avoid confirmation for pinned tabs.
|
||||
|
||||
@@ -911,7 +1007,7 @@ Close all tabs except for the current one.
|
||||
=== tab-pin
|
||||
Pin/Unpin the current/[count]th tab.
|
||||
|
||||
Pinning a tab shrinks it to tabs->pinned-width size. Attempting to close a pinned tab will cause a confirmation, unless --force is passed.
|
||||
Pinning a tab shrinks it to the size of its title text. Attempting to close a pinned tab will cause a confirmation, unless --force is passed.
|
||||
|
||||
==== count
|
||||
The tab index to pin or unpin
|
||||
@@ -925,18 +1021,24 @@ How many tabs to switch back.
|
||||
|
||||
[[unbind]]
|
||||
=== unbind
|
||||
Syntax: +:unbind 'key' ['mode']+
|
||||
Syntax: +:unbind [*--mode* 'mode'] 'key'+
|
||||
|
||||
Unbind a keychain.
|
||||
|
||||
==== positional arguments
|
||||
* +'key'+: The keychain or special key (inside <...>) to unbind.
|
||||
* +'mode'+: A comma-separated list of modes to unbind the key in (default: `normal`).
|
||||
|
||||
==== optional arguments
|
||||
* +*-m*+, +*--mode*+: A mode to unbind the key in (default: `normal`). See `:help bindings.commands` for the available modes.
|
||||
|
||||
|
||||
[[undo]]
|
||||
=== undo
|
||||
Re-open a closed tab (optionally skipping [count] closed tabs).
|
||||
Re-open a closed tab.
|
||||
|
||||
[[version]]
|
||||
=== version
|
||||
Show version information.
|
||||
|
||||
[[view-source]]
|
||||
=== view-source
|
||||
@@ -946,15 +1048,6 @@ Show the source of the current page in a new tab.
|
||||
=== window-only
|
||||
Close all windows except for the current one.
|
||||
|
||||
[[wq]]
|
||||
=== wq
|
||||
Syntax: +:wq ['name']+
|
||||
|
||||
Save open pages and quit.
|
||||
|
||||
==== positional arguments
|
||||
* +'name'+: The name of the session.
|
||||
|
||||
[[yank]]
|
||||
=== yank
|
||||
Syntax: +:yank [*--sel*] [*--keep*] ['what']+
|
||||
@@ -1043,6 +1136,7 @@ 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.
|
||||
@@ -1066,8 +1160,8 @@ How many steps to zoom out.
|
||||
|<<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-perc,scroll-perc>>|Scroll to a specific percentage of the page.
|
||||
|<<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.
|
||||
@@ -1290,11 +1384,15 @@ 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 `general -> editor` config option.
|
||||
The editor which should be launched can be configured via the `editor.command` config option.
|
||||
|
||||
[[prompt-accept]]
|
||||
=== prompt-accept
|
||||
@@ -1483,9 +1581,22 @@ Scroll the frame page-wise.
|
||||
==== count
|
||||
multiplier
|
||||
|
||||
[[scroll-perc]]
|
||||
=== scroll-perc
|
||||
Syntax: +:scroll-perc [*--horizontal*] ['perc']+
|
||||
[[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.
|
||||
|
||||
@@ -1500,19 +1611,6 @@ The percentage can be given either as argument or as count. If no percentage is
|
||||
==== count
|
||||
Percentage to scroll.
|
||||
|
||||
[[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
|
||||
|
||||
[[search-next]]
|
||||
=== search-next
|
||||
Continue the search to the ([count]th) next term.
|
||||
@@ -1553,6 +1651,7 @@ These commands are mainly intended for debugging. They are hidden if qutebrowser
|
||||
|<<debug-clear-ssl-errors,debug-clear-ssl-errors>>|Clear remembered SSL error answers.
|
||||
|<<debug-console,debug-console>>|Show the debugging console.
|
||||
|<<debug-crash,debug-crash>>|Crash for debugging purposes.
|
||||
|<<debug-dump-history,debug-dump-history>>|Dump the history to a file in the old pre-SQL format.
|
||||
|<<debug-dump-page,debug-dump-page>>|Dump the current page's content to a file.
|
||||
|<<debug-log-capacity,debug-log-capacity>>|Change the number of log lines to be stored in RAM.
|
||||
|<<debug-log-filter,debug-log-filter>>|Change the log filter for console logging.
|
||||
@@ -1587,6 +1686,15 @@ Crash for debugging purposes.
|
||||
==== positional arguments
|
||||
* +'typ'+: either 'exception' or 'segfault'.
|
||||
|
||||
[[debug-dump-history]]
|
||||
=== debug-dump-history
|
||||
Syntax: +:debug-dump-history 'dest'+
|
||||
|
||||
Dump the history to a file in the old pre-SQL format.
|
||||
|
||||
==== positional arguments
|
||||
* +'dest'+: Where to write the file to.
|
||||
|
||||
[[debug-dump-page]]
|
||||
=== debug-dump-page
|
||||
Syntax: +:debug-dump-page [*--plain*] 'dest'+
|
||||
|
||||
369
doc/help/configuring.asciidoc
Normal file
369
doc/help/configuring.asciidoc
Normal file
@@ -0,0 +1,369 @@
|
||||
Configuring qutebrowser
|
||||
=======================
|
||||
|
||||
IMPORTANT: qutebrowser's configuration system was completely rewritten in
|
||||
September 2017. This information is not applicable to older releases, and older
|
||||
information elsewhere might be outdated. **If you had an old configuration
|
||||
around and upgraded, this page will automatically open once**. To view it at a
|
||||
later time, use the `:help` command.
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
+
|
||||
----
|
||||
:bind -f -m command <Up> completion-item-focus prev
|
||||
:bind -f -m command <Down> completion-item-focus next
|
||||
----
|
||||
|
||||
- The default for `completion.web_history_max_items` is now set to `-1`, showing
|
||||
an unlimited number of items in the completion for `:open` as the new
|
||||
sqlite-based completion is much faster. If the `:open` completion is too slow
|
||||
on your machine, set an appropriate limit again.
|
||||
|
||||
Configuring qutebrowser via the user interface
|
||||
----------------------------------------------
|
||||
|
||||
The easy (but less flexible) way to configure qutebrowser is using its user
|
||||
interface or command line. Changes you make this way are immediately active
|
||||
(with the exception of a few settings, where this is pointed out in the
|
||||
documentation) and are persisted in an `autoconfig.yml` file.
|
||||
|
||||
The `autoconfig.yml` file is located in the "config" folder listed on the
|
||||
link:qute://version[] page. On macOS, the "auto config" folder is used, which is
|
||||
different from where hand-written config files are kept.
|
||||
|
||||
However, **do not** edit `autoconfig.yml` by hand. Instead, see the next
|
||||
section.
|
||||
|
||||
If you want to customize many settings, you can open the link:qute://settings[]
|
||||
page by running `:set` without any arguments, where all settings are listed and
|
||||
customizable.
|
||||
|
||||
Using the link:commands.html#set[`:set`] command and command completion, you
|
||||
can quickly set settings interactively, for example `:set tabs.position left`.
|
||||
|
||||
To get more help about a setting, use e.g. `:help tabs.position`.
|
||||
|
||||
To bind and unbind keys, you can use the link:commands.html#bind[`:bind`] and
|
||||
link:commands.html#unbind[`:unbind`] commands:
|
||||
|
||||
- Binding the key chain `,v` to the `:spawn mpv {url}` command:
|
||||
`:bind ,v spawn mpv {url}`
|
||||
- Unbinding the same key chain: `:unbind ,v`
|
||||
|
||||
Key chains starting with a comma are ideal for custom bindings, as the comma key
|
||||
will never be used in a default keybinding.
|
||||
|
||||
See the help pages linked above (or `:help :bind`, `:help :unbind`) for more
|
||||
information.
|
||||
|
||||
Other useful commands for config manipulation are
|
||||
link:commands.html#config-unset[`:config-unset`] to reset a value to its default,
|
||||
link:commands.html#config-clear[`:config-clear`] to reset the entire configuration,
|
||||
and link:commands.html#config-cycle[`:config-cycle`] to cycle a setting between
|
||||
different values.
|
||||
|
||||
Configuring qutebrowser via config.py
|
||||
-------------------------------------
|
||||
|
||||
For more powerful configuration possibilities, you can create a `config.py`
|
||||
file. Since it's a Python file, you have much more flexibility for
|
||||
configuration. Note that qutebrowser will never touch this file - this means
|
||||
you'll be responsible for updating it when upgrading to a newer qutebrowser
|
||||
version.
|
||||
|
||||
You can run `:config-edit` inside qutebrowser to open the file in your editor,
|
||||
`:config-source` to reload the file (`:config-edit` does this automatically), or
|
||||
`:config-write-py --defaults` to write a template file to work with.
|
||||
|
||||
The file should be located in the "config" location listed on
|
||||
link:qute://version[], which is typically `~/.config/qutebrowser/config.py` on
|
||||
Linux, `~/.qutebrowser/config.py` on macOS, and
|
||||
`%APPDATA%/qutebrowser/config.py` on Windows.
|
||||
|
||||
Two global objects are pre-defined when running `config.py`: `c` and `config`.
|
||||
|
||||
Changing settings
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
While you can set settings using the `config.set()` method (which is explained
|
||||
in the next section), it's easier to use the `c` shorthand object to easily set
|
||||
settings like this:
|
||||
|
||||
.config.py:
|
||||
[source,python]
|
||||
----
|
||||
c.tabs.position = "left"
|
||||
c.completion.shrink = True
|
||||
----
|
||||
|
||||
Note that qutebrowser does some Python magic so it's able to warn you about
|
||||
mistyped config settings. As an example, if you do `c.tabs.possition = "left"`,
|
||||
you'll get an error when starting.
|
||||
|
||||
See the link:settings.html[settings help page] for all available settings. The
|
||||
accepted values depend on the type of the option. Commonly used are:
|
||||
|
||||
- Strings: `c.tabs.position = "left"`
|
||||
- Booleans: `c.completion.shrink = True`
|
||||
- Integers: `c.messages.timeout = 5000`
|
||||
- Dictionaries:
|
||||
* `c.headers.custom = {'X-Hello': 'World', 'X-Awesome': 'yes'}` to override
|
||||
any other values in the dictionary.
|
||||
* `c.aliases['foo'] = 'message-info foo'` to add a single value.
|
||||
- Lists:
|
||||
* `c.url.start_pages = ["https://www.qutebrowser.org/"]` to override any
|
||||
previous elements.
|
||||
* `c.url.start_pages.append("https://www.python.org/")` to add a new value.
|
||||
|
||||
Any other config types (e.g. a color) are specified as a string. The only
|
||||
exception is the `Regex` type, which can take either a string (with an `r`
|
||||
prefix to preserve backslashes) or a Python regex object:
|
||||
|
||||
- `c.hints.next_regexes.append(r'\bvor\b')`
|
||||
- `c.hints.prev_regexes.append(re.compile(r'\bzurück\b'))`
|
||||
|
||||
If you want to read a setting, you can use the `c` object to do so as well:
|
||||
`c.colors.tabs.even.bg = c.colors.tabs.odd.bg`.
|
||||
|
||||
|
||||
Using strings for setting names
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you want to set settings based on their name as a string, use the
|
||||
`config.set` method:
|
||||
|
||||
.config.py:
|
||||
[source,python]
|
||||
----
|
||||
# Equivalent to:
|
||||
# c.content.javascript.enabled = False
|
||||
config.set('content.javascript.enabled', False)
|
||||
----
|
||||
|
||||
To read a setting, use the `config.get` method:
|
||||
|
||||
[source,python]
|
||||
----
|
||||
# Equivalent to:
|
||||
# color = c.colors.completion.fg
|
||||
color = config.get('colors.completion.fg')
|
||||
----
|
||||
|
||||
Binding keys
|
||||
~~~~~~~~~~~~
|
||||
|
||||
While it's possible to change the `bindings.commands` setting to bind keys, it's
|
||||
preferred to use the `config.bind` command. Doing so ensures the commands are
|
||||
valid and normalizes different expressions which map to the same key.
|
||||
|
||||
For details on how to specify keys and the available modes, see the
|
||||
link:settings.html#bindings.commands[documentation] for the `bindings.commands`
|
||||
setting.
|
||||
|
||||
To bind a key:
|
||||
|
||||
.config.py:
|
||||
[source,python]
|
||||
----
|
||||
config.bind('<Ctrl-v>', 'spawn mpv {url}')
|
||||
----
|
||||
|
||||
To bind a key in a mode other than `'normal'`, add a `mode` argument:
|
||||
|
||||
[source,python]
|
||||
----
|
||||
config.bind('<Ctrl-y>', 'prompt-yes', mode='prompt')
|
||||
----
|
||||
|
||||
To unbind a key (either a key which has been bound before, or a default binding):
|
||||
|
||||
[source,python]
|
||||
----
|
||||
config.unbind('<Ctrl-v>', mode='normal')
|
||||
----
|
||||
|
||||
To bind keys without modifiers, specify a key chain to bind as a string. Key
|
||||
chains starting with a comma are ideal for custom bindings, as the comma key
|
||||
will never be used in a default keybinding.
|
||||
|
||||
[source,python]
|
||||
----
|
||||
config.bind(',v', 'spawn mpv {url}')
|
||||
----
|
||||
|
||||
To suppress loading of any default keybindings, you can set
|
||||
`c.bindings.default = {}`.
|
||||
|
||||
Loading `autoconfig.yml`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
By default, all customization done via `:set`, `:bind` and `:unbind` is
|
||||
temporary as soon as a `config.py` exists. The settings done that way are always
|
||||
saved in the `autoconfig.yml` file, but you'll need to explicitly load it in
|
||||
your `config.py` by doing:
|
||||
|
||||
.config.py:
|
||||
[source,python]
|
||||
----
|
||||
config.load_autoconfig()
|
||||
----
|
||||
|
||||
If you do so at the top of your file, your `config.py` settings will take
|
||||
precedence as they overwrite the settings done in `autoconfig.yml`.
|
||||
|
||||
Importing other modules
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can import any module from the
|
||||
https://docs.python.org/3/library/index.html[Python standard library] (e.g.
|
||||
`import os.path`), as well as any module installed in the environment
|
||||
qutebrowser is run with.
|
||||
|
||||
If you have an `utils.py` file in your qutebrowser config folder, you can import
|
||||
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.
|
||||
|
||||
Getting the config directory
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you need to get the qutebrowser config directory, you can do so by reading
|
||||
`config.configdir`. Similarily, you can get the qutebrowser data directory via
|
||||
`config.datadir`.
|
||||
|
||||
This gives you a https://docs.python.org/3/library/pathlib.html[`pathlib.Path`
|
||||
object], on which you can use `/` to add more directory parts, or `str(...)` to
|
||||
get a string:
|
||||
|
||||
.config.py:
|
||||
[source,python]
|
||||
----
|
||||
print(str(config.configdir / 'config.py')
|
||||
----
|
||||
|
||||
Handling errors
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
If there are errors in your `config.py`, qutebrowser will try to apply as much
|
||||
of it as possible, and show an error dialog before starting.
|
||||
|
||||
qutebrowser tries to display errors which are easy to understand even for people
|
||||
who are not used to writing Python. If you see a config error which you find
|
||||
confusing or you think qutebrowser could handle better, please
|
||||
https://github.com/qutebrowser/qutebrowser/issues[open an issue]!
|
||||
|
||||
Recipes
|
||||
~~~~~~~
|
||||
|
||||
Reading a YAML file
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To read a YAML config like this:
|
||||
|
||||
.config.yml:
|
||||
----
|
||||
tabs.position: left
|
||||
tabs.show: switching
|
||||
----
|
||||
|
||||
You can use:
|
||||
|
||||
.config.py:
|
||||
[source,python]
|
||||
----
|
||||
import yaml
|
||||
|
||||
with (config.configdir / 'config.yml').open() as f:
|
||||
yaml_data = yaml.load(f)
|
||||
|
||||
for k, v in yaml_data.items():
|
||||
config.set(k, v)
|
||||
----
|
||||
|
||||
Reading a nested YAML file
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To read a YAML file with nested values like this:
|
||||
|
||||
.colors.yml:
|
||||
----
|
||||
colors:
|
||||
statusbar:
|
||||
normal:
|
||||
bg: lime
|
||||
fg: black
|
||||
url:
|
||||
fg: red
|
||||
----
|
||||
|
||||
You can use:
|
||||
|
||||
.config.py:
|
||||
[source,python]
|
||||
----
|
||||
import yaml
|
||||
|
||||
with (config.configdir / 'colors.yml').open() as f:
|
||||
yaml_data = yaml.load(f)
|
||||
|
||||
def dict_attrs(obj, path=''):
|
||||
if isinstance(obj, dict):
|
||||
for k, v in obj.items():
|
||||
yield from dict_attrs(v, '{}.{}'.format(path, k) if path else k)
|
||||
else:
|
||||
yield path, obj
|
||||
|
||||
for k, v in dict_attrs(yaml_data):
|
||||
config.set(k, v)
|
||||
----
|
||||
|
||||
Note that this won't work for values which are dictionaries.
|
||||
|
||||
Binding chained commands
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you have a lot of chained commands you want to bind, you can write a helper
|
||||
to do so:
|
||||
|
||||
[source,python]
|
||||
----
|
||||
def bind_chained(key, *commands):
|
||||
config.bind(key, ' ;; '.join(commands))
|
||||
|
||||
bind_chained('<Escape>', 'clear-keychain', 'search')
|
||||
----
|
||||
|
||||
Avoiding flake8 errors
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you use an editor with flake8 integration which complains about `c` and `config` being undefined, you can use:
|
||||
|
||||
[source,python]
|
||||
----
|
||||
c = c # noqa: F821
|
||||
config = config # noqa: F821
|
||||
----
|
||||
|
||||
For type annotation support (note that those imports aren't guaranteed to be
|
||||
stable across qutebrowser versions):
|
||||
|
||||
[source,python]
|
||||
----
|
||||
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
|
||||
----
|
||||
@@ -7,12 +7,13 @@ Documentation
|
||||
The following help pages are currently available:
|
||||
|
||||
* link:../quickstart.html[Quick start guide]
|
||||
* link:../../FAQ.html[Frequently asked questions]
|
||||
* link:../../CHANGELOG.html[Change Log]
|
||||
* link:../faq.html[Frequently asked questions]
|
||||
* link:../changelog.html[Change Log]
|
||||
* link:commands.html[Documentation of commands]
|
||||
* link:configuring.html[Configuring qutebrowser]
|
||||
* link:settings.html[Documentation of settings]
|
||||
* link:../userscripts.html[How to write userscripts]
|
||||
* link:../../CONTRIBUTING.html[Contributing to qutebrowser]
|
||||
* link:../contributing.html[Contributing to qutebrowser]
|
||||
|
||||
Getting help
|
||||
------------
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
380
doc/install.asciidoc
Normal file
380
doc/install.asciidoc
Normal file
@@ -0,0 +1,380 @@
|
||||
Installing qutebrowser
|
||||
======================
|
||||
|
||||
toc::[]
|
||||
|
||||
NOTE: qutebrowser recently had some bigger dependency changes for v1.0.0, which
|
||||
means those instructions might be out of date in some places.
|
||||
https://github.com/qutebrowser/qutebrowser/blob/master/doc/contributing.asciidoc[Please help]
|
||||
updating them if you notice something being broken!
|
||||
|
||||
On Debian / Ubuntu
|
||||
------------------
|
||||
|
||||
How to install qutebrowser depends a lot on the version of Debian/Ubuntu you're
|
||||
running.
|
||||
|
||||
Debian Jessie / Ubuntu 14.04 LTS / Linux Mint < 18
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
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://github.com/pyenv/pyenv[pyenv], but nobody tried that yet.
|
||||
|
||||
If you get qutebrowser running on those distributions, please
|
||||
https://github.com/qutebrowser/qutebrowser/blob/master/doc/contributing.asciidoc[contribute]
|
||||
to update this documentation!
|
||||
|
||||
Ubuntu 16.04 LTS / Linux Mint 18
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
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].
|
||||
|
||||
Install the packages:
|
||||
|
||||
----
|
||||
# dpkg -i python3-pypeg2_*_all.deb
|
||||
# dpkg -i qutebrowser_*_all.deb
|
||||
----
|
||||
|
||||
Some additional hints:
|
||||
|
||||
- Alternatively, you can <<tox,install qutebrowser via tox>> to get a newer
|
||||
QtWebEngine version.
|
||||
- If running from git, run the following to generate the documentation for the
|
||||
`:help` command:
|
||||
+
|
||||
----
|
||||
# apt-get 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.
|
||||
- If video or sound don't work with QtWebKit, try installing the gstreamer plugins:
|
||||
+
|
||||
----
|
||||
# apt-get install gstreamer1.0-plugins-{bad,base,good,ugly}
|
||||
----
|
||||
|
||||
On Fedora
|
||||
---------
|
||||
|
||||
qutebrowser is available in the official repositories for Fedora 22 and newer.
|
||||
|
||||
----
|
||||
# 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.
|
||||
|
||||
On Archlinux
|
||||
------------
|
||||
|
||||
qutebrowser is available in the official [community] repository.
|
||||
|
||||
----
|
||||
# pacman -S qutebrowser
|
||||
----
|
||||
|
||||
There is also a -git version available in the AUR:
|
||||
https://aur.archlinux.org/packages/qutebrowser-git/[qutebrowser-git].
|
||||
|
||||
You can install it using `makepkg` like this:
|
||||
|
||||
----
|
||||
$ git clone https://aur.archlinux.org/qutebrowser-git.git
|
||||
$ cd qutebrowser-git
|
||||
$ makepkg -si
|
||||
$ cd ..
|
||||
$ rm -r qutebrowser-git
|
||||
----
|
||||
|
||||
or you could use an AUR helper, e.g. `yaourt -S qutebrowser-git`.
|
||||
|
||||
If video or sound don't work with QtWebKit, try installing the gstreamer plugins:
|
||||
|
||||
----
|
||||
# pacman -S gst-plugins-{base,good,bad,ugly} gst-libav
|
||||
----
|
||||
|
||||
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.]
|
||||
|
||||
It's recommended to <<tox,install qutebrowser via tox>> instead.
|
||||
|
||||
To get an up-to-date QtWebKit, you can use
|
||||
https://gist.github.com/annulen/309569fb61e5d64a703c055c1e726f71[this ebuild].
|
||||
|
||||
If video or sound don't work with QtWebKit, try installing the gstreamer
|
||||
plugins:
|
||||
|
||||
----
|
||||
# emerge -av gst-plugins-{base,good,bad,ugly,libav}
|
||||
----
|
||||
|
||||
To be able to play videos with proprietary codecs with QtWebEngine, you will
|
||||
need to turn off the `bindist` flag for `dev-qt/qtwebengine`.
|
||||
|
||||
See the https://wiki.gentoo.org/wiki/Qutebrowser#USE_flags[Gentoo Wiki] for
|
||||
more information.
|
||||
|
||||
On Void Linux
|
||||
-------------
|
||||
|
||||
qutebrowser is available in the official repositories and can be installed
|
||||
with:
|
||||
|
||||
----
|
||||
# xbps-install qutebrowser
|
||||
----
|
||||
|
||||
It's currently recommended to install `python3-PyQt5-webengine` and
|
||||
`python3-PyQt5-opengl`, then start with `--backend webengine` to use the new
|
||||
backend.
|
||||
|
||||
Since the v1.0 release, qutebrowser uses QtWebEngine by default.
|
||||
|
||||
On NixOS
|
||||
--------
|
||||
|
||||
Nixpkgs collection contains `pkgs.qutebrowser` since June 2015. You can install
|
||||
it with:
|
||||
|
||||
----
|
||||
$ nix-env -i qutebrowser
|
||||
----
|
||||
|
||||
It's recommended to install `qt5.qtwebengine` and start with
|
||||
`--backend webengine` to use the new backend.
|
||||
|
||||
Since the v1.0 release, qutebrowser uses QtWebEngine by default.
|
||||
|
||||
On openSUSE
|
||||
-----------
|
||||
|
||||
There are prebuilt RPMs available at https://software.opensuse.org/download.html?project=network&package=qutebrowser[OBS].
|
||||
|
||||
To use the QtWebEngine backend, install `libqt5-qtwebengine`.
|
||||
|
||||
On OpenBSD
|
||||
----------
|
||||
|
||||
qutebrowser is in http://cvsweb.openbsd.org/cgi-bin/cvsweb/ports/www/qutebrowser/[OpenBSD ports].
|
||||
|
||||
Install the package:
|
||||
|
||||
----
|
||||
# pkg_add qutebrowser
|
||||
----
|
||||
|
||||
Or alternatively, use the ports system :
|
||||
|
||||
----
|
||||
# cd /usr/ports/www/qutebrowser
|
||||
# make install
|
||||
----
|
||||
|
||||
On Windows
|
||||
----------
|
||||
|
||||
There are different ways to install qutebrowser on Windows:
|
||||
|
||||
Prebuilt binaries
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Prebuilt standalone packages and installers
|
||||
https://github.com/qutebrowser/qutebrowser/releases[are built] for every
|
||||
release.
|
||||
|
||||
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). You can install a newer version
|
||||
without uninstalling the older one.
|
||||
|
||||
https://chocolatey.org/packages/qutebrowser[Chocolatey package]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* PackageManagement PowerShell module
|
||||
----
|
||||
PS C:\> Install-Package qutebrowser
|
||||
----
|
||||
* Chocolatey's client
|
||||
----
|
||||
C:\> choco install qutebrowser
|
||||
----
|
||||
|
||||
Manual install
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* Use the installer from http://www.python.org/downloads[python.org] to get
|
||||
Python 3 (be sure to install pip).
|
||||
* Install https://testrun.org/tox/latest/index.html[tox] via
|
||||
https://pip.pypa.io/en/latest/[pip]:
|
||||
|
||||
----
|
||||
$ pip install tox
|
||||
----
|
||||
|
||||
Then <<tox,install qutebrowser via tox>>.
|
||||
|
||||
On macOS
|
||||
--------
|
||||
|
||||
Prebuilt binary
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
The easiest way to install qutebrowser on macOS is to use the prebuilt `.app`
|
||||
files from the
|
||||
https://github.com/qutebrowser/qutebrowser/releases[release page].
|
||||
|
||||
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).
|
||||
|
||||
This binary is also available through the
|
||||
https://caskroom.github.io/[Homebrew Cask] package manager:
|
||||
|
||||
----
|
||||
$ brew cask install qutebrowser
|
||||
----
|
||||
|
||||
Manual Install
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Alternatively, you can install the dependencies via a package manager (like
|
||||
http://brew.sh/[Homebrew] or https://www.macports.org/[MacPorts]) and run
|
||||
qutebrowser from source.
|
||||
|
||||
==== Homebrew
|
||||
|
||||
----
|
||||
$ brew install qt5
|
||||
$ pip3 install qutebrowser
|
||||
----
|
||||
|
||||
Since the v1.0 release, qutebrowser uses QtWebEngine by default.
|
||||
|
||||
Homebrew's builds of Qt and PyQt don't come with QtWebKit (and `--with-qtwebkit`
|
||||
uses an old version of QtWebKit which qutebrowser doesn't support anymore). If
|
||||
you want QtWebKit support, you'll need to build an up-to-date QtWebKit
|
||||
https://github.com/annulen/webkit/wiki/Building-QtWebKit-on-OS-X[manually].
|
||||
|
||||
Packagers
|
||||
---------
|
||||
|
||||
There are example .desktop and icon files provided. They would go in the
|
||||
standard location for your distro (`/usr/share/applications` and
|
||||
`/usr/share/pixmaps` for example).
|
||||
|
||||
The normal `setup.py install` doesn't install these files, so you'll have to do
|
||||
it as part of the packaging process.
|
||||
|
||||
[[tox]]
|
||||
Installing qutebrowser with tox
|
||||
-------------------------------
|
||||
|
||||
Getting the repository
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
First of all, clone the repository using http://git-scm.org/[git] and switch
|
||||
into the repository folder:
|
||||
|
||||
----
|
||||
$ git clone https://github.com/qutebrowser/qutebrowser.git
|
||||
$ cd qutebrowser
|
||||
----
|
||||
|
||||
Installing depdendencies (including Qt)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Then run tox inside the qutebrowser repository to set up a
|
||||
https://docs.python.org/3/library/venv.html[virtual environment]:
|
||||
|
||||
----
|
||||
$ tox -e mkvenv-pypi
|
||||
----
|
||||
|
||||
This installs all needed Python dependencies in a `.venv` subfolder.
|
||||
|
||||
This comes with an up-to-date Qt/PyQt including QtWebEngine, but has a few
|
||||
caveats:
|
||||
|
||||
- Make sure your `python3` is Python 3.5 or newer, otherwise you'll get a "No
|
||||
matching distribution found" error. Note that qutebrowser itself also requires
|
||||
this.
|
||||
- It only works on 64-bit x86 systems, with other architectures you'll get the
|
||||
same error.
|
||||
- If your distribution uses OpenSSL 1.1 (like Debian Stretch or Archlinux),
|
||||
you'll need to set `LD_LIBRARY_PATH` to the OpenSSL 1.0 directory
|
||||
(`export LD_LIBRARY_PATH=/usr/lib/openssl-1.0` on Archlinux) before starting
|
||||
qutebrowser if you want SSL to work in certain downloads (e.g. for
|
||||
`:adblock-update` or `:download`).
|
||||
- It comes with a QtWebEngine compiled without proprietary codec support (such
|
||||
as h.264).
|
||||
|
||||
See the next section for an alternative.
|
||||
|
||||
Installing dependencies (system-wide Qt)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Alternatively, you can use `tox -e mkvenv` (without `-pypi`) to symlink your
|
||||
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.
|
||||
|
||||
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`):
|
||||
|
||||
----
|
||||
#!/bin/bash
|
||||
~/path/to/qutebrowser/.venv/bin/python3 -m qutebrowser "$@"
|
||||
----
|
||||
|
||||
Updating
|
||||
~~~~~~~~
|
||||
|
||||
When you updated your local copy of the code (e.g. by pulling the git repo, or
|
||||
extracting a new version), the virtualenv should automatically use the updated
|
||||
code. However, if dependencies got added, this won't be reflected in the
|
||||
virtualenv. Thus it's recommended to run the following command to recreate the
|
||||
virtualenv:
|
||||
|
||||
----
|
||||
$ tox -r -e mkvenv-pypi
|
||||
----
|
||||
196
doc/notes
196
doc/notes
@@ -1,196 +0,0 @@
|
||||
henk's thoughts
|
||||
===============
|
||||
|
||||
1. Power to the user! Protect privacy!
|
||||
Things the browser should only do with explicit consent from the user, if
|
||||
applicable the user should be able to choose which protocol/host/port triplets
|
||||
to white/blacklist:
|
||||
|
||||
- load/run executable code, like js, flash, java applets, ... (think NoScript)
|
||||
- requests to other domains, ports or using a different protocol than what the
|
||||
user requested (think RequestPolicy)
|
||||
- accept cookies
|
||||
- storing/saving/caching things, e.g. open tabs ("session"), cookies, page
|
||||
contents, browsing/download history, form data, ...
|
||||
- send referrer
|
||||
- disclose any (presence, type, version, settings, capabilities, etc.)
|
||||
information about OS, browser, installed fonts, plugins, addons, etc.
|
||||
|
||||
2. Be efficient!
|
||||
I tend to leave a lot of tabs open and nobody can deny that some websites
|
||||
simply suck, so the browser should, unless told otherwise by the user:
|
||||
|
||||
- load tabs only when needed
|
||||
- run code in tabs only when needed, i.e. when the tab is currently being
|
||||
used/viewed (background tabs doing some JS magic even when they are not being
|
||||
used can create a lot of unnecessary load on the machine)
|
||||
- finish requests to the domain the user requested (e.g. www.example.org)
|
||||
before doing any requests to other subdomains (e.g. images.example.org) and
|
||||
finish those before doing requests to thirdparty domains (e.g. example.com)
|
||||
|
||||
3. Be stable!
|
||||
- one site should not make the complete browser crash, only that site's tab
|
||||
|
||||
|
||||
Upstream Bugs
|
||||
=============
|
||||
|
||||
- Web inspector is blank unless .hide()/.show() is called.
|
||||
Asked on SO: http://stackoverflow.com/q/23499159/2085149
|
||||
TODO: Report to PyQt/Qt
|
||||
|
||||
- Report some other crashes
|
||||
|
||||
|
||||
/u/angelic_sedition's thoughts
|
||||
==============================
|
||||
|
||||
Well support for greasemonkey scripts and bookmarklets/js (which was mentioned
|
||||
in the arch forum post) would be a big addition. What I've usually missed when
|
||||
using other vim-like browsers is things that allow for different settings and
|
||||
key bindings for different contexts. With that implemented I think I could
|
||||
switch to a lightweight browser (and believe me, I'd like to) for the most part
|
||||
and only use firefox when I needed downthemall or something.
|
||||
|
||||
For example, I have different bindings based on tab position that are reloaded
|
||||
with a pentadactyl autocmd so that <space><homerow keys> will take me to tab
|
||||
1-10 if I'm in that range or 2-20 if I'm in that range. I have an autocmd that
|
||||
will run on completed downloads that passes the file path to a script that will
|
||||
open ranger in a floating window with that file cut (this is basically like
|
||||
using ranger to save files instead of the crappy gui popup).
|
||||
|
||||
I also have a few bindings based on tabgroups. Tabgroups are a firefox feature,
|
||||
but I find them very useful for sorting things by topic so that only the tabs
|
||||
I'm interested at the moment are visible.
|
||||
|
||||
Pentadactyl has a feature it calls groups. You can create a group that will
|
||||
activate for sites/urls that match a pattern with some regex support. This
|
||||
allows me, for example, to set up different (more convenient) bindings for
|
||||
zooming only on images. I'll never need use the equivalent of vim n (next text
|
||||
search match), so I can bind that to zoom. This allows setting up custom
|
||||
quickmarks/gotos using the same keys for different websites. For example, on
|
||||
reddit I have different g(some key) bindings to go to different subreddits.
|
||||
This can also be used to pass certain keys directly to the site (e.g. for use
|
||||
with RES). For sites that don't have modifiable bindings, I can use this with
|
||||
pentadactyl's feedkeys or xdotool to create my own custom bindings. I even have
|
||||
a binding that will call out to bash script with different arguments depending
|
||||
on the site to download an image or an image gallery depending on the site (in
|
||||
some cases passing the url to some cli program).
|
||||
|
||||
I've also noticed the lack of completion. For example, on "o" pentadactyl will
|
||||
show sites (e.g. from history) that can be completed. I think I've been spoiled
|
||||
by pentadactyl having completion for just about everything.
|
||||
|
||||
|
||||
suckless surf ML post
|
||||
=====================
|
||||
|
||||
From: Ben Woolley <tautolog_AT_gmail.com>
|
||||
Date: Wed, 7 Jan 2015 18:29:25 -0800
|
||||
|
||||
Hi all,
|
||||
|
||||
This patch is a bit of a beast for surf. It is intended to be applied after
|
||||
the disk cache patch. It breaks some internal interfaces, so it could
|
||||
conflict with other patches.
|
||||
|
||||
I have been wanting a browser to implement a complete same-origin policy,
|
||||
and have been investigating how to do this in various browsers for many
|
||||
months. When I saw how surf opened new windows in a separate process, and
|
||||
was so simple, I knew I could do it quickly. Over the last two weeks, I
|
||||
have been developing this implementation on surf.
|
||||
|
||||
The basic idea is to prevent browser-based tracking as you browse from site
|
||||
to site, or origin to origin. By "origin" domain, I mean the "first-party"
|
||||
domain, the domain normally in the location bar (of the typical browser
|
||||
interface). Each origin domain effectively gets its own browser profile,
|
||||
and a browser process only ever deals with one origin domain at a time.
|
||||
This isolates origins vertically, preventing cookies, disk cache, memory
|
||||
cache, and window.name vulnerabilities. Basically, all known
|
||||
vulnerabilities that google and Mozilla cite as counter-examples when they
|
||||
explain why they haven't disabled third-party cookies yet.
|
||||
|
||||
When you are on msnbc.com, the tracking pixels will be stored in a cookie
|
||||
file for msnbc.com. When you go to cnn.com, the tracking pixels will be
|
||||
stored in a cookie file for cnn.com. You will not be tracked between them.
|
||||
However, third-party cookies, and the caching of third party resources will
|
||||
still work, but they will be isolated between origin domains. Instead of
|
||||
blocking cookies and cache entries, they are "double-keyed", or *also*
|
||||
keyed by origin.
|
||||
|
||||
There is a unidirectional communication channel, however, from one origin
|
||||
to the next, through navigation from one origin to the next. That is, the
|
||||
query string is passed from one origin to the next, and may embed
|
||||
identifiers. One example is an affiliate link that identifies where the
|
||||
lead came from. I have implemented what I call "horizontal isolation", in
|
||||
the form of an "Origin Crossing Gate".
|
||||
|
||||
Whenever you follow a link to a new domain, or even are just redirected to
|
||||
a new domain, a new window/tab is opened, and passed the referring origin
|
||||
via -R. The page passed to -O, for example -O originprompt.html, is an HTML
|
||||
page that is loaded in the new origin's context. That page tells you the
|
||||
origin you were on, the new origin, and the full link, and you can decide
|
||||
to go just to the new origin, or go to the full URL, after reviewing it for
|
||||
tracking data.
|
||||
|
||||
Also, you may click links that store your trust of that relationship with
|
||||
various expiration times, the same way you would trust geolocation requests
|
||||
for a particular origin for a period of time. The database used is actually
|
||||
the new origin's cookie file. Since the origin prompt is loaded in the new
|
||||
origin's context, I can set a cookie on behalf of the new origin. The
|
||||
expiration time of the trust is the expiration time of the cookie. The
|
||||
cookie implementation in webkit automatically expires the trust as part of
|
||||
how cookies work. Each time you cross an origin, the origin crossing page
|
||||
checks the cookie to see if trust is still established. If so, it will use
|
||||
window.location.replace() to continue on automatically. The initial page
|
||||
renders blank until the trust is invalidated, in which case the content of
|
||||
the gate is made visible.
|
||||
|
||||
However, the new origin is technically able to mess with those cookies, so
|
||||
a website could set trust for an origin crossing. I have addressed that by
|
||||
hashing the key with a salt, and setting the real expiration time as the
|
||||
value, along with an HMAC to verify the contents of the value. If the
|
||||
cookie is messed with in any way, the trust will be disabled, and the
|
||||
prompt will appear again. So it has a fail-safe function.
|
||||
|
||||
I know it seems a bit convoluted, but it just started out as a nice little
|
||||
rabbit hole, and I just wanted to get something workable. At first I
|
||||
thought using the cookie expiration time was convenient, but then when I
|
||||
realized that I needed to protect the cookie, things got a bit hairy. But
|
||||
it works.
|
||||
|
||||
Each profile is, by default, stored in ~/.surf/origins/$origin/
|
||||
The interesting side effect is that if there is a problem where a website
|
||||
relies on the cross-site cookie vulnerability to make a connection, you can
|
||||
simply make a symbolic link from one origin folder to another, and they
|
||||
will share the same profile. And if you want to delete cookies and/or cache
|
||||
for a particular origin, you just rm -rf the origin's profile folder, and
|
||||
don't have to interfere with your other sites that are working just fine.
|
||||
|
||||
One thing I don't handle are cross-origins POSTs. They just end up as GET
|
||||
requests right now. I intend to do something about that, but I haven't
|
||||
figured that out yet.
|
||||
|
||||
I have only been using this functionality for a few days myself, so I have
|
||||
absolutely no feedback yet. I wanted to provide the first implementation of
|
||||
the management of identity as a system resource the same way that things
|
||||
like geolocation, camera, and microphone resources are managed in browsers
|
||||
and mobile apps.
|
||||
|
||||
Currently, Mozilla and Tor have are working on third-party tracking issues
|
||||
in Firefox.
|
||||
https://blog.mozilla.org/privacy/2014/11/10/introducing-polaris-privacy-initiative-to-accelerate-user-focused-privacy-online/
|
||||
|
||||
Up to this point, Tor has provided a patch that double-keys cookies with
|
||||
the origin domain, but no other progress is visible. I have seen no
|
||||
discussion of how horizontal isolation is supposed to happen, and I wanted
|
||||
to show people that it can be done, and this is one way it can be done, and
|
||||
to compel the other browser makers to catch up, and hopefully the community
|
||||
can work toward a standard *without* the tracking loopholes, by showing
|
||||
people what a *complete* solution looks like.
|
||||
|
||||
Thank you,
|
||||
|
||||
Ben Woolley
|
||||
|
||||
Patch: http://lists.suckless.org/dev/att-25070/0005-same-origin-policy.patch
|
||||
@@ -31,7 +31,7 @@ image:http://qutebrowser.org/img/cheatsheet-small.png["qutebrowser key binding c
|
||||
* Run `:adblock-update` to download adblock lists and activate adblocking.
|
||||
* If you just cloned the repository, you'll need to run
|
||||
`scripts/asciidoc2html.py` to generate the documentation.
|
||||
* Go to the link:qute://settings[settings page] to set up qutebrowser the way you want it. (Currently not available with the QtWebEngine backend and on the OS X build - use the `:set` command instead)
|
||||
* Go to the link:qute://settings[settings page] to set up qutebrowser the way you want it.
|
||||
* Subscribe to
|
||||
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[the mailinglist] or
|
||||
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[the announce-only mailinglist].
|
||||
|
||||
@@ -17,7 +17,7 @@ qutebrowser - a keyboard-driven, vim-like browser based on PyQt5.
|
||||
|
||||
== DESCRIPTION
|
||||
qutebrowser is a keyboard-focused browser with a minimal GUI. It's based
|
||||
on Python, PyQt5 and QtWebKit and free software, licensed under the GPL.
|
||||
on Python and Qt5 and is free software, licensed under the GPL.
|
||||
|
||||
It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl.
|
||||
|
||||
@@ -44,7 +44,7 @@ show it.
|
||||
*-V*, *--version*::
|
||||
Show version and quit.
|
||||
|
||||
*-s* 'SECTION' 'OPTION' 'VALUE', *--set* 'SECTION' 'OPTION' 'VALUE'::
|
||||
*-s* 'OPTION' 'VALUE', *--set* 'OPTION' 'VALUE'::
|
||||
Set a temporary setting for this session.
|
||||
|
||||
*-r* 'SESSION', *--restore* 'SESSION'::
|
||||
@@ -57,7 +57,7 @@ show it.
|
||||
How URLs should be opened if there is already a qutebrowser instance running.
|
||||
|
||||
*--backend* '{webkit,webengine}'::
|
||||
Which backend to use (webengine backend is EXPERIMENTAL!).
|
||||
Which backend to use.
|
||||
|
||||
*--enable-webengine-inspector*::
|
||||
Enable the web inspector for QtWebEngine. Note that this is a SECURITY RISK and you should not visit untrusted websites with the inspector turned on. See https://bugreports.qt.io/browse/QTBUG-50725 for more details.
|
||||
@@ -84,12 +84,6 @@ show it.
|
||||
*--force-color*::
|
||||
Force colored logging
|
||||
|
||||
*--harfbuzz* '{old,new,system,auto}'::
|
||||
HarfBuzz engine version to use. Default: auto.
|
||||
|
||||
*--relaxed-config*::
|
||||
Silently remove unknown config options.
|
||||
|
||||
*--nowindow*::
|
||||
Don't show the main window.
|
||||
|
||||
@@ -111,9 +105,9 @@ show it.
|
||||
|
||||
== FILES
|
||||
|
||||
- '~/.config/qutebrowser/qutebrowser.conf': Main config file.
|
||||
- '~/.config/qutebrowser/config.py': Configuration file.
|
||||
- '~/.config/qutebrowser/autoconfig.yml': Configuration done via the GUI.
|
||||
- '~/.config/qutebrowser/quickmarks': Saved quickmarks.
|
||||
- '~/.config/qutebrowser/keys.conf': Defined key bindings.
|
||||
- '~/.local/share/qutebrowser/': Various state information.
|
||||
- '~/.cache/qutebrowser/': Temporary data.
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ Sending commands
|
||||
Normal qutebrowser commands can be written to `$QUTE_FIFO` and will be
|
||||
executed.
|
||||
|
||||
On Unix/OS X, this is a named pipe and commands written to it will get executed
|
||||
On Unix/macOS, this is a named pipe and commands written to it will get executed
|
||||
immediately.
|
||||
|
||||
On Windows, this is a regular file, and the commands in it will be executed as
|
||||
@@ -78,3 +78,11 @@ Opening the currently selected word on http://www.dict.cc/[dict.cc]:
|
||||
|
||||
echo "open -t http://www.dict.cc/?s=$QUTE_SELECTED_TEXT" >> "$QUTE_FIFO"
|
||||
----
|
||||
|
||||
Libraries
|
||||
---------
|
||||
|
||||
Some third-party libraries are available to make writing userscripts easier:
|
||||
|
||||
- Python: https://github.com/hiway/python-qutescript[python-qutescript]
|
||||
- Node.js: https://www.npmjs.com/package/qutejs[qutejs]
|
||||
|
||||
@@ -23,7 +23,7 @@ SetCompressor /solid lzma
|
||||
!define MUI_ICON "../icons/qutebrowser.ico"
|
||||
!define MUI_UNICON "../icons/qutebrowser.ico"
|
||||
|
||||
!insertmacro MUI_PAGE_LICENSE "..\COPYING"
|
||||
!insertmacro MUI_PAGE_LICENSE "..\LICENSE"
|
||||
!insertmacro MUI_PAGE_DIRECTORY
|
||||
!insertmacro MUI_PAGE_INSTFILES
|
||||
!insertmacro MUI_UNPAGE_CONFIRM
|
||||
|
||||
@@ -15,7 +15,8 @@ def get_data_files():
|
||||
('../qutebrowser/img', 'img'),
|
||||
('../qutebrowser/javascript', 'javascript'),
|
||||
('../qutebrowser/html/doc', 'html/doc'),
|
||||
('../qutebrowser/git-commit-id', '')
|
||||
('../qutebrowser/git-commit-id', ''),
|
||||
('../qutebrowser/config/configdata.yml', 'config'),
|
||||
]
|
||||
|
||||
# if os.path.exists(os.path.join('qutebrowser', '3rdparty', 'pdfjs')):
|
||||
@@ -41,7 +42,7 @@ a = Analysis(['../qutebrowser/__main__.py'],
|
||||
pathex=['misc'],
|
||||
binaries=None,
|
||||
datas=get_data_files(),
|
||||
hiddenimports=['PyQt5.QtOpenGL'],
|
||||
hiddenimports=['PyQt5.QtOpenGL', 'PyQt5._QOpenGLFunctions_2_0'],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=['tkinter'],
|
||||
@@ -70,9 +71,5 @@ coll = COLLECT(exe,
|
||||
app = BUNDLE(coll,
|
||||
name='qutebrowser.app',
|
||||
icon=icon,
|
||||
info_plist={
|
||||
'NSHighResolutionCapable': 'True',
|
||||
'NSSupportsAutomaticGraphicsSwitching': 'True',
|
||||
},
|
||||
# https://github.com/pyinstaller/pyinstaller/blob/b78bfe530cdc2904f65ce098bdf2de08c9037abb/PyInstaller/hooks/hook-PyQt5.QtWebEngineWidgets.py#L24
|
||||
bundle_identifier='org.qt-project.Qt.QtWebEngineCore')
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
certifi==2017.4.17
|
||||
certifi==2017.7.27.1
|
||||
chardet==3.0.4
|
||||
codecov==2.0.9
|
||||
coverage==4.4.1
|
||||
idna==2.5
|
||||
requests==2.18.1
|
||||
urllib3==1.21.1
|
||||
idna==2.6
|
||||
requests==2.18.4
|
||||
urllib3==1.22
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
flake8==2.6.2 # rq.filter: < 3.0.0
|
||||
flake8-copyright==0.2.0
|
||||
flake8-debugger==1.4.0 # rq.filter: != 2.0.0
|
||||
flake8-deprecated==1.2
|
||||
flake8-deprecated==1.2.1
|
||||
flake8-docstrings==1.0.3 # rq.filter: < 1.1.0
|
||||
flake8-future-import==0.4.3
|
||||
flake8-mock==0.3
|
||||
@@ -11,13 +11,13 @@ flake8-pep3101==1.0 # rq.filter: < 1.1
|
||||
flake8-polyfill==1.0.1
|
||||
flake8-putty==0.4.0
|
||||
flake8-string-format==0.2.3
|
||||
flake8-tidy-imports==1.0.6
|
||||
flake8-tidy-imports==1.1.0
|
||||
flake8-tuple==0.2.13
|
||||
mccabe==0.6.1
|
||||
packaging==16.8
|
||||
pep8-naming==0.4.1
|
||||
pycodestyle==2.3.1
|
||||
pydocstyle==1.1.1 # rq.filter: < 2.0.0
|
||||
pyflakes==1.5.0
|
||||
pyflakes==1.6.0
|
||||
pyparsing==2.2.0
|
||||
six==1.10.0
|
||||
six==1.11.0
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
appdirs==1.4.3
|
||||
packaging==16.8
|
||||
pyparsing==2.2.0
|
||||
setuptools==36.0.1
|
||||
six==1.10.0
|
||||
wheel==0.29.0
|
||||
setuptools==36.5.0
|
||||
six==1.11.0
|
||||
wheel==0.30.0
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
-e git+https://github.com/xoviat/pyinstaller.git@qtweb#egg=PyInstaller
|
||||
altgraph==0.14
|
||||
future==0.16.0
|
||||
macholib==1.8
|
||||
pefile==2017.9.3
|
||||
-e git+https://github.com/pyinstaller/pyinstaller.git@develop#egg=PyInstaller
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-e git+https://github.com/xoviat/pyinstaller.git@qtweb#egg=PyInstaller
|
||||
-e git+https://github.com/pyinstaller/pyinstaller.git@develop#egg=PyInstaller
|
||||
|
||||
# remove @commit-id for scm installs
|
||||
#@ replace: @.*# @qtweb#
|
||||
#@ replace: @.*# @develop#
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
-e git+https://github.com/PyCQA/astroid.git#egg=astroid
|
||||
certifi==2017.4.17
|
||||
certifi==2017.7.27.1
|
||||
chardet==3.0.4
|
||||
github3.py==0.9.6
|
||||
idna==2.5
|
||||
idna==2.6
|
||||
isort==4.2.15
|
||||
lazy-object-proxy==1.3.1
|
||||
mccabe==0.6.1
|
||||
-e git+https://github.com/PyCQA/pylint.git#egg=pylint
|
||||
./scripts/dev/pylint_checkers
|
||||
requests==2.18.1
|
||||
six==1.10.0
|
||||
requests==2.18.4
|
||||
six==1.11.0
|
||||
uritemplate==3.0.0
|
||||
uritemplate.py==3.0.2
|
||||
urllib3==1.21.1
|
||||
wrapt==1.10.10
|
||||
urllib3==1.22
|
||||
wrapt==1.10.11
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
astroid==1.5.3
|
||||
certifi==2017.4.17
|
||||
certifi==2017.7.27.1
|
||||
chardet==3.0.4
|
||||
github3.py==0.9.6
|
||||
idna==2.5
|
||||
idna==2.6
|
||||
isort==4.2.15
|
||||
lazy-object-proxy==1.3.1
|
||||
mccabe==0.6.1
|
||||
pylint==1.7.1
|
||||
pylint==1.7.4
|
||||
./scripts/dev/pylint_checkers
|
||||
requests==2.18.1
|
||||
six==1.10.0
|
||||
requests==2.18.4
|
||||
six==1.11.0
|
||||
uritemplate==3.0.0
|
||||
uritemplate.py==3.0.2
|
||||
urllib3==1.21.1
|
||||
wrapt==1.10.10
|
||||
urllib3==1.22
|
||||
wrapt==1.10.11
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.8.2
|
||||
sip==4.19.2
|
||||
PyQt5==5.9
|
||||
sip==4.19.3
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
docutils==0.13.1
|
||||
docutils==0.14
|
||||
pyroma==2.2
|
||||
|
||||
@@ -4,3 +4,4 @@ pyPEG2
|
||||
PyYAML
|
||||
colorama
|
||||
cssutils
|
||||
attrs
|
||||
|
||||
@@ -4,7 +4,6 @@ hg+https://bitbucket.org/ned/coveragepy
|
||||
git+https://github.com/micheles/decorator.git
|
||||
git+https://github.com/pallets/flask.git
|
||||
git+https://github.com/miracle2k/python-glob2.git
|
||||
git+https://github.com/Runscope/httpbin.git
|
||||
git+https://github.com/HypothesisWorks/hypothesis-python.git
|
||||
git+https://github.com/pallets/itsdangerous.git
|
||||
git+https://bitbucket.org/zzzeek/mako.git
|
||||
@@ -41,6 +40,7 @@ git+https://github.com/pallets/jinja.git
|
||||
git+https://github.com/pallets/markupsafe.git
|
||||
hg+http://bitbucket.org/birkenfeld/pygments-main
|
||||
hg+https://bitbucket.org/fdik/pypeg
|
||||
git+https://github.com/python-attrs/attrs.git
|
||||
|
||||
# Fails to build:
|
||||
# gcc: error: ext/_yaml.c: No such file or directory
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
attrs==17.2.0
|
||||
beautifulsoup4==4.6.0
|
||||
cheroot==5.5.2
|
||||
cheroot==5.8.3
|
||||
click==6.7
|
||||
# colorama==0.3.9
|
||||
coverage==4.4.1
|
||||
decorator==4.0.11
|
||||
EasyProcess==0.2.3
|
||||
fields==5.0.0
|
||||
Flask==0.12.2
|
||||
glob2==0.5
|
||||
httpbin==0.5.0
|
||||
hunter==1.4.1
|
||||
hypothesis==3.11.6
|
||||
glob2==0.6
|
||||
hunter==2.0.1
|
||||
hypothesis==3.32.0
|
||||
itsdangerous==0.24
|
||||
# Jinja2==2.9.6
|
||||
Mako==1.0.6
|
||||
Mako==1.0.7
|
||||
# MarkupSafe==1.0
|
||||
parse==1.8.2
|
||||
parse-type==0.3.4
|
||||
parse-type==0.4.2
|
||||
py==1.4.34
|
||||
pytest==3.1.2
|
||||
py-cpuinfo==3.3.0
|
||||
pytest==3.2.3
|
||||
pytest-bdd==2.18.2
|
||||
pytest-benchmark==3.0.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.0
|
||||
pytest-qt==2.1.0
|
||||
pytest-mock==1.6.3
|
||||
pytest-qt==2.2.1
|
||||
pytest-repeat==0.4.1
|
||||
pytest-rerunfailures==2.1.0
|
||||
pytest-rerunfailures==3.1
|
||||
pytest-travis-fold==1.2.0
|
||||
pytest-xvfb==1.0.0
|
||||
PyVirtualDisplay==0.2.1
|
||||
six==1.10.0
|
||||
vulture==0.14
|
||||
six==1.11.0
|
||||
vulture==0.26
|
||||
Werkzeug==0.12.2
|
||||
|
||||
@@ -3,7 +3,6 @@ cheroot
|
||||
coverage
|
||||
Flask
|
||||
hunter
|
||||
httpbin
|
||||
hypothesis
|
||||
pytest
|
||||
pytest-bdd
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
pluggy==0.4.0
|
||||
pluggy==0.5.2
|
||||
py==1.4.34
|
||||
tox==2.7.0
|
||||
tox==2.9.1
|
||||
virtualenv==15.1.0
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
vulture==0.14
|
||||
vulture==0.26
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
# If run from qutebrowser as a userscript, it runs :open on the URL
|
||||
# If not, it opens a new qutebrowser window at the URL
|
||||
#
|
||||
# Ideal for use with tabs-are-windows. Set a hotkey to launch this script, then:
|
||||
# Ideal for use with tabs_are_windows. Set a hotkey to launch this script, then:
|
||||
# :bind o spawn --userscript dmenu_qutebrowser
|
||||
#
|
||||
# Use the hotkey to open in new tab/window, press 'o' to open URL in current tab/window
|
||||
|
||||
47
misc/userscripts/format_json
Executable file
47
misc/userscripts/format_json
Executable file
@@ -0,0 +1,47 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Behavior:
|
||||
# Userscript for qutebrowser which will take the raw JSON text of the current
|
||||
# page, format it using `jq`, will add syntax highlighting using `pygments`,
|
||||
# and open the syntax highlighted pretty printed html in a new tab. If the file
|
||||
# is larger than 10MB then this script will only indent the json and will forego
|
||||
# syntax highlighting using pygments.
|
||||
#
|
||||
# In order to use this script, just start it using `spawn --userscript` from
|
||||
# qutebrowser. I recommend using an alias, e.g. put this in the
|
||||
# [alias]-section of qutebrowser.conf:
|
||||
#
|
||||
# json = spawn --userscript /path/to/json_format
|
||||
#
|
||||
# Note that the color style defaults to monokai, but a different pygments style
|
||||
# can be passed as the first parameter to the script. A full list of the pygments
|
||||
# styles can be found at: https://help.farbox.com/pygments.html
|
||||
#
|
||||
# Bryan Gilbert, 2017
|
||||
|
||||
# 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
|
||||
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"
|
||||
@@ -12,7 +12,7 @@
|
||||
# - rofi (in a recent version)
|
||||
# - xdg-open and xdg-mime
|
||||
# - You should configure qutebrowser to download files to a single directory
|
||||
# - It comes in handy if you enable remove-finished-downloads. If you want to
|
||||
# - It comes in handy if you enable downloads.remove_finished. If you want to
|
||||
# see the recent downloads, just press "sd".
|
||||
#
|
||||
# Thorsten Wißmann, 2015 (thorsten` on freenode)
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
# Opens all links to feeds defined in the head of a site
|
||||
#
|
||||
# Ideal for use with tabs-are-windows. Set a hotkey to launch this script, then:
|
||||
# Ideal for use with tabs_are_windows. Set a hotkey to launch this script, then:
|
||||
# :bind gF spawn --userscript openfeeds
|
||||
#
|
||||
# Use the hotkey to open the feeds in new tab/window, press 'gF' to open
|
||||
|
||||
@@ -189,7 +189,7 @@ choose_entry_rofi() {
|
||||
}
|
||||
|
||||
choose_entry_zenity() {
|
||||
MENU_COMMAND=( zenity --list --title "Qutebrowser password fill"
|
||||
MENU_COMMAND=( zenity --list --title "qutebrowser password fill"
|
||||
--text "Pick the password entry:"
|
||||
--column "Name" )
|
||||
choose_entry_menu || true
|
||||
@@ -199,7 +199,7 @@ choose_entry_zenity_radio() {
|
||||
zenity_helper() {
|
||||
awk '{ print $0 ; print $0 }' \
|
||||
| zenity --list --radiolist \
|
||||
--title "Qutebrowser password fill" \
|
||||
--title "qutebrowser password fill" \
|
||||
--text "Pick the password entry:" \
|
||||
--column " " --column "Name"
|
||||
}
|
||||
@@ -327,6 +327,17 @@ open_entry "$file"
|
||||
|
||||
js() {
|
||||
cat <<EOF
|
||||
function isVisible(elem) {
|
||||
var style = elem.ownerDocument.defaultView.getComputedStyle(elem, null);
|
||||
|
||||
if (style.getPropertyValue("visibility") !== "visible" ||
|
||||
style.getPropertyValue("display") === "none" ||
|
||||
style.getPropertyValue("opacity") === "0") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return elem.offsetWidth > 0 && elem.offsetHeight > 0;
|
||||
};
|
||||
function hasPasswordField(form) {
|
||||
var inputs = form.getElementsByTagName("input");
|
||||
for (var j = 0; j < inputs.length; j++) {
|
||||
@@ -341,7 +352,7 @@ cat <<EOF
|
||||
var inputs = form.getElementsByTagName("input");
|
||||
for (var j = 0; j < inputs.length; j++) {
|
||||
var input = inputs[j];
|
||||
if (input.type == "text" || input.type == "email") {
|
||||
if (isVisible(input) && (input.type == "text" || input.type == "email")) {
|
||||
input.focus();
|
||||
input.value = "$(javascript_escape "${username}")";
|
||||
input.blur();
|
||||
|
||||
@@ -1,21 +1,33 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Executes python-readability on current page and opens the summary as new tab.
|
||||
#
|
||||
# Depends on the python-readability package, or its fork:
|
||||
#
|
||||
# - https://github.com/buriy/python-readability
|
||||
# - https://github.com/bookieio/breadability
|
||||
#
|
||||
# Usage:
|
||||
# :spawn --userscript readability
|
||||
#
|
||||
from __future__ import absolute_import
|
||||
import codecs, os
|
||||
from readability.readability import Document
|
||||
|
||||
tmpfile=os.path.expanduser('~/.local/share/qutebrowser/userscripts/readability.html')
|
||||
if not os.path.exists(os.path.dirname(tmpfile)):
|
||||
os.makedirs(os.path.dirname(tmpfile))
|
||||
|
||||
with codecs.open(os.environ['QUTE_HTML'], 'r', 'utf-8') as source:
|
||||
doc = Document(source.read())
|
||||
content = doc.summary().replace('<html>', '<html><head><title>%s</title></head>' % doc.title())
|
||||
data = source.read()
|
||||
|
||||
try:
|
||||
from breadability.readable import Article as reader
|
||||
doc = reader(data)
|
||||
content = doc.readable
|
||||
except ImportError:
|
||||
from readability import Document
|
||||
doc = Document(data)
|
||||
content = doc.summary().replace('<html>', '<html><head><title>%s</title></head>' % doc.title())
|
||||
|
||||
with codecs.open(tmpfile, 'w', 'utf-8') as target:
|
||||
target.write('<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Adds DuckDuckGo bang as searchengine.
|
||||
#
|
||||
@@ -8,14 +8,21 @@
|
||||
# Example:
|
||||
# :spawn --userscript ripbang amazon maps
|
||||
#
|
||||
import os, re, requests, sys, urllib
|
||||
|
||||
from __future__ import print_function
|
||||
import os, re, requests, sys
|
||||
|
||||
try:
|
||||
from urllib.parse import unquote
|
||||
except ImportError:
|
||||
from urllib import unquote
|
||||
|
||||
for argument in sys.argv[1:]:
|
||||
bang = '!' + argument
|
||||
r = requests.get('https://duckduckgo.com/',
|
||||
params={'q': bang + ' SEARCHTEXT'})
|
||||
|
||||
searchengine = urllib.unquote(re.search("url=[^']+", r.text).group(0))
|
||||
searchengine = unquote(re.search("url=[^']+", r.text).group(0))
|
||||
searchengine = searchengine.replace('url=', '')
|
||||
searchengine = searchengine.replace('/l/?kh=-1&uddg=', '')
|
||||
searchengine = searchengine.replace('SEARCHTEXT', '{}')
|
||||
@@ -24,4 +31,4 @@ for argument in sys.argv[1:]:
|
||||
with open(os.environ['QUTE_FIFO'], 'w') as fifo:
|
||||
fifo.write('set searchengines %s %s' % (bang, searchengine))
|
||||
else:
|
||||
print '%s %s' % (bang, searchengine)
|
||||
print('%s %s' % (bang, searchengine))
|
||||
|
||||
@@ -49,7 +49,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 }
|
||||
MPV_FLAGS=${MPV_FLAGS:- --force-window --no-terminal --keep-open=yes --ytdl --ytdl-raw-options=yes-playlist=}
|
||||
video_command=( "$MPV_COMMAND" $MPV_FLAGS )
|
||||
|
||||
js() {
|
||||
@@ -140,4 +140,4 @@ printjs() {
|
||||
echo "jseval -q $(printjs)" >> "$QUTE_FIFO"
|
||||
|
||||
msg info "Opening $QUTE_URL with mpv"
|
||||
"${video_command[@]}" "$QUTE_URL"
|
||||
"${video_command[@]}" "$@" "$QUTE_URL"
|
||||
|
||||
19
pytest.ini
19
pytest.ini
@@ -1,12 +1,13 @@
|
||||
[pytest]
|
||||
addopts = --strict -rfEw --faulthandler-timeout=70 --instafail --pythonwarnings error
|
||||
addopts = --strict -rfEw --faulthandler-timeout=90 --instafail --pythonwarnings error --benchmark-columns=Min,Max,Median
|
||||
testpaths = tests
|
||||
markers =
|
||||
gui: Tests using the GUI (e.g. spawning widgets)
|
||||
posix: Tests which only can run on a POSIX OS.
|
||||
windows: Tests which only can run on Windows.
|
||||
linux: Tests which only can run on Linux.
|
||||
osx: Tests which only can run on OS X.
|
||||
not_osx: Tests which can not run on OS X.
|
||||
mac: Tests which only can run on macOS.
|
||||
not_mac: Tests which can not run on macOS.
|
||||
not_frozen: Tests which can't be run if sys.frozen is True.
|
||||
no_xvfb: Tests which can't be run with Xvfb.
|
||||
frozen: Tests which can only be run if sys.frozen is True.
|
||||
@@ -14,17 +15,17 @@ markers =
|
||||
end2end: End to end tests which run qutebrowser as subprocess
|
||||
xfail_norun: xfail the test with out running it
|
||||
ci: Tests which should only run on CI.
|
||||
no_ci: Tests which should not run on CI.
|
||||
qtwebengine_todo: Features still missing with QtWebEngine
|
||||
qtwebengine_skip: Tests not applicable with QtWebEngine
|
||||
qtwebkit_skip: Tests not applicable with QtWebKit
|
||||
qtwebkit_ng_xfail: Tests failing with QtWebKit-NG
|
||||
qtwebkit_ng_skip: Tests skipped with QtWebKit-NG
|
||||
qtwebengine_flaky: Tests which are flaky (and currently skipped) with QtWebEngine
|
||||
qtwebengine_osx_xfail: Tests which fail on OS X with QtWebEngine
|
||||
qtwebengine_mac_xfail: Tests which fail on macOS with QtWebEngine
|
||||
js_prompt: Tests needing to display a javascript prompt
|
||||
this: Used to mark tests during development
|
||||
no_invalid_lines: Don't fail on unparseable lines in end2end tests
|
||||
issue2478: Tests which are broken on Windows with QtWebEngine, https://github.com/qutebrowser/qutebrowser/issues/2478
|
||||
fake_os: Fake utils.is_* to a fake operating system
|
||||
qt_log_level_fail = WARNING
|
||||
qt_log_ignore =
|
||||
^SpellCheck: .*
|
||||
@@ -43,15 +44,15 @@ qt_log_ignore =
|
||||
^QWaitCondition: Destroyed while threads are still waiting
|
||||
^QXcbXSettings::QXcbXSettings\(QXcbScreen\*\) Failed to get selection owner for XSETTINGS_S atom
|
||||
^QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to .*
|
||||
^QGeoclueMaster error creating GeoclueMasterClient\.
|
||||
^Geoclue error: Process org\.freedesktop\.Geoclue\.Master exited with status 127
|
||||
^QObject::connect: Cannot connect \(null\)::stateChanged\(QNetworkSession::State\) to QNetworkReplyHttpImpl::_q_networkSessionStateChanged\(QNetworkSession::State\)
|
||||
^QXcbClipboard: Cannot transfer data, no data available
|
||||
^load glyph failed
|
||||
^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 SSLv[23]_(client|server)_method
|
||||
^QSslSocket: cannot resolve *
|
||||
^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"
|
||||
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__ = (0, 10, 1)
|
||||
__version_info__ = (1, 0, 2)
|
||||
__version__ = '.'.join(str(e) for e in __version_info__)
|
||||
__description__ = "A keyboard-driven, vim-like browser based on PyQt5."
|
||||
|
||||
|
||||
@@ -17,12 +17,29 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Initialization of qutebrowser and application-wide things."""
|
||||
"""Initialization of qutebrowser and application-wide things.
|
||||
|
||||
The run() function will get called once early initialization (in
|
||||
qutebrowser.py/earlyinit.py) is done. See the qutebrowser.py docstring for
|
||||
details about early initialization.
|
||||
|
||||
As we need to access the config before the QApplication is created, we
|
||||
initialize everything the config needs before the QApplication is created, and
|
||||
then leave it in a partially initialized state (no saving, no config errors
|
||||
shown yet).
|
||||
|
||||
We then set up the QApplication object and initialize a few more low-level
|
||||
things.
|
||||
|
||||
After that, init() and _init_modules() take over and initialize the rest.
|
||||
|
||||
After all initialization is done, the qt_mainloop() function is called, which
|
||||
blocks and spins the Qt mainloop.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import configparser
|
||||
import functools
|
||||
import json
|
||||
import shutil
|
||||
@@ -42,9 +59,10 @@ except ImportError:
|
||||
|
||||
import qutebrowser
|
||||
import qutebrowser.resources
|
||||
from qutebrowser.completion.models import instances as completionmodels
|
||||
from qutebrowser.completion import completiondelegate
|
||||
from qutebrowser.completion.models import miscmodels
|
||||
from qutebrowser.commands import cmdutils, runners, cmdexc
|
||||
from qutebrowser.config import style, config, websettings, configexc
|
||||
from qutebrowser.config import config, websettings, configfiles, configinit
|
||||
from qutebrowser.browser import (urlmarks, adblock, history, browsertab,
|
||||
downloads)
|
||||
from qutebrowser.browser.network import proxy
|
||||
@@ -53,11 +71,15 @@ from qutebrowser.browser.webkit.network import networkmanager
|
||||
from qutebrowser.keyinput import macros
|
||||
from qutebrowser.mainwindow import mainwindow, prompt
|
||||
from qutebrowser.misc import (readline, ipc, savemanager, sessions,
|
||||
crashsignal, earlyinit, objects)
|
||||
from qutebrowser.misc import utilcmds # pylint: disable=unused-import
|
||||
from qutebrowser.utils import (log, version, message, utils, qtutils, urlutils,
|
||||
objreg, usertypes, standarddir, error, debug)
|
||||
# We import utilcmds to run the cmdutils.register decorators.
|
||||
crashsignal, earlyinit, sql, cmdhistory,
|
||||
backendproblem)
|
||||
from qutebrowser.utils import (log, version, message, utils, urlutils, objreg,
|
||||
usertypes, standarddir, error)
|
||||
# pylint: disable=unused-import
|
||||
# We import those to run the cmdutils.register decorators.
|
||||
from qutebrowser.mainwindow.statusbar import command
|
||||
from qutebrowser.misc import utilcmds
|
||||
# pylint: enable=unused-import
|
||||
|
||||
|
||||
qApp = None
|
||||
@@ -71,6 +93,12 @@ def run(args):
|
||||
quitter = Quitter(args)
|
||||
objreg.register('quitter', quitter)
|
||||
|
||||
log.init.debug("Initializing directories...")
|
||||
standarddir.init(args)
|
||||
|
||||
log.init.debug("Initializing config...")
|
||||
configinit.early_init(args)
|
||||
|
||||
global qApp
|
||||
qApp = Application(args)
|
||||
qApp.setOrganizationName("qutebrowser")
|
||||
@@ -78,9 +106,6 @@ def run(args):
|
||||
qApp.setApplicationVersion(qutebrowser.__version__)
|
||||
qApp.lastWindowClosed.connect(quitter.on_last_window_closed)
|
||||
|
||||
log.init.debug("Initializing directories...")
|
||||
standarddir.init(args)
|
||||
|
||||
if args.version:
|
||||
print(version.version())
|
||||
sys.exit(usertypes.Exit.ok)
|
||||
@@ -147,8 +172,6 @@ def init(args, crash_handler):
|
||||
objreg.register('event-filter', event_filter)
|
||||
|
||||
log.init.debug("Connecting signals...")
|
||||
config_obj = objreg.get('config')
|
||||
config_obj.style_changed.connect(style.get_stylesheet.cache_clear)
|
||||
qApp.focusChanged.connect(on_focus_changed)
|
||||
|
||||
_process_args(args)
|
||||
@@ -157,7 +180,7 @@ def init(args, crash_handler):
|
||||
QDesktopServices.setUrlHandler('https', open_desktopservices_url)
|
||||
QDesktopServices.setUrlHandler('qute', open_desktopservices_url)
|
||||
|
||||
QTimer.singleShot(10, functools.partial(_init_late_modules, args))
|
||||
objreg.get('web-history').import_txt()
|
||||
|
||||
log.init.debug("Init done!")
|
||||
crash_handler.raise_crashdlg()
|
||||
@@ -183,13 +206,6 @@ def _init_icon():
|
||||
|
||||
def _process_args(args):
|
||||
"""Open startpage etc. and process commandline args."""
|
||||
config_obj = objreg.get('config')
|
||||
for sect, opt, val in args.temp_settings:
|
||||
try:
|
||||
config_obj.set('temp', sect, opt, val)
|
||||
except (configexc.Error, configparser.Error) as e:
|
||||
message.error("set: {} - {}".format(e.__class__.__name__, e))
|
||||
|
||||
if not args.override_restore:
|
||||
_load_session(args.session)
|
||||
session_manager = objreg.get('session-manager')
|
||||
@@ -214,13 +230,12 @@ def _load_session(name):
|
||||
Args:
|
||||
name: The name of the session to load, or None to read state file.
|
||||
"""
|
||||
state_config = objreg.get('state-config')
|
||||
session_manager = objreg.get('session-manager')
|
||||
if name is None and session_manager.exists('_autosave'):
|
||||
name = '_autosave'
|
||||
elif name is None:
|
||||
try:
|
||||
name = state_config['general']['session']
|
||||
name = configfiles.state['general']['session']
|
||||
except KeyError:
|
||||
# No session given as argument and none in the session file ->
|
||||
# start without loading a session
|
||||
@@ -233,7 +248,7 @@ def _load_session(name):
|
||||
except sessions.SessionError as e:
|
||||
message.error("Failed to load session {}: {}".format(name, e))
|
||||
try:
|
||||
del state_config['general']['session']
|
||||
del configfiles.state['general']['session']
|
||||
except KeyError:
|
||||
pass
|
||||
# If this was a _restart session, delete it.
|
||||
@@ -265,7 +280,7 @@ def process_pos_args(args, via_ipc=False, cwd=None, target_arg=None):
|
||||
win_id = mainwindow.get_window(via_ipc, force_tab=True)
|
||||
log.init.debug("Startup cmd {!r}".format(cmd))
|
||||
commandrunner = runners.CommandRunner(win_id)
|
||||
commandrunner.run_safely_init(cmd[1:])
|
||||
commandrunner.run_safely(cmd[1:])
|
||||
elif not cmd:
|
||||
log.init.debug("Empty argument")
|
||||
win_id = mainwindow.get_window(via_ipc, force_window=True)
|
||||
@@ -273,11 +288,7 @@ def process_pos_args(args, via_ipc=False, cwd=None, target_arg=None):
|
||||
if via_ipc and target_arg and target_arg != 'auto':
|
||||
open_target = target_arg
|
||||
else:
|
||||
open_target = config.get('general', 'new-instance-open-target')
|
||||
win_id = mainwindow.get_window(via_ipc, force_target=open_target)
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
log.init.debug("Startup URL {}".format(cmd))
|
||||
open_target = None
|
||||
if not cwd: # could also be an empty string due to the PyQt signal
|
||||
cwd = None
|
||||
try:
|
||||
@@ -286,9 +297,30 @@ def process_pos_args(args, via_ipc=False, cwd=None, target_arg=None):
|
||||
message.error("Error in startup argument '{}': {}".format(
|
||||
cmd, e))
|
||||
else:
|
||||
background = open_target in ['tab-bg', 'tab-bg-silent']
|
||||
tabbed_browser.tabopen(url, background=background,
|
||||
explicit=True)
|
||||
win_id = open_url(url, target=open_target, via_ipc=via_ipc)
|
||||
|
||||
|
||||
def open_url(url, target=None, no_raise=False, via_ipc=True):
|
||||
"""Open an URL in new window/tab.
|
||||
|
||||
Args:
|
||||
url: An URL to open.
|
||||
target: same as new_instance_open_target (used as a default).
|
||||
no_raise: suppress target window raising.
|
||||
via_ipc: Whether the arguments were transmitted over IPC.
|
||||
|
||||
Return:
|
||||
ID of a window that was used to open URL
|
||||
"""
|
||||
target = target or config.val.new_instance_open_target
|
||||
background = target in {'tab-bg', 'tab-bg-silent'}
|
||||
win_id = mainwindow.get_window(via_ipc, force_target=target,
|
||||
no_raise=no_raise)
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
log.init.debug("About to open URL: {}".format(url.toDisplayString()))
|
||||
tabbed_browser.tabopen(url, background=background, related=False)
|
||||
return win_id
|
||||
|
||||
|
||||
def _open_startpage(win_id=None):
|
||||
@@ -308,15 +340,9 @@ def _open_startpage(win_id=None):
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=cur_win_id)
|
||||
if tabbed_browser.count() == 0:
|
||||
log.init.debug("Opening startpage")
|
||||
for urlstr in config.get('general', 'startpage'):
|
||||
try:
|
||||
url = urlutils.fuzzy_url(urlstr, do_search=False)
|
||||
except urlutils.InvalidUrlError as e:
|
||||
message.error("Error when opening startpage: {}".format(e))
|
||||
tabbed_browser.tabopen(QUrl('about:blank'))
|
||||
else:
|
||||
tabbed_browser.tabopen(url)
|
||||
log.init.debug("Opening start pages")
|
||||
for url in config.val.url.start_pages:
|
||||
tabbed_browser.tabopen(url)
|
||||
|
||||
|
||||
def _open_special_pages(args):
|
||||
@@ -333,36 +359,29 @@ def _open_special_pages(args):
|
||||
# With --basedir given, don't open anything.
|
||||
return
|
||||
|
||||
state_config = objreg.get('state-config')
|
||||
general_sect = configfiles.state['general']
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window='last-focused')
|
||||
|
||||
# Legacy QtWebKit warning
|
||||
|
||||
needs_warning = (objects.backend == usertypes.Backend.QtWebKit and
|
||||
not qtutils.is_qtwebkit_ng())
|
||||
warning_shown = state_config['general'].get('backend-warning-shown') == '1'
|
||||
|
||||
if not warning_shown and needs_warning:
|
||||
tabbed_browser.tabopen(QUrl('qute://backend-warning'),
|
||||
background=False)
|
||||
state_config['general']['backend-warning-shown'] = '1'
|
||||
|
||||
# Quickstart page
|
||||
|
||||
quickstart_done = state_config['general'].get('quickstart-done') == '1'
|
||||
quickstart_done = general_sect.get('quickstart-done') == '1'
|
||||
|
||||
if not quickstart_done:
|
||||
tabbed_browser.tabopen(
|
||||
QUrl('https://www.qutebrowser.org/quickstart.html'))
|
||||
state_config['general']['quickstart-done'] = '1'
|
||||
general_sect['quickstart-done'] = '1'
|
||||
|
||||
# Setting migration page
|
||||
|
||||
def _save_version():
|
||||
"""Save the current version to the state config."""
|
||||
state_config = objreg.get('state-config', None)
|
||||
if state_config is not None:
|
||||
state_config['general']['version'] = qutebrowser.__version__
|
||||
needs_migration = os.path.exists(
|
||||
os.path.join(standarddir.config(), 'qutebrowser.conf'))
|
||||
migration_shown = general_sect.get('config-migration-shown') == '1'
|
||||
|
||||
if needs_migration and not migration_shown:
|
||||
tabbed_browser.tabopen(QUrl('qute://help/configuring.html'),
|
||||
background=False)
|
||||
general_sect['config-migration-shown'] = '1'
|
||||
|
||||
|
||||
def on_focus_changed(_old, new):
|
||||
@@ -399,32 +418,46 @@ def _init_modules(args, crash_handler):
|
||||
crash_handler: The CrashHandler instance.
|
||||
"""
|
||||
# pylint: disable=too-many-statements
|
||||
log.init.debug("Initializing prompts...")
|
||||
prompt.init()
|
||||
|
||||
log.init.debug("Initializing save manager...")
|
||||
save_manager = savemanager.SaveManager(qApp)
|
||||
objreg.register('save-manager', save_manager)
|
||||
save_manager.add_saveable('version', _save_version)
|
||||
configinit.late_init(save_manager)
|
||||
|
||||
log.init.debug("Checking backend requirements...")
|
||||
backendproblem.init()
|
||||
|
||||
log.init.debug("Initializing prompts...")
|
||||
prompt.init()
|
||||
|
||||
log.init.debug("Initializing network...")
|
||||
networkmanager.init()
|
||||
|
||||
if qtutils.version_check('5.8'):
|
||||
# Otherwise we can only initialize it for QtWebKit because of crashes
|
||||
log.init.debug("Initializing proxy...")
|
||||
proxy.init()
|
||||
log.init.debug("Initializing proxy...")
|
||||
proxy.init()
|
||||
|
||||
log.init.debug("Initializing readline-bridge...")
|
||||
readline_bridge = readline.ReadlineBridge()
|
||||
objreg.register('readline-bridge', readline_bridge)
|
||||
|
||||
log.init.debug("Initializing config...")
|
||||
config.init(qApp)
|
||||
save_manager.init_autosave()
|
||||
try:
|
||||
log.init.debug("Initializing sql...")
|
||||
sql.init(os.path.join(standarddir.data(), 'history.sqlite'))
|
||||
|
||||
log.init.debug("Initializing web history...")
|
||||
history.init(qApp)
|
||||
log.init.debug("Initializing web history...")
|
||||
history.init(qApp)
|
||||
except sql.SqlError as e:
|
||||
if e.environmental:
|
||||
error.handle_fatal_exc(e, args, 'Error initializing SQL',
|
||||
pre_text='Error initializing SQL')
|
||||
sys.exit(usertypes.Exit.err_init)
|
||||
else:
|
||||
raise
|
||||
|
||||
log.init.debug("Initializing completion...")
|
||||
completiondelegate.init()
|
||||
|
||||
log.init.debug("Initializing command history...")
|
||||
cmdhistory.init()
|
||||
|
||||
log.init.debug("Initializing crashlog...")
|
||||
if not args.no_err_windows:
|
||||
@@ -459,36 +492,12 @@ def _init_modules(args, crash_handler):
|
||||
diskcache = cache.DiskCache(standarddir.cache(), parent=qApp)
|
||||
objreg.register('cache', diskcache)
|
||||
|
||||
log.init.debug("Initializing completions...")
|
||||
completionmodels.init()
|
||||
|
||||
log.init.debug("Misc initialization...")
|
||||
if config.get('ui', 'hide-wayland-decoration'):
|
||||
os.environ['QT_WAYLAND_DISABLE_WINDOWDECORATION'] = '1'
|
||||
else:
|
||||
os.environ.pop('QT_WAYLAND_DISABLE_WINDOWDECORATION', None)
|
||||
macros.init()
|
||||
# Init backend-specific stuff
|
||||
browsertab.init()
|
||||
|
||||
|
||||
def _init_late_modules(args):
|
||||
"""Initialize modules which can be inited after the window is shown."""
|
||||
log.init.debug("Reading web history...")
|
||||
reader = objreg.get('web-history').async_read()
|
||||
with debug.log_time(log.init, 'Reading history'):
|
||||
while True:
|
||||
QApplication.processEvents()
|
||||
try:
|
||||
next(reader)
|
||||
except StopIteration:
|
||||
break
|
||||
except (OSError, UnicodeDecodeError) as e:
|
||||
error.handle_fatal_exc(e, args, "Error while initializing!",
|
||||
pre_text="Error while initializing")
|
||||
sys.exit(usertypes.Exit.err_init)
|
||||
|
||||
|
||||
class Quitter:
|
||||
|
||||
"""Utility class to quit/restart the QApplication.
|
||||
@@ -531,12 +540,13 @@ class Quitter:
|
||||
with tokenize.open(os.path.join(dirpath, fn)) as f:
|
||||
compile(f.read(), fn, 'exec')
|
||||
|
||||
def _get_restart_args(self, pages=(), session=None):
|
||||
def _get_restart_args(self, pages=(), session=None, override_args=None):
|
||||
"""Get the current working directory and args to relaunch qutebrowser.
|
||||
|
||||
Args:
|
||||
pages: The pages to re-open.
|
||||
session: The session to load, or None.
|
||||
override_args: Argument overrides as a dict.
|
||||
|
||||
Return:
|
||||
An (args, cwd) tuple.
|
||||
@@ -587,6 +597,9 @@ class Quitter:
|
||||
argdict['temp_basedir'] = False
|
||||
argdict['temp_basedir_restarted'] = True
|
||||
|
||||
if override_args is not None:
|
||||
argdict.update(override_args)
|
||||
|
||||
# Dump the data
|
||||
data = json.dumps(argdict)
|
||||
args += ['--json-args', data]
|
||||
@@ -611,7 +624,7 @@ class Quitter:
|
||||
if ok:
|
||||
self.shutdown(restart=True)
|
||||
|
||||
def restart(self, pages=(), session=None):
|
||||
def restart(self, pages=(), session=None, override_args=None):
|
||||
"""Inner logic to restart qutebrowser.
|
||||
|
||||
The "better" way to restart is to pass a session (_restart usually) as
|
||||
@@ -624,6 +637,7 @@ class Quitter:
|
||||
Args:
|
||||
pages: A list of URLs to open.
|
||||
session: The session to load, or None.
|
||||
override_args: Argument overrides as a dict.
|
||||
|
||||
Return:
|
||||
True if the restart succeeded, False otherwise.
|
||||
@@ -633,13 +647,19 @@ class Quitter:
|
||||
log.destroy.debug("sys.path: {}".format(sys.path))
|
||||
log.destroy.debug("sys.argv: {}".format(sys.argv))
|
||||
log.destroy.debug("frozen: {}".format(hasattr(sys, 'frozen')))
|
||||
|
||||
# Save the session if one is given.
|
||||
if session is not None:
|
||||
session_manager = objreg.get('session-manager')
|
||||
session_manager.save(session)
|
||||
session_manager.save(session, with_private=True)
|
||||
|
||||
# Make sure we're not accepting a connection from the new process
|
||||
# before we fully exited.
|
||||
ipc.server.shutdown()
|
||||
|
||||
# Open a new process and immediately shutdown the existing one
|
||||
try:
|
||||
args, cwd = self._get_restart_args(pages, session)
|
||||
args, cwd = self._get_restart_args(pages, session, override_args)
|
||||
if cwd is None:
|
||||
subprocess.Popen(args)
|
||||
else:
|
||||
@@ -650,8 +670,25 @@ class Quitter:
|
||||
else:
|
||||
return True
|
||||
|
||||
@cmdutils.register(instance='quitter', name=['quit', 'q'],
|
||||
ignore_args=True)
|
||||
@cmdutils.register(instance='quitter', name='quit')
|
||||
@cmdutils.argument('session', completion=miscmodels.session)
|
||||
def quit(self, save=False, session=None):
|
||||
"""Quit qutebrowser.
|
||||
|
||||
Args:
|
||||
save: When given, save the open windows even if auto_save.session
|
||||
is turned off.
|
||||
session: The name of the session to save.
|
||||
"""
|
||||
if session is not None and not save:
|
||||
raise cmdexc.CommandError("Session name given without --save!")
|
||||
if save:
|
||||
if session is None:
|
||||
session = sessions.default
|
||||
self.shutdown(session=session)
|
||||
else:
|
||||
self.shutdown()
|
||||
|
||||
def shutdown(self, status=0, session=None, last_window=False,
|
||||
restart=False):
|
||||
"""Quit qutebrowser.
|
||||
@@ -673,7 +710,7 @@ class Quitter:
|
||||
if session is not None:
|
||||
session_manager.save(session, last_window=last_window,
|
||||
load_next_time=True)
|
||||
elif config.get('general', 'save-session'):
|
||||
elif config.val.auto_save.session:
|
||||
session_manager.save(sessions.default, last_window=last_window,
|
||||
load_next_time=True)
|
||||
|
||||
@@ -710,7 +747,7 @@ class Quitter:
|
||||
QApplication.closeAllWindows()
|
||||
# Shut down IPC
|
||||
try:
|
||||
objreg.get('ipc-server').shutdown()
|
||||
ipc.server.shutdown()
|
||||
except KeyError:
|
||||
pass
|
||||
# Save everything
|
||||
@@ -752,16 +789,6 @@ class Quitter:
|
||||
# segfaults.
|
||||
QTimer.singleShot(0, functools.partial(qApp.exit, status))
|
||||
|
||||
@cmdutils.register(instance='quitter', name='wq')
|
||||
@cmdutils.argument('name', completion=usertypes.Completion.sessions)
|
||||
def save_and_quit(self, name=sessions.default):
|
||||
"""Save open pages and quit.
|
||||
|
||||
Args:
|
||||
name: The name of the session.
|
||||
"""
|
||||
self.shutdown(session=name)
|
||||
|
||||
|
||||
class Application(QApplication):
|
||||
|
||||
@@ -782,7 +809,7 @@ class Application(QApplication):
|
||||
"""
|
||||
self._last_focus_object = None
|
||||
|
||||
qt_args = qtutils.get_args(args)
|
||||
qt_args = configinit.qt_args(args)
|
||||
log.init.debug("Qt arguments: {}, based on {}".format(qt_args, args))
|
||||
super().__init__(qt_args)
|
||||
|
||||
@@ -803,6 +830,15 @@ class Application(QApplication):
|
||||
log.misc.debug("Focus object changed: {}".format(output))
|
||||
self._last_focus_object = output
|
||||
|
||||
def event(self, e):
|
||||
"""Handle macOS FileOpen events."""
|
||||
if e.type() == QEvent.FileOpen:
|
||||
open_url(e.url(), no_raise=True)
|
||||
else:
|
||||
return super().event(e)
|
||||
|
||||
return True
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self)
|
||||
|
||||
|
||||
@@ -67,11 +67,7 @@ def is_whitelisted_host(host):
|
||||
Args:
|
||||
host: The host of the request as string.
|
||||
"""
|
||||
whitelist = config.get('content', 'host-blocking-whitelist')
|
||||
if whitelist is None:
|
||||
return False
|
||||
|
||||
for pattern in whitelist:
|
||||
for pattern in config.val.content.host_blocking.whitelist:
|
||||
if fnmatch.fnmatch(host, pattern.lower()):
|
||||
return True
|
||||
return False
|
||||
@@ -114,16 +110,16 @@ class HostBlocker:
|
||||
|
||||
data_dir = standarddir.data()
|
||||
self._local_hosts_file = os.path.join(data_dir, 'blocked-hosts')
|
||||
self.on_config_changed()
|
||||
self._update_files()
|
||||
|
||||
config_dir = standarddir.config()
|
||||
self._config_hosts_file = os.path.join(config_dir, 'blocked-hosts')
|
||||
|
||||
objreg.get('config').changed.connect(self.on_config_changed)
|
||||
config.instance.changed.connect(self._update_files)
|
||||
|
||||
def is_blocked(self, url):
|
||||
"""Check if the given URL (as QUrl) is blocked."""
|
||||
if not config.get('content', 'host-blocking-enabled'):
|
||||
if not config.val.content.host_blocking.enabled:
|
||||
return False
|
||||
host = url.host()
|
||||
return ((host in self._blocked_hosts or
|
||||
@@ -164,9 +160,9 @@ class HostBlocker:
|
||||
|
||||
if not found:
|
||||
args = objreg.get('args')
|
||||
if (config.get('content', 'host-block-lists') is not None and
|
||||
if (config.val.content.host_blocking.lists and
|
||||
args.basedir is None and
|
||||
config.get('content', 'host-blocking-enabled')):
|
||||
config.val.content.host_blocking.enabled):
|
||||
message.info("Run :adblock-update to get adblock lists.")
|
||||
|
||||
@cmdutils.register(instance='host-blocker')
|
||||
@@ -180,18 +176,16 @@ class HostBlocker:
|
||||
self._config_blocked_hosts)
|
||||
self._blocked_hosts = set()
|
||||
self._done_count = 0
|
||||
urls = config.get('content', 'host-block-lists')
|
||||
download_manager = objreg.get('qtnetwork-download-manager',
|
||||
scope='window', window='last-focused')
|
||||
if urls is None:
|
||||
return
|
||||
for url in urls:
|
||||
for url in config.val.content.host_blocking.lists:
|
||||
if url.scheme() == 'file':
|
||||
filename = url.toLocalFile()
|
||||
try:
|
||||
fileobj = open(url.path(), 'rb')
|
||||
fileobj = open(filename, 'rb')
|
||||
except OSError as e:
|
||||
message.error("adblock: Error while reading {}: {}".format(
|
||||
url.path(), e.strerror))
|
||||
filename, e.strerror))
|
||||
continue
|
||||
download = FakeDownload(fileobj)
|
||||
self._in_progress.append(download)
|
||||
@@ -292,11 +286,10 @@ class HostBlocker:
|
||||
message.info("adblock: Read {} hosts from {} sources.".format(
|
||||
len(self._blocked_hosts), self._done_count))
|
||||
|
||||
@config.change_filter('content', 'host-block-lists')
|
||||
def on_config_changed(self):
|
||||
@config.change_filter('content.host_blocking.lists')
|
||||
def _update_files(self):
|
||||
"""Update files when the config changed."""
|
||||
urls = config.get('content', 'host-block-lists')
|
||||
if urls is None:
|
||||
if not config.val.content.host_blocking.lists:
|
||||
try:
|
||||
os.remove(self._local_hosts_file)
|
||||
except FileNotFoundError:
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
import itertools
|
||||
|
||||
import attr
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QWidget, QApplication
|
||||
@@ -61,9 +62,6 @@ def init():
|
||||
if objects.backend == usertypes.Backend.QtWebEngine:
|
||||
from qutebrowser.browser.webengine import webenginetab
|
||||
webenginetab.init()
|
||||
else:
|
||||
from qutebrowser.browser.webkit import webkittab
|
||||
webkittab.init()
|
||||
|
||||
|
||||
class WebTabError(Exception):
|
||||
@@ -85,6 +83,7 @@ TerminationStatus = usertypes.enum('TerminationStatus', [
|
||||
])
|
||||
|
||||
|
||||
@attr.s
|
||||
class TabData:
|
||||
|
||||
"""A simple namespace with a fixed set of attributes.
|
||||
@@ -100,13 +99,12 @@ class TabData:
|
||||
fullscreen: Whether the tab has a video shown fullscreen currently.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.keep_icon = False
|
||||
self.viewing_source = False
|
||||
self.inspector = None
|
||||
self.override_target = None
|
||||
self.pinned = False
|
||||
self.fullscreen = False
|
||||
keep_icon = attr.ib(False)
|
||||
viewing_source = attr.ib(False)
|
||||
inspector = attr.ib(None)
|
||||
override_target = attr.ib(None)
|
||||
pinned = attr.ib(False)
|
||||
fullscreen = attr.ib(False)
|
||||
|
||||
|
||||
class AbstractAction:
|
||||
@@ -188,13 +186,28 @@ class AbstractSearch(QObject):
|
||||
self.text = None
|
||||
self.search_displayed = False
|
||||
|
||||
def search(self, text, *, ignore_case=False, reverse=False,
|
||||
def _is_case_sensitive(self, ignore_case):
|
||||
"""Check if case-sensitivity should be used.
|
||||
|
||||
This assumes self.text is already set properly.
|
||||
|
||||
Arguments:
|
||||
ignore_case: The ignore_case value from the config.
|
||||
"""
|
||||
mapping = {
|
||||
'smart': not self.text.islower(),
|
||||
'never': True,
|
||||
'always': False,
|
||||
}
|
||||
return mapping[ignore_case]
|
||||
|
||||
def search(self, text, *, ignore_case='never', reverse=False,
|
||||
result_cb=None):
|
||||
"""Find the given text on the page.
|
||||
|
||||
Args:
|
||||
text: The text to search for.
|
||||
ignore_case: Search case-insensitively. (True/False/'smart')
|
||||
ignore_case: Search case-insensitively. ('always'/'never/'smart')
|
||||
reverse: Reverse search direction.
|
||||
result_cb: Called with a bool indicating whether a match was found.
|
||||
"""
|
||||
@@ -236,7 +249,8 @@ class AbstractZoom(QObject):
|
||||
self._win_id = win_id
|
||||
self._default_zoom_changed = False
|
||||
self._init_neighborlist()
|
||||
objreg.get('config').changed.connect(self._on_config_changed)
|
||||
config.instance.changed.connect(self._on_config_changed)
|
||||
self._zoom_factor = float(config.val.zoom.default) / 100
|
||||
|
||||
# # FIXME:qtwebengine is this needed?
|
||||
# # For some reason, this signal doesn't get disconnected automatically
|
||||
@@ -245,21 +259,20 @@ class AbstractZoom(QObject):
|
||||
# self.destroyed.connect(functools.partial(
|
||||
# cfg.changed.disconnect, self.init_neighborlist))
|
||||
|
||||
@pyqtSlot(str, str)
|
||||
def _on_config_changed(self, section, option):
|
||||
if section == 'ui' and option in ['zoom-levels', 'default-zoom']:
|
||||
@pyqtSlot(str)
|
||||
def _on_config_changed(self, option):
|
||||
if option in ['zoom.levels', 'zoom.default']:
|
||||
if not self._default_zoom_changed:
|
||||
factor = float(config.get('ui', 'default-zoom')) / 100
|
||||
self._set_factor_internal(factor)
|
||||
self._default_zoom_changed = False
|
||||
factor = float(config.val.zoom.default) / 100
|
||||
self.set_factor(factor)
|
||||
self._init_neighborlist()
|
||||
|
||||
def _init_neighborlist(self):
|
||||
"""Initialize self._neighborlist."""
|
||||
levels = config.get('ui', 'zoom-levels')
|
||||
levels = config.val.zoom.levels
|
||||
self._neighborlist = usertypes.NeighborList(
|
||||
levels, mode=usertypes.NeighborList.Modes.edge)
|
||||
self._neighborlist.fuzzyval = config.get('ui', 'default-zoom')
|
||||
self._neighborlist.fuzzyval = config.val.zoom.default
|
||||
|
||||
def offset(self, offset):
|
||||
"""Increase/Decrease the zoom level by the given offset.
|
||||
@@ -288,15 +301,21 @@ class AbstractZoom(QObject):
|
||||
self._neighborlist.fuzzyval = int(factor * 100)
|
||||
if factor < 0:
|
||||
raise ValueError("Can't zoom to factor {}!".format(factor))
|
||||
self._default_zoom_changed = True
|
||||
|
||||
default_zoom_factor = float(config.val.zoom.default) / 100
|
||||
self._default_zoom_changed = (factor != default_zoom_factor)
|
||||
|
||||
self._zoom_factor = factor
|
||||
self._set_factor_internal(factor)
|
||||
|
||||
def factor(self):
|
||||
raise NotImplementedError
|
||||
return self._zoom_factor
|
||||
|
||||
def set_default(self):
|
||||
default_zoom = config.get('ui', 'default-zoom')
|
||||
self._set_factor_internal(float(default_zoom) / 100)
|
||||
self._set_factor_internal(float(config.val.zoom.default) / 100)
|
||||
|
||||
def set_current(self):
|
||||
self._set_factor_internal(self._zoom_factor)
|
||||
|
||||
|
||||
class AbstractCaret(QObject):
|
||||
@@ -465,11 +484,21 @@ class AbstractHistory:
|
||||
def current_idx(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def back(self):
|
||||
raise NotImplementedError
|
||||
def back(self, count=1):
|
||||
idx = self.current_idx() - count
|
||||
if idx >= 0:
|
||||
self._go_to_item(self._item_at(idx))
|
||||
else:
|
||||
self._go_to_item(self._item_at(0))
|
||||
raise WebTabError("At beginning of history.")
|
||||
|
||||
def forward(self):
|
||||
raise NotImplementedError
|
||||
def forward(self, count=1):
|
||||
idx = self.current_idx() + count
|
||||
if idx < len(self):
|
||||
self._go_to_item(self._item_at(idx))
|
||||
else:
|
||||
self._go_to_item(self._item_at(len(self) - 1))
|
||||
raise WebTabError("At end of history.")
|
||||
|
||||
def can_go_back(self):
|
||||
raise NotImplementedError
|
||||
@@ -477,6 +506,12 @@ class AbstractHistory:
|
||||
def can_go_forward(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def _item_at(self, i):
|
||||
raise NotImplementedError
|
||||
|
||||
def _go_to_item(self, item):
|
||||
raise NotImplementedError
|
||||
|
||||
def serialize(self):
|
||||
"""Serialize into an opaque format understood by self.deserialize."""
|
||||
raise NotImplementedError
|
||||
@@ -688,9 +723,9 @@ class AbstractTab(QWidget):
|
||||
self._set_load_status(usertypes.LoadStatus.loading)
|
||||
self.load_started.emit()
|
||||
|
||||
def _handle_auto_insert_mode(self, ok):
|
||||
"""Handle auto-insert-mode after loading finished."""
|
||||
if not config.get('input', 'auto-insert-mode') or not ok:
|
||||
def handle_auto_insert_mode(self, ok):
|
||||
"""Handle `input.insert_mode.auto_load` after loading finished."""
|
||||
if not config.val.input.insert_mode.auto_load or not ok:
|
||||
return
|
||||
|
||||
cur_mode = self._mode_manager.mode
|
||||
@@ -725,7 +760,8 @@ class AbstractTab(QWidget):
|
||||
self.load_finished.emit(ok)
|
||||
if not self.title():
|
||||
self.title_changed.emit(self.url().toDisplayString())
|
||||
self._handle_auto_insert_mode(ok)
|
||||
|
||||
self.zoom.set_current()
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_history_trigger(self):
|
||||
@@ -824,6 +860,6 @@ class AbstractTab(QWidget):
|
||||
try:
|
||||
url = utils.elide(self.url().toDisplayString(QUrl.EncodeUnicode),
|
||||
100)
|
||||
except AttributeError:
|
||||
url = '<AttributeError>'
|
||||
except (AttributeError, RuntimeError) as exc:
|
||||
url = '<{}>'.format(exc.__class__.__name__)
|
||||
return utils.get_repr(self, tab_id=self.tab_id, url=url)
|
||||
|
||||
@@ -23,8 +23,9 @@ import os
|
||||
import os.path
|
||||
import shlex
|
||||
import functools
|
||||
import typing
|
||||
|
||||
from PyQt5.QtWidgets import QApplication, QTabBar
|
||||
from PyQt5.QtWidgets import QApplication, QTabBar, QDialog
|
||||
from PyQt5.QtCore import Qt, QUrl, QEvent, QUrlQuery
|
||||
from PyQt5.QtGui import QKeyEvent
|
||||
from PyQt5.QtPrintSupport import QPrintDialog, QPrintPreviewDialog
|
||||
@@ -33,15 +34,16 @@ import pygments.lexers
|
||||
import pygments.formatters
|
||||
|
||||
from qutebrowser.commands import userscripts, cmdexc, cmdutils, runners
|
||||
from qutebrowser.config import config, configexc
|
||||
from qutebrowser.config import config, configdata
|
||||
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, typing)
|
||||
objreg, utils, debug)
|
||||
from qutebrowser.utils.usertypes import KeyMode
|
||||
from qutebrowser.misc import editor, guiprocess
|
||||
from qutebrowser.completion.models import instances, sortfilter
|
||||
from qutebrowser.completion.models import urlmodel, miscmodels
|
||||
from qutebrowser.mainwindow import mainwindow
|
||||
|
||||
|
||||
class CommandDispatcher:
|
||||
@@ -69,7 +71,6 @@ class CommandDispatcher:
|
||||
|
||||
def _new_tabbed_browser(self, private):
|
||||
"""Get a tabbed-browser from a new window."""
|
||||
from qutebrowser.mainwindow import mainwindow
|
||||
new_window = mainwindow.MainWindow(private=private)
|
||||
new_window.show()
|
||||
return new_window.tabbed_browser
|
||||
@@ -110,7 +111,7 @@ class CommandDispatcher:
|
||||
return widget
|
||||
|
||||
def _open(self, url, tab=False, background=False, window=False,
|
||||
explicit=True, private=None):
|
||||
related=False, private=None):
|
||||
"""Helper function to open a page.
|
||||
|
||||
Args:
|
||||
@@ -131,9 +132,9 @@ class CommandDispatcher:
|
||||
tabbed_browser = self._new_tabbed_browser(private)
|
||||
tabbed_browser.tabopen(url)
|
||||
elif tab:
|
||||
tabbed_browser.tabopen(url, background=False, explicit=explicit)
|
||||
tabbed_browser.tabopen(url, background=False, related=related)
|
||||
elif background:
|
||||
tabbed_browser.tabopen(url, background=True, explicit=explicit)
|
||||
tabbed_browser.tabopen(url, background=True, related=related)
|
||||
else:
|
||||
widget = self._current_widget()
|
||||
widget.openurl(url)
|
||||
@@ -178,7 +179,7 @@ class CommandDispatcher:
|
||||
prev: Force selecting the tab before the current tab.
|
||||
next_: Force selecting the tab after the current tab.
|
||||
opposite: Force selecting the tab in the opposite direction of
|
||||
what's configured in 'tabs->select-on-remove'.
|
||||
what's configured in 'tabs.select_on_remove'.
|
||||
|
||||
Return:
|
||||
QTabBar.SelectLeftTab, QTabBar.SelectRightTab, or None if no change
|
||||
@@ -190,17 +191,17 @@ class CommandDispatcher:
|
||||
elif next_:
|
||||
return QTabBar.SelectRightTab
|
||||
elif opposite:
|
||||
conf_selection = config.get('tabs', 'select-on-remove')
|
||||
conf_selection = config.val.tabs.select_on_remove
|
||||
if conf_selection == QTabBar.SelectLeftTab:
|
||||
return QTabBar.SelectRightTab
|
||||
elif conf_selection == QTabBar.SelectRightTab:
|
||||
return QTabBar.SelectLeftTab
|
||||
elif conf_selection == QTabBar.SelectPreviousTab:
|
||||
raise cmdexc.CommandError(
|
||||
"-o is not supported with 'tabs->select-on-remove' set to "
|
||||
"-o is not supported with 'tabs.select_on_remove' set to "
|
||||
"'last-used'!")
|
||||
else: # pragma: no cover
|
||||
raise ValueError("Invalid select-on-remove value "
|
||||
raise ValueError("Invalid select_on_remove value "
|
||||
"{!r}!".format(conf_selection))
|
||||
return None
|
||||
|
||||
@@ -212,7 +213,7 @@ class CommandDispatcher:
|
||||
prev: Force selecting the tab before the current tab.
|
||||
next_: Force selecting the tab after the current tab.
|
||||
opposite: Force selecting the tab in the opposite direction of
|
||||
what's configured in 'tabs->select-on-remove'.
|
||||
what's configured in 'tabs.select_on_remove'.
|
||||
count: The tab index to close, or None
|
||||
"""
|
||||
tabbar = self._tabbed_browser.tabBar()
|
||||
@@ -227,19 +228,6 @@ class CommandDispatcher:
|
||||
self._tabbed_browser.close_tab(tab)
|
||||
tabbar.setSelectionBehaviorOnRemove(old_selection_behavior)
|
||||
|
||||
def _tab_close_prompt_if_pinned(self, tab, force, yes_action):
|
||||
"""Helper method for tab_close.
|
||||
|
||||
If tab is pinned, prompt. If everything is good, run yes_action.
|
||||
"""
|
||||
if tab.data.pinned and not force:
|
||||
message.confirm_async(
|
||||
title='Pinned Tab',
|
||||
text="Are you sure you want to close a pinned tab?",
|
||||
yes_action=yes_action, default=False)
|
||||
else:
|
||||
yes_action()
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
def tab_close(self, prev=False, next_=False, opposite=False,
|
||||
@@ -250,7 +238,7 @@ class CommandDispatcher:
|
||||
prev: Force selecting the tab before the current tab.
|
||||
next_: Force selecting the tab after the current tab.
|
||||
opposite: Force selecting the tab in the opposite direction of
|
||||
what's configured in 'tabs->select-on-remove'.
|
||||
what's configured in 'tabs.select_on_remove'.
|
||||
force: Avoid confirmation for pinned tabs.
|
||||
count: The tab index to close, or None
|
||||
"""
|
||||
@@ -260,7 +248,7 @@ class CommandDispatcher:
|
||||
close = functools.partial(self._tab_close, tab, prev,
|
||||
next_, opposite)
|
||||
|
||||
self._tab_close_prompt_if_pinned(tab, force, close)
|
||||
self._tabbed_browser.tab_close_prompt_if_pinned(tab, force, close)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
name='tab-pin')
|
||||
@@ -268,7 +256,7 @@ class CommandDispatcher:
|
||||
def tab_pin(self, count=None):
|
||||
"""Pin/Unpin the current/[count]th tab.
|
||||
|
||||
Pinning a tab shrinks it to tabs->pinned-width size.
|
||||
Pinning a tab shrinks it to the size of its title text.
|
||||
Attempting to close a pinned tab will cause a confirmation,
|
||||
unless --force is passed.
|
||||
|
||||
@@ -280,15 +268,13 @@ class CommandDispatcher:
|
||||
return
|
||||
|
||||
to_pin = not tab.data.pinned
|
||||
tab_index = self._current_index() if count is None else count - 1
|
||||
cmdutils.check_overflow(tab_index + 1, 'int')
|
||||
self._tabbed_browser.set_tab_pinned(tab_index, to_pin)
|
||||
self._tabbed_browser.set_tab_pinned(tab, to_pin)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', name='open',
|
||||
maxsplit=0, scope='window')
|
||||
@cmdutils.argument('url', completion=usertypes.Completion.url)
|
||||
@cmdutils.argument('url', completion=urlmodel.url)
|
||||
@cmdutils.argument('count', count=True)
|
||||
def openurl(self, url=None, implicit=False,
|
||||
def openurl(self, url=None, related=False,
|
||||
bg=False, tab=False, window=False, count=None, secure=False,
|
||||
private=False):
|
||||
"""Open a URL in the current/[count]th tab.
|
||||
@@ -300,14 +286,14 @@ class CommandDispatcher:
|
||||
bg: Open in a new background tab.
|
||||
tab: Open in a new tab.
|
||||
window: Open in a new window.
|
||||
implicit: If opening a new tab, treat the tab as implicit (like
|
||||
clicking on a link).
|
||||
related: If opening a new tab, position the tab as related to the
|
||||
current one (like clicking on a link).
|
||||
count: The tab index to open the URL in, or None.
|
||||
secure: Force HTTPS.
|
||||
private: Open a new window in private browsing mode.
|
||||
"""
|
||||
if url is None:
|
||||
urls = [config.get('general', 'default-page')]
|
||||
urls = [config.val.url.default_page]
|
||||
else:
|
||||
urls = self._parse_url_input(url)
|
||||
|
||||
@@ -319,7 +305,7 @@ class CommandDispatcher:
|
||||
bg = True
|
||||
|
||||
if tab or bg or window or private:
|
||||
self._open(cur_url, tab, bg, window, explicit=not implicit,
|
||||
self._open(cur_url, tab, bg, window, related=related,
|
||||
private=private)
|
||||
else:
|
||||
curtab = self._cntwidget(count)
|
||||
@@ -438,9 +424,18 @@ class CommandDispatcher:
|
||||
message.error("Printing failed!")
|
||||
diag.deleteLater()
|
||||
|
||||
def do_print():
|
||||
"""Called when the dialog was closed."""
|
||||
tab.printing.to_printer(diag.printer(), print_callback)
|
||||
|
||||
diag = QPrintDialog(tab)
|
||||
diag.open(lambda: tab.printing.to_printer(diag.printer(),
|
||||
print_callback))
|
||||
if utils.is_mac:
|
||||
# For some reason we get a segfault when using open() on macOS
|
||||
ret = diag.exec_()
|
||||
if ret == QDialog.Accepted:
|
||||
do_print()
|
||||
else:
|
||||
diag.open(do_print)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', name='print',
|
||||
scope='window')
|
||||
@@ -495,7 +490,7 @@ class CommandDispatcher:
|
||||
raise cmdexc.CommandError(e)
|
||||
|
||||
# The new tab could be in a new tabbed_browser (e.g. because of
|
||||
# tabs-are-windows being set)
|
||||
# tabs.tabs_are_windows being set)
|
||||
if window:
|
||||
new_tabbed_browser = self._new_tabbed_browser(
|
||||
private=self._tabbed_browser.private)
|
||||
@@ -507,15 +502,15 @@ class CommandDispatcher:
|
||||
idx = new_tabbed_browser.indexOf(newtab)
|
||||
|
||||
new_tabbed_browser.set_page_title(idx, cur_title)
|
||||
if config.get('tabs', 'show-favicons'):
|
||||
if config.val.tabs.favicons.show:
|
||||
new_tabbed_browser.setTabIcon(idx, curtab.icon())
|
||||
if config.get('tabs', 'tabs-are-windows'):
|
||||
if config.val.tabs.tabs_are_windows:
|
||||
new_tabbed_browser.window().setWindowIcon(curtab.icon())
|
||||
|
||||
newtab.data.keep_icon = True
|
||||
newtab.history.deserialize(history)
|
||||
newtab.zoom.set_factor(curtab.zoom.factor())
|
||||
new_tabbed_browser.set_tab_pinned(idx, curtab.data.pinned)
|
||||
new_tabbed_browser.set_tab_pinned(newtab, curtab.data.pinned)
|
||||
return newtab
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@@ -542,15 +537,13 @@ class CommandDispatcher:
|
||||
else:
|
||||
widget = self._current_widget()
|
||||
|
||||
for _ in range(count):
|
||||
try:
|
||||
if forward:
|
||||
if not widget.history.can_go_forward():
|
||||
raise cmdexc.CommandError("At end of history.")
|
||||
widget.history.forward()
|
||||
widget.history.forward(count)
|
||||
else:
|
||||
if not widget.history.can_go_back():
|
||||
raise cmdexc.CommandError("At beginning of history.")
|
||||
widget.history.back()
|
||||
widget.history.back(count)
|
||||
except browsertab.WebTabError as e:
|
||||
raise cmdexc.CommandError(e)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
@@ -629,7 +622,7 @@ class CommandDispatcher:
|
||||
tab=tab, background=bg, window=window)
|
||||
elif where in ['up', 'increment', 'decrement']:
|
||||
new_url = handlers[where](url, count)
|
||||
self._open(new_url, tab, bg, window, explicit=False)
|
||||
self._open(new_url, tab, bg, window, related=True)
|
||||
else: # pragma: no cover
|
||||
raise ValueError("Got called with invalid value {} for "
|
||||
"`where'.".format(where))
|
||||
@@ -695,7 +688,7 @@ class CommandDispatcher:
|
||||
scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
@cmdutils.argument('horizontal', flag='x')
|
||||
def scroll_perc(self, perc: float = None, horizontal=False, count=None):
|
||||
def scroll_to_perc(self, perc: float = None, horizontal=False, count=None):
|
||||
"""Scroll to a specific percentage of the page.
|
||||
|
||||
The percentage can be given either as argument or as count.
|
||||
@@ -778,7 +771,7 @@ class CommandDispatcher:
|
||||
url_query.setQueryDelimiters('=', ';')
|
||||
url_query.setQuery(url_query_str)
|
||||
for key in dict(url_query.queryItems()):
|
||||
if key in config.get('general', 'yank-ignored-url-parameters'):
|
||||
if key in config.val.url.yank_ignored_parameters:
|
||||
url_query.removeQueryItem(key)
|
||||
url.setQuery(url_query)
|
||||
return url.toString(flags)
|
||||
@@ -849,7 +842,7 @@ class CommandDispatcher:
|
||||
perc = tab.zoom.offset(count)
|
||||
except ValueError as e:
|
||||
raise cmdexc.CommandError(e)
|
||||
message.info("Zoom level: {}%".format(perc), replace=True)
|
||||
message.info("Zoom level: {}%".format(int(perc)), replace=True)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
@@ -864,11 +857,11 @@ class CommandDispatcher:
|
||||
perc = tab.zoom.offset(-count)
|
||||
except ValueError as e:
|
||||
raise cmdexc.CommandError(e)
|
||||
message.info("Zoom level: {}%".format(perc), replace=True)
|
||||
message.info("Zoom level: {}%".format(int(perc)), replace=True)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
def zoom(self, zoom: int = None, count=None):
|
||||
def zoom(self, zoom=None, count=None):
|
||||
"""Set the zoom level for the current tab.
|
||||
|
||||
The zoom can be given as argument or as [count]. If neither is
|
||||
@@ -879,16 +872,23 @@ class CommandDispatcher:
|
||||
zoom: The zoom percentage to set.
|
||||
count: The zoom percentage to set.
|
||||
"""
|
||||
if zoom is not None:
|
||||
try:
|
||||
zoom = int(zoom.rstrip('%'))
|
||||
except ValueError:
|
||||
raise cmdexc.CommandError("zoom: Invalid int value {}"
|
||||
.format(zoom))
|
||||
|
||||
level = count if count is not None else zoom
|
||||
if level is None:
|
||||
level = config.get('ui', 'default-zoom')
|
||||
level = config.val.zoom.default
|
||||
tab = self._current_widget()
|
||||
|
||||
try:
|
||||
tab.zoom.set_factor(float(level) / 100)
|
||||
except ValueError:
|
||||
raise cmdexc.CommandError("Can't zoom {}%!".format(level))
|
||||
message.info("Zoom level: {}%".format(level), replace=True)
|
||||
message.info("Zoom level: {}%".format(int(level)), replace=True)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def tab_only(self, prev=False, next_=False, force=False):
|
||||
@@ -913,8 +913,9 @@ class CommandDispatcher:
|
||||
if not force:
|
||||
for i, tab in enumerate(self._tabbed_browser.widgets()):
|
||||
if _to_close(i) and tab.data.pinned:
|
||||
self._tab_close_prompt_if_pinned(
|
||||
tab, force,
|
||||
self._tabbed_browser.tab_close_prompt_if_pinned(
|
||||
tab,
|
||||
force,
|
||||
lambda: self.tab_only(
|
||||
prev=prev, next_=next_, force=True))
|
||||
return
|
||||
@@ -925,7 +926,7 @@ class CommandDispatcher:
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def undo(self):
|
||||
"""Re-open a closed tab (optionally skipping [count] closed tabs)."""
|
||||
"""Re-open a closed tab."""
|
||||
try:
|
||||
self._tabbed_browser.undo()
|
||||
except IndexError:
|
||||
@@ -946,7 +947,7 @@ class CommandDispatcher:
|
||||
newidx = self._current_index() - count
|
||||
if newidx >= 0:
|
||||
self._set_current_index(newidx)
|
||||
elif config.get('tabs', 'wrap'):
|
||||
elif config.val.tabs.wrap:
|
||||
self._set_current_index(newidx % self._count())
|
||||
else:
|
||||
raise cmdexc.CommandError("First tab")
|
||||
@@ -966,7 +967,7 @@ class CommandDispatcher:
|
||||
newidx = self._current_index() + count
|
||||
if newidx < self._count():
|
||||
self._set_current_index(newidx)
|
||||
elif config.get('tabs', 'wrap'):
|
||||
elif config.val.tabs.wrap:
|
||||
self._set_current_index(newidx % self._count())
|
||||
else:
|
||||
raise cmdexc.CommandError("Last tab")
|
||||
@@ -1009,31 +1010,39 @@ class CommandDispatcher:
|
||||
self._open(url, tab, bg, window)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('index', completion=usertypes.Completion.tab)
|
||||
def buffer(self, index):
|
||||
@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.
|
||||
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.
|
||||
"""
|
||||
index_parts = index.split('/', 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)
|
||||
|
||||
try:
|
||||
for part in index_parts:
|
||||
int(part)
|
||||
except ValueError:
|
||||
model = instances.get(usertypes.Completion.tab)
|
||||
sf = sortfilter.CompletionFilterModel(source=model)
|
||||
sf.set_pattern(index)
|
||||
if sf.count() > 0:
|
||||
index = sf.data(sf.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])
|
||||
@@ -1065,7 +1074,7 @@ class CommandDispatcher:
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('index', choices=['last'])
|
||||
@cmdutils.argument('count', count=True)
|
||||
def tab_focus(self, index: typing.Union[str, int]=None, count=None):
|
||||
def tab_focus(self, index: typing.Union[str, int] = None, count=None):
|
||||
"""Select the tab given as argument/[count].
|
||||
|
||||
If neither count nor index are given, it behaves like tab-next.
|
||||
@@ -1102,7 +1111,7 @@ class CommandDispatcher:
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('index', choices=['+', '-'])
|
||||
@cmdutils.argument('count', count=True)
|
||||
def tab_move(self, index: typing.Union[str, int]=None, count=None):
|
||||
def tab_move(self, index: typing.Union[str, int] = None, count=None):
|
||||
"""Move the current tab according to the argument and [count].
|
||||
|
||||
If neither is given, move it to the first position.
|
||||
@@ -1124,7 +1133,7 @@ class CommandDispatcher:
|
||||
elif index == '+': # pragma: no branch
|
||||
new_idx += delta
|
||||
|
||||
if config.get('tabs', 'wrap'):
|
||||
if config.val.tabs.wrap:
|
||||
new_idx %= self._count()
|
||||
else:
|
||||
# absolute moving
|
||||
@@ -1160,6 +1169,7 @@ class CommandDispatcher:
|
||||
detach: Whether the command should be detached from qutebrowser.
|
||||
cmdline: The commandline to execute.
|
||||
"""
|
||||
cmdutils.check_exclusive((userscript, detach), 'ud')
|
||||
try:
|
||||
cmd, *args = shlex.split(cmdline)
|
||||
except ValueError as e:
|
||||
@@ -1185,7 +1195,7 @@ class CommandDispatcher:
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def home(self):
|
||||
"""Open main startpage in current tab."""
|
||||
self.openurl(config.get('general', 'startpage')[0])
|
||||
self._current_widget().openurl(config.val.url.start_pages[0])
|
||||
|
||||
def _run_userscript(self, cmd, *args, verbose=False):
|
||||
"""Run a userscript given as argument.
|
||||
@@ -1234,8 +1244,7 @@ class CommandDispatcher:
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
maxsplit=0)
|
||||
@cmdutils.argument('name',
|
||||
completion=usertypes.Completion.quickmark_by_name)
|
||||
@cmdutils.argument('name', completion=miscmodels.quickmark)
|
||||
def quickmark_load(self, name, tab=False, bg=False, window=False):
|
||||
"""Load a quickmark.
|
||||
|
||||
@@ -1253,8 +1262,7 @@ class CommandDispatcher:
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
maxsplit=0)
|
||||
@cmdutils.argument('name',
|
||||
completion=usertypes.Completion.quickmark_by_name)
|
||||
@cmdutils.argument('name', completion=miscmodels.quickmark)
|
||||
def quickmark_del(self, name=None):
|
||||
"""Delete a quickmark.
|
||||
|
||||
@@ -1316,7 +1324,7 @@ class CommandDispatcher:
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
maxsplit=0)
|
||||
@cmdutils.argument('url', completion=usertypes.Completion.bookmark_by_url)
|
||||
@cmdutils.argument('url', completion=miscmodels.bookmark)
|
||||
def bookmark_load(self, url, tab=False, bg=False, window=False,
|
||||
delete=False):
|
||||
"""Load a bookmark.
|
||||
@@ -1338,7 +1346,7 @@ class CommandDispatcher:
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
maxsplit=0)
|
||||
@cmdutils.argument('url', completion=usertypes.Completion.bookmark_by_url)
|
||||
@cmdutils.argument('url', completion=miscmodels.bookmark)
|
||||
def bookmark_del(self, url=None):
|
||||
"""Delete a bookmark.
|
||||
|
||||
@@ -1443,8 +1451,18 @@ class CommandDispatcher:
|
||||
download_manager.get_mhtml(tab, target)
|
||||
else:
|
||||
qnam = tab.networkaccessmanager()
|
||||
download_manager.get(self._current_url(), user_agent=user_agent,
|
||||
qnam=qnam, target=target)
|
||||
|
||||
suggested_fn = downloads.suggested_fn_from_title(
|
||||
self._current_url().path(), tab.title()
|
||||
)
|
||||
|
||||
download_manager.get(
|
||||
self._current_url(),
|
||||
user_agent=user_agent,
|
||||
qnam=qnam,
|
||||
target=target,
|
||||
suggested_fn=suggested_fn
|
||||
)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def view_source(self):
|
||||
@@ -1512,7 +1530,7 @@ class CommandDispatcher:
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', name='help',
|
||||
scope='window')
|
||||
@cmdutils.argument('topic', completion=usertypes.Completion.helptopic)
|
||||
@cmdutils.argument('topic', completion=miscmodels.helptopic)
|
||||
def show_help(self, tab=False, bg=False, window=False, topic=None):
|
||||
r"""Show help about a command or setting.
|
||||
|
||||
@@ -1523,7 +1541,7 @@ class CommandDispatcher:
|
||||
topic: The topic to show help for.
|
||||
|
||||
- :__command__ for commands.
|
||||
- __section__\->__option__ for settings.
|
||||
- __section__.__option__ for settings.
|
||||
"""
|
||||
if topic is None:
|
||||
path = 'index.html'
|
||||
@@ -1533,20 +1551,8 @@ class CommandDispatcher:
|
||||
raise cmdexc.CommandError("Invalid command {}!".format(
|
||||
command))
|
||||
path = 'commands.html#{}'.format(command)
|
||||
elif '->' in topic:
|
||||
parts = topic.split('->')
|
||||
if len(parts) != 2:
|
||||
raise cmdexc.CommandError("Invalid help topic {}!".format(
|
||||
topic))
|
||||
try:
|
||||
config.get(*parts)
|
||||
except configexc.NoSectionError:
|
||||
raise cmdexc.CommandError("Invalid section {}!".format(
|
||||
parts[0]))
|
||||
except configexc.NoOptionError:
|
||||
raise cmdexc.CommandError("Invalid option {}!".format(
|
||||
parts[1]))
|
||||
path = 'settings.html#{}'.format(topic.replace('->', '-'))
|
||||
elif topic in configdata.DATA:
|
||||
path = 'settings.html#{}'.format(topic)
|
||||
else:
|
||||
raise cmdexc.CommandError("Invalid help topic {}!".format(topic))
|
||||
url = QUrl('qute://help/{}'.format(path))
|
||||
@@ -1599,7 +1605,7 @@ class CommandDispatcher:
|
||||
"""Open an external editor with the currently selected form field.
|
||||
|
||||
The editor which should be launched can be configured via the
|
||||
`general -> editor` config option.
|
||||
`editor.command` config option.
|
||||
"""
|
||||
tab = self._current_widget()
|
||||
tab.elements.find_focused(self._open_editor_cb)
|
||||
@@ -1652,7 +1658,7 @@ class CommandDispatcher:
|
||||
hide=True)
|
||||
@cmdutils.argument('filter_', choices=['id'])
|
||||
def click_element(self, filter_: str, value, *,
|
||||
target: usertypes.ClickTarget=
|
||||
target: usertypes.ClickTarget =
|
||||
usertypes.ClickTarget.normal,
|
||||
force_event=False):
|
||||
"""Click the element matching the given filter.
|
||||
@@ -1733,13 +1739,14 @@ class CommandDispatcher:
|
||||
"""
|
||||
self.set_mark("'")
|
||||
tab = self._current_widget()
|
||||
tab.search.clear()
|
||||
if tab.search.search_displayed:
|
||||
tab.search.clear()
|
||||
|
||||
if not text:
|
||||
return
|
||||
|
||||
options = {
|
||||
'ignore_case': config.get('general', 'ignore-case'),
|
||||
'ignore_case': config.val.ignore_case,
|
||||
'reverse': reverse,
|
||||
}
|
||||
|
||||
@@ -2004,7 +2011,7 @@ class CommandDispatcher:
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
maxsplit=0, no_cmd_split=True)
|
||||
def jseval(self, js_code, file=False, quiet=False, *,
|
||||
world: typing.Union[usertypes.JsWorld, int]=None):
|
||||
world: typing.Union[usertypes.JsWorld, int] = None):
|
||||
"""Evaluate a JavaScript string.
|
||||
|
||||
Args:
|
||||
@@ -2038,8 +2045,9 @@ class CommandDispatcher:
|
||||
message.info(out)
|
||||
|
||||
if file:
|
||||
path = os.path.expanduser(js_code)
|
||||
try:
|
||||
with open(js_code, 'r', encoding='utf-8') as f:
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
js_code = f.read()
|
||||
except OSError as e:
|
||||
raise cmdexc.CommandError(str(e))
|
||||
@@ -2092,7 +2100,7 @@ class CommandDispatcher:
|
||||
"""Navigate to a url formed in an external editor.
|
||||
|
||||
The editor which should be launched can be configured via the
|
||||
`general -> editor` config option.
|
||||
`editor.command` config option.
|
||||
|
||||
Args:
|
||||
url: URL to edit; defaults to the current page url.
|
||||
@@ -2164,6 +2172,10 @@ class CommandDispatcher:
|
||||
|
||||
window = self._tabbed_browser.window()
|
||||
if window.isFullScreen():
|
||||
window.showNormal()
|
||||
window.setWindowState(
|
||||
window.state_before_fullscreen & ~Qt.WindowFullScreen)
|
||||
else:
|
||||
window.state_before_fullscreen = window.windowState()
|
||||
window.showFullScreen()
|
||||
log.misc.debug('state before fullscreen: {}'.format(
|
||||
debug.qflags_key(Qt, window.state_before_fullscreen)))
|
||||
|
||||
@@ -69,15 +69,22 @@ class UnsupportedOperationError(Exception):
|
||||
|
||||
def download_dir():
|
||||
"""Get the download directory to use."""
|
||||
directory = config.get('storage', 'download-directory')
|
||||
remember_dir = config.get('storage', 'remember-download-directory')
|
||||
directory = config.val.downloads.location.directory
|
||||
remember_dir = config.val.downloads.location.remember
|
||||
|
||||
if remember_dir and last_used_directory is not None:
|
||||
return last_used_directory
|
||||
ddir = last_used_directory
|
||||
elif directory is None:
|
||||
return standarddir.download()
|
||||
ddir = standarddir.download()
|
||||
else:
|
||||
return directory
|
||||
ddir = directory
|
||||
|
||||
try:
|
||||
os.makedirs(ddir)
|
||||
except FileExistsError:
|
||||
pass
|
||||
|
||||
return ddir
|
||||
|
||||
|
||||
def immediate_download_path(prompt_download_directory=None):
|
||||
@@ -88,11 +95,10 @@ def immediate_download_path(prompt_download_directory=None):
|
||||
Args:
|
||||
prompt_download_directory: If this is something else than None, it
|
||||
will overwrite the
|
||||
storage->prompt-download-directory setting.
|
||||
downloads.location.prompt setting.
|
||||
"""
|
||||
if prompt_download_directory is None:
|
||||
prompt_download_directory = config.get('storage',
|
||||
'prompt-download-directory')
|
||||
prompt_download_directory = config.val.downloads.location.prompt
|
||||
|
||||
if not prompt_download_directory:
|
||||
return download_dir()
|
||||
@@ -104,7 +110,7 @@ def _path_suggestion(filename):
|
||||
Args:
|
||||
filename: The filename to use if included in the suggestion.
|
||||
"""
|
||||
suggestion = config.get('completion', 'download-path-suggestion')
|
||||
suggestion = config.val.downloads.location.suggestion
|
||||
if suggestion == 'path':
|
||||
# add trailing '/' if not present
|
||||
return os.path.join(download_dir(), '')
|
||||
@@ -168,7 +174,7 @@ def transform_path(path):
|
||||
|
||||
Returns None if the path is invalid on the current platform.
|
||||
"""
|
||||
if sys.platform != "win32":
|
||||
if not utils.is_windows:
|
||||
return path
|
||||
path = utils.expand_windows_drive(path)
|
||||
# Drive dependent working directories are not supported, e.g.
|
||||
@@ -182,6 +188,28 @@ def transform_path(path):
|
||||
return path
|
||||
|
||||
|
||||
def suggested_fn_from_title(url_path, title=None):
|
||||
"""Suggest a filename depending on the URL extension and page title.
|
||||
|
||||
Args:
|
||||
url_path: a string with the URL path
|
||||
title: the page title string
|
||||
|
||||
Return:
|
||||
The download filename based on the title, or None if the extension is
|
||||
not found in the whitelist (or if there is no page title).
|
||||
"""
|
||||
ext_whitelist = [".html", ".htm", ".php", ""]
|
||||
_, ext = os.path.splitext(url_path)
|
||||
if ext.lower() in ext_whitelist and title:
|
||||
suggested_fn = utils.sanitize_filename(title)
|
||||
if not suggested_fn.lower().endswith((".html", ".htm")):
|
||||
suggested_fn += ".html"
|
||||
else:
|
||||
suggested_fn = None
|
||||
return suggested_fn
|
||||
|
||||
|
||||
class NoFilenameError(Exception):
|
||||
|
||||
"""Raised when we can't find out a filename in DownloadTarget."""
|
||||
@@ -472,13 +500,13 @@ class AbstractDownloadItem(QObject):
|
||||
Args:
|
||||
position: The color type requested, can be 'fg' or 'bg'.
|
||||
"""
|
||||
# pylint: disable=bad-config-call
|
||||
# WORKAROUND for https://bitbucket.org/logilab/astroid/issue/104/
|
||||
assert position in ["fg", "bg"]
|
||||
start = config.get('colors', 'downloads.{}.start'.format(position))
|
||||
stop = config.get('colors', 'downloads.{}.stop'.format(position))
|
||||
system = config.get('colors', 'downloads.{}.system'.format(position))
|
||||
error = config.get('colors', 'downloads.{}.error'.format(position))
|
||||
# pylint: disable=bad-config-option
|
||||
start = getattr(config.val.colors.downloads.start, position)
|
||||
stop = getattr(config.val.colors.downloads.stop, position)
|
||||
system = getattr(config.val.colors.downloads.system, position)
|
||||
error = getattr(config.val.colors.downloads.error, position)
|
||||
# pylint: enable=bad-config-option
|
||||
if self.error_msg is not None:
|
||||
assert not self.successful
|
||||
return error
|
||||
@@ -550,7 +578,7 @@ class AbstractDownloadItem(QObject):
|
||||
Args:
|
||||
cmdline: The command to use as string. A `{}` is expanded to the
|
||||
filename. None means to use the system's default
|
||||
application or `default-open-dispatcher` if set. If no
|
||||
application or `downloads.open_dispatcher` if set. If no
|
||||
`{}` is found, the filename is appended to the cmdline.
|
||||
"""
|
||||
assert self.successful
|
||||
@@ -735,7 +763,7 @@ class AbstractDownloadManager(QObject):
|
||||
download.remove_requested.connect(functools.partial(
|
||||
self._remove_item, download))
|
||||
|
||||
delay = config.get('ui', 'remove-finished-downloads')
|
||||
delay = config.val.downloads.remove_finished
|
||||
if delay > -1:
|
||||
download.finished.connect(
|
||||
lambda: QTimer.singleShot(delay, download.remove))
|
||||
|
||||
@@ -26,7 +26,7 @@ from PyQt5.QtCore import pyqtSlot, QSize, Qt, QTimer
|
||||
from PyQt5.QtWidgets import QListView, QSizePolicy, QMenu, QStyleFactory
|
||||
|
||||
from qutebrowser.browser import downloads
|
||||
from qutebrowser.config import style
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import qtutils, utils, objreg
|
||||
|
||||
|
||||
@@ -64,8 +64,8 @@ class DownloadView(QListView):
|
||||
|
||||
STYLESHEET = """
|
||||
QListView {
|
||||
background-color: {{ color['downloads.bg.bar'] }};
|
||||
font: {{ font['downloads'] }};
|
||||
background-color: {{ conf.colors.downloads.bar.bg }};
|
||||
font: {{ conf.fonts.downloads }};
|
||||
}
|
||||
|
||||
QListView::item {
|
||||
@@ -76,7 +76,7 @@ class DownloadView(QListView):
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setStyle(QStyleFactory.create('Fusion'))
|
||||
style.set_register_stylesheet(self)
|
||||
config.set_register_stylesheet(self)
|
||||
self.setResizeMode(QListView.Adjust)
|
||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)
|
||||
|
||||
@@ -26,10 +26,11 @@ import re
|
||||
import html
|
||||
from string import ascii_lowercase
|
||||
|
||||
import attr
|
||||
from PyQt5.QtCore import pyqtSlot, QObject, Qt, QUrl
|
||||
from PyQt5.QtWidgets import QLabel
|
||||
|
||||
from qutebrowser.config import config, style
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.keyinput import modeman, modeparsers
|
||||
from qutebrowser.browser import webelem
|
||||
from qutebrowser.commands import userscripts, cmdexc, cmdutils, runners
|
||||
@@ -65,10 +66,10 @@ class HintLabel(QLabel):
|
||||
|
||||
STYLESHEET = """
|
||||
QLabel {
|
||||
background-color: {{ color['hints.bg'] }};
|
||||
color: {{ color['hints.fg'] }};
|
||||
font: {{ font['hints'] }};
|
||||
border: {{ config.get('hints', 'border') }};
|
||||
background-color: {{ conf.colors.hints.bg }};
|
||||
color: {{ conf.colors.hints.fg }};
|
||||
font: {{ conf.fonts.hints }};
|
||||
border: {{ conf.hints.border }};
|
||||
padding-left: -3px;
|
||||
padding-right: -3px;
|
||||
}
|
||||
@@ -80,7 +81,7 @@ class HintLabel(QLabel):
|
||||
self.elem = elem
|
||||
|
||||
self.setAttribute(Qt.WA_StyledBackground, True)
|
||||
style.set_register_stylesheet(self)
|
||||
config.set_register_stylesheet(self)
|
||||
|
||||
self._context.tab.contents_size_changed.connect(self._move_to_elem)
|
||||
self._move_to_elem()
|
||||
@@ -100,7 +101,7 @@ class HintLabel(QLabel):
|
||||
matched: The part of the text which was typed.
|
||||
unmatched: The part of the text which was not typed yet.
|
||||
"""
|
||||
if (config.get('hints', 'uppercase') and
|
||||
if (config.val.hints.uppercase and
|
||||
self._context.hint_mode in ['letter', 'word']):
|
||||
matched = html.escape(matched.upper())
|
||||
unmatched = html.escape(unmatched.upper())
|
||||
@@ -108,7 +109,7 @@ class HintLabel(QLabel):
|
||||
matched = html.escape(matched)
|
||||
unmatched = html.escape(unmatched)
|
||||
|
||||
match_color = html.escape(config.get('colors', 'hints.fg.match'))
|
||||
match_color = html.escape(config.val.colors.hints.match.fg)
|
||||
self.setText('<font color="{}">{}</font>{}'.format(
|
||||
match_color, matched, unmatched))
|
||||
self.adjustSize()
|
||||
@@ -121,7 +122,7 @@ class HintLabel(QLabel):
|
||||
log.hints.debug("Frame for {!r} vanished!".format(self))
|
||||
self.hide()
|
||||
return
|
||||
no_js = config.get('hints', 'find-implementation') != 'javascript'
|
||||
no_js = config.val.hints.find_implementation != 'javascript'
|
||||
rect = self.elem.rect_on_view(no_js=no_js)
|
||||
self.move(rect.x(), rect.y())
|
||||
|
||||
@@ -131,6 +132,7 @@ class HintLabel(QLabel):
|
||||
self.deleteLater()
|
||||
|
||||
|
||||
@attr.s
|
||||
class HintContext:
|
||||
|
||||
"""Context namespace used for hinting.
|
||||
@@ -158,19 +160,18 @@ class HintContext:
|
||||
group: The group of web elements to hint.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.all_labels = []
|
||||
self.labels = {}
|
||||
self.target = None
|
||||
self.baseurl = None
|
||||
self.to_follow = None
|
||||
self.rapid = False
|
||||
self.add_history = False
|
||||
self.filterstr = None
|
||||
self.args = []
|
||||
self.tab = None
|
||||
self.group = None
|
||||
self.hint_mode = None
|
||||
all_labels = attr.ib(attr.Factory(list))
|
||||
labels = attr.ib(attr.Factory(dict))
|
||||
target = attr.ib(None)
|
||||
baseurl = attr.ib(None)
|
||||
to_follow = attr.ib(None)
|
||||
rapid = attr.ib(False)
|
||||
add_history = attr.ib(False)
|
||||
filterstr = attr.ib(None)
|
||||
args = attr.ib(attr.Factory(list))
|
||||
tab = attr.ib(None)
|
||||
group = attr.ib(None)
|
||||
hint_mode = attr.ib(None)
|
||||
|
||||
def get_args(self, urlstr):
|
||||
"""Get the arguments, with {hint-url} replaced by the given URL."""
|
||||
@@ -203,7 +204,7 @@ class HintActions:
|
||||
Target.window: usertypes.ClickTarget.window,
|
||||
Target.hover: usertypes.ClickTarget.normal,
|
||||
}
|
||||
if config.get('tabs', 'background-tabs'):
|
||||
if config.val.tabs.background:
|
||||
target_mapping[Target.tab] = usertypes.ClickTarget.tab_bg
|
||||
else:
|
||||
target_mapping[Target.tab] = usertypes.ClickTarget.tab
|
||||
@@ -389,6 +390,7 @@ class HintManager(QObject):
|
||||
|
||||
def _cleanup(self):
|
||||
"""Clean up after hinting."""
|
||||
# pylint: disable=not-an-iterable
|
||||
for label in self._context.all_labels:
|
||||
label.cleanup()
|
||||
|
||||
@@ -421,9 +423,9 @@ class HintManager(QObject):
|
||||
if hint_mode == 'number':
|
||||
chars = '0123456789'
|
||||
else:
|
||||
chars = config.get('hints', 'chars')
|
||||
min_chars = config.get('hints', 'min-chars')
|
||||
if config.get('hints', 'scatter') and hint_mode != 'number':
|
||||
chars = config.val.hints.chars
|
||||
min_chars = config.val.hints.min_chars
|
||||
if config.val.hints.scatter and hint_mode != 'number':
|
||||
return self._hint_scattered(min_chars, chars, elems)
|
||||
else:
|
||||
return self._hint_linear(min_chars, chars, elems)
|
||||
@@ -603,7 +605,7 @@ class HintManager(QObject):
|
||||
modeman.enter(self._win_id, usertypes.KeyMode.hint,
|
||||
'HintManager.start')
|
||||
|
||||
# to make auto-follow == 'always' work
|
||||
# to make auto_follow == 'always' work
|
||||
self._handle_auto_follow()
|
||||
|
||||
@cmdutils.register(instance='hintmanager', scope='tab', name='hint',
|
||||
@@ -615,7 +617,7 @@ class HintManager(QObject):
|
||||
|
||||
Args:
|
||||
rapid: Whether to do rapid hinting. This is only possible with
|
||||
targets `tab` (with background-tabs=true), `tab-bg`,
|
||||
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.
|
||||
@@ -631,7 +633,7 @@ class HintManager(QObject):
|
||||
- `normal`: Open the link.
|
||||
- `current`: Open the link in the current tab.
|
||||
- `tab`: Open the link in a new tab (honoring the
|
||||
background-tabs setting).
|
||||
`tabs.background_tabs` setting).
|
||||
- `tab-fg`: Open the link in a new foreground tab.
|
||||
- `tab-bg`: Open the link in a new background tab.
|
||||
- `window`: Open the link in a new window.
|
||||
@@ -649,7 +651,7 @@ class HintManager(QObject):
|
||||
mode: The hinting mode to use.
|
||||
|
||||
- `number`: Use numeric hints.
|
||||
- `letter`: Use the chars in the hints->chars settings.
|
||||
- `letter`: Use the chars in the hints.chars setting.
|
||||
- `word`: Use hint words based on the html elements and the
|
||||
extra words.
|
||||
|
||||
@@ -684,8 +686,7 @@ class HintManager(QObject):
|
||||
Target.hover, Target.userscript, Target.spawn,
|
||||
Target.download, Target.normal, Target.current]:
|
||||
pass
|
||||
elif (target == Target.tab and
|
||||
config.get('tabs', 'background-tabs')):
|
||||
elif target == Target.tab and config.val.tabs.background:
|
||||
pass
|
||||
else:
|
||||
name = target.name.replace('_', '-')
|
||||
@@ -693,7 +694,7 @@ class HintManager(QObject):
|
||||
"target {}!".format(name))
|
||||
|
||||
if mode is None:
|
||||
mode = config.get('hints', 'mode')
|
||||
mode = config.val.hints.mode
|
||||
|
||||
self._check_args(target, *args)
|
||||
self._context = HintContext()
|
||||
@@ -720,7 +721,7 @@ class HintManager(QObject):
|
||||
return self._context.hint_mode
|
||||
|
||||
def _handle_auto_follow(self, keystr="", filterstr="", visible=None):
|
||||
"""Handle the auto-follow option."""
|
||||
"""Handle the auto_follow option."""
|
||||
if visible is None:
|
||||
visible = {string: label
|
||||
for string, label in self._context.labels.items()
|
||||
@@ -729,7 +730,7 @@ class HintManager(QObject):
|
||||
if len(visible) != 1:
|
||||
return
|
||||
|
||||
auto_follow = config.get('hints', 'auto-follow')
|
||||
auto_follow = config.val.hints.auto_follow
|
||||
|
||||
if auto_follow == "always":
|
||||
follow = True
|
||||
@@ -746,8 +747,8 @@ class HintManager(QObject):
|
||||
self._context.to_follow = list(visible.keys())[0]
|
||||
|
||||
if follow:
|
||||
# apply auto-follow-timeout
|
||||
timeout = config.get('hints', 'auto-follow-timeout')
|
||||
# apply auto_follow_timeout
|
||||
timeout = config.val.hints.auto_follow_timeout
|
||||
keyparsers = objreg.get('keyparsers', scope='window',
|
||||
window=self._win_id)
|
||||
normal_parser = keyparsers[usertypes.KeyMode.normal]
|
||||
@@ -771,9 +772,9 @@ class HintManager(QObject):
|
||||
label.show()
|
||||
else:
|
||||
# element doesn't match anymore -> hide it, unless in rapid
|
||||
# mode and hide-unmatched-rapid-hints is false (see #1799)
|
||||
# mode and hide_unmatched_rapid_hints is false (see #1799)
|
||||
if (not self._context.rapid or
|
||||
config.get('hints', 'hide-unmatched-rapid-hints')):
|
||||
config.val.hints.hide_unmatched_rapid_hints):
|
||||
label.hide()
|
||||
except webelem.Error:
|
||||
pass
|
||||
@@ -793,7 +794,10 @@ class HintManager(QObject):
|
||||
else:
|
||||
self._context.filterstr = filterstr
|
||||
|
||||
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)):
|
||||
@@ -938,7 +942,7 @@ class WordHinter:
|
||||
|
||||
def ensure_initialized(self):
|
||||
"""Generate the used words if yet uninitialized."""
|
||||
dictionary = config.get("hints", "dictionary")
|
||||
dictionary = config.val.hints.dictionary
|
||||
if not self.words or self.dictionary != dictionary:
|
||||
self.words.clear()
|
||||
self.dictionary = dictionary
|
||||
|
||||
@@ -19,214 +19,110 @@
|
||||
|
||||
"""Simple history which gets written to disk."""
|
||||
|
||||
import os
|
||||
import time
|
||||
import collections
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, QObject
|
||||
from PyQt5.QtCore import pyqtSlot, QUrl, QTimer
|
||||
|
||||
from qutebrowser.commands import cmdutils
|
||||
from qutebrowser.utils import (utils, objreg, standarddir, log, qtutils,
|
||||
usertypes, message)
|
||||
from qutebrowser.misc import lineparser, objects
|
||||
from qutebrowser.commands import cmdutils, cmdexc
|
||||
from qutebrowser.utils import (utils, objreg, log, usertypes, message,
|
||||
debug, standarddir, qtutils)
|
||||
from qutebrowser.misc import objects, sql
|
||||
|
||||
|
||||
class Entry:
|
||||
|
||||
"""A single entry in the web history.
|
||||
|
||||
Attributes:
|
||||
atime: The time the page was accessed.
|
||||
url: The URL which was accessed as QUrl.
|
||||
redirect: If True, don't save this entry to disk
|
||||
"""
|
||||
|
||||
def __init__(self, atime, url, title, redirect=False):
|
||||
self.atime = float(atime)
|
||||
self.url = url
|
||||
self.title = title
|
||||
self.redirect = redirect
|
||||
qtutils.ensure_valid(url)
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, constructor=True, atime=self.atime,
|
||||
url=self.url_str(), title=self.title,
|
||||
redirect=self.redirect)
|
||||
|
||||
def __str__(self):
|
||||
atime = str(int(self.atime))
|
||||
if self.redirect:
|
||||
atime += '-r' # redirect flag
|
||||
elems = [atime, self.url_str()]
|
||||
if self.title:
|
||||
elems.append(self.title)
|
||||
return ' '.join(elems)
|
||||
|
||||
def __eq__(self, other):
|
||||
return (self.atime == other.atime and
|
||||
self.title == other.title and
|
||||
self.url == other.url and
|
||||
self.redirect == other.redirect)
|
||||
|
||||
def url_str(self):
|
||||
"""Get the URL as a lossless string."""
|
||||
return self.url.toString(QUrl.FullyEncoded | QUrl.RemovePassword)
|
||||
|
||||
@classmethod
|
||||
def from_str(cls, line):
|
||||
"""Parse a history line like '12345 http://example.com title'."""
|
||||
data = line.split(maxsplit=2)
|
||||
if len(data) == 2:
|
||||
atime, url = data
|
||||
title = ""
|
||||
elif len(data) == 3:
|
||||
atime, url, title = data
|
||||
else:
|
||||
raise ValueError("2 or 3 fields expected")
|
||||
|
||||
url = QUrl(url)
|
||||
if not url.isValid():
|
||||
raise ValueError("Invalid URL: {}".format(url.errorString()))
|
||||
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/670
|
||||
atime = atime.lstrip('\0')
|
||||
|
||||
if '-' in atime:
|
||||
atime, flags = atime.split('-')
|
||||
else:
|
||||
flags = ''
|
||||
|
||||
if not set(flags).issubset('r'):
|
||||
raise ValueError("Invalid flags {!r}".format(flags))
|
||||
|
||||
redirect = 'r' in flags
|
||||
|
||||
return cls(atime, url, title, redirect=redirect)
|
||||
# increment to indicate that HistoryCompletion must be regenerated
|
||||
_USER_VERSION = 1
|
||||
|
||||
|
||||
class WebHistory(QObject):
|
||||
class CompletionHistory(sql.SqlTable):
|
||||
|
||||
"""The global history of visited pages.
|
||||
"""History which only has the newest entry for each URL."""
|
||||
|
||||
This is a little more complex as you'd expect so the history can be read
|
||||
from disk async while new history is already arriving.
|
||||
def __init__(self, parent=None):
|
||||
super().__init__("CompletionHistory", ['url', 'title', 'last_atime'],
|
||||
constraints={'url': 'PRIMARY KEY',
|
||||
'title': 'NOT NULL',
|
||||
'last_atime': 'NOT NULL'},
|
||||
parent=parent)
|
||||
self.create_index('CompletionHistoryAtimeIndex', 'last_atime')
|
||||
|
||||
self.history_dict is the main place where the history is stored, in an
|
||||
OrderedDict (sorted by time) of URL strings mapped to Entry objects.
|
||||
|
||||
While reading from disk is still ongoing, the history is saved in
|
||||
self._temp_history instead, and then appended to self.history_dict once
|
||||
that's fully populated.
|
||||
class WebHistory(sql.SqlTable):
|
||||
|
||||
All history which is new in this session (rather than read from disk from a
|
||||
previous browsing session) is also stored in self._new_history.
|
||||
self._saved_count tracks how many of those entries were already written to
|
||||
disk, so we can always append to the existing data.
|
||||
"""The global history of visited pages."""
|
||||
|
||||
Attributes:
|
||||
history_dict: An OrderedDict of URLs read from the on-disk history.
|
||||
_lineparser: The AppendLineParser used to save the history.
|
||||
_new_history: A list of Entry items of the current session.
|
||||
_saved_count: How many HistoryEntries have been written to disk.
|
||||
_initial_read_started: Whether async_read was called.
|
||||
_initial_read_done: Whether async_read has completed.
|
||||
_temp_history: OrderedDict of temporary history entries before
|
||||
async_read was called.
|
||||
def __init__(self, parent=None):
|
||||
super().__init__("History", ['url', 'title', 'atime', 'redirect'],
|
||||
constraints={'url': 'NOT NULL',
|
||||
'title': 'NOT NULL',
|
||||
'atime': 'NOT NULL',
|
||||
'redirect': 'NOT NULL'},
|
||||
parent=parent)
|
||||
self.completion = CompletionHistory(parent=self)
|
||||
if sql.Query('pragma user_version').run().value() < _USER_VERSION:
|
||||
self.completion.delete_all()
|
||||
if not self.completion:
|
||||
# either the table is out-of-date or the user wiped it manually
|
||||
self._rebuild_completion()
|
||||
self.create_index('HistoryIndex', 'url')
|
||||
self.create_index('HistoryAtimeIndex', 'atime')
|
||||
self._contains_query = self.contains_query('url')
|
||||
self._between_query = sql.Query('SELECT * FROM History '
|
||||
'where not redirect '
|
||||
'and not url like "qute://%" '
|
||||
'and atime > :earliest '
|
||||
'and atime <= :latest '
|
||||
'ORDER BY atime desc')
|
||||
|
||||
Signals:
|
||||
add_completion_item: Emitted before a new Entry is added.
|
||||
Used to sync with the completion.
|
||||
arg: The new Entry.
|
||||
item_added: Emitted after a new Entry is added.
|
||||
Used to tell the savemanager that the history is dirty.
|
||||
arg: The new Entry.
|
||||
cleared: Emitted after the history is cleared.
|
||||
"""
|
||||
|
||||
add_completion_item = pyqtSignal(Entry)
|
||||
item_added = pyqtSignal(Entry)
|
||||
cleared = pyqtSignal()
|
||||
async_read_done = pyqtSignal()
|
||||
|
||||
def __init__(self, hist_dir, hist_name, parent=None):
|
||||
super().__init__(parent)
|
||||
self._initial_read_started = False
|
||||
self._initial_read_done = False
|
||||
self._lineparser = lineparser.AppendLineParser(hist_dir, hist_name,
|
||||
parent=self)
|
||||
self.history_dict = collections.OrderedDict()
|
||||
self._temp_history = collections.OrderedDict()
|
||||
self._new_history = []
|
||||
self._saved_count = 0
|
||||
objreg.get('save-manager').add_saveable(
|
||||
'history', self.save, self.item_added)
|
||||
self._before_query = sql.Query('SELECT * FROM History '
|
||||
'where not redirect '
|
||||
'and not url like "qute://%" '
|
||||
'and atime <= :latest '
|
||||
'ORDER BY atime desc '
|
||||
'limit :limit offset :offset')
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, length=len(self))
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.history_dict.values())
|
||||
def __contains__(self, url):
|
||||
return self._contains_query.run(val=url).value()
|
||||
|
||||
def __len__(self):
|
||||
return len(self.history_dict)
|
||||
|
||||
def async_read(self):
|
||||
"""Read the initial history."""
|
||||
if self._initial_read_started:
|
||||
log.init.debug("Ignoring async_read() because reading is started.")
|
||||
return
|
||||
self._initial_read_started = True
|
||||
|
||||
with self._lineparser.open():
|
||||
for line in self._lineparser:
|
||||
yield
|
||||
|
||||
line = line.rstrip()
|
||||
if not line:
|
||||
continue
|
||||
|
||||
try:
|
||||
entry = Entry.from_str(line)
|
||||
except ValueError as e:
|
||||
log.init.warning("Invalid history entry {!r}: {}!".format(
|
||||
line, e))
|
||||
continue
|
||||
|
||||
# This de-duplicates history entries; only the latest
|
||||
# entry for each URL is kept. If you want to keep
|
||||
# information about previous hits change the items in
|
||||
# old_urls to be lists or change Entry to have a
|
||||
# list of atimes.
|
||||
self._add_entry(entry)
|
||||
|
||||
self._initial_read_done = True
|
||||
self.async_read_done.emit()
|
||||
|
||||
for entry in self._temp_history.values():
|
||||
self._add_entry(entry)
|
||||
self._new_history.append(entry)
|
||||
if not entry.redirect:
|
||||
self.add_completion_item.emit(entry)
|
||||
self._temp_history.clear()
|
||||
|
||||
def _add_entry(self, entry, target=None):
|
||||
"""Add an entry to self.history_dict or another given OrderedDict."""
|
||||
if target is None:
|
||||
target = self.history_dict
|
||||
url_str = entry.url_str()
|
||||
target[url_str] = entry
|
||||
target.move_to_end(url_str)
|
||||
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')
|
||||
for entry in q.run():
|
||||
data['url'].append(self._format_completion_url(QUrl(entry.url)))
|
||||
data['title'].append(entry.title)
|
||||
data['last_atime'].append(entry.atime)
|
||||
self.completion.insert_batch(data, replace=True)
|
||||
sql.Query('pragma user_version = {}'.format(_USER_VERSION)).run()
|
||||
|
||||
def get_recent(self):
|
||||
"""Get the most recent history entries."""
|
||||
old = self._lineparser.get_recent()
|
||||
return old + [str(e) for e in self._new_history]
|
||||
return self.select(sort_by='atime', sort_order='desc', limit=100)
|
||||
|
||||
def save(self):
|
||||
"""Save the history to disk."""
|
||||
new = (str(e) for e in self._new_history[self._saved_count:])
|
||||
self._lineparser.new_data = new
|
||||
self._lineparser.save()
|
||||
self._saved_count = len(self._new_history)
|
||||
def entries_between(self, earliest, latest):
|
||||
"""Iterate non-redirect, non-qute entries between two timestamps.
|
||||
|
||||
Args:
|
||||
earliest: Omit timestamps earlier than this.
|
||||
latest: Omit timestamps later than this.
|
||||
"""
|
||||
self._between_query.run(earliest=earliest, latest=latest)
|
||||
return iter(self._between_query)
|
||||
|
||||
def entries_before(self, latest, limit, offset):
|
||||
"""Iterate non-redirect, non-qute entries occurring before a timestamp.
|
||||
|
||||
Args:
|
||||
latest: Omit timestamps more recent than this.
|
||||
limit: Max number of entries to include.
|
||||
offset: Number of entries to skip.
|
||||
"""
|
||||
self._before_query.run(latest=latest, limit=limit, offset=offset)
|
||||
return iter(self._before_query)
|
||||
|
||||
@cmdutils.register(name='history-clear', instance='web-history')
|
||||
def clear(self, force=False):
|
||||
@@ -243,15 +139,22 @@ class WebHistory(QObject):
|
||||
self._do_clear()
|
||||
else:
|
||||
message.confirm_async(self._do_clear, title="Clear all browsing "
|
||||
"history?")
|
||||
"history?")
|
||||
|
||||
def _do_clear(self):
|
||||
self._lineparser.clear()
|
||||
self.history_dict.clear()
|
||||
self._temp_history.clear()
|
||||
self._new_history.clear()
|
||||
self._saved_count = 0
|
||||
self.cleared.emit()
|
||||
self.delete_all()
|
||||
self.completion.delete_all()
|
||||
|
||||
def delete_url(self, url):
|
||||
"""Remove all history entries with the given url.
|
||||
|
||||
Args:
|
||||
url: URL string to delete.
|
||||
"""
|
||||
qurl = QUrl(url)
|
||||
qtutils.ensure_valid(qurl)
|
||||
self.delete('url', self._format_url(qurl))
|
||||
self.completion.delete('url', self._format_completion_url(qurl))
|
||||
|
||||
@pyqtSlot(QUrl, QUrl, str)
|
||||
def add_from_tab(self, url, requested_url, title):
|
||||
@@ -279,23 +182,158 @@ class WebHistory(QObject):
|
||||
(hidden in completion)
|
||||
atime: Override the atime used to add the entry
|
||||
"""
|
||||
if not url.isValid(): # pragma: no cover
|
||||
# the no cover pragma is a WORKAROUND for this not being covered in
|
||||
# old Qt versions.
|
||||
if not url.isValid():
|
||||
log.misc.warning("Ignoring invalid URL being added to history")
|
||||
return
|
||||
|
||||
if atime is None:
|
||||
atime = time.time()
|
||||
entry = Entry(atime, url, title, redirect=redirect)
|
||||
if self._initial_read_done:
|
||||
self._add_entry(entry)
|
||||
self._new_history.append(entry)
|
||||
self.item_added.emit(entry)
|
||||
if not entry.redirect:
|
||||
self.add_completion_item.emit(entry)
|
||||
if 'no-sql-history' in objreg.get('args').debug_flags:
|
||||
return
|
||||
|
||||
atime = int(atime) if (atime is not None) else int(time.time())
|
||||
|
||||
try:
|
||||
self.insert({'url': self._format_url(url),
|
||||
'title': title,
|
||||
'atime': atime,
|
||||
'redirect': redirect})
|
||||
if not redirect:
|
||||
self.completion.insert({
|
||||
'url': self._format_completion_url(url),
|
||||
'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'."""
|
||||
if not line or line.startswith('#'):
|
||||
return None
|
||||
data = line.split(maxsplit=2)
|
||||
if len(data) == 2:
|
||||
atime, url = data
|
||||
title = ""
|
||||
elif len(data) == 3:
|
||||
atime, url, title = data
|
||||
else:
|
||||
self._add_entry(entry, target=self._temp_history)
|
||||
raise ValueError("2 or 3 fields expected")
|
||||
|
||||
# http://xn--pple-43d.com/ with
|
||||
# https://bugreports.qt.io/browse/QTBUG-60364
|
||||
if url in ['http://.com/', 'https://.com/',
|
||||
'http://www..com/', 'https://www..com/']:
|
||||
return None
|
||||
|
||||
url = QUrl(url)
|
||||
if not url.isValid():
|
||||
raise ValueError("Invalid URL: {}".format(url.errorString()))
|
||||
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/2646
|
||||
if url.scheme() == 'data':
|
||||
return None
|
||||
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/670
|
||||
atime = atime.lstrip('\0')
|
||||
|
||||
if '-' in atime:
|
||||
atime, flags = atime.split('-')
|
||||
else:
|
||||
flags = ''
|
||||
|
||||
if not set(flags).issubset('r'):
|
||||
raise ValueError("Invalid flags {!r}".format(flags))
|
||||
|
||||
redirect = 'r' in flags
|
||||
return (url, title, int(atime), redirect)
|
||||
|
||||
def import_txt(self):
|
||||
"""Import a history text file into sqlite if it exists.
|
||||
|
||||
In older versions of qutebrowser, history was stored in a text format.
|
||||
This converts that file into the new sqlite format and moves it to a
|
||||
backup location.
|
||||
"""
|
||||
path = os.path.join(standarddir.data(), 'history')
|
||||
if not os.path.isfile(path):
|
||||
return
|
||||
|
||||
def action():
|
||||
with debug.log_time(log.init, 'Import old history file to sqlite'):
|
||||
try:
|
||||
self._read(path)
|
||||
except ValueError as ex:
|
||||
message.error('Failed to import history: {}'.format(ex))
|
||||
else:
|
||||
self._write_backup(path)
|
||||
|
||||
# delay to give message time to appear before locking down for import
|
||||
message.info('Converting {} to sqlite...'.format(path))
|
||||
QTimer.singleShot(100, action)
|
||||
|
||||
def _read(self, path):
|
||||
"""Import a text file into the sql database."""
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
data = {'url': [], 'title': [], 'atime': [], 'redirect': []}
|
||||
completion_data = {'url': [], 'title': [], 'last_atime': []}
|
||||
for (i, line) in enumerate(f):
|
||||
try:
|
||||
parsed = self._parse_entry(line.strip())
|
||||
if parsed is None:
|
||||
continue
|
||||
url, title, atime, redirect = parsed
|
||||
data['url'].append(self._format_url(url))
|
||||
data['title'].append(title)
|
||||
data['atime'].append(atime)
|
||||
data['redirect'].append(redirect)
|
||||
if not redirect:
|
||||
completion_data['url'].append(
|
||||
self._format_completion_url(url))
|
||||
completion_data['title'].append(title)
|
||||
completion_data['last_atime'].append(atime)
|
||||
except ValueError as ex:
|
||||
raise ValueError('Failed to parse line #{} of {}: "{}"'
|
||||
.format(i, path, ex))
|
||||
self.insert_batch(data)
|
||||
self.completion.insert_batch(completion_data, replace=True)
|
||||
|
||||
def _write_backup(self, path):
|
||||
bak = path + '.bak'
|
||||
message.info('History import complete. Appending {} to {}'
|
||||
.format(path, bak))
|
||||
with open(path, 'r', encoding='utf-8') as infile:
|
||||
with open(bak, 'a', encoding='utf-8') as outfile:
|
||||
for line in infile:
|
||||
outfile.write('\n' + line)
|
||||
os.remove(path)
|
||||
|
||||
def _format_url(self, url):
|
||||
return url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
|
||||
def _format_completion_url(self, url):
|
||||
return url.toString(QUrl.RemovePassword)
|
||||
|
||||
@cmdutils.register(instance='web-history', debug=True)
|
||||
def debug_dump_history(self, dest):
|
||||
"""Dump the history to a file in the old pre-SQL format.
|
||||
|
||||
Args:
|
||||
dest: Where to write the file to.
|
||||
"""
|
||||
dest = os.path.expanduser(dest)
|
||||
|
||||
lines = ('{}{} {} {}'
|
||||
.format(int(x.atime), '-r' * x.redirect, x.url, x.title)
|
||||
for x in self.select(sort_by='atime', sort_order='asc'))
|
||||
|
||||
try:
|
||||
with open(dest, 'w', encoding='utf-8') as f:
|
||||
f.write('\n'.join(lines))
|
||||
message.info("Dumped history to {}".format(dest))
|
||||
except OSError as e:
|
||||
raise cmdexc.CommandError('Could not write history: {}', e)
|
||||
|
||||
|
||||
def init(parent=None):
|
||||
@@ -304,10 +342,9 @@ def init(parent=None):
|
||||
Args:
|
||||
parent: The parent to use for WebHistory.
|
||||
"""
|
||||
history = WebHistory(hist_dir=standarddir.data(), hist_name='history',
|
||||
parent=parent)
|
||||
history = WebHistory(parent=parent)
|
||||
objreg.register('web-history', history)
|
||||
|
||||
if objects.backend == usertypes.Backend.QtWebKit:
|
||||
if objects.backend == usertypes.Backend.QtWebKit: # pragma: no cover
|
||||
from qutebrowser.browser.webkit import webkithistory
|
||||
webkithistory.init(history)
|
||||
|
||||
@@ -24,7 +24,8 @@ import binascii
|
||||
|
||||
from PyQt5.QtWidgets import QWidget
|
||||
|
||||
from qutebrowser.utils import log, objreg, usertypes
|
||||
from qutebrowser.config import configfiles
|
||||
from qutebrowser.utils import log, usertypes
|
||||
from qutebrowser.misc import miscwidgets, objects
|
||||
|
||||
|
||||
@@ -67,9 +68,8 @@ class AbstractWebInspector(QWidget):
|
||||
|
||||
def _load_state_geometry(self):
|
||||
"""Load the geometry from the state file."""
|
||||
state_config = objreg.get('state-config')
|
||||
try:
|
||||
data = state_config['geometry']['inspector']
|
||||
data = configfiles.state['geometry']['inspector']
|
||||
geom = base64.b64decode(data, validate=True)
|
||||
except KeyError:
|
||||
# First start
|
||||
@@ -84,10 +84,9 @@ class AbstractWebInspector(QWidget):
|
||||
|
||||
def closeEvent(self, e):
|
||||
"""Save the geometry when closed."""
|
||||
state_config = objreg.get('state-config')
|
||||
data = bytes(self.saveGeometry())
|
||||
geom = base64.b64encode(data).decode('ASCII')
|
||||
state_config['geometry']['inspector'] = geom
|
||||
configfiles.state['geometry']['inspector'] = geom
|
||||
super().closeEvent(e)
|
||||
|
||||
def inspect(self, page):
|
||||
|
||||
@@ -85,7 +85,7 @@ class MouseEventFilter(QObject):
|
||||
|
||||
def _handle_mouse_press(self, e):
|
||||
"""Handle pressing of a mouse button."""
|
||||
is_rocker_gesture = (config.get('input', 'rocker-gestures') and
|
||||
is_rocker_gesture = (config.val.input.rocker_gestures and
|
||||
e.buttons() == Qt.LeftButton | Qt.RightButton)
|
||||
|
||||
if e.button() in [Qt.XButton1, Qt.XButton2] or is_rocker_gesture:
|
||||
@@ -119,7 +119,7 @@ class MouseEventFilter(QObject):
|
||||
return True
|
||||
|
||||
if e.modifiers() & Qt.ControlModifier:
|
||||
divider = config.get('input', 'mouse-zoom-divider')
|
||||
divider = config.val.zoom.mouse_divider
|
||||
if divider == 0:
|
||||
return False
|
||||
factor = self._tab.zoom.factor() + (e.angleDelta().y() / divider)
|
||||
@@ -139,7 +139,7 @@ class MouseEventFilter(QObject):
|
||||
|
||||
def _handle_context_menu(self, _e):
|
||||
"""Suppress context menus if rocker gestures are turned on."""
|
||||
return config.get('input', 'rocker-gestures')
|
||||
return config.val.input.rocker_gestures
|
||||
|
||||
def _mousepress_insertmode_cb(self, elem):
|
||||
"""Check if the clicked element is editable."""
|
||||
@@ -157,7 +157,7 @@ class MouseEventFilter(QObject):
|
||||
'click', only_if_normal=True)
|
||||
else:
|
||||
log.mouse.debug("Clicked non-editable element!")
|
||||
if config.get('input', 'auto-leave-insert-mode'):
|
||||
if config.val.input.insert_mode.auto_leave:
|
||||
modeman.leave(self._tab.win_id, usertypes.KeyMode.insert,
|
||||
'click', maybe=True)
|
||||
|
||||
@@ -179,7 +179,7 @@ class MouseEventFilter(QObject):
|
||||
'click-delayed', only_if_normal=True)
|
||||
else:
|
||||
log.mouse.debug("Clicked non-editable element (delayed)!")
|
||||
if config.get('input', 'auto-leave-insert-mode'):
|
||||
if config.val.input.insert_mode.auto_leave:
|
||||
modeman.leave(self._tab.win_id, usertypes.KeyMode.insert,
|
||||
'click-delayed', maybe=True)
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import posixpath
|
||||
from qutebrowser.browser import webelem
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import objreg, urlutils, log, message, qtutils
|
||||
from qutebrowser.mainwindow import mainwindow
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
@@ -42,7 +43,7 @@ def incdec(url, count, inc_or_dec):
|
||||
background: Open the link in a new background tab.
|
||||
window: Open the link in a new window.
|
||||
"""
|
||||
segments = set(config.get('general', 'url-incdec-segments'))
|
||||
segments = set(config.val.url.incdec_segments)
|
||||
try:
|
||||
new_url = urlutils.incdec_number(url, inc_or_dec, count,
|
||||
segments=segments)
|
||||
@@ -80,10 +81,13 @@ def _find_prevnext(prev, elems):
|
||||
|
||||
# Then check for regular links/buttons.
|
||||
elems = [e for e in elems if e.tag_name() != 'link']
|
||||
option = 'prev-regexes' if prev else 'next-regexes'
|
||||
option = 'prev_regexes' if prev else 'next_regexes'
|
||||
if not elems:
|
||||
return None
|
||||
for regex in config.get('hints', option):
|
||||
|
||||
# pylint: disable=bad-config-option
|
||||
for regex in getattr(config.val.hints, option):
|
||||
# pylint: enable=bad-config-option
|
||||
log.hints.vdebug("== Checking regex '{}'.".format(regex.pattern))
|
||||
for e in elems:
|
||||
text = str(e)
|
||||
@@ -131,7 +135,6 @@ def prevnext(*, browsertab, win_id, baseurl, prev=False,
|
||||
window=win_id)
|
||||
|
||||
if window:
|
||||
from qutebrowser.mainwindow import mainwindow
|
||||
new_window = mainwindow.MainWindow(
|
||||
private=cur_tabbed_browser.private)
|
||||
new_window.show()
|
||||
|
||||
@@ -247,10 +247,21 @@ class PACFetcher(QObject):
|
||||
self._pac_url = url
|
||||
self._manager = QNetworkAccessManager()
|
||||
self._manager.setProxy(QNetworkProxy(QNetworkProxy.NoProxy))
|
||||
self._reply = self._manager.get(QNetworkRequest(url))
|
||||
self._reply.finished.connect(self._finish)
|
||||
self._pac = None
|
||||
self._error_message = None
|
||||
self._reply = None
|
||||
|
||||
def __eq__(self, other):
|
||||
# pylint: disable=protected-access
|
||||
return self._pac_url == other._pac_url
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, url=self._pac_url, constructor=True)
|
||||
|
||||
def fetch(self):
|
||||
"""Fetch the proxy from the remote URL."""
|
||||
self._reply = self._manager.get(QNetworkRequest(self._pac_url))
|
||||
self._reply.finished.connect(self._finish)
|
||||
|
||||
@pyqtSlot()
|
||||
def _finish(self):
|
||||
|
||||
@@ -44,7 +44,7 @@ class ProxyFactory(QNetworkProxyFactory):
|
||||
Return:
|
||||
None if proxy is correct, otherwise an error message.
|
||||
"""
|
||||
proxy = config.get('network', 'proxy')
|
||||
proxy = config.val.content.proxy
|
||||
if isinstance(proxy, pac.PACFetcher):
|
||||
return proxy.fetch_error()
|
||||
else:
|
||||
@@ -59,7 +59,7 @@ class ProxyFactory(QNetworkProxyFactory):
|
||||
Return:
|
||||
A list of QNetworkProxy objects in order of preference.
|
||||
"""
|
||||
proxy = config.get('network', 'proxy')
|
||||
proxy = config.val.content.proxy
|
||||
if proxy is configtypes.SYSTEM_PROXY:
|
||||
proxies = QNetworkProxyFactory.systemProxyForQuery(query)
|
||||
elif isinstance(proxy, pac.PACFetcher):
|
||||
@@ -69,7 +69,7 @@ class ProxyFactory(QNetworkProxyFactory):
|
||||
for p in proxies:
|
||||
if p.type() != QNetworkProxy.NoProxy:
|
||||
capabilities = p.capabilities()
|
||||
if config.get('network', 'proxy-dns-requests'):
|
||||
if config.val.content.proxy_dns_requests:
|
||||
capabilities |= QNetworkProxy.HostNameLookupCapability
|
||||
else:
|
||||
capabilities &= ~QNetworkProxy.HostNameLookupCapability
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
import io
|
||||
import shutil
|
||||
import functools
|
||||
import collections
|
||||
|
||||
import attr
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QTimer
|
||||
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
|
||||
|
||||
@@ -34,7 +34,11 @@ from qutebrowser.browser.webkit import http
|
||||
from qutebrowser.browser.webkit.network import networkmanager
|
||||
|
||||
|
||||
_RetryInfo = collections.namedtuple('_RetryInfo', ['request', 'manager'])
|
||||
@attr.s
|
||||
class _RetryInfo:
|
||||
|
||||
request = attr.ib()
|
||||
manager = attr.ib()
|
||||
|
||||
|
||||
class DownloadItem(downloads.AbstractDownloadItem):
|
||||
@@ -368,7 +372,7 @@ class DownloadManager(downloads.AbstractDownloadManager):
|
||||
super().__init__(parent)
|
||||
self._networkmanager = networkmanager.NetworkManager(
|
||||
win_id=win_id, tab_id=None,
|
||||
private=config.get('general', 'private-browsing'), parent=self)
|
||||
private=config.val.content.private_browsing, parent=self)
|
||||
|
||||
@pyqtSlot('QUrl')
|
||||
def get(self, url, *, user_agent=None, **kwargs):
|
||||
@@ -412,7 +416,8 @@ class DownloadManager(downloads.AbstractDownloadManager):
|
||||
mhtml.start_download_checked, tab=tab))
|
||||
message.global_bridge.ask(question, blocking=False)
|
||||
|
||||
def get_request(self, request, *, target=None, **kwargs):
|
||||
def get_request(self, request, *, target=None,
|
||||
suggested_fn=None, **kwargs):
|
||||
"""Start a download with a QNetworkRequest.
|
||||
|
||||
Args:
|
||||
@@ -428,7 +433,9 @@ class DownloadManager(downloads.AbstractDownloadManager):
|
||||
request.setAttribute(QNetworkRequest.CacheLoadControlAttribute,
|
||||
QNetworkRequest.AlwaysNetwork)
|
||||
|
||||
if request.url().scheme().lower() != 'data':
|
||||
if suggested_fn is not None:
|
||||
pass
|
||||
elif request.url().scheme().lower() != 'data':
|
||||
suggested_fn = urlutils.filename_from_url(request.url())
|
||||
else:
|
||||
# We might be downloading a binary blob embedded on a page or even
|
||||
@@ -480,7 +487,7 @@ class DownloadManager(downloads.AbstractDownloadManager):
|
||||
reply: The QNetworkReply to download.
|
||||
target: Where to save the download as downloads.DownloadTarget.
|
||||
auto_remove: Whether to remove the download even if
|
||||
ui -> remove-finished-downloads is set to -1.
|
||||
downloads.remove_finished is set to -1.
|
||||
|
||||
Return:
|
||||
The created DownloadItem.
|
||||
|
||||
@@ -26,18 +26,17 @@ Module attributes:
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import urllib.parse
|
||||
import datetime
|
||||
import textwrap
|
||||
import pkg_resources
|
||||
|
||||
from PyQt5.QtCore import QUrlQuery, QUrl
|
||||
|
||||
import qutebrowser
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.config import config, configdata, configexc, configdiff
|
||||
from qutebrowser.utils import (version, utils, jinja, log, message, docutils,
|
||||
objreg, usertypes, qtutils)
|
||||
objreg)
|
||||
from qutebrowser.misc import objects
|
||||
|
||||
|
||||
@@ -123,8 +122,7 @@ class add_handler: # pylint: disable=invalid-name
|
||||
title="Error while opening qute://url",
|
||||
url=url.toDisplayString(),
|
||||
error='{} is not available with this '
|
||||
'backend'.format(url.toDisplayString()),
|
||||
icon='')
|
||||
'backend'.format(url.toDisplayString()))
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
@@ -186,88 +184,36 @@ def qute_bookmarks(_url):
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
def history_data(start_time): # noqa
|
||||
"""Return history data
|
||||
def history_data(start_time, offset=None):
|
||||
"""Return history data.
|
||||
|
||||
Arguments:
|
||||
start_time -- select history starting from this timestamp.
|
||||
start_time: select history starting from this timestamp.
|
||||
offset: number of items to skip
|
||||
"""
|
||||
def history_iter(start_time, reverse=False):
|
||||
"""Iterate through the history and get items we're interested.
|
||||
|
||||
Arguments:
|
||||
reverse -- whether to reverse the history_dict before iterating.
|
||||
"""
|
||||
history = objreg.get('web-history').history_dict.values()
|
||||
if reverse:
|
||||
history = reversed(history)
|
||||
|
||||
# when history_dict is not reversed, we need to keep track of last item
|
||||
# so that we can yield its atime
|
||||
last_item = None
|
||||
|
||||
# history atimes are stored as ints, ensure start_time is not a float
|
||||
start_time = int(start_time)
|
||||
hist = objreg.get('web-history')
|
||||
if offset is not None:
|
||||
entries = hist.entries_before(start_time, limit=1000, offset=offset)
|
||||
else:
|
||||
# end is 24hrs earlier than start
|
||||
end_time = start_time - 24*60*60
|
||||
entries = hist.entries_between(end_time, start_time)
|
||||
|
||||
for item in history:
|
||||
# Skip redirects
|
||||
# Skip qute:// links
|
||||
if item.redirect or item.url.scheme() == 'qute':
|
||||
continue
|
||||
|
||||
# Skip items out of time window
|
||||
item_newer = item.atime > start_time
|
||||
item_older = item.atime <= end_time
|
||||
if reverse:
|
||||
# history_dict is reversed, we are going back in history.
|
||||
# so:
|
||||
# abort if item is older than start_time+24hr
|
||||
# skip if item is newer than start
|
||||
if item_older:
|
||||
yield {"next": int(item.atime)}
|
||||
return
|
||||
if item_newer:
|
||||
continue
|
||||
else:
|
||||
# history_dict isn't reversed, we are going forward in history.
|
||||
# so:
|
||||
# abort if item is newer than start_time
|
||||
# skip if item is older than start_time+24hrs
|
||||
if item_older:
|
||||
last_item = item
|
||||
continue
|
||||
if item_newer:
|
||||
yield {"next": int(last_item.atime if last_item else -1)}
|
||||
return
|
||||
|
||||
# Use item's url as title if there's no title.
|
||||
item_url = item.url.toDisplayString()
|
||||
item_title = item.title if item.title else item_url
|
||||
item_time = int(item.atime * 1000)
|
||||
|
||||
yield {"url": item_url, "title": item_title, "time": item_time}
|
||||
|
||||
# if we reached here, we had reached the end of history
|
||||
yield {"next": int(last_item.atime if last_item else -1)}
|
||||
|
||||
if sys.hexversion >= 0x03050000:
|
||||
# On Python >= 3.5 we can reverse the ordereddict in-place and thus
|
||||
# apply an additional performance improvement in history_iter.
|
||||
# On my machine, this gets us down from 550ms to 72us with 500k old
|
||||
# items.
|
||||
history = history_iter(start_time, reverse=True)
|
||||
else:
|
||||
# On Python 3.4, we can't do that, so we'd need to copy the entire
|
||||
# history to a list. There, filter first and then reverse it here.
|
||||
history = reversed(list(history_iter(start_time, reverse=False)))
|
||||
|
||||
return list(history)
|
||||
return [{"url": e.url, "title": e.title or e.url, "time": e.atime}
|
||||
for e in entries]
|
||||
|
||||
|
||||
@add_handler('history')
|
||||
def qute_history(url):
|
||||
"""Handler for qute://history. Display and serve history."""
|
||||
if url.path() == '/data':
|
||||
try:
|
||||
offset = QUrlQuery(url).queryItemValue("offset")
|
||||
offset = int(offset) if offset else None
|
||||
except ValueError as e:
|
||||
raise QuteSchemeError("Query parameter offset is invalid", e)
|
||||
# Use start_time in query or current time.
|
||||
try:
|
||||
start_time = QUrlQuery(url).queryItemValue("start_time")
|
||||
@@ -275,52 +221,15 @@ def qute_history(url):
|
||||
except ValueError as e:
|
||||
raise QuteSchemeError("Query parameter start_time is invalid", e)
|
||||
|
||||
return 'text/html', json.dumps(history_data(start_time))
|
||||
return 'text/html', json.dumps(history_data(start_time, offset))
|
||||
else:
|
||||
if (
|
||||
config.get('content', 'allow-javascript') and
|
||||
(objects.backend == usertypes.Backend.QtWebEngine or
|
||||
qtutils.is_qtwebkit_ng())
|
||||
):
|
||||
return 'text/html', jinja.render(
|
||||
'history.html',
|
||||
title='History',
|
||||
session_interval=config.get('ui', 'history-session-interval')
|
||||
)
|
||||
else:
|
||||
# Get current date from query parameter, if not given choose today.
|
||||
curr_date = datetime.date.today()
|
||||
try:
|
||||
query_date = QUrlQuery(url).queryItemValue("date")
|
||||
if query_date:
|
||||
curr_date = datetime.datetime.strptime(query_date,
|
||||
"%Y-%m-%d").date()
|
||||
except ValueError:
|
||||
log.misc.debug("Invalid date passed to qute:history: " +
|
||||
query_date)
|
||||
|
||||
one_day = datetime.timedelta(days=1)
|
||||
next_date = curr_date + one_day
|
||||
prev_date = curr_date - one_day
|
||||
|
||||
# start_time is the last second of curr_date
|
||||
start_time = time.mktime(next_date.timetuple()) - 1
|
||||
history = [
|
||||
(i["url"], i["title"],
|
||||
datetime.datetime.fromtimestamp(i["time"]/1000),
|
||||
QUrl(i["url"]).host())
|
||||
for i in history_data(start_time) if "next" not in i
|
||||
]
|
||||
|
||||
return 'text/html', jinja.render(
|
||||
'history_nojs.html',
|
||||
title='History',
|
||||
history=history,
|
||||
curr_date=curr_date,
|
||||
next_date=next_date,
|
||||
prev_date=prev_date,
|
||||
today=datetime.date.today(),
|
||||
)
|
||||
if not config.val.content.javascript.enabled:
|
||||
return 'text/plain', b'JavaScript is required for qute://history'
|
||||
return 'text/html', jinja.render(
|
||||
'history.html',
|
||||
title='History',
|
||||
gap_interval=config.val.history_gap_interval
|
||||
)
|
||||
|
||||
|
||||
@add_handler('javascript')
|
||||
@@ -398,26 +307,12 @@ 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/COPYING.html')
|
||||
return 'text/html', utils.read_file('html/LICENSE.html')
|
||||
|
||||
|
||||
@add_handler('help')
|
||||
def qute_help(url):
|
||||
"""Handler for qute://help."""
|
||||
try:
|
||||
utils.read_file('html/doc/index.html')
|
||||
except OSError:
|
||||
html = jinja.render(
|
||||
'error.html',
|
||||
title="Error while loading documentation",
|
||||
url=url.toDisplayString(),
|
||||
error="This most likely means the documentation was not generated "
|
||||
"properly. If you are running qutebrowser from the git "
|
||||
"repository, please run scripts/asciidoc2html.py. "
|
||||
"If you're running a released version this is a bug, please "
|
||||
"use :report to report it.",
|
||||
icon='')
|
||||
return 'text/html', html
|
||||
urlpath = url.path()
|
||||
if not urlpath or urlpath == '/':
|
||||
urlpath = 'index.html'
|
||||
@@ -426,11 +321,45 @@ def qute_help(url):
|
||||
if not docutils.docs_up_to_date(urlpath):
|
||||
message.error("Your documentation is outdated! Please re-run "
|
||||
"scripts/asciidoc2html.py.")
|
||||
|
||||
path = 'html/doc/{}'.format(urlpath)
|
||||
if urlpath.endswith('.png'):
|
||||
return 'image/png', utils.read_file(path, binary=True)
|
||||
else:
|
||||
|
||||
try:
|
||||
data = utils.read_file(path)
|
||||
except OSError:
|
||||
# No .html around, let's see if we find the asciidoc
|
||||
asciidoc_path = path.replace('.html', '.asciidoc')
|
||||
if asciidoc_path.startswith('html/doc/'):
|
||||
asciidoc_path = asciidoc_path.replace('html/doc/', '../doc/help/')
|
||||
|
||||
try:
|
||||
asciidoc = utils.read_file(asciidoc_path)
|
||||
except OSError:
|
||||
asciidoc = None
|
||||
|
||||
if asciidoc is None:
|
||||
raise
|
||||
|
||||
preamble = textwrap.dedent("""
|
||||
There was an error loading the documentation!
|
||||
|
||||
This most likely means the documentation was not generated
|
||||
properly. If you are running qutebrowser from the git repository,
|
||||
please (re)run scripts/asciidoc2html.py and reload this page.
|
||||
|
||||
If you're running a released version this is a bug, please use
|
||||
:report to report it.
|
||||
|
||||
Falling back to the plaintext version.
|
||||
|
||||
---------------------------------------------------------------
|
||||
|
||||
|
||||
""")
|
||||
return 'text/plain', (preamble + asciidoc).encode('utf-8')
|
||||
else:
|
||||
return 'text/html', data
|
||||
|
||||
|
||||
@@ -443,3 +372,51 @@ def qute_backend_warning(_url):
|
||||
version=pkg_resources.parse_version,
|
||||
title="Legacy backend warning")
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
def _qute_settings_set(url):
|
||||
"""Handler for qute://settings/set."""
|
||||
query = QUrlQuery(url)
|
||||
option = query.queryItemValue('option', QUrl.FullyDecoded)
|
||||
value = query.queryItemValue('value', QUrl.FullyDecoded)
|
||||
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/727
|
||||
if option == 'content.javascript.enabled' and value == 'false':
|
||||
msg = ("Refusing to disable javascript via qute://settings "
|
||||
"as it needs javascript support.")
|
||||
message.error(msg)
|
||||
return 'text/html', b'error: ' + msg.encode('utf-8')
|
||||
|
||||
try:
|
||||
config.instance.set_str(option, value, save_yaml=True)
|
||||
return 'text/html', b'ok'
|
||||
except configexc.Error as e:
|
||||
message.error(str(e))
|
||||
return 'text/html', b'error: ' + str(e).encode('utf-8')
|
||||
|
||||
|
||||
@add_handler('settings')
|
||||
def qute_settings(url):
|
||||
"""Handler for qute://settings. View/change qute configuration."""
|
||||
if url.path() == '/set':
|
||||
return _qute_settings_set(url)
|
||||
|
||||
html = jinja.render('settings.html', title='settings',
|
||||
configdata=configdata,
|
||||
confget=config.instance.get_str)
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
@add_handler('configdiff')
|
||||
def qute_configdiff(url):
|
||||
"""Handler for qute://configdiff."""
|
||||
if url.path() == '/old':
|
||||
try:
|
||||
return 'text/html', configdiff.get_diff()
|
||||
except OSError as e:
|
||||
error = (b'Failed to read old config: ' +
|
||||
str(e.strerror).encode('utf-8'))
|
||||
return 'text/plain', error
|
||||
else:
|
||||
data = config.instance.dump_userconfig().encode('utf-8')
|
||||
return 'text/plain', data
|
||||
|
||||
@@ -21,10 +21,9 @@
|
||||
|
||||
import html
|
||||
|
||||
import jinja2
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import usertypes, message, log, objreg
|
||||
from qutebrowser.utils import usertypes, message, log, objreg, jinja
|
||||
from qutebrowser.mainwindow import mainwindow
|
||||
|
||||
|
||||
class CallSuper(Exception):
|
||||
@@ -35,16 +34,18 @@ class CallSuper(Exception):
|
||||
def custom_headers():
|
||||
"""Get the combined custom headers."""
|
||||
headers = {}
|
||||
dnt = b'1' if config.get('network', 'do-not-track') else b'0'
|
||||
headers[b'DNT'] = dnt
|
||||
headers[b'X-Do-Not-Track'] = dnt
|
||||
|
||||
config_headers = config.get('network', 'custom-headers')
|
||||
if config_headers is not None:
|
||||
for header, value in config_headers.items():
|
||||
headers[header.encode('ascii')] = value.encode('ascii')
|
||||
dnt_config = config.val.content.headers.do_not_track
|
||||
if dnt_config is not None:
|
||||
dnt = b'1' if dnt_config else b'0'
|
||||
headers[b'DNT'] = dnt
|
||||
headers[b'X-Do-Not-Track'] = dnt
|
||||
|
||||
accept_language = config.get('network', 'accept-language')
|
||||
conf_headers = config.val.content.headers.custom
|
||||
for header, value in conf_headers.items():
|
||||
headers[header.encode('ascii')] = value.encode('ascii')
|
||||
|
||||
accept_language = config.val.content.headers.accept_language
|
||||
if accept_language is not None:
|
||||
headers[b'Accept-Language'] = accept_language.encode('ascii')
|
||||
|
||||
@@ -72,7 +73,7 @@ def authentication_required(url, authenticator, abort_on):
|
||||
def javascript_confirm(url, js_msg, abort_on):
|
||||
"""Display a javascript confirm prompt."""
|
||||
log.js.debug("confirm: {}".format(js_msg))
|
||||
if config.get('ui', 'modal-js-dialog'):
|
||||
if config.val.content.javascript.modal_dialog:
|
||||
raise CallSuper
|
||||
|
||||
msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
|
||||
@@ -86,9 +87,9 @@ def javascript_confirm(url, js_msg, abort_on):
|
||||
def javascript_prompt(url, js_msg, default, abort_on):
|
||||
"""Display a javascript prompt."""
|
||||
log.js.debug("prompt: {}".format(js_msg))
|
||||
if config.get('ui', 'modal-js-dialog'):
|
||||
if config.val.content.javascript.modal_dialog:
|
||||
raise CallSuper
|
||||
if config.get('content', 'ignore-javascript-prompt'):
|
||||
if not config.val.content.javascript.prompt:
|
||||
return (False, "")
|
||||
|
||||
msg = '<b>{}</b> asks:<br/>{}'.format(html.escape(url.toDisplayString()),
|
||||
@@ -107,10 +108,10 @@ def javascript_prompt(url, js_msg, default, abort_on):
|
||||
def javascript_alert(url, js_msg, abort_on):
|
||||
"""Display a javascript alert."""
|
||||
log.js.debug("alert: {}".format(js_msg))
|
||||
if config.get('ui', 'modal-js-dialog'):
|
||||
if config.val.content.javascript.modal_dialog:
|
||||
raise CallSuper
|
||||
|
||||
if config.get('content', 'ignore-javascript-alert'):
|
||||
if not config.val.content.javascript.alert:
|
||||
return
|
||||
|
||||
msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
|
||||
@@ -119,6 +120,22 @@ def javascript_alert(url, js_msg, abort_on):
|
||||
abort_on=abort_on)
|
||||
|
||||
|
||||
def javascript_log_message(level, source, line, msg):
|
||||
"""Display a JavaScript log message."""
|
||||
logstring = "[{}:{}] {}".format(source, line, msg)
|
||||
# Needs to line up with the values allowed for the
|
||||
# content.javascript.log setting.
|
||||
logmap = {
|
||||
'none': lambda arg: None,
|
||||
'debug': log.js.debug,
|
||||
'info': log.js.info,
|
||||
'warning': log.js.warning,
|
||||
'error': log.js.error,
|
||||
}
|
||||
logger = logmap[config.val.content.javascript.log[level.name]]
|
||||
logger(logstring)
|
||||
|
||||
|
||||
def ignore_certificate_errors(url, errors, abort_on):
|
||||
"""Display a certificate error question.
|
||||
|
||||
@@ -129,7 +146,7 @@ def ignore_certificate_errors(url, errors, abort_on):
|
||||
Return:
|
||||
True if the error should be ignored, False otherwise.
|
||||
"""
|
||||
ssl_strict = config.get('network', 'ssl-strict')
|
||||
ssl_strict = config.val.content.ssl_strict
|
||||
log.webview.debug("Certificate errors {!r}, strict {}".format(
|
||||
errors, ssl_strict))
|
||||
|
||||
@@ -137,7 +154,7 @@ def ignore_certificate_errors(url, errors, abort_on):
|
||||
assert error.is_overridable(), repr(error)
|
||||
|
||||
if ssl_strict == 'ask':
|
||||
err_template = jinja2.Template("""
|
||||
err_template = jinja.environment.from_string("""
|
||||
Errors while loading <b>{{url.toDisplayString()}}</b>:<br/>
|
||||
<ul>
|
||||
{% for err in errors %}
|
||||
@@ -155,7 +172,7 @@ def ignore_certificate_errors(url, errors, abort_on):
|
||||
ignore = False
|
||||
return ignore
|
||||
elif ssl_strict is False:
|
||||
log.webview.debug("ssl-strict is False, only warning about errors")
|
||||
log.webview.debug("ssl_strict is False, only warning about errors")
|
||||
for err in errors:
|
||||
# FIXME we might want to use warn here (non-fatal error)
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/114
|
||||
@@ -173,7 +190,7 @@ def feature_permission(url, option, msg, yes_action, no_action, abort_on):
|
||||
|
||||
Args:
|
||||
url: The URL the request was done for.
|
||||
option: A (section, option) tuple for the option to check.
|
||||
option: An option name to check.
|
||||
msg: A string like "show notifications"
|
||||
yes_action: A callable to call if the request was approved
|
||||
no_action: A callable to call if the request was denied
|
||||
@@ -182,7 +199,7 @@ def feature_permission(url, option, msg, yes_action, no_action, abort_on):
|
||||
Return:
|
||||
The Question object if a question was asked, None otherwise.
|
||||
"""
|
||||
config_val = config.get(*option)
|
||||
config_val = config.instance.get(option)
|
||||
if config_val == 'ask':
|
||||
if url.isValid():
|
||||
text = "Allow the website at <b>{}</b> to {}?".format(
|
||||
@@ -218,7 +235,6 @@ def get_tab(win_id, target):
|
||||
elif target == usertypes.ClickTarget.window:
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
from qutebrowser.mainwindow import mainwindow
|
||||
window = mainwindow.MainWindow(private=tabbed_browser.private)
|
||||
window.show()
|
||||
win_id = window.win_id
|
||||
@@ -233,15 +249,14 @@ def get_tab(win_id, target):
|
||||
|
||||
def get_user_stylesheet():
|
||||
"""Get the combined user-stylesheet."""
|
||||
filename = config.get('ui', 'user-stylesheet')
|
||||
css = ''
|
||||
stylesheets = config.val.content.user_stylesheets
|
||||
|
||||
if filename is None:
|
||||
css = ''
|
||||
else:
|
||||
for filename in stylesheets:
|
||||
with open(filename, 'r', encoding='utf-8') as f:
|
||||
css = f.read()
|
||||
css += f.read()
|
||||
|
||||
if config.get('ui', 'hide-scrollbar'):
|
||||
if not config.val.scrolling.bar:
|
||||
css += '\nhtml > ::-webkit-scrollbar { width: 0px; height: 0px; }'
|
||||
|
||||
return css
|
||||
|
||||
@@ -77,13 +77,9 @@ class UrlMarkManager(QObject):
|
||||
|
||||
Signals:
|
||||
changed: Emitted when anything changed.
|
||||
added: Emitted when a new quickmark/bookmark was added.
|
||||
removed: Emitted when an existing quickmark/bookmark was removed.
|
||||
"""
|
||||
|
||||
changed = pyqtSignal()
|
||||
added = pyqtSignal(str, str)
|
||||
removed = pyqtSignal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
"""Initialize and read quickmarks."""
|
||||
@@ -121,7 +117,6 @@ class UrlMarkManager(QObject):
|
||||
"""
|
||||
del self.marks[key]
|
||||
self.changed.emit()
|
||||
self.removed.emit(key)
|
||||
|
||||
|
||||
class QuickmarkManager(UrlMarkManager):
|
||||
@@ -133,7 +128,6 @@ class QuickmarkManager(UrlMarkManager):
|
||||
- self.marks maps names to URLs.
|
||||
- changed gets emitted with the name as first argument and the URL as
|
||||
second argument.
|
||||
- removed gets emitted with the name as argument.
|
||||
"""
|
||||
|
||||
def _init_lineparser(self):
|
||||
@@ -193,7 +187,6 @@ class QuickmarkManager(UrlMarkManager):
|
||||
"""Really set the quickmark."""
|
||||
self.marks[name] = url
|
||||
self.changed.emit()
|
||||
self.added.emit(name, url)
|
||||
log.misc.debug("Added quickmark {} for {}".format(name, url))
|
||||
|
||||
if name in self.marks:
|
||||
@@ -243,7 +236,6 @@ class BookmarkManager(UrlMarkManager):
|
||||
- self.marks maps URLs to titles.
|
||||
- changed gets emitted with the URL as first argument and the title as
|
||||
second argument.
|
||||
- removed gets emitted with the URL as argument.
|
||||
"""
|
||||
|
||||
def _init_lineparser(self):
|
||||
@@ -295,5 +287,4 @@ class BookmarkManager(UrlMarkManager):
|
||||
else:
|
||||
self.marks[urlstr] = title
|
||||
self.changed.emit()
|
||||
self.added.emit(title, urlstr)
|
||||
return True
|
||||
|
||||
@@ -31,6 +31,7 @@ from PyQt5.QtGui import QMouseEvent
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.keyinput import modeman
|
||||
from qutebrowser.mainwindow import mainwindow
|
||||
from qutebrowser.utils import log, usertypes, utils, qtutils, objreg
|
||||
|
||||
|
||||
@@ -182,7 +183,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
||||
# at least a classid attribute. Oh, and let's hope images/...
|
||||
# DON'T have a classid attribute. HTML sucks.
|
||||
log.webelem.debug("<object type='{}'> clicked.".format(objtype))
|
||||
return config.get('input', 'insert-mode-on-plugins')
|
||||
return config.val.input.insert_mode.plugins
|
||||
else:
|
||||
# Image/Audio/...
|
||||
return False
|
||||
@@ -247,7 +248,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
||||
return self.is_writable()
|
||||
elif tag in ['embed', 'applet']:
|
||||
# Flash/Java/...
|
||||
return config.get('input', 'insert-mode-on-plugins') and not strict
|
||||
return config.val.input.insert_mode.plugins and not strict
|
||||
elif tag == 'object':
|
||||
return self._is_editable_object() and not strict
|
||||
elif tag in ['div', 'pre']:
|
||||
@@ -329,7 +330,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
||||
usertypes.ClickTarget.tab: Qt.ControlModifier,
|
||||
usertypes.ClickTarget.tab_bg: Qt.ControlModifier,
|
||||
}
|
||||
if config.get('tabs', 'background-tabs'):
|
||||
if config.val.tabs.background:
|
||||
modifiers[usertypes.ClickTarget.tab] |= Qt.ShiftModifier
|
||||
else:
|
||||
modifiers[usertypes.ClickTarget.tab_bg] |= Qt.ShiftModifier
|
||||
@@ -372,7 +373,6 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
||||
background = click_target == usertypes.ClickTarget.tab_bg
|
||||
tabbed_browser.tabopen(url, background=background)
|
||||
elif click_target == usertypes.ClickTarget.window:
|
||||
from qutebrowser.mainwindow import mainwindow
|
||||
window = mainwindow.MainWindow(private=tabbed_browser.private)
|
||||
window.show()
|
||||
window.tabbed_browser.tabopen(url)
|
||||
|
||||
@@ -63,6 +63,6 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
|
||||
for header, value in shared.custom_headers():
|
||||
info.setHttpHeader(header, value)
|
||||
|
||||
user_agent = config.get('network', 'user-agent')
|
||||
user_agent = config.val.content.headers.user_agent
|
||||
if user_agent is not None:
|
||||
info.setHttpHeader(b'User-Agent', user_agent.encode('ascii'))
|
||||
|
||||
46
qutebrowser/browser/webengine/spell.py
Normal file
46
qutebrowser/browser/webengine/spell.py
Normal file
@@ -0,0 +1,46 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2017 Michal Siedlaczek <michal.siedlaczek@gmail.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/>.
|
||||
|
||||
"""Installing and configuring spell-checking for QtWebEngine."""
|
||||
|
||||
import glob
|
||||
import os
|
||||
|
||||
from PyQt5.QtCore import QLibraryInfo
|
||||
|
||||
|
||||
def dictionary_dir():
|
||||
"""Return the path (str) to the QtWebEngine's dictionaries directory."""
|
||||
datapath = QLibraryInfo.location(QLibraryInfo.DataPath)
|
||||
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.
|
||||
"""
|
||||
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
|
||||
@@ -150,7 +150,7 @@ def _get_suggested_filename(path):
|
||||
"""
|
||||
filename = os.path.basename(path)
|
||||
filename = re.sub(r'\([0-9]+\)(?=\.|$)', '', filename)
|
||||
if not qtutils.version_check('5.9'):
|
||||
if not qtutils.version_check('5.9', compiled=False):
|
||||
# https://bugreports.qt.io/browse/QTBUG-58155
|
||||
filename = urllib.parse.unquote(filename)
|
||||
# Doing basename a *second* time because there could be a %2F in
|
||||
|
||||
@@ -162,7 +162,7 @@ class WebEngineElement(webelem.AbstractWebElement):
|
||||
top = rect['top']
|
||||
if width > 1 and height > 1:
|
||||
# Fix coordinates according to zoom level
|
||||
# We're not checking for zoom-text-only here as that doesn't
|
||||
# We're not checking for zoom.text_only here as that doesn't
|
||||
# exist for QtWebEngine.
|
||||
zoom = self._tab.zoom.factor()
|
||||
rect = QRect(left * zoom, top * zoom,
|
||||
|
||||
@@ -28,16 +28,15 @@ Module attributes:
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
from PyQt5.QtGui import QFont
|
||||
from PyQt5.QtWebEngineWidgets import (QWebEngineSettings, QWebEngineProfile,
|
||||
QWebEngineScript)
|
||||
|
||||
from qutebrowser.browser import shared
|
||||
from qutebrowser.browser.webengine import spell
|
||||
from qutebrowser.config import config, websettings
|
||||
from qutebrowser.utils import objreg, utils, standarddir, javascript, qtutils
|
||||
|
||||
from qutebrowser.utils import utils, standarddir, javascript, qtutils, message
|
||||
|
||||
# The default QWebEngineProfile
|
||||
default_profile = None
|
||||
@@ -110,7 +109,7 @@ class DefaultProfileSetter(websettings.Base):
|
||||
|
||||
class PersistentCookiePolicy(DefaultProfileSetter):
|
||||
|
||||
"""The cookies -> store setting is different from other settings."""
|
||||
"""The content.cookies.store setting is different from other settings."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__('setPersistentCookiesPolicy')
|
||||
@@ -126,15 +125,35 @@ class PersistentCookiePolicy(DefaultProfileSetter):
|
||||
)
|
||||
|
||||
|
||||
class DictionaryLanguageSetter(DefaultProfileSetter):
|
||||
|
||||
"""Sets paths to dictionary files based on language codes."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__('setSpellCheckLanguages', default=[])
|
||||
|
||||
def _find_installed(self, code):
|
||||
installed_file = spell.installed_file(code)
|
||||
if not installed_file:
|
||||
message.warning(
|
||||
"Language {} is not installed - see scripts/install_dict.py "
|
||||
"in qutebrowser's sources".format(code))
|
||||
return installed_file
|
||||
|
||||
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]
|
||||
super()._set([f for f in filenames if f], settings)
|
||||
|
||||
|
||||
def _init_stylesheet(profile):
|
||||
"""Initialize custom stylesheets.
|
||||
|
||||
Mostly 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
|
||||
|
||||
FIXME:qtwebengine Use QWebEngineStyleSheet once that's available
|
||||
https://codereview.qt-project.org/#/c/148671/
|
||||
"""
|
||||
old_script = profile.scripts().findScript('_qute_stylesheet')
|
||||
if not old_script.isNull():
|
||||
@@ -159,26 +178,29 @@ def _init_stylesheet(profile):
|
||||
profile.scripts().insert(script)
|
||||
|
||||
|
||||
def _set_user_agent(profile):
|
||||
"""Set the user agent for the given profile.
|
||||
def _set_http_headers(profile):
|
||||
"""Set the user agent and accept-language for the given profile.
|
||||
|
||||
We override this per request in the URL interceptor (to allow for
|
||||
per-domain user agents), but this one still gets used for things like
|
||||
window.navigator.userAgent in JS.
|
||||
We override those per request in the URL interceptor (to allow for
|
||||
per-domain values), but this one still gets used for things like
|
||||
window.navigator.userAgent/.languages in JS.
|
||||
"""
|
||||
user_agent = config.get('network', 'user-agent')
|
||||
profile.setHttpUserAgent(user_agent)
|
||||
profile.setHttpUserAgent(config.val.content.headers.user_agent)
|
||||
accept_language = config.val.content.headers.accept_language
|
||||
if accept_language is not None:
|
||||
profile.setHttpAcceptLanguage(accept_language)
|
||||
|
||||
|
||||
def update_settings(section, option):
|
||||
def _update_settings(option):
|
||||
"""Update global settings when qwebsettings changed."""
|
||||
websettings.update_mappings(MAPPINGS, section, option)
|
||||
if section == 'ui' and option in ['hide-scrollbar', 'user-stylesheet']:
|
||||
websettings.update_mappings(MAPPINGS, option)
|
||||
if option in ['scrolling.bar', 'content.user_stylesheets']:
|
||||
_init_stylesheet(default_profile)
|
||||
_init_stylesheet(private_profile)
|
||||
elif section == 'network' and option == 'user-agent':
|
||||
_set_user_agent(default_profile)
|
||||
_set_user_agent(private_profile)
|
||||
elif option in ['content.headers.user_agent',
|
||||
'content.headers.accept_language']:
|
||||
_set_http_headers(default_profile)
|
||||
_set_http_headers(private_profile)
|
||||
|
||||
|
||||
def _init_profiles():
|
||||
@@ -190,12 +212,16 @@ def _init_profiles():
|
||||
default_profile.setPersistentStoragePath(
|
||||
os.path.join(standarddir.data(), 'webengine'))
|
||||
_init_stylesheet(default_profile)
|
||||
_set_user_agent(default_profile)
|
||||
_set_http_headers(default_profile)
|
||||
|
||||
private_profile = QWebEngineProfile()
|
||||
assert private_profile.isOffTheRecord()
|
||||
_init_stylesheet(private_profile)
|
||||
_set_user_agent(private_profile)
|
||||
_set_http_headers(private_profile)
|
||||
|
||||
if qtutils.version_check('5.8'):
|
||||
default_profile.setSpellCheckEnabled(True)
|
||||
private_profile.setSpellCheckEnabled(True)
|
||||
|
||||
|
||||
def init(args):
|
||||
@@ -203,23 +229,16 @@ def init(args):
|
||||
if args.enable_webengine_inspector:
|
||||
os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = str(utils.random_port())
|
||||
|
||||
# Workaround for a black screen with some setups
|
||||
# https://github.com/spyder-ide/spyder/issues/3226
|
||||
if not os.environ.get('QUTE_NO_OPENGL_WORKAROUND'):
|
||||
# Hide "No OpenGL_accelerate module loaded: ..." message
|
||||
logging.getLogger('OpenGL.acceleratesupport').propagate = False
|
||||
from OpenGL import GL # pylint: disable=unused-variable
|
||||
|
||||
_init_profiles()
|
||||
|
||||
# We need to do this here as a WORKAROUND for
|
||||
# https://bugreports.qt.io/browse/QTBUG-58650
|
||||
if not qtutils.version_check('5.9'):
|
||||
PersistentCookiePolicy().set(config.get('content', 'cookies-store'))
|
||||
if not qtutils.version_check('5.9', compiled=False):
|
||||
PersistentCookiePolicy().set(config.val.content.cookies.store)
|
||||
Attribute(QWebEngineSettings.FullScreenSupportEnabled).set(True)
|
||||
|
||||
websettings.init_mappings(MAPPINGS)
|
||||
objreg.get('config').changed.connect(update_settings)
|
||||
config.instance.changed.connect(_update_settings)
|
||||
|
||||
|
||||
def shutdown():
|
||||
@@ -240,85 +259,80 @@ def shutdown():
|
||||
|
||||
|
||||
MAPPINGS = {
|
||||
'content': {
|
||||
'allow-images':
|
||||
Attribute(QWebEngineSettings.AutoLoadImages),
|
||||
'allow-javascript':
|
||||
Attribute(QWebEngineSettings.JavascriptEnabled),
|
||||
'javascript-can-open-windows-automatically':
|
||||
Attribute(QWebEngineSettings.JavascriptCanOpenWindows),
|
||||
'javascript-can-access-clipboard':
|
||||
Attribute(QWebEngineSettings.JavascriptCanAccessClipboard),
|
||||
'allow-plugins':
|
||||
Attribute(QWebEngineSettings.PluginsEnabled),
|
||||
'hyperlink-auditing':
|
||||
Attribute(QWebEngineSettings.HyperlinkAuditingEnabled),
|
||||
'local-content-can-access-remote-urls':
|
||||
Attribute(QWebEngineSettings.LocalContentCanAccessRemoteUrls),
|
||||
'local-content-can-access-file-urls':
|
||||
Attribute(QWebEngineSettings.LocalContentCanAccessFileUrls),
|
||||
'webgl':
|
||||
Attribute(QWebEngineSettings.WebGLEnabled),
|
||||
},
|
||||
'input': {
|
||||
'spatial-navigation':
|
||||
Attribute(QWebEngineSettings.SpatialNavigationEnabled),
|
||||
'links-included-in-focus-chain':
|
||||
Attribute(QWebEngineSettings.LinksIncludedInFocusChain),
|
||||
},
|
||||
'fonts': {
|
||||
'web-family-standard':
|
||||
FontFamilySetter(QWebEngineSettings.StandardFont),
|
||||
'web-family-fixed':
|
||||
FontFamilySetter(QWebEngineSettings.FixedFont),
|
||||
'web-family-serif':
|
||||
FontFamilySetter(QWebEngineSettings.SerifFont),
|
||||
'web-family-sans-serif':
|
||||
FontFamilySetter(QWebEngineSettings.SansSerifFont),
|
||||
'web-family-cursive':
|
||||
FontFamilySetter(QWebEngineSettings.CursiveFont),
|
||||
'web-family-fantasy':
|
||||
FontFamilySetter(QWebEngineSettings.FantasyFont),
|
||||
'web-size-minimum':
|
||||
Setter(QWebEngineSettings.setFontSize,
|
||||
args=[QWebEngineSettings.MinimumFontSize]),
|
||||
'web-size-minimum-logical':
|
||||
Setter(QWebEngineSettings.setFontSize,
|
||||
args=[QWebEngineSettings.MinimumLogicalFontSize]),
|
||||
'web-size-default':
|
||||
Setter(QWebEngineSettings.setFontSize,
|
||||
args=[QWebEngineSettings.DefaultFontSize]),
|
||||
'web-size-default-fixed':
|
||||
Setter(QWebEngineSettings.setFontSize,
|
||||
args=[QWebEngineSettings.DefaultFixedFontSize]),
|
||||
},
|
||||
'ui': {
|
||||
'smooth-scrolling':
|
||||
Attribute(QWebEngineSettings.ScrollAnimatorEnabled),
|
||||
},
|
||||
'storage': {
|
||||
'local-storage':
|
||||
Attribute(QWebEngineSettings.LocalStorageEnabled),
|
||||
'cache-size':
|
||||
# 0: automatically managed by QtWebEngine
|
||||
DefaultProfileSetter('setHttpCacheMaximumSize', default=0),
|
||||
},
|
||||
'general': {
|
||||
'xss-auditing':
|
||||
Attribute(QWebEngineSettings.XSSAuditingEnabled),
|
||||
'default-encoding':
|
||||
Setter(QWebEngineSettings.setDefaultTextEncoding),
|
||||
}
|
||||
'content.images':
|
||||
Attribute(QWebEngineSettings.AutoLoadImages),
|
||||
'content.javascript.enabled':
|
||||
Attribute(QWebEngineSettings.JavascriptEnabled),
|
||||
'content.javascript.can_open_tabs_automatically':
|
||||
Attribute(QWebEngineSettings.JavascriptCanOpenWindows),
|
||||
'content.javascript.can_access_clipboard':
|
||||
Attribute(QWebEngineSettings.JavascriptCanAccessClipboard),
|
||||
'content.plugins':
|
||||
Attribute(QWebEngineSettings.PluginsEnabled),
|
||||
'content.hyperlink_auditing':
|
||||
Attribute(QWebEngineSettings.HyperlinkAuditingEnabled),
|
||||
'content.local_content_can_access_remote_urls':
|
||||
Attribute(QWebEngineSettings.LocalContentCanAccessRemoteUrls),
|
||||
'content.local_content_can_access_file_urls':
|
||||
Attribute(QWebEngineSettings.LocalContentCanAccessFileUrls),
|
||||
'content.webgl':
|
||||
Attribute(QWebEngineSettings.WebGLEnabled),
|
||||
'content.local_storage':
|
||||
Attribute(QWebEngineSettings.LocalStorageEnabled),
|
||||
'content.cache.size':
|
||||
# 0: automatically managed by QtWebEngine
|
||||
DefaultProfileSetter('setHttpCacheMaximumSize', default=0),
|
||||
'content.xss_auditing':
|
||||
Attribute(QWebEngineSettings.XSSAuditingEnabled),
|
||||
'content.default_encoding':
|
||||
Setter(QWebEngineSettings.setDefaultTextEncoding),
|
||||
|
||||
'input.spatial_navigation':
|
||||
Attribute(QWebEngineSettings.SpatialNavigationEnabled),
|
||||
'input.links_included_in_focus_chain':
|
||||
Attribute(QWebEngineSettings.LinksIncludedInFocusChain),
|
||||
|
||||
'fonts.web.family.standard':
|
||||
FontFamilySetter(QWebEngineSettings.StandardFont),
|
||||
'fonts.web.family.fixed':
|
||||
FontFamilySetter(QWebEngineSettings.FixedFont),
|
||||
'fonts.web.family.serif':
|
||||
FontFamilySetter(QWebEngineSettings.SerifFont),
|
||||
'fonts.web.family.sans_serif':
|
||||
FontFamilySetter(QWebEngineSettings.SansSerifFont),
|
||||
'fonts.web.family.cursive':
|
||||
FontFamilySetter(QWebEngineSettings.CursiveFont),
|
||||
'fonts.web.family.fantasy':
|
||||
FontFamilySetter(QWebEngineSettings.FantasyFont),
|
||||
'fonts.web.size.minimum':
|
||||
Setter(QWebEngineSettings.setFontSize,
|
||||
args=[QWebEngineSettings.MinimumFontSize]),
|
||||
'fonts.web.size.minimum_logical':
|
||||
Setter(QWebEngineSettings.setFontSize,
|
||||
args=[QWebEngineSettings.MinimumLogicalFontSize]),
|
||||
'fonts.web.size.default':
|
||||
Setter(QWebEngineSettings.setFontSize,
|
||||
args=[QWebEngineSettings.DefaultFontSize]),
|
||||
'fonts.web.size.default_fixed':
|
||||
Setter(QWebEngineSettings.setFontSize,
|
||||
args=[QWebEngineSettings.DefaultFixedFontSize]),
|
||||
|
||||
'scrolling.smooth':
|
||||
Attribute(QWebEngineSettings.ScrollAnimatorEnabled),
|
||||
}
|
||||
|
||||
try:
|
||||
MAPPINGS['general']['print-element-backgrounds'] = Attribute(
|
||||
MAPPINGS['content.print_element_backgrounds'] = Attribute(
|
||||
QWebEngineSettings.PrintElementBackgrounds)
|
||||
except AttributeError:
|
||||
# Added in Qt 5.8
|
||||
pass
|
||||
|
||||
|
||||
if qtutils.version_check('5.9'):
|
||||
if qtutils.version_check('5.8'):
|
||||
MAPPINGS['spellcheck.languages'] = DictionaryLanguageSetter()
|
||||
|
||||
|
||||
if qtutils.version_check('5.9', compiled=False):
|
||||
# https://bugreports.qt.io/browse/QTBUG-58650
|
||||
MAPPINGS['content']['cookies-store'] = PersistentCookiePolicy()
|
||||
MAPPINGS['content.cookies.store'] = PersistentCookiePolicy()
|
||||
|
||||
@@ -19,8 +19,9 @@
|
||||
|
||||
"""Wrapper over a QWebEngineView."""
|
||||
|
||||
import os
|
||||
import math
|
||||
import functools
|
||||
import html as html_utils
|
||||
|
||||
import sip
|
||||
from PyQt5.QtCore import pyqtSlot, Qt, QEvent, QPoint, QUrl, QTimer
|
||||
@@ -36,7 +37,7 @@ from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory,
|
||||
webenginesettings)
|
||||
from qutebrowser.misc import miscwidgets
|
||||
from qutebrowser.utils import (usertypes, qtutils, log, javascript, utils,
|
||||
objreg, jinja, debug, version)
|
||||
message, objreg, jinja, debug)
|
||||
|
||||
|
||||
_qute_scheme_handler = None
|
||||
@@ -48,15 +49,8 @@ def init():
|
||||
# won't work...
|
||||
# https://www.riverbankcomputing.com/pipermail/pyqt/2016-September/038075.html
|
||||
global _qute_scheme_handler
|
||||
|
||||
app = QApplication.instance()
|
||||
|
||||
software_rendering = os.environ.get('LIBGL_ALWAYS_SOFTWARE') == '1'
|
||||
if version.opengl_vendor() == 'nouveau' and not software_rendering:
|
||||
# FIXME:qtwebengine display something more sophisticated here
|
||||
raise browsertab.WebTabError(
|
||||
"QtWebEngine is not supported with Nouveau graphics (unless "
|
||||
"LIBGL_ALWAYS_SOFTWARE is set as environment variable).")
|
||||
|
||||
log.init.debug("Initializing qute://* handler...")
|
||||
_qute_scheme_handler = webenginequtescheme.QuteSchemeHandler(parent=app)
|
||||
_qute_scheme_handler.install(webenginesettings.default_profile)
|
||||
@@ -151,20 +145,16 @@ class WebEngineSearch(browsertab.AbstractSearch):
|
||||
callback(found)
|
||||
self._widget.findText(text, flags, wrapped_callback)
|
||||
|
||||
def search(self, text, *, ignore_case=False, reverse=False,
|
||||
def search(self, text, *, ignore_case='never', reverse=False,
|
||||
result_cb=None):
|
||||
flags = QWebEnginePage.FindFlags(0)
|
||||
if ignore_case == 'smart':
|
||||
if not text.islower():
|
||||
flags |= QWebEnginePage.FindCaseSensitively
|
||||
elif not ignore_case:
|
||||
flags |= QWebEnginePage.FindCaseSensitively
|
||||
if reverse:
|
||||
flags |= QWebEnginePage.FindBackward
|
||||
|
||||
self.text = text
|
||||
self._flags = flags
|
||||
self._find(text, flags, result_cb, 'search')
|
||||
self._flags = QWebEnginePage.FindFlags(0)
|
||||
if self._is_case_sensitive(ignore_case):
|
||||
self._flags |= QWebEnginePage.FindCaseSensitively
|
||||
if reverse:
|
||||
self._flags |= QWebEnginePage.FindBackward
|
||||
|
||||
self._find(text, self._flags, result_cb, 'search')
|
||||
|
||||
def clear(self):
|
||||
self.search_displayed = False
|
||||
@@ -342,7 +332,7 @@ class WebEngineScroller(browsertab.AbstractScroller):
|
||||
else:
|
||||
perc_y = min(100, round(100 / dy * jsret['px']['y']))
|
||||
|
||||
self._at_bottom = dy >= jsret['px']['y']
|
||||
self._at_bottom = math.ceil(jsret['px']['y']) >= dy
|
||||
self._pos_perc = perc_x, perc_y
|
||||
|
||||
self.perc_changed.emit(*self._pos_perc)
|
||||
@@ -409,20 +399,20 @@ class WebEngineHistory(browsertab.AbstractHistory):
|
||||
def current_idx(self):
|
||||
return self._history.currentItemIndex()
|
||||
|
||||
def back(self):
|
||||
self._history.back()
|
||||
|
||||
def forward(self):
|
||||
self._history.forward()
|
||||
|
||||
def can_go_back(self):
|
||||
return self._history.canGoBack()
|
||||
|
||||
def can_go_forward(self):
|
||||
return self._history.canGoForward()
|
||||
|
||||
def _item_at(self, i):
|
||||
return self._history.itemAt(i)
|
||||
|
||||
def _go_to_item(self, item):
|
||||
return self._history.goToItem(item)
|
||||
|
||||
def serialize(self):
|
||||
if not qtutils.version_check('5.9'):
|
||||
if not qtutils.version_check('5.9', compiled=False):
|
||||
# WORKAROUND for
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/2289
|
||||
# Don't use the history's currentItem here, because of
|
||||
@@ -455,9 +445,6 @@ class WebEngineZoom(browsertab.AbstractZoom):
|
||||
def _set_factor_internal(self, factor):
|
||||
self._widget.setZoomFactor(factor)
|
||||
|
||||
def factor(self):
|
||||
return self._widget.zoomFactor()
|
||||
|
||||
|
||||
class WebEngineElements(browsertab.AbstractElements):
|
||||
|
||||
@@ -614,7 +601,8 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
|
||||
def shutdown(self):
|
||||
self.shutting_down.emit()
|
||||
if qtutils.version_check('5.8', exact=True):
|
||||
self.action.exit_fullscreen()
|
||||
if qtutils.version_check('5.8', exact=True, compiled=False):
|
||||
# WORKAROUND for
|
||||
# https://bugreports.qt.io/browse/QTBUG-58563
|
||||
self.search.clear()
|
||||
@@ -662,6 +650,15 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_history_trigger(self):
|
||||
try:
|
||||
self._widget.page()
|
||||
except RuntimeError:
|
||||
# Looks like this slot can be triggered on destroyed tabs:
|
||||
# https://crashes.qutebrowser.org/view/3abffbed (Qt 5.9.1)
|
||||
# wrapped C/C++ object of type WebEngineView has been deleted
|
||||
log.misc.debug("Ignoring history trigger for destroyed tab")
|
||||
return
|
||||
|
||||
url = self.url()
|
||||
requested_url = self.url(requested=True)
|
||||
|
||||
@@ -679,6 +676,32 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
|
||||
self.add_history_item.emit(url, requested_url, title)
|
||||
|
||||
@pyqtSlot(QUrl, 'QAuthenticator*', 'QString')
|
||||
def _on_proxy_authentication_required(self, url, authenticator,
|
||||
proxy_host):
|
||||
"""Called when a proxy needs authentication."""
|
||||
msg = "<b>{}</b> requires a username and password.".format(
|
||||
html_utils.escape(proxy_host))
|
||||
answer = message.ask(
|
||||
title="Proxy authentication required", text=msg,
|
||||
mode=usertypes.PromptMode.user_pwd,
|
||||
abort_on=[self.shutting_down, self.load_started])
|
||||
if answer is not None:
|
||||
authenticator.setUser(answer.user)
|
||||
authenticator.setPassword(answer.password)
|
||||
else:
|
||||
try:
|
||||
# pylint: disable=no-member, useless-suppression
|
||||
sip.assign(authenticator, QAuthenticator())
|
||||
except AttributeError:
|
||||
url_string = url.toDisplayString()
|
||||
error_page = jinja.render(
|
||||
'error.html',
|
||||
title="Error loading page: {}".format(url_string),
|
||||
url=url_string, error="Proxy authentication required",
|
||||
icon='')
|
||||
self.set_html(error_page)
|
||||
|
||||
@pyqtSlot(QUrl, 'QAuthenticator*')
|
||||
def _on_authentication_required(self, url, authenticator):
|
||||
# FIXME:qtwebengine support .netrc
|
||||
@@ -696,7 +719,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
error_page = jinja.render(
|
||||
'error.html',
|
||||
title="Error loading page: {}".format(url_string),
|
||||
url=url_string, error="Authentication required", icon='')
|
||||
url=url_string, error="Authentication required")
|
||||
self.set_html(error_page)
|
||||
|
||||
@pyqtSlot('QWebEngineFullScreenRequest')
|
||||
@@ -714,7 +737,8 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
@pyqtSlot()
|
||||
def _on_load_started(self):
|
||||
"""Clear search when a new load is started if needed."""
|
||||
if qtutils.version_check('5.9'):
|
||||
if (qtutils.version_check('5.9', compiled=False) and
|
||||
not qtutils.version_check('5.9.2', compiled=False)):
|
||||
# WORKAROUND for
|
||||
# https://bugreports.qt.io/browse/QTBUG-61506
|
||||
self.search.clear()
|
||||
@@ -755,6 +779,8 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
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(
|
||||
self._on_proxy_authentication_required)
|
||||
page.fullScreenRequested.connect(self._on_fullscreen_requested)
|
||||
page.contentsSizeChanged.connect(self.contents_size_changed)
|
||||
|
||||
|
||||
@@ -28,8 +28,7 @@ from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
|
||||
from qutebrowser.browser import shared
|
||||
from qutebrowser.browser.webengine import certificateerror, webenginesettings
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import (log, debug, usertypes, jinja, urlutils, message,
|
||||
objreg)
|
||||
from qutebrowser.utils import log, debug, usertypes, jinja, urlutils, message
|
||||
|
||||
|
||||
class WebEngineView(QWebEngineView):
|
||||
@@ -80,10 +79,10 @@ class WebEngineView(QWebEngineView):
|
||||
The new QWebEngineView object.
|
||||
"""
|
||||
debug_type = debug.qenum_key(QWebEnginePage, wintype)
|
||||
background_tabs = config.get('tabs', 'background-tabs')
|
||||
background = config.val.tabs.background
|
||||
|
||||
log.webview.debug("createWindow with type {}, background_tabs "
|
||||
"{}".format(debug_type, background_tabs))
|
||||
log.webview.debug("createWindow with type {}, background {}".format(
|
||||
debug_type, background))
|
||||
|
||||
if wintype == QWebEnginePage.WebBrowserWindow:
|
||||
# Shift-Alt-Click
|
||||
@@ -95,13 +94,13 @@ class WebEngineView(QWebEngineView):
|
||||
elif wintype == QWebEnginePage.WebBrowserTab:
|
||||
# Middle-click / Ctrl-Click with Shift
|
||||
# FIXME:qtwebengine this also affects target=_blank links...
|
||||
if background_tabs:
|
||||
if background:
|
||||
target = usertypes.ClickTarget.tab
|
||||
else:
|
||||
target = usertypes.ClickTarget.tab_bg
|
||||
elif wintype == QWebEnginePage.WebBrowserBackgroundTab:
|
||||
# Middle-click / Ctrl-Click
|
||||
if background_tabs:
|
||||
if background:
|
||||
target = usertypes.ClickTarget.tab_bg
|
||||
else:
|
||||
target = usertypes.ClickTarget.tab
|
||||
@@ -135,11 +134,11 @@ class WebEnginePage(QWebEnginePage):
|
||||
self._on_feature_permission_requested)
|
||||
self._theme_color = theme_color
|
||||
self._set_bg_color()
|
||||
objreg.get('config').changed.connect(self._set_bg_color)
|
||||
config.instance.changed.connect(self._set_bg_color)
|
||||
|
||||
@config.change_filter('colors', 'webpage.bg')
|
||||
@config.change_filter('colors.webpage.bg')
|
||||
def _set_bg_color(self):
|
||||
col = config.get('colors', 'webpage.bg')
|
||||
col = config.val.colors.webpage.bg
|
||||
if col is None:
|
||||
col = self._theme_color
|
||||
self.setBackgroundColor(col)
|
||||
@@ -148,11 +147,10 @@ class WebEnginePage(QWebEnginePage):
|
||||
def _on_feature_permission_requested(self, url, feature):
|
||||
"""Ask the user for approval for geolocation/media/etc.."""
|
||||
options = {
|
||||
QWebEnginePage.Geolocation: ('content', 'geolocation'),
|
||||
QWebEnginePage.MediaAudioCapture: ('content', 'media-capture'),
|
||||
QWebEnginePage.MediaVideoCapture: ('content', 'media-capture'),
|
||||
QWebEnginePage.MediaAudioVideoCapture:
|
||||
('content', 'media-capture'),
|
||||
QWebEnginePage.Geolocation: 'content.geolocation',
|
||||
QWebEnginePage.MediaAudioCapture: 'content.media_capture',
|
||||
QWebEnginePage.MediaVideoCapture: 'content.media_capture',
|
||||
QWebEnginePage.MediaAudioVideoCapture: 'content.media_capture',
|
||||
}
|
||||
messages = {
|
||||
QWebEnginePage.Geolocation: 'access your location',
|
||||
@@ -214,7 +212,7 @@ class WebEnginePage(QWebEnginePage):
|
||||
url_string = url.toDisplayString()
|
||||
error_page = jinja.render(
|
||||
'error.html', title="Error loading page: {}".format(url_string),
|
||||
url=url_string, error=str(error), icon='')
|
||||
url=url_string, error=str(error))
|
||||
|
||||
if error.is_overridable():
|
||||
ignore = shared.ignore_certificate_errors(
|
||||
@@ -276,19 +274,12 @@ class WebEnginePage(QWebEnginePage):
|
||||
|
||||
def javaScriptConsoleMessage(self, level, msg, line, source):
|
||||
"""Log javascript messages to qutebrowser's log."""
|
||||
# FIXME:qtwebengine maybe unify this in the tab api somehow?
|
||||
setting = config.get('general', 'log-javascript-console')
|
||||
if setting == 'none':
|
||||
return
|
||||
|
||||
level_to_logger = {
|
||||
QWebEnginePage.InfoMessageLevel: log.js.info,
|
||||
QWebEnginePage.WarningMessageLevel: log.js.warning,
|
||||
QWebEnginePage.ErrorMessageLevel: log.js.error,
|
||||
level_map = {
|
||||
QWebEnginePage.InfoMessageLevel: usertypes.JsLogLevel.info,
|
||||
QWebEnginePage.WarningMessageLevel: usertypes.JsLogLevel.warning,
|
||||
QWebEnginePage.ErrorMessageLevel: usertypes.JsLogLevel.error,
|
||||
}
|
||||
logstring = "[{}:{}] {}".format(source, line, msg)
|
||||
logger = level_to_logger[level]
|
||||
logger(logstring)
|
||||
shared.javascript_log_message(level_map[level], source, line, msg)
|
||||
|
||||
def acceptNavigationRequest(self,
|
||||
url: QUrl,
|
||||
|
||||
@@ -24,7 +24,7 @@ import os.path
|
||||
from PyQt5.QtNetwork import QNetworkDiskCache
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import utils, objreg, qtutils
|
||||
from qutebrowser.utils import utils, qtutils
|
||||
|
||||
|
||||
class DiskCache(QNetworkDiskCache):
|
||||
@@ -35,21 +35,20 @@ class DiskCache(QNetworkDiskCache):
|
||||
super().__init__(parent)
|
||||
self.setCacheDirectory(os.path.join(cache_dir, 'http'))
|
||||
self._set_cache_size()
|
||||
objreg.get('config').changed.connect(self._set_cache_size)
|
||||
config.instance.changed.connect(self._set_cache_size)
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, size=self.cacheSize(),
|
||||
maxsize=self.maximumCacheSize(),
|
||||
path=self.cacheDirectory())
|
||||
|
||||
@config.change_filter('storage', 'cache-size')
|
||||
@config.change_filter('content.cache.size')
|
||||
def _set_cache_size(self):
|
||||
"""Set the cache size based on the config."""
|
||||
size = config.get('storage', 'cache-size')
|
||||
size = config.val.content.cache.size
|
||||
if size is None:
|
||||
size = 1024 * 1024 * 50 # default from QNetworkDiskCachePrivate
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-59909
|
||||
if (qtutils.version_check('5.7.1') and
|
||||
not qtutils.version_check('5.9')): # pragma: no cover
|
||||
size = 0
|
||||
if not qtutils.version_check('5.9', compiled=False):
|
||||
size = 0 # pragma: no cover
|
||||
self.setMaximumCacheSize(size)
|
||||
|
||||
@@ -38,12 +38,7 @@ class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper):
|
||||
string=str(self))
|
||||
|
||||
def __hash__(self):
|
||||
try:
|
||||
# Qt >= 5.4
|
||||
return hash(self._error)
|
||||
except TypeError: # pragma: no cover
|
||||
return hash((self._error.certificate().toDer(),
|
||||
self._error.error()))
|
||||
return hash(self._error)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self._error == other._error # pylint: disable=protected-access
|
||||
|
||||
@@ -50,7 +50,7 @@ class RAMCookieJar(QNetworkCookieJar):
|
||||
Return:
|
||||
True if one or more cookies are set for 'url', otherwise False.
|
||||
"""
|
||||
if config.get('content', 'cookies-accept') == 'never':
|
||||
if config.val.content.cookies.accept == 'never':
|
||||
return False
|
||||
else:
|
||||
self.changed.emit()
|
||||
@@ -74,10 +74,10 @@ class CookieJar(RAMCookieJar):
|
||||
self._lineparser = lineparser.LineParser(
|
||||
standarddir.data(), 'cookies', binary=True, parent=self)
|
||||
self.parse_cookies()
|
||||
objreg.get('config').changed.connect(self.cookies_store_changed)
|
||||
config.instance.changed.connect(self._on_cookies_store_changed)
|
||||
objreg.get('save-manager').add_saveable(
|
||||
'cookies', self.save, self.changed,
|
||||
config_opt=('content', 'cookies-store'))
|
||||
config_opt='content.cookies.store')
|
||||
|
||||
def parse_cookies(self):
|
||||
"""Parse cookies from lineparser and store them."""
|
||||
@@ -105,10 +105,10 @@ class CookieJar(RAMCookieJar):
|
||||
self._lineparser.data = lines
|
||||
self._lineparser.save()
|
||||
|
||||
@config.change_filter('content', 'cookies-store')
|
||||
def cookies_store_changed(self):
|
||||
"""Delete stored cookies if cookies-store changed."""
|
||||
if not config.get('content', 'cookies-store'):
|
||||
@config.change_filter('content.cookies.store')
|
||||
def _on_cookies_store_changed(self):
|
||||
"""Delete stored cookies if cookies.store changed."""
|
||||
if not config.val.content.cookies.store:
|
||||
self._lineparser.data = []
|
||||
self._lineparser.save()
|
||||
self.changed.emit()
|
||||
|
||||
@@ -25,7 +25,6 @@ import io
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import collections
|
||||
import uuid
|
||||
import email.policy
|
||||
import email.generator
|
||||
@@ -34,15 +33,21 @@ import email.mime.multipart
|
||||
import email.message
|
||||
import quopri
|
||||
|
||||
import attr
|
||||
from PyQt5.QtCore import QUrl
|
||||
|
||||
from qutebrowser.browser import downloads
|
||||
from qutebrowser.browser.webkit import webkitelem
|
||||
from qutebrowser.utils import log, objreg, message, usertypes, utils, urlutils
|
||||
|
||||
_File = collections.namedtuple('_File',
|
||||
['content', 'content_type', 'content_location',
|
||||
'transfer_encoding'])
|
||||
|
||||
@attr.s
|
||||
class _File:
|
||||
|
||||
content = attr.ib()
|
||||
content_type = attr.ib()
|
||||
content_location = attr.ib()
|
||||
transfer_encoding = attr.ib()
|
||||
|
||||
|
||||
_CSS_URL_PATTERNS = [re.compile(x) for x in [
|
||||
@@ -174,7 +179,7 @@ class MHTMLWriter:
|
||||
root_content: The root content as bytes.
|
||||
content_location: The url of the page as str.
|
||||
content_type: The MIME-type of the root content as str.
|
||||
_files: Mapping of location->_File namedtuple.
|
||||
_files: Mapping of location->_File object.
|
||||
"""
|
||||
|
||||
def __init__(self, root_content, content_location, content_type):
|
||||
|
||||
@@ -101,13 +101,12 @@ def dirbrowser_html(path):
|
||||
except OSError as e:
|
||||
html = jinja.render('error.html',
|
||||
title="Error while reading directory",
|
||||
url='file:///{}'.format(path), error=str(e),
|
||||
icon='')
|
||||
url='file:///{}'.format(path), error=str(e))
|
||||
return html.encode('UTF-8', errors='xmlcharrefreplace')
|
||||
|
||||
files = get_file_list(path, all_files, os.path.isfile)
|
||||
directories = get_file_list(path, all_files, os.path.isdir)
|
||||
html = jinja.render('dirbrowser.html', title=title, url=path, icon='',
|
||||
html = jinja.render('dirbrowser.html', title=title, url=path,
|
||||
parent=parent, files=files, directories=directories)
|
||||
return html.encode('UTF-8', errors='xmlcharrefreplace')
|
||||
|
||||
|
||||
@@ -24,13 +24,13 @@ import collections
|
||||
import netrc
|
||||
import html
|
||||
|
||||
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, PYQT_VERSION, QCoreApplication,
|
||||
QUrl, QByteArray)
|
||||
import attr
|
||||
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QCoreApplication, QUrl,
|
||||
QByteArray)
|
||||
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply, QSslSocket
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import (message, log, usertypes, utils, objreg, qtutils,
|
||||
urlutils)
|
||||
from qutebrowser.utils import message, log, usertypes, utils, objreg, urlutils
|
||||
from qutebrowser.browser import shared
|
||||
from qutebrowser.browser.webkit import certificateerror
|
||||
from qutebrowser.browser.webkit.network import (webkitqutescheme, networkreply,
|
||||
@@ -38,10 +38,19 @@ from qutebrowser.browser.webkit.network import (webkitqutescheme, networkreply,
|
||||
|
||||
|
||||
HOSTBLOCK_ERROR_STRING = '%HOSTBLOCK%'
|
||||
ProxyId = collections.namedtuple('ProxyId', 'type, hostname, port')
|
||||
_proxy_auth_cache = {}
|
||||
|
||||
|
||||
@attr.s(frozen=True)
|
||||
class ProxyId:
|
||||
|
||||
"""Information identifying a proxy server."""
|
||||
|
||||
type = attr.ib()
|
||||
hostname = attr.ib()
|
||||
port = attr.ib()
|
||||
|
||||
|
||||
def _is_secure_cipher(cipher):
|
||||
"""Check if a given SSL cipher (hopefully) isn't broken yet."""
|
||||
tokens = [e.upper() for e in cipher.name().split('-')]
|
||||
@@ -49,7 +58,7 @@ def _is_secure_cipher(cipher):
|
||||
# https://codereview.qt-project.org/#/c/75943/
|
||||
return False
|
||||
# OpenSSL should already protect against this in a better way
|
||||
elif cipher.keyExchangeMethod() == 'DH' and os.name == 'nt':
|
||||
elif cipher.keyExchangeMethod() == 'DH' and utils.is_windows:
|
||||
# https://weakdh.org/
|
||||
return False
|
||||
elif cipher.encryptionMethod().upper().startswith('RC4'):
|
||||
@@ -88,15 +97,9 @@ def _is_secure_cipher(cipher):
|
||||
|
||||
def init():
|
||||
"""Disable insecure SSL ciphers on old Qt versions."""
|
||||
if qtutils.version_check('5.3.0'):
|
||||
default_ciphers = QSslSocket.defaultCiphers()
|
||||
log.init.debug("Default Qt ciphers: {}".format(
|
||||
', '.join(c.name() for c in default_ciphers)))
|
||||
else:
|
||||
# https://codereview.qt-project.org/#/c/75943/
|
||||
default_ciphers = QSslSocket.supportedCiphers()
|
||||
log.init.debug("Supported Qt ciphers: {}".format(
|
||||
', '.join(c.name() for c in default_ciphers)))
|
||||
default_ciphers = QSslSocket.defaultCiphers()
|
||||
log.init.debug("Default Qt ciphers: {}".format(
|
||||
', '.join(c.name() for c in default_ciphers)))
|
||||
|
||||
good_ciphers = []
|
||||
bad_ciphers = []
|
||||
@@ -274,7 +277,7 @@ class NetworkManager(QNetworkAccessManager):
|
||||
# altogether.
|
||||
reply.netrc_used = True
|
||||
try:
|
||||
net = netrc.netrc(config.get('network', 'netrc-file'))
|
||||
net = netrc.netrc(config.val.content.netrc_file)
|
||||
authenticators = net.authenticators(reply.url().host())
|
||||
if authenticators is not None:
|
||||
(user, _account, password) = authenticators
|
||||
@@ -338,7 +341,7 @@ class NetworkManager(QNetworkAccessManager):
|
||||
|
||||
def set_referer(self, req, current_url):
|
||||
"""Set the referer header."""
|
||||
referer_header_conf = config.get('network', 'referer-header')
|
||||
referer_header_conf = config.val.content.headers.referer
|
||||
|
||||
try:
|
||||
if referer_header_conf == 'never':
|
||||
@@ -409,24 +412,11 @@ class NetworkManager(QNetworkAccessManager):
|
||||
tab = objreg.get('tab', scope='tab', window=self._win_id,
|
||||
tab=self._tab_id)
|
||||
current_url = tab.url()
|
||||
except (KeyError, RuntimeError, TypeError):
|
||||
except (KeyError, RuntimeError):
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/889
|
||||
# Catching RuntimeError and TypeError because we could be in
|
||||
# the middle of the webpage shutdown here.
|
||||
# Catching RuntimeError because we could be in the middle of
|
||||
# the webpage shutdown here.
|
||||
current_url = QUrl()
|
||||
|
||||
self.set_referer(req, current_url)
|
||||
|
||||
if PYQT_VERSION < 0x050301:
|
||||
# WORKAROUND (remove this when we bump the requirements to 5.3.1)
|
||||
#
|
||||
# If we don't disable our message handler, we get a freeze if a
|
||||
# warning is printed due to a PyQt bug, e.g. when clicking a
|
||||
# currency on http://ch.mouser.com/localsites/
|
||||
#
|
||||
# See http://www.riverbankcomputing.com/pipermail/pyqt/2014-June/034420.html
|
||||
with log.disable_qt_msghandler():
|
||||
reply = super().createRequest(op, req, outgoing_data)
|
||||
else:
|
||||
reply = super().createRequest(op, req, outgoing_data)
|
||||
return reply
|
||||
return super().createRequest(op, req, outgoing_data)
|
||||
|
||||
@@ -20,16 +20,12 @@
|
||||
"""QtWebKit specific qute://* handlers and glue code."""
|
||||
|
||||
import mimetypes
|
||||
import functools
|
||||
import configparser
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QObject
|
||||
from PyQt5.QtNetwork import QNetworkReply
|
||||
|
||||
from qutebrowser.browser import pdfjs, qutescheme
|
||||
from qutebrowser.browser.webkit.network import schemehandler, networkreply
|
||||
from qutebrowser.utils import jinja, log, message, objreg, usertypes, qtutils
|
||||
from qutebrowser.config import configexc, configdata
|
||||
from qutebrowser.utils import log, usertypes, qtutils
|
||||
|
||||
|
||||
class QuteSchemeHandler(schemehandler.SchemeHandler):
|
||||
@@ -70,34 +66,6 @@ class QuteSchemeHandler(schemehandler.SchemeHandler):
|
||||
self.parent())
|
||||
|
||||
|
||||
class JSBridge(QObject):
|
||||
|
||||
"""Javascript-bridge for special qute://... pages."""
|
||||
|
||||
@pyqtSlot(str, str, str)
|
||||
def set(self, sectname, optname, value):
|
||||
"""Slot to set a setting from qute://settings."""
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/727
|
||||
if ((sectname, optname) == ('content', 'allow-javascript') and
|
||||
value == 'false'):
|
||||
message.error("Refusing to disable javascript via qute://settings "
|
||||
"as it needs javascript support.")
|
||||
return
|
||||
try:
|
||||
objreg.get('config').set('conf', sectname, optname, value)
|
||||
except (configexc.Error, configparser.Error) as e:
|
||||
message.error(str(e))
|
||||
|
||||
|
||||
@qutescheme.add_handler('settings', backend=usertypes.Backend.QtWebKit)
|
||||
def qute_settings(_url):
|
||||
"""Handler for qute://settings. View/change qute configuration."""
|
||||
config_getter = functools.partial(objreg.get('config').get, raw=True)
|
||||
html = jinja.render('settings.html', title='settings', config=configdata,
|
||||
confget=config_getter)
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
@qutescheme.add_handler('pdfjs', backend=usertypes.Backend.QtWebKit)
|
||||
def qute_pdfjs(url):
|
||||
"""Handler for qute://pdfjs. Return the pdf.js viewer."""
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
|
||||
"""pyPEG parsing for the RFC 6266 (Content-Disposition) header."""
|
||||
|
||||
import collections
|
||||
import urllib.parse
|
||||
import string
|
||||
import re
|
||||
|
||||
import attr
|
||||
import pypeg2 as peg
|
||||
|
||||
from qutebrowser.utils import utils
|
||||
@@ -210,7 +210,13 @@ class ContentDispositionValue:
|
||||
peg.optional(';'))
|
||||
|
||||
|
||||
LangTagged = collections.namedtuple('LangTagged', ['string', 'langtag'])
|
||||
@attr.s
|
||||
class LangTagged:
|
||||
|
||||
"""A string with an associated language."""
|
||||
|
||||
string = attr.ib()
|
||||
langtag = attr.ib()
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
|
||||
@@ -25,13 +25,7 @@ from PyQt5.QtCore import QByteArray, QDataStream, QIODevice, QUrl
|
||||
from qutebrowser.utils import qtutils
|
||||
|
||||
|
||||
def _encode_url(url):
|
||||
"""Encode a QUrl suitable to pass to QWebHistory."""
|
||||
data = bytes(QUrl.toPercentEncoding(url.toString(), b':/#?&+=@%*'))
|
||||
return data.decode('ascii')
|
||||
|
||||
|
||||
def _serialize_ng(items, current_idx, stream):
|
||||
def _serialize_items(items, current_idx, stream):
|
||||
# {'currentItemIndex': 0,
|
||||
# 'history': [{'children': [],
|
||||
# 'documentSequenceNumber': 1485030525573123,
|
||||
@@ -47,13 +41,13 @@ def _serialize_ng(items, current_idx, stream):
|
||||
# 'urlString': 'about:blank'}]}
|
||||
data = {'currentItemIndex': current_idx, 'history': []}
|
||||
for item in items:
|
||||
data['history'].append(_serialize_item_ng(item))
|
||||
data['history'].append(_serialize_item(item))
|
||||
|
||||
stream.writeInt(3) # history stream version
|
||||
stream.writeQVariantMap(data)
|
||||
|
||||
|
||||
def _serialize_item_ng(item):
|
||||
def _serialize_item(item):
|
||||
data = {
|
||||
'originalURLString': item.original_url.toString(QUrl.FullyEncoded),
|
||||
'scrollPosition': {'x': 0, 'y': 0},
|
||||
@@ -68,82 +62,6 @@ def _serialize_item_ng(item):
|
||||
return data
|
||||
|
||||
|
||||
def _serialize_old(items, current_idx, stream):
|
||||
### Source/WebKit/qt/Api/qwebhistory.cpp operator<<
|
||||
stream.writeInt(2) # history stream version
|
||||
stream.writeInt(len(items))
|
||||
stream.writeInt(current_idx)
|
||||
|
||||
for i, item in enumerate(items):
|
||||
_serialize_item_old(i, item, stream)
|
||||
|
||||
|
||||
def _serialize_item_old(i, item, stream):
|
||||
"""Serialize a single WebHistoryItem into a QDataStream.
|
||||
|
||||
Args:
|
||||
i: The index of the current item.
|
||||
item: The WebHistoryItem to write.
|
||||
stream: The QDataStream to write to.
|
||||
"""
|
||||
### Source/WebCore/history/qt/HistoryItemQt.cpp restoreState
|
||||
## urlString
|
||||
stream.writeQString(_encode_url(item.url))
|
||||
## title
|
||||
stream.writeQString(item.title)
|
||||
## originalURLString
|
||||
stream.writeQString(_encode_url(item.original_url))
|
||||
|
||||
### Source/WebCore/history/HistoryItem.cpp decodeBackForwardTree
|
||||
## backForwardTreeEncodingVersion
|
||||
stream.writeUInt32(2)
|
||||
## size (recursion stack)
|
||||
stream.writeUInt64(0)
|
||||
## node->m_documentSequenceNumber
|
||||
# If two HistoryItems have the same document sequence number, then they
|
||||
# refer to the same instance of a document. Traversing history from one
|
||||
# such HistoryItem to another preserves the document.
|
||||
stream.writeInt64(i + 1)
|
||||
## size (node->m_documentState)
|
||||
stream.writeUInt64(0)
|
||||
## node->m_formContentType
|
||||
# info used to repost form data
|
||||
stream.writeQString(None)
|
||||
## hasFormData
|
||||
stream.writeBool(False)
|
||||
## node->m_itemSequenceNumber
|
||||
# If two HistoryItems have the same item sequence number, then they are
|
||||
# clones of one another. Traversing history from one such HistoryItem to
|
||||
# another is a no-op. HistoryItem clones are created for parent and
|
||||
# sibling frames when only a subframe navigates.
|
||||
stream.writeInt64(i + 1)
|
||||
## node->m_referrer
|
||||
stream.writeQString(None)
|
||||
## node->m_scrollPoint (x)
|
||||
try:
|
||||
stream.writeInt32(item.user_data['scroll-pos'].x())
|
||||
except (KeyError, TypeError):
|
||||
stream.writeInt32(0)
|
||||
## node->m_scrollPoint (y)
|
||||
try:
|
||||
stream.writeInt32(item.user_data['scroll-pos'].y())
|
||||
except (KeyError, TypeError):
|
||||
stream.writeInt32(0)
|
||||
## node->m_pageScaleFactor
|
||||
stream.writeFloat(1)
|
||||
## hasStateObject
|
||||
# Support for HTML5 History
|
||||
stream.writeBool(False)
|
||||
## node->m_target
|
||||
stream.writeQString(None)
|
||||
|
||||
### Source/WebCore/history/qt/HistoryItemQt.cpp restoreState
|
||||
## validUserData
|
||||
# We could restore the user data here, but we prefer to use the
|
||||
# QWebHistoryItem API for that.
|
||||
stream.writeBool(False)
|
||||
|
||||
|
||||
def serialize(items):
|
||||
"""Serialize a list of QWebHistoryItems to a data stream.
|
||||
|
||||
@@ -180,10 +98,7 @@ def serialize(items):
|
||||
else:
|
||||
current_idx = 0
|
||||
|
||||
if qtutils.is_qtwebkit_ng():
|
||||
_serialize_ng(items, current_idx, stream)
|
||||
else:
|
||||
_serialize_old(items, current_idx, stream)
|
||||
_serialize_items(items, current_idx, stream)
|
||||
|
||||
user_data += [item.user_data for item in items]
|
||||
|
||||
|
||||
@@ -168,7 +168,7 @@ class WebKitElement(webelem.AbstractWebElement):
|
||||
if width > 1 and height > 1:
|
||||
# fix coordinates according to zoom level
|
||||
zoom = self._elem.webFrame().zoomFactor()
|
||||
if not config.get('ui', 'zoom-text-only'):
|
||||
if not config.val.zoom.text_only:
|
||||
rect["left"] *= zoom
|
||||
rect["top"] *= zoom
|
||||
width *= zoom
|
||||
@@ -292,9 +292,6 @@ class WebKitElement(webelem.AbstractWebElement):
|
||||
elem = elem._parent() # pylint: disable=protected-access
|
||||
|
||||
def _move_text_cursor(self):
|
||||
if self is None:
|
||||
# old PyQt versions call the slot after the element is deleted.
|
||||
return
|
||||
if self.is_text_input() and self.is_editable():
|
||||
self._tab.caret.move_to_end_of_document()
|
||||
|
||||
|
||||
@@ -19,9 +19,12 @@
|
||||
|
||||
"""QtWebKit specific part of history."""
|
||||
|
||||
import functools
|
||||
|
||||
from PyQt5.QtWebKit import QWebHistoryInterface
|
||||
|
||||
from qutebrowser.utils import debug
|
||||
|
||||
|
||||
class WebHistoryInterface(QWebHistoryInterface):
|
||||
|
||||
@@ -34,11 +37,13 @@ class WebHistoryInterface(QWebHistoryInterface):
|
||||
def __init__(self, webhistory, parent=None):
|
||||
super().__init__(parent)
|
||||
self._history = webhistory
|
||||
self._history.changed.connect(self.historyContains.cache_clear)
|
||||
|
||||
def addHistoryEntry(self, url_string):
|
||||
"""Required for a QWebHistoryInterface impl, obsoleted by add_url."""
|
||||
pass
|
||||
|
||||
@functools.lru_cache(maxsize=32768)
|
||||
def historyContains(self, url_string):
|
||||
"""Called by WebKit to determine if a URL is contained in the history.
|
||||
|
||||
@@ -48,7 +53,8 @@ class WebHistoryInterface(QWebHistoryInterface):
|
||||
Return:
|
||||
True if the url is in the history, False otherwise.
|
||||
"""
|
||||
return url_string in self._history.history_dict
|
||||
with debug.log_time('sql', 'historyContains'):
|
||||
return url_string in self._history
|
||||
|
||||
|
||||
def init(history):
|
||||
|
||||
@@ -36,9 +36,9 @@ class WebKitInspector(inspector.AbstractWebInspector):
|
||||
self._set_widget(qwebinspector)
|
||||
|
||||
def inspect(self, page):
|
||||
if not config.get('general', 'developer-extras'):
|
||||
if not config.val.content.developer_extras:
|
||||
raise inspector.WebInspectorError(
|
||||
"Please enable developer-extras before using the "
|
||||
"Please enable content.developer_extras before using the "
|
||||
"webinspector!")
|
||||
self._widget.setPage(page)
|
||||
self.show()
|
||||
|
||||
@@ -33,7 +33,7 @@ from PyQt5.QtGui import QFont
|
||||
from PyQt5.QtWebKit import QWebSettings
|
||||
|
||||
from qutebrowser.config import config, websettings
|
||||
from qutebrowser.utils import standarddir, objreg, urlutils, qtutils
|
||||
from qutebrowser.utils import standarddir, urlutils
|
||||
from qutebrowser.browser import shared
|
||||
|
||||
|
||||
@@ -111,12 +111,11 @@ def _set_user_stylesheet():
|
||||
QWebSettings.globalSettings().setUserStyleSheetUrl(url)
|
||||
|
||||
|
||||
def update_settings(section, option):
|
||||
def _update_settings(option):
|
||||
"""Update global settings when qwebsettings changed."""
|
||||
if section == 'ui' and option in ['hide-scrollbar', 'user-stylesheet']:
|
||||
if option in ['scrollbar.hide', 'content.user_stylesheets']:
|
||||
_set_user_stylesheet()
|
||||
|
||||
websettings.update_mappings(MAPPINGS, section, option)
|
||||
websettings.update_mappings(MAPPINGS, option)
|
||||
|
||||
|
||||
def init(_args):
|
||||
@@ -132,16 +131,9 @@ def init(_args):
|
||||
QWebSettings.setOfflineStoragePath(
|
||||
os.path.join(data_path, 'offline-storage'))
|
||||
|
||||
if (config.get('general', 'private-browsing') and
|
||||
not qtutils.version_check('5.4.2')):
|
||||
# WORKAROUND for https://codereview.qt-project.org/#/c/108936/
|
||||
# Won't work when private browsing is not enabled globally, but that's
|
||||
# the best we can do...
|
||||
QWebSettings.setIconDatabasePath('')
|
||||
|
||||
websettings.init_mappings(MAPPINGS)
|
||||
_set_user_stylesheet()
|
||||
objreg.get('config').changed.connect(update_settings)
|
||||
config.instance.changed.connect(_update_settings)
|
||||
|
||||
|
||||
def shutdown():
|
||||
@@ -152,96 +144,79 @@ def shutdown():
|
||||
|
||||
|
||||
MAPPINGS = {
|
||||
'content': {
|
||||
'allow-images':
|
||||
Attribute(QWebSettings.AutoLoadImages),
|
||||
'allow-javascript':
|
||||
Attribute(QWebSettings.JavascriptEnabled),
|
||||
'javascript-can-open-windows-automatically':
|
||||
Attribute(QWebSettings.JavascriptCanOpenWindows),
|
||||
'javascript-can-close-windows':
|
||||
Attribute(QWebSettings.JavascriptCanCloseWindows),
|
||||
'javascript-can-access-clipboard':
|
||||
Attribute(QWebSettings.JavascriptCanAccessClipboard),
|
||||
'allow-plugins':
|
||||
Attribute(QWebSettings.PluginsEnabled),
|
||||
'webgl':
|
||||
Attribute(QWebSettings.WebGLEnabled),
|
||||
'hyperlink-auditing':
|
||||
Attribute(QWebSettings.HyperlinkAuditingEnabled),
|
||||
'local-content-can-access-remote-urls':
|
||||
Attribute(QWebSettings.LocalContentCanAccessRemoteUrls),
|
||||
'local-content-can-access-file-urls':
|
||||
Attribute(QWebSettings.LocalContentCanAccessFileUrls),
|
||||
'cookies-accept':
|
||||
CookiePolicy(),
|
||||
},
|
||||
'network': {
|
||||
'dns-prefetch':
|
||||
Attribute(QWebSettings.DnsPrefetchEnabled),
|
||||
},
|
||||
'input': {
|
||||
'spatial-navigation':
|
||||
Attribute(QWebSettings.SpatialNavigationEnabled),
|
||||
'links-included-in-focus-chain':
|
||||
Attribute(QWebSettings.LinksIncludedInFocusChain),
|
||||
},
|
||||
'fonts': {
|
||||
'web-family-standard':
|
||||
FontFamilySetter(QWebSettings.StandardFont),
|
||||
'web-family-fixed':
|
||||
FontFamilySetter(QWebSettings.FixedFont),
|
||||
'web-family-serif':
|
||||
FontFamilySetter(QWebSettings.SerifFont),
|
||||
'web-family-sans-serif':
|
||||
FontFamilySetter(QWebSettings.SansSerifFont),
|
||||
'web-family-cursive':
|
||||
FontFamilySetter(QWebSettings.CursiveFont),
|
||||
'web-family-fantasy':
|
||||
FontFamilySetter(QWebSettings.FantasyFont),
|
||||
'web-size-minimum':
|
||||
Setter(QWebSettings.setFontSize,
|
||||
args=[QWebSettings.MinimumFontSize]),
|
||||
'web-size-minimum-logical':
|
||||
Setter(QWebSettings.setFontSize,
|
||||
args=[QWebSettings.MinimumLogicalFontSize]),
|
||||
'web-size-default':
|
||||
Setter(QWebSettings.setFontSize,
|
||||
args=[QWebSettings.DefaultFontSize]),
|
||||
'web-size-default-fixed':
|
||||
Setter(QWebSettings.setFontSize,
|
||||
args=[QWebSettings.DefaultFixedFontSize]),
|
||||
},
|
||||
'ui': {
|
||||
'zoom-text-only':
|
||||
Attribute(QWebSettings.ZoomTextOnly),
|
||||
'frame-flattening':
|
||||
Attribute(QWebSettings.FrameFlatteningEnabled),
|
||||
# user-stylesheet is handled separately
|
||||
'smooth-scrolling':
|
||||
Attribute(QWebSettings.ScrollAnimatorEnabled),
|
||||
#'accelerated-compositing':
|
||||
# Attribute(QWebSettings.AcceleratedCompositingEnabled),
|
||||
#'tiled-backing-store':
|
||||
# Attribute(QWebSettings.TiledBackingStoreEnabled),
|
||||
},
|
||||
'storage': {
|
||||
'offline-web-application-cache':
|
||||
Attribute(QWebSettings.OfflineWebApplicationCacheEnabled),
|
||||
'local-storage':
|
||||
Attribute(QWebSettings.LocalStorageEnabled,
|
||||
QWebSettings.OfflineStorageDatabaseEnabled),
|
||||
'maximum-pages-in-cache':
|
||||
StaticSetter(QWebSettings.setMaximumPagesInCache),
|
||||
},
|
||||
'general': {
|
||||
'developer-extras':
|
||||
Attribute(QWebSettings.DeveloperExtrasEnabled),
|
||||
'print-element-backgrounds':
|
||||
Attribute(QWebSettings.PrintElementBackgrounds),
|
||||
'xss-auditing':
|
||||
Attribute(QWebSettings.XSSAuditingEnabled),
|
||||
'default-encoding':
|
||||
Setter(QWebSettings.setDefaultTextEncoding),
|
||||
}
|
||||
'content.images':
|
||||
Attribute(QWebSettings.AutoLoadImages),
|
||||
'content.javascript.enabled':
|
||||
Attribute(QWebSettings.JavascriptEnabled),
|
||||
'content.javascript.can_open_tabs_automatically':
|
||||
Attribute(QWebSettings.JavascriptCanOpenWindows),
|
||||
'content.javascript.can_close_tabs':
|
||||
Attribute(QWebSettings.JavascriptCanCloseWindows),
|
||||
'content.javascript.can_access_clipboard':
|
||||
Attribute(QWebSettings.JavascriptCanAccessClipboard),
|
||||
'content.plugins':
|
||||
Attribute(QWebSettings.PluginsEnabled),
|
||||
'content.webgl':
|
||||
Attribute(QWebSettings.WebGLEnabled),
|
||||
'content.hyperlink_auditing':
|
||||
Attribute(QWebSettings.HyperlinkAuditingEnabled),
|
||||
'content.local_content_can_access_remote_urls':
|
||||
Attribute(QWebSettings.LocalContentCanAccessRemoteUrls),
|
||||
'content.local_content_can_access_file_urls':
|
||||
Attribute(QWebSettings.LocalContentCanAccessFileUrls),
|
||||
'content.cookies.accept':
|
||||
CookiePolicy(),
|
||||
'content.dns_prefetch':
|
||||
Attribute(QWebSettings.DnsPrefetchEnabled),
|
||||
'content.frame_flattening':
|
||||
Attribute(QWebSettings.FrameFlatteningEnabled),
|
||||
'content.cache.appcache':
|
||||
Attribute(QWebSettings.OfflineWebApplicationCacheEnabled),
|
||||
'content.local_storage':
|
||||
Attribute(QWebSettings.LocalStorageEnabled,
|
||||
QWebSettings.OfflineStorageDatabaseEnabled),
|
||||
'content.cache.maximum_pages':
|
||||
StaticSetter(QWebSettings.setMaximumPagesInCache),
|
||||
'content.developer_extras':
|
||||
Attribute(QWebSettings.DeveloperExtrasEnabled),
|
||||
'content.print_element_backgrounds':
|
||||
Attribute(QWebSettings.PrintElementBackgrounds),
|
||||
'content.xss_auditing':
|
||||
Attribute(QWebSettings.XSSAuditingEnabled),
|
||||
'content.default_encoding':
|
||||
Setter(QWebSettings.setDefaultTextEncoding),
|
||||
# content.user_stylesheets is handled separately
|
||||
|
||||
'input.spatial_navigation':
|
||||
Attribute(QWebSettings.SpatialNavigationEnabled),
|
||||
'input.links_included_in_focus_chain':
|
||||
Attribute(QWebSettings.LinksIncludedInFocusChain),
|
||||
|
||||
'fonts.web.family.standard':
|
||||
FontFamilySetter(QWebSettings.StandardFont),
|
||||
'fonts.web.family.fixed':
|
||||
FontFamilySetter(QWebSettings.FixedFont),
|
||||
'fonts.web.family.serif':
|
||||
FontFamilySetter(QWebSettings.SerifFont),
|
||||
'fonts.web.family.sans_serif':
|
||||
FontFamilySetter(QWebSettings.SansSerifFont),
|
||||
'fonts.web.family.cursive':
|
||||
FontFamilySetter(QWebSettings.CursiveFont),
|
||||
'fonts.web.family.fantasy':
|
||||
FontFamilySetter(QWebSettings.FantasyFont),
|
||||
'fonts.web.size.minimum':
|
||||
Setter(QWebSettings.setFontSize, args=[QWebSettings.MinimumFontSize]),
|
||||
'fonts.web.size.minimum_logical':
|
||||
Setter(QWebSettings.setFontSize,
|
||||
args=[QWebSettings.MinimumLogicalFontSize]),
|
||||
'fonts.web.size.default':
|
||||
Setter(QWebSettings.setFontSize, args=[QWebSettings.DefaultFontSize]),
|
||||
'fonts.web.size.default_fixed':
|
||||
Setter(QWebSettings.setFontSize,
|
||||
args=[QWebSettings.DefaultFixedFontSize]),
|
||||
|
||||
'zoom.text_only':
|
||||
Attribute(QWebSettings.ZoomTextOnly),
|
||||
'scrolling.smooth':
|
||||
Attribute(QWebSettings.ScrollAnimatorEnabled),
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
"""Wrapper over our (QtWebKit) WebView."""
|
||||
|
||||
import sys
|
||||
import functools
|
||||
import xml.etree.ElementTree
|
||||
|
||||
@@ -27,32 +26,15 @@ import sip
|
||||
from PyQt5.QtCore import (pyqtSlot, Qt, QEvent, QUrl, QPoint, QTimer, QSizeF,
|
||||
QSize)
|
||||
from PyQt5.QtGui import QKeyEvent
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from PyQt5.QtWebKitWidgets import QWebPage, QWebFrame
|
||||
from PyQt5.QtWebKit import QWebSettings
|
||||
from PyQt5.QtPrintSupport import QPrinter
|
||||
|
||||
from qutebrowser.browser import browsertab
|
||||
from qutebrowser.browser.network import proxy
|
||||
from qutebrowser.browser.webkit import webview, tabhistory, webkitelem
|
||||
from qutebrowser.browser.webkit.network import webkitqutescheme
|
||||
from qutebrowser.utils import qtutils, objreg, usertypes, utils, log, debug
|
||||
|
||||
|
||||
def init():
|
||||
"""Initialize QtWebKit-specific modules."""
|
||||
qapp = QApplication.instance()
|
||||
|
||||
if not qtutils.version_check('5.8'):
|
||||
# Otherwise we initialize it globally in app.py
|
||||
log.init.debug("Initializing proxy...")
|
||||
proxy.init()
|
||||
|
||||
log.init.debug("Initializing js-bridge...")
|
||||
js_bridge = webkitqutescheme.JSBridge(qapp)
|
||||
objreg.register('js-bridge', js_bridge)
|
||||
|
||||
|
||||
class WebKitAction(browsertab.AbstractAction):
|
||||
|
||||
"""QtWebKit implementations related to web actions."""
|
||||
@@ -72,20 +54,14 @@ class WebKitPrinting(browsertab.AbstractPrinting):
|
||||
|
||||
"""QtWebKit implementations related to printing."""
|
||||
|
||||
def _do_check(self):
|
||||
if not qtutils.check_print_compat():
|
||||
# WORKAROUND (remove this when we bump the requirements to 5.3.0)
|
||||
raise browsertab.WebTabError(
|
||||
"Printing on Qt < 5.3.0 on Windows is broken, please upgrade!")
|
||||
|
||||
def check_pdf_support(self):
|
||||
self._do_check()
|
||||
pass
|
||||
|
||||
def check_printer_support(self):
|
||||
self._do_check()
|
||||
pass
|
||||
|
||||
def check_preview_support(self):
|
||||
self._do_check()
|
||||
pass
|
||||
|
||||
def to_pdf(self, filename):
|
||||
printer = QPrinter()
|
||||
@@ -140,24 +116,21 @@ class WebKitSearch(browsertab.AbstractSearch):
|
||||
self._widget.findText('')
|
||||
self._widget.findText('', QWebPage.HighlightAllOccurrences)
|
||||
|
||||
def search(self, text, *, ignore_case=False, reverse=False,
|
||||
def search(self, text, *, ignore_case='never', reverse=False,
|
||||
result_cb=None):
|
||||
self.search_displayed = True
|
||||
flags = QWebPage.FindWrapsAroundDocument
|
||||
if ignore_case == 'smart':
|
||||
if not text.islower():
|
||||
flags |= QWebPage.FindCaseSensitively
|
||||
elif not ignore_case:
|
||||
flags |= QWebPage.FindCaseSensitively
|
||||
self.text = text
|
||||
self._flags = QWebPage.FindWrapsAroundDocument
|
||||
if self._is_case_sensitive(ignore_case):
|
||||
self._flags |= QWebPage.FindCaseSensitively
|
||||
if reverse:
|
||||
flags |= QWebPage.FindBackward
|
||||
self._flags |= QWebPage.FindBackward
|
||||
# We actually search *twice* - once to highlight everything, then again
|
||||
# to get a mark so we can navigate.
|
||||
found = self._widget.findText(text, flags)
|
||||
self._widget.findText(text, flags | QWebPage.HighlightAllOccurrences)
|
||||
self.text = text
|
||||
self._flags = flags
|
||||
self._call_cb(result_cb, found, text, flags, 'search')
|
||||
found = self._widget.findText(text, self._flags)
|
||||
self._widget.findText(text,
|
||||
self._flags | QWebPage.HighlightAllOccurrences)
|
||||
self._call_cb(result_cb, found, text, self._flags, 'search')
|
||||
|
||||
def next_result(self, *, result_cb=None):
|
||||
self.search_displayed = True
|
||||
@@ -249,11 +222,11 @@ class WebKitCaret(browsertab.AbstractCaret):
|
||||
def move_to_end_of_word(self, count=1):
|
||||
if not self.selection_enabled:
|
||||
act = [QWebPage.MoveToNextWord]
|
||||
if sys.platform == 'win32': # pragma: no cover
|
||||
if utils.is_windows: # pragma: no cover
|
||||
act.append(QWebPage.MoveToPreviousChar)
|
||||
else:
|
||||
act = [QWebPage.SelectNextWord]
|
||||
if sys.platform == 'win32': # pragma: no cover
|
||||
if utils.is_windows: # pragma: no cover
|
||||
act.append(QWebPage.SelectPreviousChar)
|
||||
for _ in range(count):
|
||||
for a in act:
|
||||
@@ -262,11 +235,11 @@ class WebKitCaret(browsertab.AbstractCaret):
|
||||
def move_to_next_word(self, count=1):
|
||||
if not self.selection_enabled:
|
||||
act = [QWebPage.MoveToNextWord]
|
||||
if sys.platform != 'win32': # pragma: no branch
|
||||
if not utils.is_windows: # pragma: no branch
|
||||
act.append(QWebPage.MoveToNextChar)
|
||||
else:
|
||||
act = [QWebPage.SelectNextWord]
|
||||
if sys.platform != 'win32': # pragma: no branch
|
||||
if not utils.is_windows: # pragma: no branch
|
||||
act.append(QWebPage.SelectNextChar)
|
||||
for _ in range(count):
|
||||
for a in act:
|
||||
@@ -405,9 +378,6 @@ class WebKitZoom(browsertab.AbstractZoom):
|
||||
def _set_factor_internal(self, factor):
|
||||
self._widget.setZoomFactor(factor)
|
||||
|
||||
def factor(self):
|
||||
return self._widget.zoomFactor()
|
||||
|
||||
|
||||
class WebKitScroller(browsertab.AbstractScroller):
|
||||
|
||||
@@ -513,18 +483,18 @@ class WebKitHistory(browsertab.AbstractHistory):
|
||||
def current_idx(self):
|
||||
return self._history.currentItemIndex()
|
||||
|
||||
def back(self):
|
||||
self._history.back()
|
||||
|
||||
def forward(self):
|
||||
self._history.forward()
|
||||
|
||||
def can_go_back(self):
|
||||
return self._history.canGoBack()
|
||||
|
||||
def can_go_forward(self):
|
||||
return self._history.canGoForward()
|
||||
|
||||
def _item_at(self, i):
|
||||
return self._history.itemAt(i)
|
||||
|
||||
def _go_to_item(self, item):
|
||||
return self._history.goToItem(item)
|
||||
|
||||
def serialize(self):
|
||||
return qtutils.serialize(self._history)
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
import html
|
||||
import functools
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, PYQT_VERSION, Qt, QUrl, QPoint
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QUrl, QPoint
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
|
||||
from PyQt5.QtWidgets import QFileDialog
|
||||
@@ -33,8 +33,8 @@ from qutebrowser.config import config
|
||||
from qutebrowser.browser import pdfjs, shared
|
||||
from qutebrowser.browser.webkit import http
|
||||
from qutebrowser.browser.webkit.network import networkmanager
|
||||
from qutebrowser.utils import (message, usertypes, log, jinja, qtutils, utils,
|
||||
objreg, debug, urlutils)
|
||||
from qutebrowser.utils import (message, usertypes, log, jinja, objreg, debug,
|
||||
urlutils)
|
||||
|
||||
|
||||
class BrowserPage(QWebPage):
|
||||
@@ -87,22 +87,16 @@ class BrowserPage(QWebPage):
|
||||
self.restoreFrameStateRequested.connect(
|
||||
self.on_restore_frame_state_requested)
|
||||
|
||||
if PYQT_VERSION > 0x050300:
|
||||
# WORKAROUND (remove this when we bump the requirements to 5.3.1)
|
||||
# We can't override javaScriptPrompt with older PyQt-versions because
|
||||
# of a bug in PyQt.
|
||||
# See http://www.riverbankcomputing.com/pipermail/pyqt/2014-June/034385.html
|
||||
|
||||
def javaScriptPrompt(self, frame, js_msg, default):
|
||||
"""Override javaScriptPrompt to use qutebrowser prompts."""
|
||||
if self._is_shutting_down:
|
||||
return (False, "")
|
||||
try:
|
||||
return shared.javascript_prompt(frame.url(), js_msg, default,
|
||||
abort_on=[self.loadStarted,
|
||||
self.shutting_down])
|
||||
except shared.CallSuper:
|
||||
return super().javaScriptPrompt(frame, js_msg, default)
|
||||
def javaScriptPrompt(self, frame, js_msg, default):
|
||||
"""Override javaScriptPrompt to use qutebrowser prompts."""
|
||||
if self._is_shutting_down:
|
||||
return (False, "")
|
||||
try:
|
||||
return shared.javascript_prompt(frame.url(), js_msg, default,
|
||||
abort_on=[self.loadStarted,
|
||||
self.shutting_down])
|
||||
except shared.CallSuper:
|
||||
return super().javaScriptPrompt(frame, js_msg, default)
|
||||
|
||||
def _handle_errorpage(self, info, errpage):
|
||||
"""Display an error page if needed.
|
||||
@@ -170,7 +164,7 @@ class BrowserPage(QWebPage):
|
||||
title = "Error loading page: {}".format(urlstr)
|
||||
error_html = jinja.render(
|
||||
'error.html',
|
||||
title=title, url=urlstr, error=error_str, icon='')
|
||||
title=title, url=urlstr, error=error_str)
|
||||
errpage.content = error_html.encode('utf-8')
|
||||
errpage.encoding = 'utf-8'
|
||||
return True
|
||||
@@ -225,10 +219,6 @@ class BrowserPage(QWebPage):
|
||||
|
||||
def on_print_requested(self, frame):
|
||||
"""Handle printing when requested via javascript."""
|
||||
if not qtutils.check_print_compat():
|
||||
message.error("Printing on Qt < 5.3.0 on Windows is broken, "
|
||||
"please upgrade!")
|
||||
return
|
||||
printdiag = QPrintDialog()
|
||||
printdiag.setAttribute(Qt.WA_DeleteOnClose)
|
||||
printdiag.open(lambda: frame.print(printdiag.printer()))
|
||||
@@ -277,7 +267,7 @@ class BrowserPage(QWebPage):
|
||||
reply.finished.connect(functools.partial(
|
||||
self.display_content, reply, 'image/jpeg'))
|
||||
elif (mimetype in ['application/pdf', 'application/x-pdf'] and
|
||||
config.get('content', 'enable-pdfjs')):
|
||||
config.val.content.pdfjs):
|
||||
# Use pdf.js to display the page
|
||||
self._show_pdfjs(reply)
|
||||
else:
|
||||
@@ -304,8 +294,8 @@ class BrowserPage(QWebPage):
|
||||
return
|
||||
|
||||
options = {
|
||||
QWebPage.Notifications: ('content', 'notifications'),
|
||||
QWebPage.Geolocation: ('content', 'geolocation'),
|
||||
QWebPage.Notifications: 'content.notifications',
|
||||
QWebPage.Geolocation: 'content.geolocation',
|
||||
}
|
||||
messages = {
|
||||
QWebPage.Notifications: 'show notifications',
|
||||
@@ -350,15 +340,7 @@ class BrowserPage(QWebPage):
|
||||
frame: The QWebFrame which gets saved.
|
||||
item: The QWebHistoryItem to be saved.
|
||||
"""
|
||||
try:
|
||||
if frame != self.mainFrame():
|
||||
return
|
||||
except RuntimeError:
|
||||
# With Qt 5.2.1 (Ubuntu Trusty) we get this when closing a tab:
|
||||
# RuntimeError: wrapped C/C++ object of type BrowserPage has
|
||||
# been deleted
|
||||
# Since the information here isn't that important for closing web
|
||||
# views anyways, we ignore this error.
|
||||
if frame != self.mainFrame():
|
||||
return
|
||||
data = {
|
||||
'zoom': frame.zoomFactor(),
|
||||
@@ -384,7 +366,7 @@ class BrowserPage(QWebPage):
|
||||
|
||||
def userAgentForUrl(self, url):
|
||||
"""Override QWebPage::userAgentForUrl to customize the user agent."""
|
||||
ua = config.get('network', 'user-agent')
|
||||
ua = config.val.content.headers.user_agent
|
||||
if ua is None:
|
||||
return super().userAgentForUrl(url)
|
||||
else:
|
||||
@@ -401,9 +383,6 @@ class BrowserPage(QWebPage):
|
||||
"""
|
||||
return ext in self._extension_handlers
|
||||
|
||||
# WORKAROUND for:
|
||||
# http://www.riverbankcomputing.com/pipermail/pyqt/2014-August/034722.html
|
||||
@utils.prevent_exceptions(False, PYQT_VERSION < 0x50302)
|
||||
def extension(self, ext, opt, out):
|
||||
"""Override QWebPage::extension to provide error pages.
|
||||
|
||||
@@ -446,15 +425,8 @@ class BrowserPage(QWebPage):
|
||||
|
||||
def javaScriptConsoleMessage(self, msg, line, source):
|
||||
"""Override javaScriptConsoleMessage to use debug log."""
|
||||
log_javascript_console = config.get('general',
|
||||
'log-javascript-console')
|
||||
logstring = "[{}:{}] {}".format(source, line, msg)
|
||||
logmap = {
|
||||
'debug': log.js.debug,
|
||||
'info': log.js.info,
|
||||
'none': lambda arg: None
|
||||
}
|
||||
logmap[log_javascript_console](logstring)
|
||||
shared.javascript_log_message(usertypes.JsLogLevel.unknown,
|
||||
source, line, msg)
|
||||
|
||||
def acceptNavigationRequest(self,
|
||||
_frame: QWebFrame,
|
||||
|
||||
@@ -19,17 +19,15 @@
|
||||
|
||||
"""The main browser widgets."""
|
||||
|
||||
import sys
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QUrl
|
||||
from PyQt5.QtGui import QPalette
|
||||
from PyQt5.QtWidgets import QStyleFactory
|
||||
from PyQt5.QtWebKit import QWebSettings
|
||||
from PyQt5.QtWebKitWidgets import QWebView, QWebPage, QWebFrame
|
||||
from PyQt5.QtWebKitWidgets import QWebView, QWebPage
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.keyinput import modeman
|
||||
from qutebrowser.utils import log, usertypes, utils, qtutils, objreg, debug
|
||||
from qutebrowser.utils import log, usertypes, utils, objreg, debug
|
||||
from qutebrowser.browser.webkit import webpage
|
||||
|
||||
|
||||
@@ -57,7 +55,7 @@ class WebView(QWebView):
|
||||
|
||||
def __init__(self, *, win_id, tab_id, tab, private, parent=None):
|
||||
super().__init__(parent)
|
||||
if sys.platform == 'darwin' and qtutils.version_check('5.4'):
|
||||
if utils.is_mac:
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-42948
|
||||
# See https://github.com/qutebrowser/qutebrowser/issues/462
|
||||
self.setStyle(QStyleFactory.create('Fusion'))
|
||||
@@ -74,13 +72,9 @@ class WebView(QWebView):
|
||||
page = webpage.BrowserPage(win_id=self.win_id, tab_id=self._tab_id,
|
||||
tabdata=tab.data, private=private,
|
||||
parent=self)
|
||||
|
||||
try:
|
||||
page.setVisibilityState(
|
||||
QWebPage.VisibilityStateVisible if self.isVisible()
|
||||
else QWebPage.VisibilityStateHidden)
|
||||
except AttributeError:
|
||||
pass
|
||||
page.setVisibilityState(
|
||||
QWebPage.VisibilityStateVisible if self.isVisible()
|
||||
else QWebPage.VisibilityStateHidden)
|
||||
|
||||
self.setPage(page)
|
||||
|
||||
@@ -88,7 +82,7 @@ class WebView(QWebView):
|
||||
window=win_id)
|
||||
mode_manager.entered.connect(self.on_mode_entered)
|
||||
mode_manager.left.connect(self.on_mode_left)
|
||||
objreg.get('config').changed.connect(self._set_bg_color)
|
||||
config.instance.changed.connect(self._set_bg_color)
|
||||
|
||||
def __repr__(self):
|
||||
url = utils.elide(self.url().toDisplayString(QUrl.EncodeUnicode), 100)
|
||||
@@ -107,10 +101,10 @@ class WebView(QWebView):
|
||||
# deleted
|
||||
pass
|
||||
|
||||
@config.change_filter('colors', 'webpage.bg')
|
||||
@config.change_filter('colors.webpage.bg')
|
||||
def _set_bg_color(self):
|
||||
"""Set the webpage background color as configured."""
|
||||
col = config.get('colors', 'webpage.bg')
|
||||
col = config.val.colors.webpage.bg
|
||||
palette = self.palette()
|
||||
if col is None:
|
||||
col = self.style().standardPalette().color(QPalette.Base)
|
||||
@@ -135,22 +129,6 @@ class WebView(QWebView):
|
||||
url: The URL to load as QUrl
|
||||
"""
|
||||
self.load(url)
|
||||
if url.scheme() == 'qute':
|
||||
frame = self.page().mainFrame()
|
||||
frame.javaScriptWindowObjectCleared.connect(self.add_js_bridge)
|
||||
|
||||
@pyqtSlot()
|
||||
def add_js_bridge(self):
|
||||
"""Add the javascript bridge for qute://... pages."""
|
||||
frame = self.sender()
|
||||
if not isinstance(frame, QWebFrame):
|
||||
log.webview.error("Got non-QWebFrame {!r} in "
|
||||
"add_js_bridge!".format(frame))
|
||||
return
|
||||
|
||||
if frame.url().scheme() == 'qute':
|
||||
bridge = objreg.get('js-bridge')
|
||||
frame.addToJavaScriptWindowObject('qute', bridge)
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def on_mode_entered(self, mode):
|
||||
@@ -256,12 +234,8 @@ class WebView(QWebView):
|
||||
Return:
|
||||
The superclass event return value.
|
||||
"""
|
||||
try:
|
||||
self.page().setVisibilityState(QWebPage.VisibilityStateVisible)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
super().showEvent(e)
|
||||
self.page().setVisibilityState(QWebPage.VisibilityStateVisible)
|
||||
|
||||
def hideEvent(self, e):
|
||||
"""Extend hideEvent to set the page visibility state to hidden.
|
||||
@@ -272,12 +246,8 @@ class WebView(QWebView):
|
||||
Return:
|
||||
The superclass event return value.
|
||||
"""
|
||||
try:
|
||||
self.page().setVisibilityState(QWebPage.VisibilityStateHidden)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
super().hideEvent(e)
|
||||
self.page().setVisibilityState(QWebPage.VisibilityStateHidden)
|
||||
|
||||
def mousePressEvent(self, e):
|
||||
"""Set the tabdata ClickTarget on a mousepress.
|
||||
@@ -285,10 +255,10 @@ class WebView(QWebView):
|
||||
This is implemented here as we don't need it for QtWebEngine.
|
||||
"""
|
||||
if e.button() == Qt.MidButton or e.modifiers() & Qt.ControlModifier:
|
||||
background_tabs = config.get('tabs', 'background-tabs')
|
||||
background = config.val.tabs.background
|
||||
if e.modifiers() & Qt.ShiftModifier:
|
||||
background_tabs = not background_tabs
|
||||
if background_tabs:
|
||||
background = not background
|
||||
if background:
|
||||
target = usertypes.ClickTarget.tab_bg
|
||||
else:
|
||||
target = usertypes.ClickTarget.tab
|
||||
|
||||
@@ -23,33 +23,33 @@ Defined here to avoid circular dependency hell.
|
||||
"""
|
||||
|
||||
|
||||
class CommandError(Exception):
|
||||
class Error(Exception):
|
||||
|
||||
"""Base class for all cmdexc errors."""
|
||||
|
||||
|
||||
class CommandError(Error):
|
||||
|
||||
"""Raised when a command encounters an error while running."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class CommandMetaError(Exception):
|
||||
|
||||
"""Common base class for exceptions occurring before a command is run."""
|
||||
|
||||
|
||||
class NoSuchCommandError(CommandMetaError):
|
||||
class NoSuchCommandError(Error):
|
||||
|
||||
"""Raised when a command wasn't found."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ArgumentTypeError(CommandMetaError):
|
||||
class ArgumentTypeError(Error):
|
||||
|
||||
"""Raised when an argument had an invalid type."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PrerequisitesError(CommandMetaError):
|
||||
class PrerequisitesError(Error):
|
||||
|
||||
"""Raised when a cmd can't be used because some prerequisites aren't met.
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
|
||||
Module attributes:
|
||||
cmd_dict: A mapping from command-strings to command objects.
|
||||
aliases: A list of all aliases, needed for doc generation.
|
||||
"""
|
||||
|
||||
import inspect
|
||||
@@ -30,7 +29,6 @@ from qutebrowser.utils import qtutils, log
|
||||
from qutebrowser.commands import command, cmdexc
|
||||
|
||||
cmd_dict = {}
|
||||
aliases = []
|
||||
|
||||
|
||||
def check_overflow(arg, ctype):
|
||||
@@ -88,28 +86,6 @@ class register: # pylint: disable=invalid-name
|
||||
self._name = name
|
||||
self._kwargs = kwargs
|
||||
|
||||
def _get_names(self, func):
|
||||
"""Get the name(s) which should be used for the current command.
|
||||
|
||||
If the name hasn't been overridden explicitly, the function name is
|
||||
transformed.
|
||||
|
||||
If it has been set, it can either be a string which is
|
||||
used directly, or an iterable.
|
||||
|
||||
Args:
|
||||
func: The function to get the name of.
|
||||
|
||||
Return:
|
||||
A list of names, with the main name being the first item.
|
||||
"""
|
||||
if self._name is None:
|
||||
return [func.__name__.lower().replace('_', '-')]
|
||||
elif isinstance(self._name, str):
|
||||
return [self._name]
|
||||
else:
|
||||
return self._name
|
||||
|
||||
def __call__(self, func):
|
||||
"""Register the command before running the function.
|
||||
|
||||
@@ -124,17 +100,17 @@ class register: # pylint: disable=invalid-name
|
||||
Return:
|
||||
The original function (unmodified).
|
||||
"""
|
||||
global aliases
|
||||
names = self._get_names(func)
|
||||
log.commands.vdebug("Registering command {}".format(names[0]))
|
||||
for name in names:
|
||||
if name in cmd_dict:
|
||||
raise ValueError("{} is already registered!".format(name))
|
||||
cmd = command.Command(name=names[0], instance=self._instance,
|
||||
if self._name is None:
|
||||
name = func.__name__.lower().replace('_', '-')
|
||||
else:
|
||||
assert isinstance(self._name, str), self._name
|
||||
name = self._name
|
||||
log.commands.vdebug("Registering command {}".format(name))
|
||||
if name in cmd_dict:
|
||||
raise ValueError("{} is already registered!".format(name))
|
||||
cmd = command.Command(name=name, instance=self._instance,
|
||||
handler=func, **self._kwargs)
|
||||
for name in names:
|
||||
cmd_dict[name] = cmd
|
||||
aliases += names[1:]
|
||||
cmd_dict[name] = cmd
|
||||
return func
|
||||
|
||||
|
||||
|
||||
@@ -22,44 +22,32 @@
|
||||
import inspect
|
||||
import collections
|
||||
import traceback
|
||||
import typing
|
||||
|
||||
import attr
|
||||
|
||||
from qutebrowser.commands import cmdexc, argparser
|
||||
from qutebrowser.utils import (log, utils, message, docutils, objreg,
|
||||
usertypes, typing)
|
||||
from qutebrowser.utils import log, message, docutils, objreg, usertypes
|
||||
from qutebrowser.utils import debug as debug_utils
|
||||
from qutebrowser.misc import objects
|
||||
|
||||
|
||||
@attr.s
|
||||
class ArgInfo:
|
||||
|
||||
"""Information about an argument."""
|
||||
|
||||
def __init__(self, win_id=False, count=False, hide=False, metavar=None,
|
||||
flag=None, completion=None, choices=None):
|
||||
if win_id and count:
|
||||
win_id = attr.ib(False)
|
||||
count = attr.ib(False)
|
||||
hide = attr.ib(False)
|
||||
metavar = attr.ib(None)
|
||||
flag = attr.ib(None)
|
||||
completion = attr.ib(None)
|
||||
choices = attr.ib(None)
|
||||
|
||||
def __attrs_post_init__(self):
|
||||
if self.win_id and self.count:
|
||||
raise TypeError("Argument marked as both count/win_id!")
|
||||
self.win_id = win_id
|
||||
self.count = count
|
||||
self.flag = flag
|
||||
self.hide = hide
|
||||
self.metavar = metavar
|
||||
self.completion = completion
|
||||
self.choices = choices
|
||||
|
||||
def __eq__(self, other):
|
||||
return (self.win_id == other.win_id and
|
||||
self.count == other.count and
|
||||
self.flag == other.flag and
|
||||
self.hide == other.hide and
|
||||
self.metavar == other.metavar and
|
||||
self.completion == other.completion and
|
||||
self.choices == other.choices)
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, win_id=self.win_id, count=self.count,
|
||||
flag=self.flag, hide=self.hide,
|
||||
metavar=self.metavar, completion=self.completion,
|
||||
choices=self.choices, constructor=True)
|
||||
|
||||
|
||||
class Command:
|
||||
@@ -90,7 +78,7 @@ class Command:
|
||||
|
||||
def __init__(self, *, handler, name, instance=None, maxsplit=None,
|
||||
hide=False, modes=None, not_modes=None, debug=False,
|
||||
ignore_args=False, deprecated=False, no_cmd_split=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.
|
||||
@@ -121,7 +109,6 @@ class Command:
|
||||
self._scope = scope
|
||||
self._star_args_optional = star_args_optional
|
||||
self.debug = debug
|
||||
self.ignore_args = ignore_args
|
||||
self.handler = handler
|
||||
self.no_cmd_split = no_cmd_split
|
||||
self.backend = backend
|
||||
@@ -225,33 +212,31 @@ class Command:
|
||||
else:
|
||||
self.desc = ""
|
||||
|
||||
if not self.ignore_args:
|
||||
for param in signature.parameters.values():
|
||||
# https://docs.python.org/3/library/inspect.html#inspect.Parameter.kind
|
||||
# "Python has no explicit syntax for defining positional-only
|
||||
# parameters, but many built-in and extension module functions
|
||||
# (especially those that accept only one or two parameters)
|
||||
# accept them."
|
||||
assert param.kind != inspect.Parameter.POSITIONAL_ONLY
|
||||
if param.name == 'self':
|
||||
continue
|
||||
if self._inspect_special_param(param):
|
||||
continue
|
||||
if (param.kind == inspect.Parameter.KEYWORD_ONLY and
|
||||
param.default is inspect.Parameter.empty):
|
||||
raise TypeError("{}: handler has keyword only argument "
|
||||
"{!r} without default!".format(self.name,
|
||||
param.name))
|
||||
typ = self._get_type(param)
|
||||
is_bool = typ is bool
|
||||
kwargs = self._param_to_argparse_kwargs(param, is_bool)
|
||||
args = self._param_to_argparse_args(param, is_bool)
|
||||
callsig = debug_utils.format_call(
|
||||
self.parser.add_argument, args, kwargs,
|
||||
full=False)
|
||||
log.commands.vdebug('Adding arg {} of type {} -> {}'.format(
|
||||
param.name, typ, callsig))
|
||||
self.parser.add_argument(*args, **kwargs)
|
||||
for param in signature.parameters.values():
|
||||
# https://docs.python.org/3/library/inspect.html#inspect.Parameter.kind
|
||||
# "Python has no explicit syntax for defining positional-only
|
||||
# parameters, but many built-in and extension module functions
|
||||
# (especially those that accept only one or two parameters) accept
|
||||
# them."
|
||||
assert param.kind != inspect.Parameter.POSITIONAL_ONLY
|
||||
if param.name == 'self':
|
||||
continue
|
||||
if self._inspect_special_param(param):
|
||||
continue
|
||||
if (param.kind == inspect.Parameter.KEYWORD_ONLY and
|
||||
param.default is inspect.Parameter.empty):
|
||||
raise TypeError("{}: handler has keyword only argument {!r} "
|
||||
"without default!".format(
|
||||
self.name, param.name))
|
||||
typ = self._get_type(param)
|
||||
is_bool = typ is bool
|
||||
kwargs = self._param_to_argparse_kwargs(param, is_bool)
|
||||
args = self._param_to_argparse_args(param, is_bool)
|
||||
callsig = debug_utils.format_call(self.parser.add_argument, args,
|
||||
kwargs, full=False)
|
||||
log.commands.vdebug('Adding arg {} of type {} -> {}'.format(
|
||||
param.name, typ, callsig))
|
||||
self.parser.add_argument(*args, **kwargs)
|
||||
return signature.parameters.values()
|
||||
|
||||
def _param_to_argparse_kwargs(self, param, is_bool):
|
||||
@@ -419,9 +404,10 @@ class Command:
|
||||
# support that.
|
||||
# pylint: disable=no-member,useless-suppression
|
||||
try:
|
||||
types = list(typ.__union_params__)
|
||||
except AttributeError:
|
||||
types = list(typ.__args__)
|
||||
except AttributeError:
|
||||
# Older Python 3.5 patch versions
|
||||
types = list(typ.__union_params__)
|
||||
# pylint: enable=no-member,useless-suppression
|
||||
if param.default is not inspect.Parameter.empty:
|
||||
types.append(type(param.default))
|
||||
@@ -453,12 +439,6 @@ class Command:
|
||||
kwargs = {}
|
||||
signature = inspect.signature(self.handler)
|
||||
|
||||
if self.ignore_args:
|
||||
if self._instance is not None:
|
||||
param = list(signature.parameters.values())[0]
|
||||
self._get_self_arg(win_id, param, args)
|
||||
return args, kwargs
|
||||
|
||||
for i, param in enumerate(signature.parameters.values()):
|
||||
arg_info = self.get_arg_info(param)
|
||||
if i == 0 and self._instance is not None:
|
||||
@@ -535,3 +515,7 @@ class Command:
|
||||
raise cmdexc.PrerequisitesError(
|
||||
"{}: This command is only allowed in {} mode, not {}.".format(
|
||||
self.name, mode_names, mode.name))
|
||||
|
||||
def takes_count(self):
|
||||
"""Return true iff this command can take a count argument."""
|
||||
return any(arg.count for arg in self._qute_args)
|
||||
|
||||
@@ -19,22 +19,31 @@
|
||||
|
||||
"""Module containing command managers (SearchRunner and CommandRunner)."""
|
||||
|
||||
import collections
|
||||
import traceback
|
||||
import re
|
||||
|
||||
import attr
|
||||
from PyQt5.QtCore import pyqtSlot, QUrl, QObject
|
||||
|
||||
from qutebrowser.config import config, configexc
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.commands import cmdexc, cmdutils
|
||||
from qutebrowser.utils import message, objreg, qtutils, usertypes, utils
|
||||
from qutebrowser.misc import split
|
||||
|
||||
|
||||
ParseResult = collections.namedtuple('ParseResult', ['cmd', 'args', 'cmdline'])
|
||||
last_command = {}
|
||||
|
||||
|
||||
@attr.s
|
||||
class ParseResult:
|
||||
|
||||
"""The result of parsing a commandline."""
|
||||
|
||||
cmd = attr.ib()
|
||||
args = attr.ib()
|
||||
cmdline = attr.ib()
|
||||
|
||||
|
||||
def _current_url(tabbed_browser):
|
||||
"""Convenience method to get the current url."""
|
||||
try:
|
||||
@@ -81,19 +90,17 @@ def replace_variables(win_id, arglist):
|
||||
return args
|
||||
|
||||
|
||||
class CommandRunner(QObject):
|
||||
class CommandParser:
|
||||
|
||||
"""Parse and run qutebrowser commandline commands.
|
||||
"""Parse qutebrowser commandline commands.
|
||||
|
||||
Attributes:
|
||||
_win_id: The window this CommandRunner is associated with.
|
||||
|
||||
_partial_match: Whether to allow partial command matches.
|
||||
"""
|
||||
|
||||
def __init__(self, win_id, partial_match=False, parent=None):
|
||||
super().__init__(parent)
|
||||
def __init__(self, partial_match=False):
|
||||
self._partial_match = partial_match
|
||||
self._win_id = win_id
|
||||
|
||||
def _get_alias(self, text, default=None):
|
||||
"""Get an alias from the config.
|
||||
@@ -108,9 +115,10 @@ class CommandRunner(QObject):
|
||||
"""
|
||||
parts = text.strip().split(maxsplit=1)
|
||||
try:
|
||||
alias = config.get('aliases', parts[0])
|
||||
except (configexc.NoOptionError, configexc.NoSectionError):
|
||||
alias = config.val.aliases[parts[0]]
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
try:
|
||||
new_cmd = '{} {}'.format(alias, parts[1])
|
||||
except IndexError:
|
||||
@@ -119,7 +127,7 @@ class CommandRunner(QObject):
|
||||
new_cmd += ' '
|
||||
return new_cmd
|
||||
|
||||
def parse_all(self, text, aliases=True, *args, **kwargs):
|
||||
def _parse_all_gen(self, text, aliases=True, *args, **kwargs):
|
||||
"""Split a command on ;; and parse all parts.
|
||||
|
||||
If the first command in the commandline is a non-split one, it only
|
||||
@@ -154,6 +162,10 @@ class CommandRunner(QObject):
|
||||
for sub in sub_texts:
|
||||
yield self.parse(sub, *args, **kwargs)
|
||||
|
||||
def parse_all(self, *args, **kwargs):
|
||||
"""Wrapper over parse_all."""
|
||||
return list(self._parse_all_gen(*args, **kwargs))
|
||||
|
||||
def parse(self, text, *, fallback=False, keep=False):
|
||||
"""Split the commandline text into command and arguments.
|
||||
|
||||
@@ -253,6 +265,20 @@ class CommandRunner(QObject):
|
||||
# already.
|
||||
return split_args
|
||||
|
||||
|
||||
class CommandRunner(QObject):
|
||||
|
||||
"""Parse and run qutebrowser commandline commands.
|
||||
|
||||
Attributes:
|
||||
_win_id: The window this CommandRunner is associated with.
|
||||
"""
|
||||
|
||||
def __init__(self, win_id, partial_match=False, parent=None):
|
||||
super().__init__(parent)
|
||||
self._parser = CommandParser(partial_match=partial_match)
|
||||
self._win_id = win_id
|
||||
|
||||
def run(self, text, count=None):
|
||||
"""Parse a command from a line of text and run it.
|
||||
|
||||
@@ -267,7 +293,7 @@ class CommandRunner(QObject):
|
||||
window=self._win_id)
|
||||
cur_mode = mode_manager.mode
|
||||
|
||||
for result in self.parse_all(text):
|
||||
for result in self._parser.parse_all(text):
|
||||
if result.cmd.no_replace_variables:
|
||||
args = result.args
|
||||
else:
|
||||
@@ -294,17 +320,5 @@ class CommandRunner(QObject):
|
||||
"""Run a command and display exceptions in the statusbar."""
|
||||
try:
|
||||
self.run(text, count)
|
||||
except (cmdexc.CommandMetaError, cmdexc.CommandError) as e:
|
||||
message.error(str(e), stack=traceback.format_exc())
|
||||
|
||||
@pyqtSlot(str, int)
|
||||
def run_safely_init(self, text, count=None):
|
||||
"""Run a command and display exceptions in the statusbar.
|
||||
|
||||
Contrary to run_safely, error messages are queued so this is more
|
||||
suitable to use while initializing.
|
||||
"""
|
||||
try:
|
||||
self.run(text, count)
|
||||
except (cmdexc.CommandMetaError, cmdexc.CommandError) as e:
|
||||
except cmdexc.Error as e:
|
||||
message.error(str(e), stack=traceback.format_exc())
|
||||
|
||||
@@ -25,7 +25,7 @@ import tempfile
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QSocketNotifier
|
||||
|
||||
from qutebrowser.utils import message, log, objreg, standarddir
|
||||
from qutebrowser.utils import message, log, objreg, standarddir, utils
|
||||
from qutebrowser.commands import runners
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.misc import guiprocess
|
||||
@@ -376,7 +376,7 @@ def _lookup_path(cmd):
|
||||
"""
|
||||
directories = [
|
||||
os.path.join(standarddir.data(), "userscripts"),
|
||||
os.path.join(standarddir.system_data(), "userscripts"),
|
||||
os.path.join(standarddir.data(system=True), "userscripts"),
|
||||
]
|
||||
for directory in directories:
|
||||
cmd_path = os.path.join(directory, cmd)
|
||||
@@ -406,9 +406,9 @@ def run_async(tab, cmd, *args, win_id, env, verbose=False):
|
||||
window=win_id)
|
||||
commandrunner = runners.CommandRunner(win_id, parent=tabbed_browser)
|
||||
|
||||
if os.name == 'posix':
|
||||
if utils.is_posix:
|
||||
runner = _POSIXUserscriptRunner(tabbed_browser)
|
||||
elif os.name == 'nt': # pragma: no cover
|
||||
elif utils.is_windows: # pragma: no cover
|
||||
runner = _WindowsUserscriptRunner(tabbed_browser)
|
||||
else: # pragma: no cover
|
||||
raise UnsupportedError
|
||||
@@ -417,7 +417,7 @@ def run_async(tab, cmd, *args, win_id, env, verbose=False):
|
||||
lambda cmd:
|
||||
log.commands.debug("Got userscript command: {}".format(cmd)))
|
||||
runner.got_cmd.connect(commandrunner.run_safely)
|
||||
user_agent = config.get('network', 'user-agent')
|
||||
user_agent = config.val.content.headers.user_agent
|
||||
if user_agent is not None:
|
||||
env['QUTE_USER_AGENT'] = user_agent
|
||||
|
||||
|
||||
@@ -19,12 +19,22 @@
|
||||
|
||||
"""Completer attached to a CompletionView."""
|
||||
|
||||
import attr
|
||||
from PyQt5.QtCore import pyqtSlot, QObject, QTimer
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.commands import cmdutils, runners
|
||||
from qutebrowser.utils import usertypes, log, utils
|
||||
from qutebrowser.completion.models import instances, sortfilter
|
||||
from qutebrowser.utils import log, utils, debug
|
||||
from qutebrowser.completion.models import miscmodels
|
||||
|
||||
|
||||
@attr.s
|
||||
class CompletionInfo:
|
||||
|
||||
"""Context passed into all completion functions."""
|
||||
|
||||
config = attr.ib()
|
||||
keyconf = attr.ib()
|
||||
|
||||
|
||||
class Completer(QObject):
|
||||
@@ -34,16 +44,15 @@ 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 window ID this completer is in.
|
||||
_timer: The timer used to trigger the completion update.
|
||||
_last_cursor_pos: The old cursor position so we avoid double completion
|
||||
updates.
|
||||
_last_text: The old command text so we avoid double completion updates.
|
||||
_last_completion_func: The completion function used for the last text.
|
||||
"""
|
||||
|
||||
def __init__(self, cmd, win_id, parent=None):
|
||||
def __init__(self, cmd, parent=None):
|
||||
super().__init__(parent)
|
||||
self._win_id = win_id
|
||||
self._cmd = cmd
|
||||
self._ignore_change = False
|
||||
self._timer = QTimer()
|
||||
@@ -52,6 +61,7 @@ class Completer(QObject):
|
||||
self._timer.timeout.connect(self._update_completion)
|
||||
self._last_cursor_pos = None
|
||||
self._last_text = None
|
||||
self._last_completion_func = None
|
||||
self._cmd.update_completion.connect(self.schedule_completion_update)
|
||||
|
||||
def __repr__(self):
|
||||
@@ -62,37 +72,8 @@ class Completer(QObject):
|
||||
completion = self.parent()
|
||||
return completion.model()
|
||||
|
||||
def _get_completion_model(self, completion, pos_args):
|
||||
"""Get a completion model based on an enum member.
|
||||
|
||||
Args:
|
||||
completion: A usertypes.Completion member.
|
||||
pos_args: The positional args entered before the cursor.
|
||||
|
||||
Return:
|
||||
A completion model or None.
|
||||
"""
|
||||
if completion == usertypes.Completion.option:
|
||||
section = pos_args[0]
|
||||
model = instances.get(completion).get(section)
|
||||
elif completion == usertypes.Completion.value:
|
||||
section = pos_args[0]
|
||||
option = pos_args[1]
|
||||
try:
|
||||
model = instances.get(completion)[section][option]
|
||||
except KeyError:
|
||||
# No completion model for this section/option.
|
||||
model = None
|
||||
else:
|
||||
model = instances.get(completion)
|
||||
|
||||
if model is None:
|
||||
return None
|
||||
else:
|
||||
return sortfilter.CompletionFilterModel(source=model, parent=self)
|
||||
|
||||
def _get_new_completion(self, before_cursor, under_cursor):
|
||||
"""Get a new completion.
|
||||
"""Get the completion function based on the current command text.
|
||||
|
||||
Args:
|
||||
before_cursor: The command chunks before the cursor.
|
||||
@@ -109,8 +90,8 @@ class Completer(QObject):
|
||||
log.completion.debug("After removing flags: {}".format(before_cursor))
|
||||
if not before_cursor:
|
||||
# '|' or 'set|'
|
||||
model = instances.get(usertypes.Completion.command)
|
||||
return sortfilter.CompletionFilterModel(source=model, parent=self)
|
||||
log.completion.debug('Starting command completion')
|
||||
return miscmodels.command
|
||||
try:
|
||||
cmd = cmdutils.cmd_dict[before_cursor[0]]
|
||||
except KeyError:
|
||||
@@ -119,14 +100,11 @@ class Completer(QObject):
|
||||
return None
|
||||
argpos = len(before_cursor) - 1
|
||||
try:
|
||||
completion = cmd.get_pos_arg_info(argpos).completion
|
||||
func = cmd.get_pos_arg_info(argpos).completion
|
||||
except IndexError:
|
||||
log.completion.debug("No completion in position {}".format(argpos))
|
||||
return None
|
||||
if completion is None:
|
||||
return None
|
||||
model = self._get_completion_model(completion, before_cursor[1:])
|
||||
return model
|
||||
return func
|
||||
|
||||
def _quote(self, s):
|
||||
"""Quote s if it needs quoting for the commandline.
|
||||
@@ -136,7 +114,7 @@ class Completer(QObject):
|
||||
"""
|
||||
if not s:
|
||||
return "''"
|
||||
elif any(c in s for c in ' \'\t\n\\'):
|
||||
elif any(c in s for c in ' "\'\t\n\\'):
|
||||
# use single quotes, and put single quotes into double quotes
|
||||
# the string $'b is then quoted as '$'"'"'b'
|
||||
return "'" + s.replace("'", "'\"'\"'") + "'"
|
||||
@@ -153,9 +131,11 @@ class Completer(QObject):
|
||||
if not text or not text.strip():
|
||||
# Only ":", empty part under the cursor with nothing before/after
|
||||
return [], '', []
|
||||
runner = runners.CommandRunner(self._win_id)
|
||||
result = runner.parse(text, fallback=True, keep=True)
|
||||
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,
|
||||
@@ -174,6 +154,9 @@ class Completer(QObject):
|
||||
"partitioned: {} '{}' {}".format(prefix, center, postfix))
|
||||
return prefix, center, postfix
|
||||
|
||||
# We should always return above
|
||||
assert False, parts
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_selection_changed(self, text):
|
||||
"""Change the completed part if a new item was selected.
|
||||
@@ -194,7 +177,7 @@ class Completer(QObject):
|
||||
if maxsplit is None:
|
||||
text = self._quote(text)
|
||||
model = self._model()
|
||||
if model.count() == 1 and config.get('completion', 'quick-complete'):
|
||||
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)
|
||||
@@ -241,6 +224,7 @@ class Completer(QObject):
|
||||
# FIXME complete searches
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/32
|
||||
completion.set_model(None)
|
||||
self._last_completion_func = None
|
||||
return
|
||||
|
||||
before_cursor, pattern, after_cursor = self._partition()
|
||||
@@ -249,13 +233,26 @@ class Completer(QObject):
|
||||
before_cursor, pattern, after_cursor))
|
||||
|
||||
pattern = pattern.strip("'\"")
|
||||
model = self._get_new_completion(before_cursor, pattern)
|
||||
func = self._get_new_completion(before_cursor, pattern)
|
||||
|
||||
log.completion.debug("Setting completion model to {} with pattern '{}'"
|
||||
.format(model.srcmodel.__class__.__name__ if model else 'None',
|
||||
pattern))
|
||||
if func is None:
|
||||
log.completion.debug('Clearing completion')
|
||||
completion.set_model(None)
|
||||
self._last_completion_func = None
|
||||
return
|
||||
|
||||
completion.set_model(model, pattern)
|
||||
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__)):
|
||||
info = CompletionInfo(config=config.instance,
|
||||
keyconf=config.key_instance)
|
||||
model = func(*args, info=info)
|
||||
with debug.log_time(log.completion, 'Set completion model'):
|
||||
completion.set_model(model)
|
||||
|
||||
completion.set_pattern(pattern)
|
||||
|
||||
def _change_completed_part(self, newtext, before, after, immediate=False):
|
||||
"""Change the part we're currently completing in the commandline.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user