Compare commits
405 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
7e3df43463 | |
|
|
9ae082b29b | |
|
|
b417f2a23b | |
|
|
615cee7309 | |
|
|
65c1ca9691 | |
|
|
12bed611c5 | |
|
|
bc72687d7d | |
|
|
c32b7d4b60 | |
|
|
3f9ef123e7 | |
|
|
f2547f8a09 | |
|
|
edd5114492 | |
|
|
184a242937 | |
|
|
17c19a09b7 | |
|
|
88aa47c377 | |
|
|
0570545342 | |
|
|
f408f20ad9 | |
|
|
500a8df209 | |
|
|
13d9904b90 | |
|
|
b3e4dba731 | |
|
|
4164205663 | |
|
|
f5e2660890 | |
|
|
69f3882ce3 | |
|
|
8e42727d31 | |
|
|
31a5737c61 | |
|
|
8ae5e3d83b | |
|
|
4f40a8b46b | |
|
|
59a64af67f | |
|
|
66cbe0d9c9 | |
|
|
0ef5053a65 | |
|
|
6ddff3ae0d | |
|
|
9316d428ef | |
|
|
71ed8cdbf5 | |
|
|
62fdb15532 | |
|
|
bc191b798d | |
|
|
f8fbb0609f | |
|
|
55fb26fce1 | |
|
|
25dc019886 | |
|
|
81d7b6a74c | |
|
|
6ec5504ab3 | |
|
|
9b69c889ef | |
|
|
4e87ef303f | |
|
|
2f8234ee2e | |
|
|
a8f0b47451 | |
|
|
4a5b7bd6e4 | |
|
|
b89bf07d1e | |
|
|
70bf4689fc | |
|
|
1cbb6fccf0 | |
|
|
d3e4245d0f | |
|
|
3160048619 | |
|
|
aa93eb1614 | |
|
|
df9cef3a58 | |
|
|
214e2e9ac2 | |
|
|
68574b88cc | |
|
|
6e8e24050d | |
|
|
5fe9bf97e3 | |
|
|
b646d606d7 | |
|
|
1e4ddc2c6b | |
|
|
3fce0518bd | |
|
|
3808ebfdb3 | |
|
|
0421aacd64 | |
|
|
0c2a673e27 | |
|
|
afa456f396 | |
|
|
461077b6cf | |
|
|
e208f5e121 | |
|
|
0f320051e0 | |
|
|
ee40f13a30 | |
|
|
06c4fac54a | |
|
|
1392d55f53 | |
|
|
269d81371d | |
|
|
346a59a6f0 | |
|
|
d8079515fa | |
|
|
294534cf66 | |
|
|
da9bd515fd | |
|
|
5ac0cb97a8 | |
|
|
242cf2a22e | |
|
|
6f9cf43d6d | |
|
|
00b3bba99a | |
|
|
111178358a | |
|
|
e098609a8a | |
|
|
721ba1e036 | |
|
|
c5e7a73ab7 | |
|
|
67db09a9c9 | |
|
|
4925715dfc | |
|
|
6f8576b343 | |
|
|
7bffed47ed | |
|
|
642c5fe2fe | |
|
|
621f20adb3 | |
|
|
9d80bc2076 | |
|
|
88415c6aa0 | |
|
|
5c2076ab2d | |
|
|
7b2773814b | |
|
|
467b9a7d4a | |
|
|
816b8aaa00 | |
|
|
d38b5a423e | |
|
|
7f45b178c9 | |
|
|
26368f0b90 | |
|
|
ff76871f4e | |
|
|
9ddbc964d4 | |
|
|
2b4e5757b0 | |
|
|
ddfd17d749 | |
|
|
6f21accfae | |
|
|
6b48ae4084 | |
|
|
e300327eea | |
|
|
9d1dfcfe5c | |
|
|
897fc40f4d | |
|
|
24aab77607 | |
|
|
c37b9ebf5b | |
|
|
2ae3086244 | |
|
|
4aa807032f | |
|
|
0867a95abb | |
|
|
25ff649efe | |
|
|
f57afa39e8 | |
|
|
da8428f9ba | |
|
|
75475ee87b | |
|
|
8ebce896ad | |
|
|
43b5618c0a | |
|
|
c9498995df | |
|
|
aaba038a16 | |
|
|
92f3633662 | |
|
|
7ce0e03614 | |
|
|
d64781c31c | |
|
|
a8f9fc1396 | |
|
|
a13306a79f | |
|
|
c6b486ce81 | |
|
|
18bea32975 | |
|
|
7e770765a9 | |
|
|
1aaf621409 | |
|
|
7d8d14eb07 | |
|
|
226d167ec7 | |
|
|
a7822d1e2a | |
|
|
1f4ca40f78 | |
|
|
75239be8b6 | |
|
|
488339b3d5 | |
|
|
52917f9c31 | |
|
|
326a83309a | |
|
|
264d91eaec | |
|
|
9e280eb80f | |
|
|
c278069946 | |
|
|
5894221924 | |
|
|
af2450591c | |
|
|
65de4fde16 | |
|
|
4d6dccfed5 | |
|
|
45483d0994 | |
|
|
c3eef39050 | |
|
|
a93f2e917d | |
|
|
d8beaa1e9d | |
|
|
aa6fcab23a | |
|
|
1e243b0d06 | |
|
|
ce6be4f741 | |
|
|
195d19ec3f | |
|
|
78b55f84cc | |
|
|
7f532f69d2 | |
|
|
50080e8607 | |
|
|
17522478db | |
|
|
44dbef482e | |
|
|
c0216a1309 | |
|
|
c63a98e3e6 | |
|
|
08f5509bed | |
|
|
4180e04f24 | |
|
|
3cdacde13e | |
|
|
ada069b3e3 | |
|
|
9e99d6df34 | |
|
|
afd1515d8d | |
|
|
6d1cd0282a | |
|
|
21082a85a4 | |
|
|
745bb7f4f0 | |
|
|
ec86816be1 | |
|
|
85e1a132bb | |
|
|
d8fd61811c | |
|
|
40b3e70659 | |
|
|
0c035c41d8 | |
|
|
f2ec540a1b | |
|
|
b9aec6777a | |
|
|
6b3b83981a | |
|
|
d80d80b867 | |
|
|
c9d3b61ca2 | |
|
|
87ad9dce57 | |
|
|
961b4a212d | |
|
|
e151c2d0cf | |
|
|
c79257dc8d | |
|
|
22f1b57347 | |
|
|
2536cc2313 | |
|
|
d15052a67a | |
|
|
be87927923 | |
|
|
7315c34957 | |
|
|
8bf6a9c448 | |
|
|
9e459a004c | |
|
|
e7af54898e | |
|
|
7664fdbb34 | |
|
|
81546c97ae | |
|
|
b2f58856a8 | |
|
|
5e565f4dc7 | |
|
|
879c964c35 | |
|
|
2ec0f8fa77 | |
|
|
9d5aeefa8c | |
|
|
ed6329737f | |
|
|
04a1d11905 | |
|
|
47724f913e | |
|
|
a45d57feee | |
|
|
eca17870ae | |
|
|
959dc5a9e5 | |
|
|
7b388017c3 | |
|
|
55ca67f3e2 | |
|
|
6b86a9072f | |
|
|
96e535c7ed | |
|
|
1217f7fa45 | |
|
|
c1e7b6e8f3 | |
|
|
2e343403de | |
|
|
2ec16a947f | |
|
|
ced24549df | |
|
|
272693ef3e | |
|
|
5926932a6d | |
|
|
3f31a0005a | |
|
|
9219869cb9 | |
|
|
0b5f70f029 | |
|
|
bff2ad9086 | |
|
|
696a4490ff | |
|
|
9cf95165a9 | |
|
|
f9eb71b4c8 | |
|
|
422680046e | |
|
|
060f4a59d6 | |
|
|
291dda69e5 | |
|
|
329977b42c | |
|
|
072b03a631 | |
|
|
9ba931075b | |
|
|
0de8337969 | |
|
|
aef5355436 | |
|
|
856aeb3f47 | |
|
|
c86ddd11be | |
|
|
aab33cc596 | |
|
|
bb7bbb6ead | |
|
|
9447182809 | |
|
|
9c7a75616c | |
|
|
858606c18d | |
|
|
049cf49705 | |
|
|
4633a0704e | |
|
|
a2072db15d | |
|
|
9685c5a383 | |
|
|
c682ccb1b2 | |
|
|
81f65dbb46 | |
|
|
6cbf2847c8 | |
|
|
74c7ff2641 | |
|
|
a4b3ca48df | |
|
|
9f16a1f879 | |
|
|
ed99c82440 | |
|
|
20a9130cee | |
|
|
102f43cbde | |
|
|
c5fe31e828 | |
|
|
9aef02e96c | |
|
|
06e7950588 | |
|
|
1b7557b73b | |
|
|
5e6a878f64 | |
|
|
a3bdcca37b | |
|
|
0a9b6aab73 | |
|
|
8f8748e486 | |
|
|
a14b986326 | |
|
|
422d268fe3 | |
|
|
b3138d1be8 | |
|
|
e67aea60e8 | |
|
|
67e7677523 | |
|
|
9249c5cb0e | |
|
|
c601bd0c7a | |
|
|
f9933d2f3e | |
|
|
95f9472a6b | |
|
|
701046cd9f | |
|
|
aa41b6719f | |
|
|
05b42c57ee | |
|
|
77b5c0c1cd | |
|
|
84ec45c13f | |
|
|
ecbca59dea | |
|
|
67d8e012a3 | |
|
|
dea1de5dab | |
|
|
9aa53ea205 | |
|
|
7bc6c33bb5 | |
|
|
9e0f7ccc51 | |
|
|
1d2faf2fa2 | |
|
|
f6f2a1252b | |
|
|
86c89e00c5 | |
|
|
4f4ad4147a | |
|
|
13b87e5968 | |
|
|
7204168684 | |
|
|
3dc212a815 | |
|
|
351fef8c1e | |
|
|
4053249229 | |
|
|
8b820f015b | |
|
|
afeb1ebbaa | |
|
|
6b5ebe7187 | |
|
|
c073a30afe | |
|
|
b16551548f | |
|
|
961a9390da | |
|
|
b5a399831b | |
|
|
bbdf9faf5e | |
|
|
d9d8701696 | |
|
|
3280c8dacc | |
|
|
54b6c92713 | |
|
|
de3fa06eff | |
|
|
ef2ceccd29 | |
|
|
9a23ce6c3c | |
|
|
21b2d63f4d | |
|
|
c32f5afcc4 | |
|
|
ab3b77cb1e | |
|
|
b09a1385f5 | |
|
|
4e5eb90857 | |
|
|
72647298b7 | |
|
|
71381c0da6 | |
|
|
3aa839998b | |
|
|
65a3a3cf9a | |
|
|
163bb9fa0c | |
|
|
c165d1a0da | |
|
|
1ab93bad33 | |
|
|
69f0eae600 | |
|
|
ef397e9417 | |
|
|
c60ebe8eef | |
|
|
4dde8e1594 | |
|
|
b6d5a5cf14 | |
|
|
c4d8502872 | |
|
|
2f20e3b60b | |
|
|
873b874c5f | |
|
|
a460770414 | |
|
|
e4ab45e3b2 | |
|
|
d0407852f4 | |
|
|
59be37007d | |
|
|
b43c79989e | |
|
|
939e0fa817 | |
|
|
18db4cc937 | |
|
|
4a675115e1 | |
|
|
02b9f5eed1 | |
|
|
cc20112a67 | |
|
|
d49c96aa29 | |
|
|
b0e05ee160 | |
|
|
4c9e989c39 | |
|
|
237e90985d | |
|
|
e3db31e29e | |
|
|
f25604b819 | |
|
|
b183e6a39a | |
|
|
1f90da7694 | |
|
|
7ad4bb70fe | |
|
|
b271559f82 | |
|
|
51541add20 | |
|
|
328b5966ef | |
|
|
4cc67d3603 | |
|
|
8c2cfe8f7e | |
|
|
c35fc88211 | |
|
|
90c40152ed | |
|
|
27606f2fe3 | |
|
|
561986cad8 | |
|
|
2810021896 | |
|
|
a875f3e8be | |
|
|
7b4c163142 | |
|
|
8b2ab6b01d | |
|
|
4f985feede | |
|
|
2efaf92b7c | |
|
|
a0fd88aac8 | |
|
|
42c9973c01 | |
|
|
c9374fd80a | |
|
|
dd66300caf | |
|
|
33ba05c657 | |
|
|
990c4d4cfc | |
|
|
1a6d32cc3d | |
|
|
de8a2c04b8 | |
|
|
12e6838267 | |
|
|
fdfa9109ff | |
|
|
0eff340063 | |
|
|
eb2e39de1e | |
|
|
0b221d3e24 | |
|
|
f7c8c67818 | |
|
|
accce7fdef | |
|
|
718d66fb6d | |
|
|
60f526d81f | |
|
|
4004799f2c | |
|
|
1615e8a40e | |
|
|
bfb580ee91 | |
|
|
4a1a30bc33 | |
|
|
6b3901ccee | |
|
|
6b25fdf648 | |
|
|
9bae38ede6 | |
|
|
7bd941cda0 | |
|
|
e63781d49b | |
|
|
03224a0015 | |
|
|
322977dc0d | |
|
|
60177ab975 | |
|
|
9b59cd02cb | |
|
|
b3090870d3 | |
|
|
253a818ecc | |
|
|
0a32d6fef5 | |
|
|
c5395c23f7 | |
|
|
d272804126 | |
|
|
ed8141cb4b | |
|
|
2b8c053aee | |
|
|
7eb9bc45de | |
|
|
7d1179e0e3 | |
|
|
977c90939c | |
|
|
980105aa11 | |
|
|
3934d727e4 | |
|
|
3144264a9d | |
|
|
a52f378787 | |
|
|
1bcb536a44 | |
|
|
3f5ce51502 | |
|
|
58dec07d27 | |
|
|
85435c7574 | |
|
|
5691e04afc | |
|
|
9fc6ea0434 | |
|
|
de5fbf711d | |
|
|
431b2c1633 | |
|
|
faf692657c |
|
|
@ -1,19 +0,0 @@
|
|||
[bumpversion]
|
||||
current_version = 3.4.0
|
||||
commit = True
|
||||
message = Release v{new_version}
|
||||
tag = True
|
||||
sign_tags = True
|
||||
tag_name = v{new_version}
|
||||
|
||||
[bumpversion:file:qutebrowser/__init__.py]
|
||||
parse = __version__ = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)
|
||||
|
||||
[bumpversion:file:misc/org.qutebrowser.qutebrowser.appdata.xml]
|
||||
search = <!-- Add new releases here -->
|
||||
replace = <!-- Add new releases here -->
|
||||
<release version="{new_version}" date="{now:%Y-%m-%d}"/>
|
||||
|
||||
[bumpversion:file:doc/changelog.asciidoc]
|
||||
search = (unreleased)
|
||||
replace = ({now:%Y-%m-%d})
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
[tool.bumpversion]
|
||||
current_version = "3.6.3"
|
||||
commit = true
|
||||
message = "Release v{new_version}"
|
||||
tag = true
|
||||
sign_tags = true
|
||||
tag_name = "v{new_version}"
|
||||
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
|
||||
serialize = ["{major}.{minor}.{patch}"]
|
||||
allow_dirty = false
|
||||
|
||||
[[tool.bumpversion.files]]
|
||||
filename = "qutebrowser/__init__.py"
|
||||
search = "__version__ = \"{current_version}\""
|
||||
replace = "__version__ = \"{new_version}\""
|
||||
|
||||
[[tool.bumpversion.files]]
|
||||
filename = "misc/org.qutebrowser.qutebrowser.appdata.xml"
|
||||
search = "<!-- Add new releases here -->"
|
||||
replace = """<!-- Add new releases here -->
|
||||
<release version='{new_version}' date='{now:%Y-%m-%d}'/>"""
|
||||
|
||||
[[tool.bumpversion.files]]
|
||||
filename = "doc/changelog.asciidoc"
|
||||
search = "(unreleased)"
|
||||
replace = "({now:%Y-%m-%d})"
|
||||
|
|
@ -4,6 +4,7 @@ include =
|
|||
tests/*
|
||||
scripts/*
|
||||
branch = true
|
||||
patch = subprocess
|
||||
omit =
|
||||
qutebrowser/__main__.py
|
||||
*/__init__.py
|
||||
|
|
|
|||
3
.flake8
3
.flake8
|
|
@ -42,6 +42,7 @@ exclude = .*,__pycache__,resources.py
|
|||
# W503: like break before binary operator
|
||||
# W504: line break after binary operator
|
||||
# FI18: __future__ import "annotations" missing
|
||||
# FI58: __future__ import "annotations" present
|
||||
# PT004: fixture '{name}' does not return anything, add leading underscore
|
||||
# PT011: pytest.raises(ValueError) is too broad, set the match parameter or use a more specific exception
|
||||
# PT012: pytest.raises() block should contain a single simple statement
|
||||
|
|
@ -54,7 +55,7 @@ ignore =
|
|||
D102,D103,D106,D107,D104,D105,D209,D211,D401,D402,D403,D412,D413,
|
||||
A003,
|
||||
W503, W504,
|
||||
FI18,
|
||||
FI18,FI58,
|
||||
PT004,
|
||||
PT011,
|
||||
PT012
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@ jobs:
|
|||
matrix:
|
||||
include:
|
||||
- testenv: bleeding
|
||||
image: "archlinux-webengine-unstable-qt6"
|
||||
- testenv: bleeding-qt5
|
||||
image: "archlinux-webengine-unstable"
|
||||
container:
|
||||
image: "qutebrowser/ci:${{ matrix.image }}"
|
||||
|
|
@ -33,14 +31,13 @@ jobs:
|
|||
- /home/runner/work/_temp/:/home/runner/work/_temp/
|
||||
options: --privileged --tty
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up problem matchers
|
||||
run: "python scripts/dev/ci/problemmatchers.py py3 ${{ runner.temp }}"
|
||||
- name: Upgrade 3rd party assets
|
||||
run: "tox exec -e ${{ matrix.testenv }} -- python scripts/dev/update_3rdparty.py --gh-token ${{ secrets.GITHUB_TOKEN }} --modern-pdfjs"
|
||||
if: "endsWith(matrix.image, '-qt6')"
|
||||
- name: Run tox
|
||||
run: dbus-run-session tox -e ${{ matrix.testenv }}
|
||||
- name: Gather info
|
||||
|
|
@ -51,7 +48,7 @@ jobs:
|
|||
shell: bash
|
||||
if: failure()
|
||||
- name: Upload screenshots
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: "end2end-screenshots-${{ steps.info.outputs.date }}-${{ steps.info.outputs.sha_short }}-${{ matrix.image }}"
|
||||
path: |
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ jobs:
|
|||
linters:
|
||||
if: "!contains(github.event.head_commit.message, '[ci skip]')"
|
||||
timeout-minutes: 10
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
|
@ -27,7 +27,6 @@ jobs:
|
|||
- testenv: vulture
|
||||
- testenv: misc
|
||||
- testenv: pyroma
|
||||
- testenv: check-manifest
|
||||
- testenv: eslint
|
||||
- testenv: shellcheck
|
||||
args: "-f gcc" # For problem matchers
|
||||
|
|
@ -35,20 +34,20 @@ jobs:
|
|||
- testenv: actionlint
|
||||
- testenv: package
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
.mypy_cache
|
||||
.tox
|
||||
~/.cache/pip
|
||||
key: "${{ matrix.testenv }}-${{ hashFiles('misc/requirements/requirements-*.txt') }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('scripts/dev/pylint_checkers/qute_pylint/*.py') }}"
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.10'
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '22.x'
|
||||
if: "matrix.testenv == 'eslint'"
|
||||
|
|
@ -86,21 +85,15 @@ jobs:
|
|||
tests-docker:
|
||||
if: "!contains(github.event.head_commit.message, '[ci skip]')"
|
||||
timeout-minutes: 45
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-22.04 # not 24.04 because sandboxing fails by default (#8424)
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- testenv: py-qt5
|
||||
image: archlinux-webkit
|
||||
- testenv: py-qt5
|
||||
- testenv: py
|
||||
image: archlinux-webengine
|
||||
- testenv: py-qt5
|
||||
- testenv: py
|
||||
image: archlinux-webengine-unstable
|
||||
- testenv: py
|
||||
image: archlinux-webengine-qt6
|
||||
- testenv: py
|
||||
image: archlinux-webengine-unstable-qt6
|
||||
container:
|
||||
image: "qutebrowser/ci:${{ matrix.image }}"
|
||||
env:
|
||||
|
|
@ -113,7 +106,7 @@ jobs:
|
|||
- /home/runner/work/_temp/:/home/runner/work/_temp/
|
||||
options: --privileged --tty
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up problem matchers
|
||||
|
|
@ -128,7 +121,7 @@ jobs:
|
|||
shell: bash
|
||||
if: failure()
|
||||
- name: Upload screenshots
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: "end2end-screenshots-${{ steps.info.outputs.date }}-${{ steps.info.outputs.sha_short }}-${{ matrix.image }}"
|
||||
path: |
|
||||
|
|
@ -146,7 +139,7 @@ jobs:
|
|||
include:
|
||||
### PyQt 5.15.2 (Python 3.9)
|
||||
- testenv: py39-pyqt5152
|
||||
os: ubuntu-20.04
|
||||
os: ubuntu-22.04
|
||||
python: "3.9"
|
||||
### PyQt 5.15 (Python 3.10, with coverage)
|
||||
# FIXME:qt6
|
||||
|
|
@ -159,7 +152,7 @@ jobs:
|
|||
python: "3.11"
|
||||
### PyQt 6.2 (Python 3.9)
|
||||
- testenv: py39-pyqt62
|
||||
os: ubuntu-20.04
|
||||
os: ubuntu-22.04
|
||||
python: "3.9"
|
||||
### PyQt 6.3 (Python 3.9)
|
||||
- testenv: py39-pyqt63
|
||||
|
|
@ -193,26 +186,41 @@ jobs:
|
|||
- testenv: py313-pyqt68
|
||||
os: ubuntu-24.04
|
||||
python: "3.13"
|
||||
### macOS Ventura
|
||||
### PyQt 6.8 (Python 3.13)
|
||||
- testenv: py313-pyqt68
|
||||
os: macos-13
|
||||
os: ubuntu-24.04
|
||||
python: "3.13"
|
||||
args: "tests/unit" # Only run unit tests on macOS
|
||||
### PyQt 6.9 (Python 3.14)
|
||||
- testenv: py314-pyqt69
|
||||
os: ubuntu-24.04
|
||||
python: "3.14"
|
||||
### PyQt 6.10 (Python 3.14)
|
||||
- testenv: py314-pyqt610
|
||||
os: ubuntu-24.04
|
||||
python: "3.14"
|
||||
### macOS Sonoma (M1 runner)
|
||||
- testenv: py313-pyqt68
|
||||
- testenv: py314-pyqt610
|
||||
os: macos-14
|
||||
python: "3.13"
|
||||
python: "3.14"
|
||||
args: "tests/unit" # Only run unit tests on macOS
|
||||
### macOS Sequoia (Intel runner)
|
||||
- testenv: py314-pyqt610
|
||||
os: macos-15-intel
|
||||
python: "3.14"
|
||||
args: "tests/unit" # Only run unit tests on macOS
|
||||
### Windows
|
||||
- testenv: py313-pyqt68
|
||||
os: windows-2019
|
||||
python: "3.13"
|
||||
- testenv: py314-pyqt610
|
||||
os: windows-2022
|
||||
python: "3.14"
|
||||
- testenv: py314-pyqt610
|
||||
os: windows-2025
|
||||
python: "3.14"
|
||||
runs-on: "${{ matrix.os }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
.mypy_cache
|
||||
|
|
@ -220,7 +228,7 @@ jobs:
|
|||
~/.cache/pip
|
||||
key: "${{ matrix.testenv }}-${{ matrix.os }}-${{ matrix.python }}-${{ hashFiles('misc/requirements/requirements-*.txt') }}-${{ hashFiles('requirements.txt') }}"
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "${{ matrix.python }}"
|
||||
- name: Set up problem matchers
|
||||
|
|
@ -228,7 +236,7 @@ jobs:
|
|||
- name: Install apt dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install --no-install-recommends libyaml-dev libegl1 libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-shape0 libxcb-cursor0
|
||||
sudo apt-get install --no-install-recommends libyaml-dev libegl1 libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-shape0 libxcb-cursor0 libjpeg-dev
|
||||
if: "startsWith(matrix.os, 'ubuntu-')"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
|
|
@ -261,7 +269,7 @@ jobs:
|
|||
shell: bash
|
||||
if: failure()
|
||||
- name: Upload screenshots
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: "end2end-screenshots-${{ steps.info.outputs.date }}-${{ steps.info.outputs.sha_short }}-${{ matrix.testenv }}-${{ matrix.os }}"
|
||||
path: |
|
||||
|
|
@ -274,24 +282,24 @@ jobs:
|
|||
permissions:
|
||||
security-events: write
|
||||
timeout-minutes: 15
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: javascript, python
|
||||
queries: +security-extended
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@v4
|
||||
|
||||
irc:
|
||||
timeout-minutes: 2
|
||||
continue-on-error: true
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
needs: [linters, tests, tests-docker, codeql]
|
||||
if: "always() && github.repository_owner == 'qutebrowser'"
|
||||
steps:
|
||||
|
|
|
|||
|
|
@ -13,14 +13,11 @@ jobs:
|
|||
fail-fast: false
|
||||
matrix:
|
||||
image:
|
||||
- archlinux-webkit
|
||||
- archlinux-webengine
|
||||
- archlinux-webengine-unstable
|
||||
- archlinux-webengine-unstable-qt6
|
||||
- archlinux-webengine-qt6
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- run: pip install jinja2
|
||||
|
|
|
|||
|
|
@ -14,36 +14,45 @@ jobs:
|
|||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: macos-13
|
||||
- os: macos-15-intel
|
||||
toxenv: build-release
|
||||
name: macos-intel
|
||||
- os: macos-14
|
||||
toxenv: build-release
|
||||
name: macos-apple-silicon
|
||||
- os: windows-2019
|
||||
- os: windows-latest
|
||||
toxenv: build-release
|
||||
name: windows
|
||||
- os: macos-13
|
||||
- os: macos-15-intel
|
||||
args: --debug
|
||||
toxenv: build-release
|
||||
name: macos-debug-intel
|
||||
- os: macos-14
|
||||
toxenv: build-release
|
||||
name: macos-debug-apple-silicon
|
||||
- os: windows-2019
|
||||
- os: windows-latest
|
||||
args: --debug
|
||||
toxenv: build-release
|
||||
name: windows-debug
|
||||
runs-on: "${{ matrix.os }}"
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.13"
|
||||
- name: Install nsis
|
||||
if: "matrix.os == 'windows-latest'"
|
||||
run: |
|
||||
irm get.scoop.sh | iex
|
||||
scoop update
|
||||
scoop bucket add extras
|
||||
scoop install nsis
|
||||
Add-Content $env:GITHUB_PATH "C:\Users\runneradmin\scoop\shims"
|
||||
shell: pwsh
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install -U pip
|
||||
|
|
@ -63,7 +72,7 @@ jobs:
|
|||
echo "sha_short=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"
|
||||
shell: bash
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: "qutebrowser-nightly-${{ steps.info.outputs.date }}-${{ steps.info.outputs.sha_short }}-${{ matrix.name }}"
|
||||
path: |
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@ jobs:
|
|||
timeout-minutes: 20
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.9'
|
||||
- name: Recompile requirements
|
||||
|
|
@ -41,7 +41,7 @@ jobs:
|
|||
- name: Run qutebrowser smoke test
|
||||
run: "xvfb-run .venv/bin/python3 -m qutebrowser --no-err-windows --nowindow --temp-basedir about:blank ':later 500 quit'"
|
||||
- name: Create pull request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
uses: peter-evans/create-pull-request@v8
|
||||
with:
|
||||
committer: qutebrowser bot <bot@qutebrowser.org>
|
||||
author: qutebrowser bot <bot@qutebrowser.org>
|
||||
|
|
|
|||
|
|
@ -12,11 +12,12 @@ on:
|
|||
- 'patch'
|
||||
- 'minor'
|
||||
- 'major'
|
||||
- 'reupload' # reupload last release
|
||||
# FIXME do we want a possibility to do prereleases here?
|
||||
python_version:
|
||||
description: 'Python version'
|
||||
required: true
|
||||
default: '3.13'
|
||||
default: '3.14'
|
||||
type: choice
|
||||
options:
|
||||
- '3.9'
|
||||
|
|
@ -24,18 +25,20 @@ on:
|
|||
- '3.11'
|
||||
- '3.12'
|
||||
- '3.13'
|
||||
- '3.14'
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
version: ${{ steps.bump.outputs.version }}
|
||||
release_id: ${{ steps.create-release.outputs.id }}
|
||||
version_x: ${{ steps.bump.outputs.version_x }}
|
||||
release_id: ${{ inputs.release_type == 'reupload' && steps.find-release.outputs.result || steps.create-release.outputs.id }}
|
||||
permissions:
|
||||
contents: write # To push release commit/tag
|
||||
steps:
|
||||
- name: Find release branch
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
id: find-branch
|
||||
with:
|
||||
script: |
|
||||
|
|
@ -59,9 +62,9 @@ jobs:
|
|||
console.log(`sorted: ${sorted}`);
|
||||
return sorted.at(-1);
|
||||
result-encoding: string
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
# Doesn't really matter what we prepare the release with, but let's
|
||||
# use the same version for consistency.
|
||||
|
|
@ -75,7 +78,7 @@ jobs:
|
|||
git config --global user.name "qutebrowser bot"
|
||||
git config --global user.email "bot@qutebrowser.org"
|
||||
- name: Switch to release branch
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ steps.find-branch.outputs.result }}
|
||||
- name: Import GPG Key
|
||||
|
|
@ -83,9 +86,9 @@ jobs:
|
|||
gpg --import <<< "${{ secrets.QUTEBROWSER_BOT_GPGKEY }}"
|
||||
- name: Bump version
|
||||
id: bump
|
||||
run: "tox -e update-version -- ${{ github.event.inputs.release_type }}"
|
||||
run: "tox -e update-version -- ${{ inputs.release_type }}"
|
||||
- name: Check milestone
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const milestones = await github.paginate(github.rest.issues.listMilestones, {
|
||||
|
|
@ -100,35 +103,60 @@ jobs:
|
|||
core.setFailed(`Found open milestone ${milestone.title} with ${milestone.open_issues} open and ${milestone.closed_issues} closed issues!`);
|
||||
}
|
||||
- name: Push release commit/tag
|
||||
if: ${{ inputs.release_type != 'reupload' }}
|
||||
run: |
|
||||
git push origin ${{ steps.find-branch.outputs.result }}
|
||||
git push origin v${{ steps.bump.outputs.version }}
|
||||
- name: Cherry-pick release commit
|
||||
if: ${{ github.event.inputs.release_type == 'patch' }}
|
||||
if: ${{ inputs.release_type == 'patch' }}
|
||||
run: |
|
||||
git fetch origin main
|
||||
git checkout main
|
||||
git cherry-pick -x v${{ steps.bump.outputs.version }}
|
||||
git push origin main
|
||||
git checkout v${{ steps.bump.outputs.version_x }}
|
||||
- name: Create release branch
|
||||
if: ${{ github.event.inputs.release_type != 'patch' }}
|
||||
if: ${{ inputs.release_type == 'minor' || inputs.release_type == 'major' }}
|
||||
run: |
|
||||
git checkout -b v${{ steps.bump.outputs.version_x }}
|
||||
git push --set-upstream origin v${{ steps.bump.outputs.version_x }}
|
||||
- name: Create GitHub draft release
|
||||
if: ${{ inputs.release_type != 'reupload' }}
|
||||
id: create-release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: v${{ steps.bump.outputs.version }}
|
||||
draft: true
|
||||
body: "*Release artifacts for this release are currently being uploaded...*"
|
||||
- name: Find GitHub draft release
|
||||
if: ${{ inputs.release_type == 'reupload' }}
|
||||
id: find-release
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const releases = await github.paginate(github.rest.repos.listReleases, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
});
|
||||
const names = releases.map(release => release.name);
|
||||
console.log(`releases: ${names}`);
|
||||
|
||||
const release = releases.find(release => release.tag_name === "v${{ steps.bump.outputs.version }}");
|
||||
if (release === undefined) {
|
||||
core.setFailed(`No release found with tag v${{ steps.bump.outputs.version }}!`);
|
||||
}
|
||||
if (!release.draft) {
|
||||
core.setFailed(`Release ${release.tag_name} is not a draft release!`);
|
||||
}
|
||||
return release.id;
|
||||
result-encoding: string
|
||||
release:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: macos-13
|
||||
- os: macos-14
|
||||
- os: windows-2019
|
||||
- os: macos-14-large # Intel
|
||||
- os: macos-14 # Apple Silicon
|
||||
- os: windows-2022
|
||||
- os: ubuntu-24.04
|
||||
runs-on: "${{ matrix.os }}"
|
||||
timeout-minutes: 45
|
||||
|
|
@ -136,13 +164,13 @@ jobs:
|
|||
permissions:
|
||||
contents: write # To upload release artifacts
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: v${{ needs.prepare.outputs.version }}
|
||||
ref: v${{ inputs.release_type == 'reupload' && needs.prepare.outputs.version_x || needs.prepare.outputs.version }}
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ github.event.inputs.python_version }}
|
||||
python-version: ${{ inputs.python_version }}
|
||||
- name: Import GPG Key
|
||||
if: ${{ startsWith(matrix.os, 'ubuntu-') }}
|
||||
run: |
|
||||
|
|
@ -167,7 +195,7 @@ jobs:
|
|||
# FIXME consider switching to trusted publishers:
|
||||
# https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/
|
||||
- name: Build and upload release
|
||||
run: "tox -e build-release -- --upload --no-confirm"
|
||||
run: "tox -e build-release -- --upload --no-confirm ${{ inputs.release_type == 'reupload' && '--reupload' || '' }}"
|
||||
env:
|
||||
TWINE_USERNAME: __token__
|
||||
TWINE_PASSWORD: ${{ secrets.QUTEBROWSER_BOT_PYPI_TOKEN }}
|
||||
|
|
@ -180,7 +208,7 @@ jobs:
|
|||
contents: write # To change release
|
||||
steps:
|
||||
- name: Publish final release
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
await github.rest.repos.updateRelease({
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ strict_equality = True
|
|||
warn_unreachable = True
|
||||
disallow_any_unimported = True
|
||||
enable_error_code = ignore-without-code
|
||||
strict_bytes = True
|
||||
|
||||
### Output
|
||||
show_error_context = True
|
||||
|
|
|
|||
|
|
@ -15,6 +15,220 @@ breaking changes (such as renamed commands) can happen in minor releases.
|
|||
// `Fixed` for any bug fixes.
|
||||
// `Security` to invite users to upgrade in case of vulnerabilities.
|
||||
|
||||
[[v3.6.4]]
|
||||
v3.6.4 (unreleased)
|
||||
-------------------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- datalist dropdowns not opening correctly on Wayland/Sway (#8831).
|
||||
This was caused by an old workaround for a different QtWebEngine issue,
|
||||
which is now disabled for QtWebEngine 6.6.3 and newer.
|
||||
|
||||
[[v3.6.3]]
|
||||
v3.6.3 (2025-11-30)
|
||||
-------------------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- New `qt.workarounds.disable_accessibility` setting, which disables Chromium
|
||||
accessibility support. By default, is it set to `auto`, which only disables
|
||||
accessibility on Qt versions with known issues. This works around a bug in Qt
|
||||
6.10.1 causing frequent segfaults (#8797).
|
||||
|
||||
[[v3.6.2]]
|
||||
v3.6.2 (2025-11-27)
|
||||
-------------------
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
* Windows and macOS releases now ship with Qt 6.10.1, which include
|
||||
security patches up to Chromium 142.0.7444.162.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- The version info now includes the Wayland compositor name if wayland-client is
|
||||
available under a different name than `libwayland-client.so` (#8771).
|
||||
- The list of Chromium extensions in `--version` / `:version` now uses the
|
||||
correct Chromium data profile, also fixing a crash with Qt 6.10.1 (#8785).
|
||||
- With Qt 6.10.1, `qt.workarounds.disable_hangouts_extension` now doesn't apply
|
||||
on private profiles, avoiding a Qt bug leading to a crash (#8785).
|
||||
|
||||
[[v3.6.1]]
|
||||
v3.6.1 (2025-11-03)
|
||||
-------------------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- A regression in v3.6.0 where the page didn't have keyboard focus after closing
|
||||
the completion, so e.g. typing in an input field after hinting didn't work.
|
||||
(#8750)
|
||||
|
||||
[[v3.6.0]]
|
||||
v3.6.0 (2025-10-24)
|
||||
-------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- The `:version` info now shows additional information:
|
||||
* The X11 window manager / Wayland compositor name (mostly useful for
|
||||
bug/crash reports).
|
||||
* Loaded WebExtensions (partial support landed in QtWebEngine 6.10, no
|
||||
official qutebrowser support yet).
|
||||
- Support for hinting elements which are part of an (open) shadow DOM.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- The `qutedmenu` userscript now sorts history by the last access time.
|
||||
- Hardware accelerated 2D canvas is now enabled by default on Qt 6.8.2+,
|
||||
as graphic glitches with e.g. PDF.js and Google Sheets should be fixed
|
||||
nowadays. If you still run into issues, please report them and set
|
||||
`qt.workarounds.disable_accelerated_2d_canvas` to `always` to disable it
|
||||
again.
|
||||
- Changes to binary releases:
|
||||
* Windows and macOS releases are now built with Qt 6.10.0, which is based
|
||||
on Chromium 134.0.6998.208 with security patches up to 140.0.7339.207.
|
||||
* Windows and macOS releases are now built with Python 3.14.
|
||||
* Windows releases are now built on Windows Server 2022 (previously 2019),
|
||||
which might break compatibility with older Windows releases (untested).
|
||||
* If using `mkvenv.py` on Linux, note that Qt now requires glibc v2.34 (v2.28
|
||||
previously). This is available down to Ubuntu 22.04 LTS and Debian Bookworm
|
||||
(oldstable), so this should not affect most users of desktop distributions.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Fixed crash if two new downloads start while a download prompt is already open
|
||||
(#8674).
|
||||
- Fixed exception when closing a qutebrowser window while a download prompt is
|
||||
still open.
|
||||
- Hopefully proper fix for some web pages jumping to the top when the statusbar
|
||||
is hidden (#8223).
|
||||
- Fix for the page header being shown on YouTube after the fullscreen
|
||||
notification was hidden (#8625).
|
||||
- Fix for videos losing keyboard focus when the fullscreen notification shows
|
||||
(#8174).
|
||||
- The workaround for microphone/camera permissions not being requested with
|
||||
QtWebEngine 6.9 on Google Meet, Zoom, or other pages using the new
|
||||
`<permission>` element now got extended to Qt 6.9.1+ as it's still not fixed
|
||||
upstream. (#8612)
|
||||
- The package version for Jinja 3.3+ is now correctly displayed in `:version`.
|
||||
- Fixed crash with Qt 6.10 (and possibly older Qt versions) when navigating
|
||||
from a `qute://` page to a web page, e.g. when searching on `qute://start`.
|
||||
- On Wayland with Qt <= 6.9, `EGL_PLATFORM=wayland` is now set by qutebrowser to
|
||||
get hardware rendering. Qt 6.10 includes an equivalent fix (#8637).
|
||||
- Added workaround for per-domain User-Agent header not being used on redirects
|
||||
(#8679).
|
||||
- Added site-specific quirk for gitlab.gnome.org agressively blocking old
|
||||
Chromium versions (and thus QtWebEngine) (#8509).
|
||||
- Using `:config-list-remove` with an invalid value for the respective option
|
||||
type now corrently displays an error instead of crashing.
|
||||
|
||||
[[v3.5.1]]
|
||||
v3.5.1 (2025-06-05)
|
||||
-------------------
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- QtWebKit (legacy) support got removed from CI and is now untested. If it
|
||||
breaks, it's not going to be fixed, and support will be removed over the next
|
||||
releases.
|
||||
- Qt 5 support is currently still tested, but is also planned to get removed
|
||||
over the next releases. Same goes for support for older Qt 6 versions (likely
|
||||
6.2/6.3/6.4 and perhaps 6.5, see https://github.com/qutebrowser/qutebrowser/issues/8464[#8464]).
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- Windows/macOS releases now bundle Qt 6.9.1, including many graphics-related
|
||||
bugfixes, as well as security patches up to Chromium 136.0.7103.114.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- A bogus "wildcard call disconnects from destroyed signal" warning from Qt is
|
||||
now suppressed.
|
||||
- PDF.js now loads correctly on Windows installations with broken mimetype
|
||||
configurations.
|
||||
- A "Ignoring new child ..." debug log message which got spammy with Qt 6.9 is
|
||||
now removed.
|
||||
- A unknown crash (possibly related to using devtools) due to weird (Py)Qt
|
||||
behavior now has a workaround.
|
||||
- No "QtWebEngine version mismatch" warning is now logged anymore with newer Qt
|
||||
5.15 releases (but you should still stop using Qt 5).
|
||||
- The PDF.js version can now correctly be extracted/displayed with newer PDF.js
|
||||
versions.
|
||||
- The `qute-bitwarden`, `-lastpass` and `-pass` userscripts now properly avoid
|
||||
a `DeprecationWarning` from the upcoming 6.0 release of `tldextract`. The
|
||||
previous fix in v3.5.1 was insufficient.
|
||||
|
||||
[[v3.5.0]]
|
||||
v3.5.0 (2025-04-12)
|
||||
-------------------
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- Windows/macOS releases are now built with Qt 6.9.0
|
||||
* Based on Chromium 130.0.6723.192
|
||||
* Security fixes up to Chromium 133.0.6943.141
|
||||
* Also fixes issues with opening links on macOS
|
||||
- The `content.headers.user_agent` setting now has a new
|
||||
`{upstream_browser_version_short}` template field, which is the
|
||||
upstream/Chromium version but shortened to only major version.
|
||||
- The default user agent now uses the shortened Chromium version and doesn't
|
||||
expose the `QtWebEngine/...` part anymore, thus making it equal to the
|
||||
corresponding Chromium user agent. This increases compatibilty due to various
|
||||
overzealous "security" products used by a variety of websites that block
|
||||
QtWebEngine, presumably as a bot (known issues existed with Whatsapp Web, UPS,
|
||||
Digitec Galaxus).
|
||||
- Changed features in userscripts:
|
||||
* `qute-bitwarden` now passes your password to the subprocess in an
|
||||
environment variable when unlocking your vault, instead of as a command
|
||||
line argument. (#7781)
|
||||
- New `-D no-system-pdfjs` debug flag to ignore system-wide PDF.js installations
|
||||
for testing.
|
||||
- Polyfill for missing `URL.parse` with PDF.js v5 and QtWebEngine < 6.9. Note
|
||||
this is a "best effort" fix and you should be using the "older browsers"
|
||||
("legacy") build of PDF.js instead.
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- The `ua-slack` site-specific quirk, as things seem to work better nowadays
|
||||
without a quirk needed.
|
||||
- The `ua-whatsapp` site-specific quirk, as it's unneeded with the default UA
|
||||
change described above.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Crash when trying to use the `DocumentPictureInPicture` JS API, such as done
|
||||
by the new Google Workspaces Huddle feature. The API is unsupported by
|
||||
QtWebEngine and now correctly disabled on the JS side. (#8449)
|
||||
- Crash when a buggy notification presenter returns a duplicate ID (now an
|
||||
error is shown instead).
|
||||
- Crashes when running `:tab-move` or `:yank title` at startup, before a tab is
|
||||
available.
|
||||
- Crash with `input.insert_mode.auto_load`, when closing a new tab quickly after
|
||||
opening it, but before it was fully loaded. (#3895, #8400)
|
||||
- Workaround for microphone/camera permissions not being requested with
|
||||
QtWebEngine 6.9.0 on Google Meet, Zoom, or other pages using the new
|
||||
`<permission>` element. (#8539)
|
||||
- Resolved issues in userscripts:
|
||||
* `qute-bitwarden` will now prompt a re-login if its cached session has
|
||||
been invalidated since last used. (#8456)
|
||||
* `qute-bitwarden`, `-lastpass` and `-pass` now avoid a
|
||||
`DeprecationWarning` from the upcoming 6.0 release of `tldextract`
|
||||
|
||||
[[v3.4.0]]
|
||||
v3.4.0 (2024-12-14)
|
||||
-------------------
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ If you want to find something useful to do, check the
|
|||
https://github.com/qutebrowser/qutebrowser/issues[issue tracker]. Some
|
||||
pointers:
|
||||
|
||||
* https://github.com/qutebrowser/qutebrowser/labels/easy[Issues which should
|
||||
* https://github.com/qutebrowser/qutebrowser/contribute[Issues which should
|
||||
be easy to solve]
|
||||
* https://github.com/qutebrowser/qutebrowser/labels/component%3A%20docs[Documentation issues which require little/no coding]
|
||||
|
||||
|
|
@ -121,8 +121,6 @@ Currently, the following tox environments are available:
|
|||
* `pyroma`: Check packaging practices with
|
||||
https://pypi.python.org/pypi/pyroma/[pyroma].
|
||||
* `eslint`: Run https://eslint.org/[ESLint] javascript checker.
|
||||
* `check-manifest`: Check MANIFEST.in completeness with
|
||||
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
|
||||
|
|
@ -604,6 +602,7 @@ Info pages:
|
|||
- chrome://device-log/ (QtWebEngine >= 6.3)
|
||||
- chrome://gpu/
|
||||
- chrome://sandbox/ (Linux only)
|
||||
- chrome://qt/ (QtWebEngine >= 6.7)
|
||||
|
||||
Misc. / Debugging pages:
|
||||
|
||||
|
|
@ -614,6 +613,7 @@ Misc. / Debugging pages:
|
|||
- chrome://ukm/ (QtWebEngine >= 5.15.3)
|
||||
- chrome://user-actions/ (QtWebEngine >= 5.15.3)
|
||||
- chrome://webrtc-logs/ (QtWebEngine >= 5.15.3)
|
||||
- chrome://extensions/ (QtWebEngine >= 6.10)
|
||||
|
||||
Internals pages:
|
||||
|
||||
|
|
@ -804,7 +804,8 @@ qutebrowser release
|
|||
**Automatic release via GitHub Actions (starting with v3.0.0):**
|
||||
|
||||
* Double check Python version in `.github/workflows/release.yml`
|
||||
* Run the `release` workflow on the `main` branch, e.g. via `gh workflow run release -f release_type=major` (`release_type` can be `major`, `minor` or `patch`; you can also override `python_version`)
|
||||
* Run the `release` workflow on the `main` branch, e.g. via `gh workflow run release -f release_type=minor` (`release_type` can be `major`, `minor` or `patch`; you can also override `python_version`)
|
||||
* Consider running `gh run watch` or `gh run view --web` to watch the progress
|
||||
|
||||
**Manual release:**
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ Why Python?::
|
|||
point, I wasn't comfortable with C++ so that wasn't an alternative.
|
||||
|
||||
But isn't Python too slow for a browser?::
|
||||
https://www.infoworld.com/d/application-development/van-rossum-python-not-too-slow-188715[It's generally less of a problem than one would expect.]
|
||||
https://www.infoworld.com/article/2303031/van-rossum-python-is-not-too-slow-2.html[It's generally less of a problem than one would expect.]
|
||||
Most of the heavy lifting of qutebrowser is done by Qt and
|
||||
QtWebKit/QtWebEngine in C++, with the
|
||||
https://wiki.python.org/moin/GlobalInterpreterLock[GIL] released.
|
||||
|
|
@ -141,7 +141,7 @@ The comma prefix is used to make sure user-defined bindings don't conflict with
|
|||
the built-in ones.
|
||||
+
|
||||
Note that you might need an additional package (e.g.
|
||||
https://www.archlinux.org/packages/community/any/youtube-dl/[youtube-dl] on
|
||||
https://archlinux.org/packages/extra/any/yt-dlp/[yt-dlp] on
|
||||
Archlinux) to play web videos with mpv.
|
||||
+
|
||||
There is a very useful script for mpv, which emulates "unique application"
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ customizable for a given <<patterns,URL patterns>>.
|
|||
|
||||
[source,python]
|
||||
----
|
||||
config.set('content.images', False, '*://example.com/')
|
||||
config.set('content.images', False, '*://example.com/*')
|
||||
----
|
||||
|
||||
Alternatively, you can use `with config.pattern(...) as p:` to get a shortcut
|
||||
|
|
@ -187,7 +187,7 @@ similar to `c.` which is scoped to the given domain:
|
|||
|
||||
[source,python]
|
||||
----
|
||||
with config.pattern('*://example.com/') as p:
|
||||
with config.pattern('*://example.com/*') as p:
|
||||
p.content.images = False
|
||||
----
|
||||
|
||||
|
|
@ -416,6 +416,8 @@ Pre-built colorschemes
|
|||
- https://github.com/gicrisf/qute-city-lights[City Lights (matte dark)]
|
||||
- https://github.com/catppuccin/qutebrowser[Catppuccin]
|
||||
- https://github.com/iruzo/matrix-qutebrowser[Matrix]
|
||||
- https://github.com/harmtemolder/qutebrowser-solarized[Solarized]
|
||||
- https://github.com/Rehpotsirhc-z/qutebrowser-doom-one[Doom One]
|
||||
|
||||
Avoiding flake8 errors
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ Getting help
|
|||
You can get help in the IRC channel
|
||||
link:ircs://irc.libera.chat:6697/#qutebrowser[`#qutebrowser`] on
|
||||
https://libera.chat/[Libera Chat]
|
||||
(https://web.libera.chat/#qutebrowser[webchat], https://matrix.to/#qutebrowser:libera.chat[via Matrix]),
|
||||
(https://web.libera.chat/#qutebrowser[webchat]),
|
||||
or by writing a message to the
|
||||
https://listi.jpberlin.de/mailman/listinfo/qutebrowser[mailinglist] at
|
||||
mailto:qutebrowser@lists.qutebrowser.org[].
|
||||
|
|
|
|||
|
|
@ -302,6 +302,7 @@
|
|||
|<<qt.force_software_rendering,qt.force_software_rendering>>|Force software rendering for QtWebEngine.
|
||||
|<<qt.highdpi,qt.highdpi>>|Turn on Qt HighDPI scaling.
|
||||
|<<qt.workarounds.disable_accelerated_2d_canvas,qt.workarounds.disable_accelerated_2d_canvas>>|Disable accelerated 2d canvas to avoid graphical glitches.
|
||||
|<<qt.workarounds.disable_accessibility,qt.workarounds.disable_accessibility>>|Disable accessibility to avoid crashes on Qt 6.10.1.
|
||||
|<<qt.workarounds.disable_hangouts_extension,qt.workarounds.disable_hangouts_extension>>|Disable the Hangouts extension.
|
||||
|<<qt.workarounds.locale,qt.workarounds.locale>>|Work around locale parsing issues in QtWebEngine 5.15.3.
|
||||
|<<qt.workarounds.remove_service_workers,qt.workarounds.remove_service_workers>>|Delete the QtWebEngine Service Worker directory on every start.
|
||||
|
|
@ -2275,21 +2276,22 @@ The following placeholders are defined:
|
|||
* `{upstream_browser_key}`: "Version" for QtWebKit, "Chrome" for
|
||||
QtWebEngine.
|
||||
* `{upstream_browser_version}`: The corresponding Safari/Chrome version.
|
||||
* `{upstream_browser_version_short}`: The corresponding Safari/Chrome
|
||||
version, but only with its major version.
|
||||
* `{qutebrowser_version}`: The currently running qutebrowser version.
|
||||
|
||||
The default value is equal to the unchanged user agent of
|
||||
QtWebKit/QtWebEngine.
|
||||
The default value is equal to the default user agent of
|
||||
QtWebKit/QtWebEngine, but with the `QtWebEngine/...` part removed for
|
||||
increased compatibility.
|
||||
|
||||
Note that the value read from JavaScript is always the global value. With
|
||||
QtWebEngine between 5.12 and 5.14 (inclusive), changing the value exposed
|
||||
to JavaScript requires a restart.
|
||||
Note that the value read from JavaScript is always the global value.
|
||||
|
||||
|
||||
This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
|
||||
|
||||
Type: <<types,FormatString>>
|
||||
|
||||
Default: +pass:[Mozilla/5.0 ({os_info}) AppleWebKit/{webkit_version} (KHTML, like Gecko) {qt_key}/{qt_version} {upstream_browser_key}/{upstream_browser_version} Safari/{webkit_version}]+
|
||||
Default: +pass:[Mozilla/5.0 ({os_info}) AppleWebKit/{webkit_version} (KHTML, like Gecko) {upstream_browser_key}/{upstream_browser_version_short} Safari/{webkit_version}]+
|
||||
|
||||
[[content.hyperlink_auditing]]
|
||||
=== content.hyperlink_auditing
|
||||
|
|
@ -2767,10 +2769,9 @@ Type: <<types,FlagList>>
|
|||
|
||||
Valid values:
|
||||
|
||||
* +ua-whatsapp+
|
||||
* +ua-google+
|
||||
* +ua-slack+
|
||||
* +ua-googledocs+
|
||||
* +ua-gnome-gitlab+
|
||||
* +js-whatsapp-web+
|
||||
* +js-discord+
|
||||
* +js-string-replaceall+
|
||||
|
|
@ -3991,11 +3992,29 @@ Type: <<types,String>>
|
|||
Valid values:
|
||||
|
||||
* +always+: Disable accelerated 2d canvas
|
||||
* +auto+: Disable on Qt6 < 6.6.0, enable otherwise
|
||||
* +auto+: Disable on Qt versions with known issues, enable otherwise
|
||||
* +never+: Enable accelerated 2d canvas
|
||||
|
||||
Default: +pass:[auto]+
|
||||
|
||||
[[qt.workarounds.disable_accessibility]]
|
||||
=== qt.workarounds.disable_accessibility
|
||||
Disable accessibility to avoid crashes on Qt 6.10.1.
|
||||
|
||||
This setting requires a restart.
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,String>>
|
||||
|
||||
Valid values:
|
||||
|
||||
* +always+: Disable renderer accessibility
|
||||
* +auto+: Disable on Qt versions with known issues, enable otherwise
|
||||
* +never+: Enable renderer accessibility
|
||||
|
||||
Default: +pass:[auto]+
|
||||
|
||||
[[qt.workarounds.disable_hangouts_extension]]
|
||||
=== qt.workarounds.disable_hangouts_extension
|
||||
Disable the Hangouts extension.
|
||||
|
|
|
|||
|
|
@ -103,12 +103,18 @@ To be able to play videos with proprietary codecs with QtWebEngine, you will
|
|||
need to install an additional package from the RPM Fusion Free repository.
|
||||
For more information see https://rpmfusion.org/Configuration.
|
||||
|
||||
With Qt 6 (recommended):
|
||||
|
||||
-----
|
||||
# dnf install libavcodec-freeworld
|
||||
-----
|
||||
|
||||
With Qt 5:
|
||||
|
||||
-----
|
||||
# dnf install qt5-qtwebengine-freeworld
|
||||
-----
|
||||
|
||||
It's currently unknown what the Qt 6 equivalent of this is.
|
||||
|
||||
On Archlinux
|
||||
------------
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,12 @@
|
|||
</content_rating>
|
||||
<releases>
|
||||
<!-- Add new releases here -->
|
||||
<release version='3.6.3' date='2025-11-30'/>
|
||||
<release version='3.6.2' date='2025-11-27'/>
|
||||
<release version='3.6.1' date='2025-11-03'/>
|
||||
<release version='3.6.0' date='2025-10-24'/>
|
||||
<release version='3.5.1' date='2025-06-05'/>
|
||||
<release version='3.5.0' date='2025-04-12'/>
|
||||
<release version="3.4.0" date="2024-12-14"/>
|
||||
<release version="3.3.1" date="2024-10-12"/>
|
||||
<release version="3.3.0" date="2024-10-12"/>
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
build==1.2.2.post1
|
||||
check-manifest==0.50
|
||||
importlib_metadata==8.5.0
|
||||
packaging==24.2
|
||||
pyproject_hooks==1.2.0
|
||||
tomli==2.2.1
|
||||
zipp==3.21.0
|
||||
|
|
@ -1 +0,0 @@
|
|||
check-manifest
|
||||
|
|
@ -1,54 +1,73 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
annotated-types==0.7.0
|
||||
anyio==4.12.0
|
||||
autocommand==2.2.2
|
||||
backports.tarfile==1.2.0
|
||||
build==1.2.2.post1
|
||||
bump2version==1.0.1
|
||||
certifi==2024.8.30
|
||||
cffi==1.17.1
|
||||
charset-normalizer==3.4.0
|
||||
cryptography==44.0.0
|
||||
docutils==0.21.2
|
||||
bracex==2.6
|
||||
build==1.3.0
|
||||
bump-my-version==1.2.5
|
||||
certifi==2025.11.12
|
||||
cffi==2.0.0
|
||||
charset-normalizer==3.4.4
|
||||
click==8.1.8
|
||||
cryptography==46.0.3
|
||||
docutils==0.22.3
|
||||
exceptiongroup==1.3.1
|
||||
github3.py==4.0.1
|
||||
hunter==3.7.0
|
||||
idna==3.10
|
||||
importlib_metadata==8.5.0
|
||||
importlib_resources==6.4.5
|
||||
h11==0.16.0
|
||||
httpcore==1.0.9
|
||||
httpx==0.28.1
|
||||
hunter==3.9.0
|
||||
id==1.5.0
|
||||
idna==3.11
|
||||
importlib_metadata==8.7.0
|
||||
importlib_resources==6.5.2
|
||||
inflect==7.3.1
|
||||
jaraco.classes==3.4.0
|
||||
jaraco.collections==5.1.0
|
||||
jaraco.context==6.0.1
|
||||
jaraco.functools==4.1.0
|
||||
jaraco.functools==4.0.1
|
||||
jaraco.text==3.12.1
|
||||
jeepney==0.8.0
|
||||
keyring==25.5.0
|
||||
jeepney==0.9.0
|
||||
keyring==25.7.0
|
||||
manhole==1.8.1
|
||||
markdown-it-py==3.0.0
|
||||
mdurl==0.1.2
|
||||
more-itertools==10.5.0
|
||||
nh3==0.2.19
|
||||
packaging==24.2
|
||||
pkginfo==1.12.0
|
||||
platformdirs==4.3.6
|
||||
pycparser==2.22
|
||||
Pygments==2.18.0
|
||||
more-itertools==10.8.0
|
||||
nh3==0.3.2
|
||||
packaging==25.0
|
||||
platformdirs==4.4.0
|
||||
prompt_toolkit==3.0.52
|
||||
pycparser==2.23
|
||||
pydantic==2.12.5
|
||||
pydantic-settings==2.11.0
|
||||
pydantic_core==2.41.5
|
||||
Pygments==2.19.2
|
||||
PyJWT==2.10.1
|
||||
Pympler==1.1
|
||||
pyproject_hooks==1.2.0
|
||||
PyQt-builder==1.17.0
|
||||
PyQt-builder==1.19.1
|
||||
python-dateutil==2.9.0.post0
|
||||
python-dotenv==1.2.1
|
||||
questionary==2.1.1
|
||||
readme_renderer==44.0
|
||||
requests==2.32.3
|
||||
requests==2.32.5
|
||||
requests-toolbelt==1.0.0
|
||||
rfc3986==2.0.0
|
||||
rich==13.9.4
|
||||
rich==14.2.0
|
||||
rich-click==1.9.4
|
||||
SecretStorage==3.3.3
|
||||
sip==6.9.1
|
||||
sip==6.14.0
|
||||
six==1.17.0
|
||||
tomli==2.2.1
|
||||
twine==6.0.1
|
||||
tomli==2.3.0
|
||||
tomlkit==0.13.3
|
||||
twine==6.2.0
|
||||
typeguard==4.3.0
|
||||
typing_extensions==4.12.2
|
||||
uritemplate==4.1.1
|
||||
# urllib3==2.2.3
|
||||
zipp==3.21.0
|
||||
typing-inspection==0.4.2
|
||||
typing_extensions==4.15.0
|
||||
uritemplate==4.2.0
|
||||
# urllib3==2.6.2
|
||||
wcmatch==10.1
|
||||
wcwidth==0.2.14
|
||||
zipp==3.23.0
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
hunter
|
||||
pympler
|
||||
github3.py
|
||||
bump2version
|
||||
bump-my-version
|
||||
requests
|
||||
pyqt-builder
|
||||
build
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
attrs==24.2.0
|
||||
flake8==7.1.1
|
||||
attrs==25.4.0
|
||||
flake8==7.3.0
|
||||
flake8-bugbear==24.12.12
|
||||
flake8-builtins==2.5.0
|
||||
flake8-comprehensions==3.16.0
|
||||
flake8-builtins==3.0.0
|
||||
flake8-comprehensions==3.17.0
|
||||
flake8-debugger==4.1.2
|
||||
flake8-deprecated==2.2.1
|
||||
flake8-docstrings==1.7.0
|
||||
flake8-future-import==0.4.7
|
||||
flake8-plugin-utils==1.3.3
|
||||
flake8-pytest-style==2.0.0
|
||||
flake8-pytest-style==2.1.0
|
||||
flake8-string-format==0.3.0
|
||||
flake8-tidy-imports==4.11.0
|
||||
flake8-tidy-imports==4.12.0
|
||||
flake8-tuple==0.4.1
|
||||
mccabe==0.7.0
|
||||
pep8-naming==0.14.1
|
||||
pycodestyle==2.12.1
|
||||
pep8-naming==0.15.1
|
||||
pycodestyle==2.14.0
|
||||
pydocstyle==6.3.0
|
||||
pyflakes==3.2.0
|
||||
pyflakes==3.4.0
|
||||
six==1.17.0
|
||||
snowballstemmer==2.2.0
|
||||
snowballstemmer==3.0.1
|
||||
|
|
|
|||
|
|
@ -1,19 +1,20 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
chardet==5.2.0
|
||||
diff_cover==9.2.0
|
||||
Jinja2==3.1.4
|
||||
lxml==5.3.0
|
||||
MarkupSafe==3.0.2
|
||||
mypy==1.13.0
|
||||
mypy-extensions==1.0.0
|
||||
pluggy==1.5.0
|
||||
Pygments==2.18.0
|
||||
diff_cover==10.0.0
|
||||
Jinja2==3.1.6
|
||||
librt==0.7.3
|
||||
lxml==6.0.2
|
||||
MarkupSafe==3.0.3
|
||||
mypy==1.19.0
|
||||
mypy_extensions==1.1.0
|
||||
pathspec==0.12.1
|
||||
pluggy==1.6.0
|
||||
Pygments==2.19.2
|
||||
PyQt5-stubs==5.15.6.0
|
||||
tomli==2.2.1
|
||||
types-colorama==0.4.15.20240311
|
||||
types-docutils==0.21.0.20241128
|
||||
types-Pygments==2.18.0.20240506
|
||||
types-PyYAML==6.0.12.20240917
|
||||
types-setuptools==75.6.0.20241126
|
||||
typing_extensions==4.12.2
|
||||
tomli==2.3.0
|
||||
types-colorama==0.4.15.20250801
|
||||
types-docutils==0.22.3.20251115
|
||||
types-Pygments==2.19.0.20251121
|
||||
types-PyYAML==6.0.12.20250915
|
||||
typing_extensions==4.15.0
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
altgraph==0.17.4
|
||||
importlib_metadata==8.5.0
|
||||
packaging==24.2
|
||||
pyinstaller==6.11.1
|
||||
pyinstaller-hooks-contrib==2024.10
|
||||
zipp==3.21.0
|
||||
altgraph==0.17.5
|
||||
importlib_metadata==8.7.0
|
||||
packaging==25.0
|
||||
pyinstaller==6.17.0
|
||||
pyinstaller-hooks-contrib==2025.10
|
||||
zipp==3.23.0
|
||||
|
|
|
|||
|
|
@ -1,26 +1,28 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
astroid==3.3.6
|
||||
certifi==2024.8.30
|
||||
cffi==1.17.1
|
||||
charset-normalizer==3.4.0
|
||||
cryptography==44.0.0
|
||||
dill==0.3.9
|
||||
astroid==3.3.11
|
||||
certifi==2025.11.12
|
||||
cffi==2.0.0
|
||||
charset-normalizer==3.4.4
|
||||
cryptography==46.0.3
|
||||
dill==0.4.0
|
||||
github3.py==4.0.1
|
||||
idna==3.10
|
||||
isort==5.13.2
|
||||
idna==3.11
|
||||
importlib_metadata==8.7.0
|
||||
isort==6.1.0
|
||||
mccabe==0.7.0
|
||||
pefile==2024.8.26
|
||||
platformdirs==4.3.6
|
||||
pycparser==2.22
|
||||
platformdirs==4.4.0
|
||||
pycparser==2.23
|
||||
PyJWT==2.10.1
|
||||
pylint==3.3.2
|
||||
pylint==3.3.9
|
||||
python-dateutil==2.9.0.post0
|
||||
./scripts/dev/pylint_checkers
|
||||
requests==2.32.3
|
||||
requests==2.32.5
|
||||
six==1.17.0
|
||||
tomli==2.2.1
|
||||
tomlkit==0.13.2
|
||||
typing_extensions==4.12.2
|
||||
uritemplate==4.1.1
|
||||
# urllib3==2.2.3
|
||||
tomli==2.3.0
|
||||
tomlkit==0.13.3
|
||||
typing_extensions==4.15.0
|
||||
uritemplate==4.2.0
|
||||
# urllib3==2.6.2
|
||||
zipp==3.23.0
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.15.2 # rq.filter: == 5.15.2
|
||||
PyQt5_sip==12.16.1
|
||||
PyQt5_sip==12.17.1
|
||||
PyQtWebEngine==5.15.2 # rq.filter: == 5.15.2
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.15.11 # rq.filter: < 5.16
|
||||
PyQt5-Qt5==5.15.15
|
||||
PyQt5_sip==12.16.1
|
||||
PyQt5-Qt5==5.15.18
|
||||
PyQt5_sip==12.17.1
|
||||
PyQtWebEngine==5.15.7 # rq.filter: < 5.16
|
||||
PyQtWebEngine-Qt5==5.15.15
|
||||
PyQtWebEngine-Qt5==5.15.18
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.15.11
|
||||
PyQt5-Qt5==5.15.15
|
||||
PyQt5_sip==12.16.1
|
||||
PyQt5-Qt5==5.15.18
|
||||
PyQt5_sip==12.17.1
|
||||
PyQtWebEngine==5.15.7
|
||||
PyQtWebEngine-Qt5==5.15.15
|
||||
PyQtWebEngine-Qt5==5.15.18
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt6==6.10.1
|
||||
PyQt6-Qt6==6.10.1
|
||||
PyQt6-WebEngine==6.10.0
|
||||
PyQt6-WebEngine-Qt6==6.10.1
|
||||
PyQt6_sip==13.10.2
|
||||
--extra-index-url https://www.riverbankcomputing.com/pypi/simple/
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
PyQt6 >= 6.10, < 6.11
|
||||
PyQt6-Qt6 >= 6.10, < 6.11
|
||||
PyQt6-WebEngine >= 6.10, < 6.11
|
||||
PyQt6-WebEngine-Qt6 >= 6.10, < 6.11
|
||||
|
||||
# WORKAROUND for https://www.riverbankcomputing.com/pipermail/pyqt/2025-October/046347.html
|
||||
#@ add: --extra-index-url https://www.riverbankcomputing.com/pypi/simple/
|
||||
--extra-index-url https://www.riverbankcomputing.com/pypi/simple/
|
||||
|
|
@ -4,4 +4,4 @@ PyQt6==6.2.3
|
|||
PyQt6-Qt6==6.2.4
|
||||
PyQt6-WebEngine==6.2.1
|
||||
PyQt6-WebEngine-Qt6==6.2.4
|
||||
PyQt6_sip==13.9.1
|
||||
PyQt6_sip==13.10.2
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@ PyQt6==6.3.1
|
|||
PyQt6-Qt6==6.3.2
|
||||
PyQt6-WebEngine==6.3.1
|
||||
PyQt6-WebEngine-Qt6==6.3.2
|
||||
PyQt6_sip==13.9.1
|
||||
PyQt6_sip==13.10.2
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@ PyQt6==6.4.2
|
|||
PyQt6-Qt6==6.4.3
|
||||
PyQt6-WebEngine==6.4.0
|
||||
PyQt6-WebEngine-Qt6==6.4.3
|
||||
PyQt6_sip==13.9.1
|
||||
PyQt6_sip==13.10.2
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@ PyQt6==6.5.3
|
|||
PyQt6-Qt6==6.5.3
|
||||
PyQt6-WebEngine==6.5.0
|
||||
PyQt6-WebEngine-Qt6==6.5.3
|
||||
PyQt6_sip==13.9.1
|
||||
PyQt6_sip==13.10.2
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@ PyQt6==6.6.1
|
|||
PyQt6-Qt6==6.6.3
|
||||
PyQt6-WebEngine==6.6.0
|
||||
PyQt6-WebEngine-Qt6==6.6.3
|
||||
PyQt6_sip==13.9.1
|
||||
PyQt6_sip==13.10.2
|
||||
|
|
|
|||
|
|
@ -5,4 +5,4 @@ PyQt6-Qt6==6.7.3
|
|||
PyQt6-WebEngine==6.7.0
|
||||
PyQt6-WebEngine-Qt6==6.7.3
|
||||
PyQt6-WebEngineSubwheel-Qt6==6.7.3
|
||||
PyQt6_sip==13.9.1
|
||||
PyQt6_sip==13.10.2
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt6==6.8.0
|
||||
PyQt6-Qt6==6.8.1
|
||||
PyQt6==6.8.1
|
||||
PyQt6-Qt6==6.8.2
|
||||
PyQt6-WebEngine==6.8.0
|
||||
PyQt6-WebEngine-Qt6==6.8.1
|
||||
PyQt6_sip==13.9.1
|
||||
PyQt6-WebEngine-Qt6==6.8.2
|
||||
PyQt6_sip==13.10.2
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt6==6.9.1
|
||||
PyQt6-Qt6==6.9.2
|
||||
PyQt6-WebEngine==6.9.0
|
||||
PyQt6-WebEngine-Qt6==6.9.2
|
||||
PyQt6_sip==13.10.2
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
PyQt6 >= 6.9, < 6.10
|
||||
PyQt6-Qt6 >= 6.9, < 6.10
|
||||
PyQt6-WebEngine >= 6.9, < 6.10
|
||||
PyQt6-WebEngine-Qt6 >= 6.9, < 6.10
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt6==6.8.0
|
||||
PyQt6-Qt6==6.8.1
|
||||
PyQt6-WebEngine==6.8.0
|
||||
PyQt6-WebEngine-Qt6==6.8.1
|
||||
PyQt6_sip==13.9.1
|
||||
PyQt6==6.10.1
|
||||
PyQt6-Qt6==6.10.1
|
||||
PyQt6-WebEngine==6.10.0
|
||||
PyQt6-WebEngine-Qt6==6.10.1
|
||||
PyQt6_sip==13.10.2
|
||||
--extra-index-url https://www.riverbankcomputing.com/pypi/simple/
|
||||
|
|
|
|||
|
|
@ -2,3 +2,7 @@ PyQt6
|
|||
PyQt6-Qt6
|
||||
PyQt6-WebEngine
|
||||
PyQt6-WebEngine-Qt6
|
||||
|
||||
# WORKAROUND for https://www.riverbankcomputing.com/pipermail/pyqt/2025-October/046347.html
|
||||
#@ add: --extra-index-url https://www.riverbankcomputing.com/pypi/simple/
|
||||
--extra-index-url https://www.riverbankcomputing.com/pypi/simple/
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt6==6.8.0
|
||||
PyQt6-Qt6==6.8.1
|
||||
PyQt6-WebEngine==6.8.0
|
||||
PyQt6-WebEngine-Qt6==6.8.1
|
||||
PyQt6_sip==13.9.1
|
||||
PyQt6==6.10.1
|
||||
PyQt6-Qt6==6.10.1
|
||||
PyQt6-WebEngine==6.10.0
|
||||
PyQt6-WebEngine-Qt6==6.10.1
|
||||
PyQt6_sip==13.10.2
|
||||
--extra-index-url https://www.riverbankcomputing.com/pypi/simple/
|
||||
|
|
|
|||
|
|
@ -2,3 +2,7 @@ PyQt6
|
|||
PyQt6-Qt6
|
||||
PyQt6-WebEngine
|
||||
PyQt6-WebEngine-Qt6
|
||||
|
||||
# WORKAROUND for https://www.riverbankcomputing.com/pipermail/pyqt/2025-October/046347.html
|
||||
#@ add: --extra-index-url https://www.riverbankcomputing.com/pypi/simple/
|
||||
--extra-index-url https://www.riverbankcomputing.com/pypi/simple/
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
build==1.2.2.post1
|
||||
certifi==2024.8.30
|
||||
charset-normalizer==3.4.0
|
||||
docutils==0.21.2
|
||||
idna==3.10
|
||||
importlib_metadata==8.5.0
|
||||
packaging==24.2
|
||||
Pygments==2.18.0
|
||||
build==1.3.0
|
||||
certifi==2025.11.12
|
||||
charset-normalizer==3.4.4
|
||||
check-manifest==0.51
|
||||
docutils==0.22.3
|
||||
idna==3.11
|
||||
importlib_metadata==8.7.0
|
||||
packaging==25.0
|
||||
Pygments==2.19.2
|
||||
pyproject_hooks==1.2.0
|
||||
pyroma==4.2
|
||||
requests==2.32.3
|
||||
tomli==2.2.1
|
||||
trove-classifiers==2024.10.21.16
|
||||
urllib3==2.2.3
|
||||
zipp==3.21.0
|
||||
pyroma==5.0.1
|
||||
requests==2.32.5
|
||||
tomli==2.3.0
|
||||
trove-classifiers==2025.12.1.14
|
||||
urllib3==2.6.2
|
||||
zipp==3.23.0
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
pyroma
|
||||
check-manifest
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
alabaster==0.7.16
|
||||
babel==2.16.0
|
||||
certifi==2024.8.30
|
||||
charset-normalizer==3.4.0
|
||||
babel==2.17.0
|
||||
certifi==2025.11.12
|
||||
charset-normalizer==3.4.4
|
||||
docutils==0.21.2
|
||||
idna==3.10
|
||||
idna==3.11
|
||||
imagesize==1.4.1
|
||||
importlib_metadata==8.5.0
|
||||
Jinja2==3.1.4
|
||||
MarkupSafe==3.0.2
|
||||
packaging==24.2
|
||||
Pygments==2.18.0
|
||||
requests==2.32.3
|
||||
snowballstemmer==2.2.0
|
||||
importlib_metadata==8.7.0
|
||||
Jinja2==3.1.6
|
||||
MarkupSafe==3.0.3
|
||||
packaging==25.0
|
||||
Pygments==2.19.2
|
||||
requests==2.32.5
|
||||
snowballstemmer==3.0.1
|
||||
Sphinx==7.4.7
|
||||
sphinxcontrib-applehelp==2.0.0
|
||||
sphinxcontrib-devhelp==2.0.0
|
||||
|
|
@ -21,6 +21,6 @@ sphinxcontrib-htmlhelp==2.1.0
|
|||
sphinxcontrib-jsmath==1.0.1
|
||||
sphinxcontrib-qthelp==2.0.0
|
||||
sphinxcontrib-serializinghtml==2.0.0
|
||||
tomli==2.2.1
|
||||
urllib3==2.2.3
|
||||
zipp==3.21.0
|
||||
tomli==2.3.0
|
||||
urllib3==2.6.2
|
||||
zipp==3.23.0
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@
|
|||
# bzr+lp:beautifulsoup
|
||||
beautifulsoup4
|
||||
git+https://github.com/cherrypy/cheroot.git
|
||||
git+https://github.com/nedbat/coveragepy.git#egg=coverage[toml]
|
||||
coverage[toml] @ git+https://github.com/nedbat/coveragepy.git
|
||||
git+https://github.com/pallets/flask.git
|
||||
git+https://github.com/pallets/werkzeug.git # transitive dep, but needed to work
|
||||
git+https://github.com/HypothesisWorks/hypothesis.git#subdirectory=hypothesis-python
|
||||
git+https://github.com/pytest-dev/pytest.git
|
||||
git+https://github.com/pytest-dev/pytest-bdd.git
|
||||
gherkin-official<31.0.0 # https://github.com/cucumber/gherkin/issues/373
|
||||
git+https://github.com/ionelmc/pytest-benchmark.git
|
||||
git+https://github.com/pytest-dev/pytest-instafail.git
|
||||
git+https://github.com/pytest-dev/pytest-mock.git
|
||||
|
|
|
|||
|
|
@ -1,67 +1,67 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
attrs==24.2.0
|
||||
attrs==25.4.0
|
||||
autocommand==2.2.2
|
||||
backports.tarfile==1.2.0
|
||||
beautifulsoup4==4.12.3
|
||||
beautifulsoup4==4.14.3
|
||||
blinker==1.9.0
|
||||
certifi==2024.8.30
|
||||
charset-normalizer==3.4.0
|
||||
cheroot==10.0.1
|
||||
click==8.1.7
|
||||
coverage==7.6.9
|
||||
exceptiongroup==1.2.2
|
||||
execnet==2.1.1
|
||||
filelock==3.16.1
|
||||
Flask==3.1.0
|
||||
certifi==2025.11.12
|
||||
charset-normalizer==3.4.4
|
||||
cheroot==11.1.2
|
||||
click==8.1.8
|
||||
coverage==7.10.7
|
||||
exceptiongroup==1.3.1
|
||||
execnet==2.1.2
|
||||
filelock==3.19.1
|
||||
Flask==3.1.2
|
||||
gherkin-official==29.0.0
|
||||
hunter==3.7.0
|
||||
hypothesis==6.122.3
|
||||
idna==3.10
|
||||
importlib_metadata==8.5.0
|
||||
importlib_resources==6.4.5
|
||||
hunter==3.9.0
|
||||
hypothesis==6.141.1
|
||||
idna==3.11
|
||||
importlib_metadata==8.7.0
|
||||
importlib_resources==6.5.2
|
||||
inflect==7.3.1
|
||||
iniconfig==2.0.0
|
||||
iniconfig==2.1.0
|
||||
itsdangerous==2.2.0
|
||||
jaraco.collections==5.1.0
|
||||
jaraco.context==6.0.1
|
||||
jaraco.functools==4.1.0
|
||||
jaraco.functools==4.0.1
|
||||
jaraco.text==3.12.1
|
||||
# Jinja2==3.1.4
|
||||
Mako==1.3.8
|
||||
# Jinja2==3.1.6
|
||||
Mako==1.3.10
|
||||
manhole==1.8.1
|
||||
# MarkupSafe==3.0.2
|
||||
more-itertools==10.5.0
|
||||
packaging==24.2
|
||||
# MarkupSafe==3.0.3
|
||||
more-itertools==10.8.0
|
||||
packaging==25.0
|
||||
parse==1.20.2
|
||||
parse_type==0.6.4
|
||||
pillow==11.0.0
|
||||
platformdirs==4.3.6
|
||||
pluggy==1.5.0
|
||||
parse_type==0.6.6
|
||||
pillow==11.3.0
|
||||
platformdirs==4.4.0
|
||||
pluggy==1.6.0
|
||||
py-cpuinfo==9.0.0
|
||||
Pygments==2.18.0
|
||||
pytest==8.3.4
|
||||
Pygments==2.19.2
|
||||
pytest==8.4.2
|
||||
pytest-bdd==8.1.0
|
||||
pytest-benchmark==5.1.0
|
||||
pytest-cov==6.0.0
|
||||
pytest-benchmark==5.2.3
|
||||
pytest-cov==7.0.0
|
||||
pytest-instafail==0.5.0
|
||||
pytest-mock==3.14.0
|
||||
pytest-qt==4.4.0
|
||||
pytest-repeat==0.9.3
|
||||
pytest-rerunfailures==15.0
|
||||
pytest-xdist==3.6.1
|
||||
pytest-xvfb==3.0.0
|
||||
pytest-mock==3.15.1
|
||||
pytest-qt==4.5.0
|
||||
pytest-repeat==0.9.4
|
||||
pytest-rerunfailures==16.0.1
|
||||
pytest-xdist==3.8.0
|
||||
pytest-xvfb==3.1.1
|
||||
PyVirtualDisplay==3.0
|
||||
requests==2.32.3
|
||||
requests-file==2.1.0
|
||||
requests==2.32.5
|
||||
requests-file==3.0.1
|
||||
six==1.17.0
|
||||
sortedcontainers==2.4.0
|
||||
soupsieve==2.6
|
||||
tldextract==5.1.3
|
||||
tomli==2.2.1
|
||||
soupsieve==2.8
|
||||
tldextract==5.3.0
|
||||
tomli==2.3.0
|
||||
typeguard==4.3.0
|
||||
typing_extensions==4.12.2
|
||||
urllib3==2.2.3
|
||||
typing_extensions==4.15.0
|
||||
urllib3==2.6.2
|
||||
vulture==2.14
|
||||
Werkzeug==3.1.3
|
||||
zipp==3.21.0
|
||||
Werkzeug==3.1.4
|
||||
zipp==3.23.0
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
cachetools==5.5.0
|
||||
cachetools==6.2.3
|
||||
chardet==5.2.0
|
||||
colorama==0.4.6
|
||||
distlib==0.3.9
|
||||
filelock==3.16.1
|
||||
packaging==24.2
|
||||
pip==24.3.1
|
||||
platformdirs==4.3.6
|
||||
pluggy==1.5.0
|
||||
pyproject-api==1.8.0
|
||||
setuptools==75.6.0
|
||||
tomli==2.2.1
|
||||
tox==4.23.2
|
||||
typing_extensions==4.12.2
|
||||
virtualenv==20.28.0
|
||||
distlib==0.4.0
|
||||
filelock==3.19.1
|
||||
packaging==25.0
|
||||
pip==25.3
|
||||
platformdirs==4.4.0
|
||||
pluggy==1.6.0
|
||||
pyproject-api==1.9.1
|
||||
setuptools==80.9.0
|
||||
tomli==2.3.0
|
||||
tox==4.30.3 ; python_full_version!="3.14.0b1"
|
||||
typing_extensions==4.15.0
|
||||
virtualenv==20.35.4
|
||||
wheel==0.45.1
|
||||
tox @ git+https://github.com/tox-dev/tox ; python_full_version=="3.14.0b1"
|
||||
|
|
|
|||
|
|
@ -1,2 +1,5 @@
|
|||
tox
|
||||
wheel
|
||||
|
||||
#@ markers: tox python_full_version!="3.14.0b1"
|
||||
#@ add: tox @ git+https://github.com/tox-dev/tox ; python_full_version=="3.14.0b1"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
tomli==2.2.1
|
||||
tomli==2.3.0
|
||||
vulture==2.14
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
pathspec==0.12.1
|
||||
PyYAML==6.0.2
|
||||
yamllint==1.35.1
|
||||
PyYAML==6.0.3
|
||||
yamllint==1.37.1
|
||||
|
|
|
|||
|
|
@ -106,6 +106,8 @@ The following userscripts can be found on their own repositories.
|
|||
More powerfully manage single window sessions
|
||||
- [qutebrowser-url-mutator](https://codeberg.org/mister_monster/qutebrowser-url-mutator):
|
||||
automatically mutates input URLs based on configurable rules
|
||||
- [qute-translate-popup](https://github.com/JohnBardoe/qute-translate-popup):
|
||||
selected text translation, with a qute popup!
|
||||
|
||||
[Zotero]: https://www.zotero.org/
|
||||
[Pocket]: https://getpocket.com/
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ no_entries_found() {
|
|||
# expected to write the username of that entry to the $username variable and
|
||||
# the corresponding password to $password
|
||||
|
||||
# shellcheck disable=SC2317
|
||||
# shellcheck disable=SC2329
|
||||
reset_backend() {
|
||||
init() { true ; }
|
||||
query_entries() { true ; }
|
||||
|
|
@ -199,7 +199,8 @@ choose_entry_zenity() {
|
|||
}
|
||||
|
||||
choose_entry_zenity_radio() {
|
||||
zenity_helper() { # shellcheck disable=SC2317
|
||||
# shellcheck disable=SC2329
|
||||
zenity_helper() {
|
||||
awk '{ print $0 ; print $0 }' \
|
||||
| zenity --list --radiolist \
|
||||
--title "qutebrowser password fill" \
|
||||
|
|
@ -279,7 +280,7 @@ pass_backend() {
|
|||
|
||||
# =======================================================
|
||||
# backend: secret
|
||||
# shellcheck disable=SC2317
|
||||
# shellcheck disable=SC2329
|
||||
secret_backend() {
|
||||
init() {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -96,7 +96,8 @@ def ask_password(password_prompt_invocation):
|
|||
raise Exception('Could not unlock vault')
|
||||
master_pass = process.stdout.strip()
|
||||
return subprocess.check_output(
|
||||
['bw', 'unlock', '--raw', master_pass],
|
||||
['bw', 'unlock', '--raw', '--passwordenv', 'BW_MASTERPASS'],
|
||||
env={**os.environ, 'BW_MASTERPASS': master_pass},
|
||||
text=True,
|
||||
).strip()
|
||||
|
||||
|
|
@ -132,7 +133,7 @@ def get_session_key(auto_lock, password_prompt_invocation):
|
|||
def pass_(domain, encoding, auto_lock, password_prompt_invocation):
|
||||
session_key = get_session_key(auto_lock, password_prompt_invocation)
|
||||
process = subprocess.run(
|
||||
['bw', 'list', 'items', '--session', session_key, '--url', domain],
|
||||
['bw', 'list', 'items', '--nointeraction', '--session', session_key, '--url', domain],
|
||||
capture_output=True,
|
||||
)
|
||||
|
||||
|
|
@ -141,6 +142,10 @@ def pass_(domain, encoding, auto_lock, password_prompt_invocation):
|
|||
msg = 'Bitwarden CLI returned for {:s} - {:s}'.format(domain, err)
|
||||
stderr(msg)
|
||||
|
||||
if "Vault is locked" in err:
|
||||
stderr("Bitwarden Vault got locked, trying again with clean session")
|
||||
return pass_(domain, encoding, 0, password_prompt_invocation)
|
||||
|
||||
if process.returncode:
|
||||
return '[]'
|
||||
|
||||
|
|
@ -152,7 +157,7 @@ def pass_(domain, encoding, auto_lock, password_prompt_invocation):
|
|||
def get_totp_code(selection_id, domain_name, encoding, auto_lock, password_prompt_invocation):
|
||||
session_key = get_session_key(auto_lock, password_prompt_invocation)
|
||||
process = subprocess.run(
|
||||
['bw', 'get', 'totp', '--session', session_key, selection_id],
|
||||
['bw', 'get', 'totp', '--nointeraction', '--session', session_key, selection_id],
|
||||
capture_output=True,
|
||||
)
|
||||
|
||||
|
|
@ -162,6 +167,10 @@ def get_totp_code(selection_id, domain_name, encoding, auto_lock, password_promp
|
|||
msg = 'Bitwarden CLI returned for {:s} - {:s}'.format(domain_name, err)
|
||||
stderr(msg)
|
||||
|
||||
if "Vault is locked" in err:
|
||||
stderr("Bitwarden Vault got locked, trying again with clean session")
|
||||
return get_totp_code(selection_id, domain_name, encoding, 0, password_prompt_invocation)
|
||||
|
||||
if process.returncode:
|
||||
return '[]'
|
||||
|
||||
|
|
@ -195,12 +204,20 @@ def main(arguments):
|
|||
# the registered domain name and finally: the IPv4 address if that's what
|
||||
# the URL represents
|
||||
candidates = []
|
||||
for target in filter(None, [
|
||||
extract_result.fqdn,
|
||||
extract_result.registered_domain,
|
||||
extract_result.subdomain + '.' + extract_result.domain,
|
||||
extract_result.domain,
|
||||
extract_result.ipv4]):
|
||||
for target in filter(
|
||||
None,
|
||||
[
|
||||
extract_result.fqdn,
|
||||
(
|
||||
extract_result.top_domain_under_public_suffix
|
||||
if hasattr(extract_result, "top_domain_under_public_suffix")
|
||||
else extract_result.registered_domain
|
||||
),
|
||||
extract_result.subdomain + "." + extract_result.domain,
|
||||
extract_result.domain,
|
||||
extract_result.ipv4,
|
||||
],
|
||||
):
|
||||
target_candidates = json.loads(
|
||||
pass_(
|
||||
target,
|
||||
|
|
|
|||
|
|
@ -117,7 +117,20 @@ def main(arguments):
|
|||
# the URL represents
|
||||
candidates = []
|
||||
seen_id = set()
|
||||
for target in filter(None, [extract_result.fqdn, extract_result.registered_domain, extract_result.subdomain + extract_result.domain, extract_result.domain, extract_result.ipv4]):
|
||||
for target in filter(
|
||||
None,
|
||||
[
|
||||
extract_result.fqdn,
|
||||
(
|
||||
extract_result.top_domain_under_public_suffix
|
||||
if hasattr(extract_result, "top_domain_under_public_suffix")
|
||||
else extract_result.registered_domain
|
||||
),
|
||||
extract_result.subdomain + extract_result.domain,
|
||||
extract_result.domain,
|
||||
extract_result.ipv4,
|
||||
],
|
||||
):
|
||||
target_candidates, err = pass_(target, arguments.io_encoding)
|
||||
if err:
|
||||
stderr("LastPass CLI returned for {:s} - {:s}".format(target, err))
|
||||
|
|
|
|||
|
|
@ -243,7 +243,20 @@ def main(arguments):
|
|||
|
||||
netloc = urlparse(arguments.url).netloc
|
||||
|
||||
for target in filter(None, [extract_result.fqdn, extract_result.registered_domain, extract_result.ipv4, private_domain, netloc]):
|
||||
for target in filter(
|
||||
None,
|
||||
[
|
||||
extract_result.fqdn,
|
||||
(
|
||||
extract_result.top_domain_under_public_suffix
|
||||
if hasattr(extract_result, "top_domain_under_public_suffix")
|
||||
else extract_result.registered_domain
|
||||
),
|
||||
extract_result.ipv4,
|
||||
private_domain,
|
||||
netloc,
|
||||
],
|
||||
):
|
||||
attempted_targets.append(target)
|
||||
target_candidates = find_pass_candidates(target, unfiltered=arguments.unfiltered)
|
||||
if not target_candidates:
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ create_menu() {
|
|||
done < "$QUTE_CONFIG_DIR"/bookmarks/urls
|
||||
|
||||
# Finally history
|
||||
printf -- '%s\n' "$(sqlite3 -separator ' ' "$QUTE_DATA_DIR/history.sqlite" 'select title, url from CompletionHistory')"
|
||||
printf -- '%s\n' "$(sqlite3 -separator ' ' "$QUTE_DATA_DIR/history.sqlite" 'select title, url from CompletionHistory ORDER BY last_atime DESC')"
|
||||
}
|
||||
|
||||
get_selection() {
|
||||
|
|
|
|||
|
|
@ -9,18 +9,16 @@
|
|||
# :spawn --userscript ripbang amazon maps
|
||||
#
|
||||
|
||||
import os, re, requests, sys
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
import os, requests, sys
|
||||
|
||||
for argument in sys.argv[1:]:
|
||||
bang = '!' + argument
|
||||
r = requests.get('https://duckduckgo.com/',
|
||||
r = requests.get('https://html.duckduckgo.com/html/',
|
||||
allow_redirects=False,
|
||||
params={'q': bang + ' SEARCHTEXT'},
|
||||
headers={'user-agent': 'qutebrowser ripbang'})
|
||||
|
||||
searchengine = re.search("url=([^']+)", r.text).group(1)
|
||||
searchengine = urlparse(searchengine).query
|
||||
searchengine = parse_qs(searchengine)['uddg'][0]
|
||||
searchengine = r.headers['location']
|
||||
searchengine = searchengine.replace('SEARCHTEXT', '{}')
|
||||
|
||||
if os.getenv('QUTE_FIFO'):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
[build-system]
|
||||
requires = ["setuptools>=42"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.check-manifest]
|
||||
ignore = [
|
||||
"qutebrowser/git-commit-id",
|
||||
"qutebrowser/html/doc",
|
||||
"qutebrowser/html/doc/*",
|
||||
"qutebrowser/html/doc/img/cheatsheet-*.png",
|
||||
"*/__pycache__",
|
||||
]
|
||||
19
pytest.ini
19
pytest.ini
|
|
@ -1,4 +1,5 @@
|
|||
[pytest]
|
||||
pythonpath = .
|
||||
log_level = NOTSET
|
||||
addopts = --strict-markers --strict-config --instafail --benchmark-columns=Min,Max,Median
|
||||
testpaths = tests
|
||||
|
|
@ -19,6 +20,7 @@ markers =
|
|||
not_frozen: Tests which can't be run if sys.frozen is True.
|
||||
not_flatpak: Tests which can't be run if running with Flatpak.
|
||||
no_xvfb: Tests which can't be run with Xvfb.
|
||||
no_offscreen: Tests which can't be run with the offscreen platform plugin.
|
||||
frozen: Tests which can only be run if sys.frozen is True.
|
||||
integration: Tests which test a bigger portion of code
|
||||
end2end: End to end tests which run qutebrowser as subprocess
|
||||
|
|
@ -41,6 +43,8 @@ markers =
|
|||
qt6_only: Tests which should only run with Qt 6
|
||||
qt5_xfail: Tests which fail with Qt 5
|
||||
qt6_xfail: Tests which fail with Qt 6
|
||||
qt69_ci_flaky: Tests which are flaky with Qt 6.9+ on CI
|
||||
qt69_ci_skip: Tests which should be skipped with Qt 6.9+ on CI
|
||||
qt_log_level_fail = WARNING
|
||||
qt_log_ignore =
|
||||
# GitHub Actions
|
||||
|
|
@ -78,6 +82,19 @@ qt_log_ignore =
|
|||
QItemSelectionModel: Selecting when no model has been set will result in a no-op.
|
||||
^QSaveFile::commit: File \(.*[/\\]test_failing_flush0[/\\]foo\) is not open$
|
||||
^The following paths were searched for Qt WebEngine dictionaries:.*
|
||||
# Qt 6.9 with Xvfb
|
||||
^Backend texture is not a Vulkan texture\.$
|
||||
^Compositor returned null texture$
|
||||
# With offscreen platform plugin
|
||||
^This plugin does not support (raise\(\)|propagateSizeHints\(\)|createPlatformVulkanInstance|grabbing the keyboard)$
|
||||
^QRhiGles2: Failed to create (temporary )?context$
|
||||
^QVulkanInstance: Failed to initialize Vulkan$
|
||||
^Unable to detect GPU vendor\.$
|
||||
# Qt 5 on CI with WebKit
|
||||
^qglx_findConfig: Failed to finding matching FBConfig for QSurfaceFormat\(version 2\.0, options QFlags<QSurfaceFormat::FormatOption>\(\), depthBufferSize -1, redBufferSize 1, greenBufferSize 1, blueBufferSize 1, alphaBufferSize -1, stencilBufferSize -1, samples -1, swapBehavior QSurfaceFormat::SingleBuffer, swapInterval 1, colorSpace QSurfaceFormat::DefaultColorSpace, profile QSurfaceFormat::NoProfile\)$
|
||||
# Qt 6.8+ debug build
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/8069#issuecomment-2017644465
|
||||
^QObject::connect: Connecting from COMPAT signal \(QWebEnginePage::featurePermissionRequest(ed|Canceled)\(QUrl,QWebEnginePage::Feature\)\)
|
||||
xfail_strict = true
|
||||
filterwarnings =
|
||||
error
|
||||
|
|
@ -85,5 +102,7 @@ filterwarnings =
|
|||
# https://github.com/cucumber/gherkin/commit/2f4830093149eae7ff7bd82f683b3d3bb7320d39
|
||||
# https://github.com/pytest-dev/pytest-bdd/issues/752
|
||||
ignore:'maxsplit' is passed as positional argument:DeprecationWarning:gherkin.gherkin_line
|
||||
# https://github.com/ionelmc/pytest-benchmark/issues/283
|
||||
ignore:FileType is deprecated\. Simply open files after parsing arguments\.:PendingDeprecationWarning:pytest_benchmark.plugin
|
||||
faulthandler_timeout = 90
|
||||
xvfb_colordepth = 24
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ _year = datetime.date.today().year
|
|||
|
||||
__author__ = "Florian Bruhin"
|
||||
__copyright__ = "Copyright 2013-{} Florian Bruhin (The Compiler)".format(_year)
|
||||
__license__ = "GPL"
|
||||
__license__ = "GPL-3.0-or-later"
|
||||
__maintainer__ = __author__
|
||||
__email__ = "mail@qutebrowser.org"
|
||||
__version__ = "3.4.0"
|
||||
__version__ = "3.6.3"
|
||||
__version_info__ = tuple(int(part) for part in __version__.split('.'))
|
||||
__description__ = "A keyboard-driven, vim-like browser based on Python and Qt."
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ if TYPE_CHECKING:
|
|||
from qutebrowser.keyinput import modeman
|
||||
from qutebrowser.config import config, websettings
|
||||
from qutebrowser.utils import (utils, objreg, usertypes, log, qtutils,
|
||||
urlutils, message, jinja, version)
|
||||
urlutils, message, jinja)
|
||||
from qutebrowser.misc import miscwidgets, objects, sessions
|
||||
from qutebrowser.browser import eventfilter, inspector
|
||||
from qutebrowser.qt import sip
|
||||
|
|
@ -1177,37 +1177,6 @@ class AbstractTab(QWidget):
|
|||
navigation.url.errorString()))
|
||||
navigation.accepted = False
|
||||
|
||||
# WORKAROUND for QtWebEngine >= 6.2 not allowing form requests from
|
||||
# qute:// to outside domains.
|
||||
needs_load_workarounds = (
|
||||
objects.backend == usertypes.Backend.QtWebEngine and
|
||||
version.qtwebengine_versions().webengine >= utils.VersionNumber(6, 2)
|
||||
)
|
||||
if (
|
||||
needs_load_workarounds and
|
||||
self.url() == QUrl("qute://start/") and
|
||||
navigation.navigation_type == navigation.Type.form_submitted and
|
||||
navigation.url.matches(
|
||||
QUrl(config.val.url.searchengines['DEFAULT']),
|
||||
urlutils.FormatOption.REMOVE_QUERY)
|
||||
):
|
||||
log.webview.debug(
|
||||
"Working around qute://start loading issue for "
|
||||
f"{navigation.url.toDisplayString()}")
|
||||
navigation.accepted = False
|
||||
self.load_url(navigation.url)
|
||||
|
||||
if (
|
||||
needs_load_workarounds and
|
||||
self.url() == QUrl("qute://bookmarks/") and
|
||||
navigation.navigation_type == navigation.Type.back_forward
|
||||
):
|
||||
log.webview.debug(
|
||||
"Working around qute://bookmarks loading issue for "
|
||||
f"{navigation.url.toDisplayString()}")
|
||||
navigation.accepted = False
|
||||
self.load_url(navigation.url)
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _on_load_finished(self, ok: bool) -> None:
|
||||
assert self._widget is not None
|
||||
|
|
|
|||
|
|
@ -71,7 +71,10 @@ class CommandDispatcher:
|
|||
|
||||
def _current_index(self):
|
||||
"""Convenience method to get the current widget index."""
|
||||
return self._tabbed_browser.widget.currentIndex()
|
||||
current_index = self._tabbed_browser.widget.currentIndex()
|
||||
if current_index == -1:
|
||||
raise cmdutils.CommandError("No WebView available yet!")
|
||||
return current_index
|
||||
|
||||
def _current_url(self):
|
||||
"""Convenience method to get the current url."""
|
||||
|
|
@ -865,10 +868,6 @@ class CommandDispatcher:
|
|||
Args:
|
||||
count: How many tabs to switch back.
|
||||
"""
|
||||
if self._count() == 0:
|
||||
# Running :tab-prev after last tab was closed
|
||||
# See https://github.com/qutebrowser/qutebrowser/issues/1448
|
||||
return
|
||||
newidx = self._current_index() - count
|
||||
if newidx >= 0:
|
||||
self._set_current_index(newidx)
|
||||
|
|
@ -885,10 +884,6 @@ class CommandDispatcher:
|
|||
Args:
|
||||
count: How many tabs to switch forward.
|
||||
"""
|
||||
if self._count() == 0:
|
||||
# Running :tab-next after last tab was closed
|
||||
# See https://github.com/qutebrowser/qutebrowser/issues/1448
|
||||
return
|
||||
newidx = self._current_index() + count
|
||||
if newidx < self._count():
|
||||
self._set_current_index(newidx)
|
||||
|
|
@ -1168,7 +1163,7 @@ class CommandDispatcher:
|
|||
if count is not None:
|
||||
env['QUTE_COUNT'] = str(count)
|
||||
|
||||
idx = self._current_index()
|
||||
idx = self._tabbed_browser.widget.currentIndex()
|
||||
if idx != -1:
|
||||
env['QUTE_TAB_INDEX'] = str(idx + 1)
|
||||
env['QUTE_TITLE'] = self._tabbed_browser.widget.page_title(idx)
|
||||
|
|
|
|||
|
|
@ -6,11 +6,12 @@
|
|||
|
||||
from qutebrowser.qt import machinery
|
||||
from qutebrowser.qt.core import QObject, QEvent, Qt, QTimer
|
||||
from qutebrowser.qt.gui import QKeyEvent
|
||||
from qutebrowser.qt.widgets import QWidget
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import log, message, usertypes, qtutils
|
||||
from qutebrowser.keyinput import modeman
|
||||
from qutebrowser.utils import log, message, usertypes, qtutils, version, utils
|
||||
from qutebrowser.keyinput import modeman, keyutils
|
||||
|
||||
|
||||
class ChildEventFilter(QObject):
|
||||
|
|
@ -37,8 +38,8 @@ class ChildEventFilter(QObject):
|
|||
if event.type() == QEvent.Type.ChildAdded:
|
||||
child = event.child()
|
||||
if not isinstance(child, QWidget):
|
||||
# Can e.g. happen when dragging text
|
||||
log.misc.debug(f"Ignoring new child {qtutils.qobj_repr(child)}")
|
||||
# Can e.g. happen when dragging text, or accessibility tree
|
||||
# nodes since Qt 6.9
|
||||
return False
|
||||
|
||||
log.misc.debug(
|
||||
|
|
@ -54,21 +55,30 @@ class ChildEventFilter(QObject):
|
|||
# - This is a child event filter on a tab (self._widget is not None)
|
||||
# - We find an old existing child which is a QQuickWidget and is
|
||||
# currently focused.
|
||||
# - We're using QtWebEngine >= 6.4 (older versions are not affected)
|
||||
# - We're using an affected QtWebEngine version
|
||||
children = [
|
||||
c for c in self._widget.findChildren(
|
||||
QWidget, "", Qt.FindChildOption.FindDirectChildrenOnly)
|
||||
if c is not child and
|
||||
c.hasFocus() and
|
||||
c.metaObject() is not None and
|
||||
c.metaObject().className() == "QQuickWidget"
|
||||
c.metaObject().className() == "QQuickWidget" # Qt 6.4+
|
||||
]
|
||||
if children:
|
||||
if children and version.qtwebengine_versions().webengine < utils.VersionNumber(6, 6, 3):
|
||||
log.misc.debug("Focusing new child")
|
||||
child.setFocus()
|
||||
|
||||
child.installEventFilter(self._filter)
|
||||
elif event.type() == QEvent.Type.ChildRemoved:
|
||||
if isinstance(event, QKeyEvent):
|
||||
# WORKAROUND for unknown (Py)Qt bug
|
||||
info = keyutils.KeyInfo.from_event(event)
|
||||
log.misc.warning(
|
||||
f"ChildEventFilter: ignoring key event {info} "
|
||||
f"on {qtutils.qobj_repr(obj)}"
|
||||
)
|
||||
return False
|
||||
|
||||
child = event.child()
|
||||
log.misc.debug(
|
||||
f"{qtutils.qobj_repr(obj)}: removed child {qtutils.qobj_repr(child)}")
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ from qutebrowser.qt.core import QUrl, QUrlQuery
|
|||
|
||||
from qutebrowser.utils import resources, javascript, jinja, standarddir, log, urlutils
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.misc import objects
|
||||
|
||||
|
||||
_SYSTEM_PATHS = [
|
||||
|
|
@ -127,7 +128,12 @@ def get_pdfjs_res_and_path(path):
|
|||
content = None
|
||||
file_path = None
|
||||
|
||||
system_paths = _SYSTEM_PATHS + [
|
||||
if 'no-system-pdfjs' in objects.debug_flags:
|
||||
system_paths = []
|
||||
else:
|
||||
system_paths = _SYSTEM_PATHS[:]
|
||||
|
||||
system_paths += [
|
||||
# fallback
|
||||
os.path.join(standarddir.data(), 'pdfjs'),
|
||||
# hardcoded fallback for --temp-basedir
|
||||
|
|
|
|||
|
|
@ -123,25 +123,24 @@ def data_for_url(url: QUrl) -> tuple[str, bytes]:
|
|||
|
||||
path = url.path()
|
||||
host = url.host()
|
||||
query = url.query()
|
||||
# A url like "qute:foo" is split as "scheme:path", not "scheme:host".
|
||||
log.misc.debug("url: {}, path: {}, host {}".format(
|
||||
url.toDisplayString(), path, host))
|
||||
if not path or not host:
|
||||
new_url = QUrl()
|
||||
new_url.setScheme('qute')
|
||||
# When path is absent, e.g. qute://help (with no trailing slash)
|
||||
if host:
|
||||
new_url.setHost(host)
|
||||
# When host is absent, e.g. qute:help
|
||||
else:
|
||||
new_url.setHost(path)
|
||||
|
||||
if not host:
|
||||
# Redirect qute:help -> qute://help/
|
||||
new_url = QUrl(url)
|
||||
new_url.setHost(path)
|
||||
new_url.setPath('/')
|
||||
if query:
|
||||
new_url.setQuery(query)
|
||||
if new_url.host(): # path was a valid host
|
||||
raise Redirect(new_url)
|
||||
if not new_url.host(): # Valid path but not valid host
|
||||
raise UrlInvalidError(f"Invalid host (from path): {path!r}")
|
||||
raise Redirect(new_url)
|
||||
|
||||
if not path:
|
||||
# Redirect qute://help -> qute://help/
|
||||
new_url = QUrl(url)
|
||||
new_url.setPath('/')
|
||||
raise Redirect(new_url)
|
||||
|
||||
try:
|
||||
handler = _HANDLERS[host]
|
||||
|
|
|
|||
|
|
@ -286,7 +286,10 @@ class NotificationBridgePresenter(QObject):
|
|||
|
||||
if replaces_id is None:
|
||||
if notification_id in self._active_notifications:
|
||||
raise Error(f"Got duplicate id {notification_id}")
|
||||
message.error(f"Got duplicate notification id {notification_id} "
|
||||
f"from {self._adapter.NAME}")
|
||||
self._drop_adapter()
|
||||
return
|
||||
|
||||
qt_notification.show()
|
||||
self._active_notifications[notification_id] = qt_notification
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ from qutebrowser.config import config, websettings
|
|||
from qutebrowser.config.websettings import AttributeInfo as Attr
|
||||
from qutebrowser.misc import pakjoy
|
||||
from qutebrowser.utils import (standarddir, qtutils, message, log,
|
||||
urlmatch, usertypes, objreg, version)
|
||||
urlmatch, usertypes, objreg, version, utils)
|
||||
if TYPE_CHECKING:
|
||||
from qutebrowser.browser.webengine import interceptor
|
||||
|
||||
|
|
@ -378,6 +378,12 @@ def _update_settings(option):
|
|||
def _init_user_agent_str(ua):
|
||||
global parsed_user_agent
|
||||
parsed_user_agent = websettings.UserAgent.parse(ua)
|
||||
if parsed_user_agent.upstream_browser_version.endswith(".0.0.0"):
|
||||
# https://codereview.qt-project.org/c/qt/qtwebengine/+/616314
|
||||
# but we still want the full version available to users if they want it.
|
||||
qtwe_versions = version.qtwebengine_versions()
|
||||
assert qtwe_versions.chromium is not None
|
||||
parsed_user_agent.upstream_browser_version = qtwe_versions.chromium
|
||||
|
||||
|
||||
def init_user_agent():
|
||||
|
|
@ -411,6 +417,37 @@ def _init_profile(profile: QWebEngineProfile) -> None:
|
|||
lambda url: profile.clearVisitedLinks([url]))
|
||||
|
||||
_global_settings.init_settings()
|
||||
_maybe_disable_hangouts_extension(profile)
|
||||
|
||||
|
||||
def _maybe_disable_hangouts_extension(profile: QWebEngineProfile) -> None:
|
||||
"""Disable the Hangouts extension for Qt 6.10+."""
|
||||
if not config.val.qt.workarounds.disable_hangouts_extension:
|
||||
return
|
||||
|
||||
if machinery.IS_QT6: # mypy
|
||||
try:
|
||||
ext_manager = profile.extensionManager()
|
||||
except AttributeError:
|
||||
return # added in QtWebEngine 6.10
|
||||
|
||||
qtwe_versions = version.qtwebengine_versions(avoid_init=True)
|
||||
if (
|
||||
qtwe_versions.webengine == utils.VersionNumber(6, 10, 1)
|
||||
and profile.isOffTheRecord()
|
||||
):
|
||||
# WORKAROUND for https://github.com/qutebrowser/qutebrowser/issues/8785
|
||||
log.misc.warning(
|
||||
"Not disabling Hangouts extension on private profile to avoid "
|
||||
"QtWebEngine crash with Qt 6.10.1")
|
||||
return
|
||||
|
||||
assert ext_manager is not None # mypy
|
||||
for info in ext_manager.extensions():
|
||||
if info.id() == pakjoy.HANGOUTS_EXT_ID:
|
||||
log.misc.debug(f"Disabling extension: {info.name()}")
|
||||
# setExtensionEnabled(info, False) seems to segfault
|
||||
ext_manager.unloadExtension(info)
|
||||
|
||||
|
||||
def _clear_webengine_permissions_json():
|
||||
|
|
@ -432,14 +469,18 @@ def _clear_webengine_permissions_json():
|
|||
)
|
||||
|
||||
|
||||
def default_qt_profile() -> QWebEngineProfile:
|
||||
"""Get the default profile from Qt."""
|
||||
if machinery.IS_QT6:
|
||||
return QWebEngineProfile("Default")
|
||||
else:
|
||||
return QWebEngineProfile.defaultProfile()
|
||||
|
||||
|
||||
def _init_default_profile():
|
||||
"""Init the default QWebEngineProfile."""
|
||||
global default_profile
|
||||
|
||||
if machinery.IS_QT6:
|
||||
default_profile = QWebEngineProfile("Default")
|
||||
else:
|
||||
default_profile = QWebEngineProfile.defaultProfile()
|
||||
default_profile = default_qt_profile()
|
||||
assert not default_profile.isOffTheRecord()
|
||||
|
||||
assert parsed_user_agent is None # avoid earlier profile initialization
|
||||
|
|
@ -447,8 +488,19 @@ def _init_default_profile():
|
|||
|
||||
init_user_agent()
|
||||
ua_version = version.qtwebengine_versions()
|
||||
|
||||
logger = log.init.warning
|
||||
if machinery.IS_QT5:
|
||||
# With Qt 5.15, we can't quite be sure about which QtWebEngine patch version
|
||||
# we're getting, as ELF parsing might be broken and there's no other way.
|
||||
# For most of the code, we don't really care about the patch version though.
|
||||
assert (
|
||||
non_ua_version.webengine.strip_patch() == ua_version.webengine.strip_patch()
|
||||
), (non_ua_version, ua_version)
|
||||
logger = log.init.debug
|
||||
|
||||
if ua_version.webengine != non_ua_version.webengine:
|
||||
log.init.warning(
|
||||
logger(
|
||||
"QtWebEngine version mismatch - unexpected behavior might occur, "
|
||||
"please open a bug about this.\n"
|
||||
f" Early version: {non_ua_version}\n"
|
||||
|
|
@ -487,13 +539,23 @@ def _init_site_specific_quirks():
|
|||
# default_ua = ("Mozilla/5.0 ({os_info}) "
|
||||
# "AppleWebKit/{webkit_version} (KHTML, like Gecko) "
|
||||
# "{qt_key}/{qt_version} "
|
||||
# "{upstream_browser_key}/{upstream_browser_version} "
|
||||
# "{upstream_browser_key}/{upstream_browser_version_short} "
|
||||
# "Safari/{webkit_version}")
|
||||
no_qtwe_ua = ("Mozilla/5.0 ({os_info}) "
|
||||
"AppleWebKit/{webkit_version} (KHTML, like Gecko) "
|
||||
"{upstream_browser_key}/{upstream_browser_version} "
|
||||
"Safari/{webkit_version}")
|
||||
firefox_ua = "Mozilla/5.0 ({os_info}; rv:133.0) Gecko/20100101 Firefox/133.0"
|
||||
firefox_ua = "Mozilla/5.0 ({os_info}; rv:145.0) Gecko/20100101 Firefox/145.0"
|
||||
|
||||
# Needed for gitlab.gnome.org which blocks old Chromium versions outright,
|
||||
# except when QtWebEngine/... is in the UA.
|
||||
#
|
||||
# We could further modify the UA to just "qutebrowser" or something so we don't get
|
||||
# Anubis at all, but it looks like their Anubis triggers to more than just
|
||||
# Mozilla/5.0 (also AppleWebKit/... and Chromium/... possibly?), so at that point
|
||||
# I'm not sure if we can strip down the UA so much without breaking
|
||||
# something in GitLab as well.
|
||||
not_mozilla_ua = (
|
||||
"Mozilla/5.0 ({os_info}) AppleWebKit/{webkit_version} (KHTML, like Gecko) "
|
||||
"{qt_key}/{qt_version} {upstream_browser_key}/{upstream_browser_version_short} "
|
||||
"Safari/{webkit_version}"
|
||||
)
|
||||
|
||||
def maybe_newer_chrome_ua(at_least_version):
|
||||
"""Return a new UA if our current chrome version isn't at least at_least_version."""
|
||||
|
|
@ -508,23 +570,14 @@ def _init_site_specific_quirks():
|
|||
"Safari/537.36"
|
||||
)
|
||||
|
||||
user_agents = [
|
||||
# Needed to avoid a ""WhatsApp works with Google Chrome 36+" error
|
||||
# page which doesn't allow to use WhatsApp Web at all. Also see the
|
||||
# additional JS quirk: qutebrowser/javascript/quirks/whatsapp_web.user.js
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/4445
|
||||
("ua-whatsapp", 'https://web.whatsapp.com/', no_qtwe_ua),
|
||||
utils.unused(maybe_newer_chrome_ua)
|
||||
|
||||
user_agents = [
|
||||
# Needed to avoid a "you're using a browser [...] that doesn't allow us
|
||||
# to keep your account secure" error.
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/5182
|
||||
("ua-google", 'https://accounts.google.com/*', firefox_ua),
|
||||
|
||||
# Needed because Slack adds an error which prevents using it relatively
|
||||
# aggressively, despite things actually working fine.
|
||||
# October 2023: Slack claims they only support 112+. On #7951 at least
|
||||
# one user claims it still works fine on 108 based Qt versions.
|
||||
("ua-slack", 'https://*.slack.com/*', maybe_newer_chrome_ua(112)),
|
||||
("ua-google", "https://accounts.google.com/*", firefox_ua),
|
||||
("ua-gnome-gitlab", "https://gitlab.gnome.org/*", not_mozilla_ua),
|
||||
]
|
||||
|
||||
for name, pattern, ua in user_agents:
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import html as html_utils
|
|||
from typing import cast, Union, Optional
|
||||
|
||||
from qutebrowser.qt.core import (pyqtSignal, pyqtSlot, Qt, QPoint, QPointF, QUrl,
|
||||
QObject, QByteArray)
|
||||
QObject, QByteArray, QTimer)
|
||||
from qutebrowser.qt.network import QAuthenticator
|
||||
from qutebrowser.qt.webenginecore import QWebEnginePage, QWebEngineScript, QWebEngineHistory
|
||||
|
||||
|
|
@ -626,7 +626,14 @@ class WebEngineHistoryPrivate(browsertab.AbstractHistoryPrivate):
|
|||
return data
|
||||
|
||||
def deserialize(self, data):
|
||||
qtutils.deserialize(data, self._history)
|
||||
try:
|
||||
qtutils.deserialize(data, self._history)
|
||||
except OSError:
|
||||
dump = "\n".join(
|
||||
bytes(line).hex(" ") for line in utils.chunk(bytes(data), 16)
|
||||
)
|
||||
log.webview.debug(f"Failed to deserialize history data:\n{dump}")
|
||||
raise
|
||||
|
||||
def _load_items_workaround(self, items):
|
||||
"""WORKAROUND for session loading not working on Qt 5.15.
|
||||
|
|
@ -933,6 +940,10 @@ class _WebEnginePermissions(QObject):
|
|||
notif = miscwidgets.FullscreenNotification(self._widget)
|
||||
notif.set_timeout(timeout)
|
||||
notif.show()
|
||||
# Restore keyboard focus to the tab. Setting a NoFocus policy
|
||||
# for FullscreenNotification doesn't seem to work.
|
||||
if self._widget.isVisible():
|
||||
self._widget.setFocus()
|
||||
|
||||
@pyqtSlot(QUrl, 'QWebEnginePage::Feature')
|
||||
def _on_feature_permission_requested(self, url, feature):
|
||||
|
|
@ -1363,6 +1374,11 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
self._widget.page().toHtml(callback)
|
||||
|
||||
def run_js_async(self, code, callback=None, *, world=None):
|
||||
if sip.isdeleted(self._widget):
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/3895
|
||||
log.misc.debug("run_js_async called on deleted tab")
|
||||
return
|
||||
|
||||
world_id_type = Union[QWebEngineScript.ScriptWorldId, int]
|
||||
if world is None:
|
||||
world_id: world_id_type = QWebEngineScript.ScriptWorldId.ApplicationWorld
|
||||
|
|
@ -1607,6 +1623,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
def _on_navigation_request(self, navigation):
|
||||
super()._on_navigation_request(navigation)
|
||||
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-103778
|
||||
local_schemes = {"qute", "file"}
|
||||
qtwe_ver = version.qtwebengine_versions().webengine
|
||||
if (
|
||||
|
|
@ -1619,7 +1636,6 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
(utils.VersionNumber(6, 2) <= qtwe_ver < utils.VersionNumber(6, 2, 5) or
|
||||
utils.VersionNumber(6, 3) <= qtwe_ver < utils.VersionNumber(6, 3, 1))
|
||||
):
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-103778
|
||||
log.webview.debug(
|
||||
"Working around blocked request from local page "
|
||||
f"{self.url().toDisplayString()}"
|
||||
|
|
@ -1627,6 +1643,51 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
navigation.accepted = False
|
||||
self.load_url(navigation.url)
|
||||
|
||||
# WORKAROUND for QtWebEngine >= 6.2 not allowing form requests from
|
||||
# qute:// to outside domains.
|
||||
if (
|
||||
qtwe_ver >= utils.VersionNumber(6, 2) and
|
||||
self.url() == QUrl("qute://start/") and
|
||||
navigation.navigation_type == navigation.Type.form_submitted and
|
||||
navigation.url.matches(
|
||||
QUrl(config.val.url.searchengines['DEFAULT']),
|
||||
urlutils.FormatOption.REMOVE_QUERY)
|
||||
):
|
||||
log.webview.debug(
|
||||
"Working around qute://start loading issue for "
|
||||
f"{navigation.url.toDisplayString()}")
|
||||
navigation.accepted = False
|
||||
# Using QTimer.singleShot as WORKAROUND for this crashing otherwise
|
||||
# with QtWebEngine 6.10: https://bugreports.qt.io/browse/QTBUG-140543
|
||||
QTimer.singleShot(0, functools.partial(self.load_url, navigation.url))
|
||||
|
||||
# WORKAROUND for QtWebEngine 6.2 - 6.5 blocking back/forward navigation too
|
||||
if (
|
||||
utils.VersionNumber(6, 6) > qtwe_ver >= utils.VersionNumber(6, 2) and
|
||||
self.url() == QUrl("qute://bookmarks/") and
|
||||
navigation.navigation_type == navigation.Type.back_forward
|
||||
):
|
||||
log.webview.debug(
|
||||
"Working around qute://bookmarks loading issue for "
|
||||
f"{navigation.url.toDisplayString()}")
|
||||
navigation.accepted = False
|
||||
self.load_url(navigation.url)
|
||||
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-140515
|
||||
ua_setting = "content.headers.user_agent"
|
||||
if (
|
||||
navigation.accepted
|
||||
and config.instance.get(ua_setting, navigation.url, fallback=False)
|
||||
is not usertypes.UNSET
|
||||
and navigation.navigation_type == usertypes.NavigationRequest.Type.redirect
|
||||
and navigation.is_main_frame
|
||||
and utils.VersionNumber(6, 5) <= qtwe_ver < utils.VersionNumber(6, 10, 1)
|
||||
):
|
||||
navigation.accepted = False
|
||||
# Using QTimer.singleShot as WORKAROUND for this crashing otherwise
|
||||
# with QtWebEngine 6.10: https://bugreports.qt.io/browse/QTBUG-140543
|
||||
QTimer.singleShot(0, functools.partial(self.load_url, navigation.url))
|
||||
|
||||
if not navigation.accepted or not navigation.is_main_frame:
|
||||
return
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
"""A model that proxies access to one or more completion categories."""
|
||||
|
||||
from typing import overload, Optional, Any, cast
|
||||
from typing import overload, Optional, Any
|
||||
from collections.abc import MutableSequence
|
||||
|
||||
from qutebrowser.qt import machinery
|
||||
|
|
@ -91,14 +91,14 @@ class CompletionModel(QAbstractItemModel):
|
|||
Return: The item flags, or Qt.ItemFlag.NoItemFlags on error.
|
||||
"""
|
||||
if not index.isValid():
|
||||
return cast(_FlagType, Qt.ItemFlag.NoItemFlags)
|
||||
return qtutils.maybe_cast(_FlagType, machinery.IS_QT5, Qt.ItemFlag.NoItemFlags)
|
||||
if index.parent().isValid():
|
||||
# item
|
||||
return (Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable |
|
||||
Qt.ItemFlag.ItemNeverHasChildren)
|
||||
else:
|
||||
# category
|
||||
return cast(_FlagType, Qt.ItemFlag.NoItemFlags)
|
||||
return qtutils.maybe_cast(_FlagType, machinery.IS_QT5, Qt.ItemFlag.NoItemFlags)
|
||||
|
||||
def index(self, row: int, col: int, parent: QModelIndex = QModelIndex()) -> QModelIndex:
|
||||
"""Get an index into the model.
|
||||
|
|
|
|||
|
|
@ -333,8 +333,13 @@ class Config(QObject):
|
|||
pattern, hide_userconfig=hide_userconfig)
|
||||
|
||||
self.changed.emit(opt.name)
|
||||
log.config.debug("Config option changed: {} = {}".format(
|
||||
opt.name, value))
|
||||
|
||||
if pattern is not None:
|
||||
log.config.debug("Config option changed: {} = {} for {}".format(
|
||||
opt.name, value, pattern))
|
||||
else:
|
||||
log.config.debug("Config option changed: {} = {}".format(
|
||||
opt.name, value))
|
||||
|
||||
def _check_yaml(self, opt: 'configdata.Option', save_yaml: bool) -> None:
|
||||
"""Make sure the given option may be set in autoconfig.yml."""
|
||||
|
|
|
|||
|
|
@ -356,9 +356,8 @@ class ConfigCommands:
|
|||
raise cmdutils.CommandError(":config-list-remove can only be used "
|
||||
"for lists")
|
||||
|
||||
converted = opt.typ.valtype.from_str(value)
|
||||
|
||||
with self._handle_config_error():
|
||||
converted = opt.typ.valtype.from_str(value)
|
||||
option_value = self._config.get_mutable_obj(option)
|
||||
|
||||
if converted not in option_value:
|
||||
|
|
|
|||
|
|
@ -391,7 +391,7 @@ qt.workarounds.disable_accelerated_2d_canvas:
|
|||
name: String
|
||||
valid_values:
|
||||
- always: Disable accelerated 2d canvas
|
||||
- auto: Disable on Qt6 < 6.6.0, enable otherwise
|
||||
- auto: Disable on Qt versions with known issues, enable otherwise
|
||||
- never: Enable accelerated 2d canvas
|
||||
default: auto
|
||||
backend: QtWebEngine
|
||||
|
|
@ -422,6 +422,19 @@ qt.workarounds.disable_hangouts_extension:
|
|||
disabled to avoid crashes on Qt 6.5.0 to 6.5.3 if dark mode is enabled,
|
||||
as well as on Qt 6.6.0.
|
||||
|
||||
qt.workarounds.disable_accessibility:
|
||||
type:
|
||||
name: String
|
||||
valid_values:
|
||||
- always: Disable renderer accessibility
|
||||
- auto: Disable on Qt versions with known issues, enable otherwise
|
||||
- never: Enable renderer accessibility
|
||||
default: auto
|
||||
backend: QtWebEngine
|
||||
restart: true
|
||||
desc: >-
|
||||
Disable accessibility to avoid crashes on Qt 6.10.1.
|
||||
|
||||
## auto_save
|
||||
|
||||
auto_save.interval:
|
||||
|
|
@ -656,10 +669,9 @@ content.site_specific_quirks.skip:
|
|||
type:
|
||||
name: FlagList
|
||||
valid_values:
|
||||
- ua-whatsapp
|
||||
- ua-google
|
||||
- ua-slack
|
||||
- ua-googledocs
|
||||
- ua-gnome-gitlab
|
||||
- js-whatsapp-web
|
||||
- js-discord
|
||||
- js-string-replaceall
|
||||
|
|
@ -752,7 +764,7 @@ content.headers.referer:
|
|||
content.headers.user_agent:
|
||||
default: 'Mozilla/5.0 ({os_info})
|
||||
AppleWebKit/{webkit_version} (KHTML, like Gecko)
|
||||
{qt_key}/{qt_version} {upstream_browser_key}/{upstream_browser_version}
|
||||
{upstream_browser_key}/{upstream_browser_version_short}
|
||||
Safari/{webkit_version}'
|
||||
type:
|
||||
name: FormatString
|
||||
|
|
@ -764,6 +776,7 @@ content.headers.user_agent:
|
|||
- qt_version
|
||||
- upstream_browser_key
|
||||
- upstream_browser_version
|
||||
- upstream_browser_version_short
|
||||
- qutebrowser_version
|
||||
completions:
|
||||
# See https://techblog.willshouse.com/2012/01/03/most-common-user-agents/
|
||||
|
|
@ -773,14 +786,14 @@ content.headers.user_agent:
|
|||
# Vim-protip: Place your cursor below this comment and run
|
||||
# :r!python scripts/dev/ua_fetch.py
|
||||
- - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36
|
||||
(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
- Chrome 131 macOS
|
||||
(KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36"
|
||||
- Chrome 142 macOS
|
||||
- - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,
|
||||
like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
- Chrome 131 Win10
|
||||
like Gecko) Chrome/142.0.0.0 Safari/537.36"
|
||||
- Chrome 142 Win10
|
||||
- - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like
|
||||
Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
- Chrome 131 Linux
|
||||
Gecko) Chrome/142.0.0.0 Safari/537.36"
|
||||
- Chrome 142 Linux
|
||||
supports_pattern: true
|
||||
desc: |
|
||||
User agent to send.
|
||||
|
|
@ -795,14 +808,15 @@ content.headers.user_agent:
|
|||
* `{upstream_browser_key}`: "Version" for QtWebKit, "Chrome" for
|
||||
QtWebEngine.
|
||||
* `{upstream_browser_version}`: The corresponding Safari/Chrome version.
|
||||
* `{upstream_browser_version_short}`: The corresponding Safari/Chrome
|
||||
version, but only with its major version.
|
||||
* `{qutebrowser_version}`: The currently running qutebrowser version.
|
||||
|
||||
The default value is equal to the unchanged user agent of
|
||||
QtWebKit/QtWebEngine.
|
||||
The default value is equal to the default user agent of
|
||||
QtWebKit/QtWebEngine, but with the `QtWebEngine/...` part removed for
|
||||
increased compatibility.
|
||||
|
||||
Note that the value read from JavaScript is always the global value. With
|
||||
QtWebEngine between 5.12 and 5.14 (inclusive), changing the value exposed
|
||||
to JavaScript requires a restart.
|
||||
Note that the value read from JavaScript is always the global value.
|
||||
|
||||
content.host_blocking.enabled:
|
||||
renamed: content.blocking.enabled
|
||||
|
|
@ -1602,6 +1616,7 @@ fileselect.single_file.command:
|
|||
- ['["xterm", "-e", "nnn", "-p", "{}"]', "nnn in xterm"]
|
||||
- ['["xterm", "-e", "fff", "-p", "{}"]', "fff in xterm"]
|
||||
- ['["xterm", "-e", "lf", "-selection-path", "{}"]', "lf in xterm"]
|
||||
- ['["xterm", "-e", "yazi", "--chooser-file", "{}"]', "yazi in xterm"]
|
||||
default: ['xterm', '-e', 'ranger', '--choosefile={}']
|
||||
desc: >-
|
||||
Command (and arguments) to use for selecting a single file in forms.
|
||||
|
|
@ -1622,6 +1637,7 @@ fileselect.multiple_files.command:
|
|||
- ['["xterm", "-e", "nnn", "-p", "{}"]', "nnn in xterm"]
|
||||
- ['["xterm", "-e", "fff", "-p", "{}"]', "fff in xterm"]
|
||||
- ['["xterm", "-e", "lf", "-selection-path", "{}"]', "lf in xterm"]
|
||||
- ['["xterm", "-e", "yazi", "--chooser-file", "{}"]', "yazi in xterm"]
|
||||
default: ['xterm', '-e', 'ranger', '--choosefiles={}']
|
||||
desc: >-
|
||||
Command (and arguments) to use for selecting multiple files in forms.
|
||||
|
|
@ -1641,6 +1657,7 @@ fileselect.folder.command:
|
|||
- ['["xterm", "-e", "ranger", "--choosedir={}"]', "Ranger in xterm"]
|
||||
- ['["xterm", "-e", "vifm", "--choose-dir", "{}"]', "vifm in xterm"]
|
||||
- ['["xterm", "-e", "nnn", "-p", "{}"]', "nnn in xterm"]
|
||||
- ['["xterm", "-e", "yazi", "--cwd-file", "{}"]', "yazi in xterm"]
|
||||
default: ['xterm', '-e', 'ranger', '--choosedir={}']
|
||||
desc: >-
|
||||
Command (and arguments) to use for selecting a single folder in forms.
|
||||
|
|
|
|||
|
|
@ -183,9 +183,9 @@ class StateConfig(configparser.ConfigParser):
|
|||
return
|
||||
|
||||
old_chromium_version_str = self['general'].get('chromium_version', None)
|
||||
if old_chromium_version_str in ['no', None]:
|
||||
if old_chromium_version_str == "no" or old_chromium_version_str is None:
|
||||
old_qtwe_version = self['general'].get('qtwe_version', None)
|
||||
if old_qtwe_version in ['no', None]:
|
||||
if old_qtwe_version == "no" or old_qtwe_version is None:
|
||||
return
|
||||
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ def qt_args(namespace: argparse.Namespace) -> list[str]:
|
|||
return argv
|
||||
|
||||
|
||||
def _qtwebengine_features(
|
||||
def _qtwebengine_features( # noqa: C901
|
||||
versions: version.WebEngineVersions,
|
||||
special_flags: Sequence[str],
|
||||
) -> tuple[Sequence[str], Sequence[str]]:
|
||||
|
|
@ -154,6 +154,16 @@ def _qtwebengine_features(
|
|||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-89740
|
||||
disabled_features.append('InstalledApp')
|
||||
|
||||
if versions.webengine >= utils.VersionNumber(6, 7):
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-132681
|
||||
# TODO adjust if fixed in Qt 6.9.2+
|
||||
disabled_features.append('DocumentPictureInPictureAPI')
|
||||
|
||||
if utils.VersionNumber(6, 9) <= versions.webengine < utils.VersionNumber(6, 10, 1):
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-135787
|
||||
# and https://bugreports.qt.io/browse/QTBUG-141096
|
||||
disabled_features.append('PermissionElement')
|
||||
|
||||
if not config.val.input.media_keys:
|
||||
disabled_features.append('HardwareMediaKeyHandling')
|
||||
|
||||
|
|
@ -171,7 +181,7 @@ def _get_pak_name(locale_name: str) -> str:
|
|||
Based on Chromium's behavior in l10n_util::CheckAndResolveLocale:
|
||||
https://source.chromium.org/chromium/chromium/src/+/master:ui/base/l10n/l10n_util.cc;l=344-428;drc=43d5378f7f363dab9271ca37774c71176c9e7b69
|
||||
"""
|
||||
if locale_name in {'en', 'en-PH', 'en-LR'}:
|
||||
if locale_name in {'en', 'en-POSIX', 'en-PH', 'en-LR'}:
|
||||
return 'en-US'
|
||||
elif locale_name.startswith('en-'):
|
||||
return 'en-GB'
|
||||
|
|
@ -346,7 +356,21 @@ _WEBENGINE_SETTINGS: dict[str, dict[Any, Optional[_SettingValueType]]] = {
|
|||
'qt.workarounds.disable_accelerated_2d_canvas': {
|
||||
'always': '--disable-accelerated-2d-canvas',
|
||||
'never': None,
|
||||
'auto': lambda _versions: '--disable-accelerated-2d-canvas' if machinery.IS_QT6 else None,
|
||||
'auto': lambda versions: '--disable-accelerated-2d-canvas'
|
||||
if machinery.IS_QT6
|
||||
and versions.webengine
|
||||
and versions.webengine < utils.VersionNumber(6, 8, 2)
|
||||
else None,
|
||||
},
|
||||
'qt.workarounds.disable_accessibility': {
|
||||
'always': '--disable-renderer-accessibility',
|
||||
'never': None,
|
||||
# WORKAROUND for https://qt-project.atlassian.net/browse/QTBUG-142320
|
||||
'auto': lambda versions: '--disable-renderer-accessibility'
|
||||
if machinery.IS_QT6
|
||||
and versions.webengine
|
||||
and versions.webengine == utils.VersionNumber(6, 10, 1)
|
||||
else None,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,13 @@ class UserAgent:
|
|||
qt_key: str
|
||||
qt_version: Optional[str]
|
||||
|
||||
@property
|
||||
def upstream_browser_version_short(self) -> str:
|
||||
"""Return a shortened version of the upstream browser version."""
|
||||
major, *rest = self.upstream_browser_version.split('.')
|
||||
shortened = [major] + ["0"] * (len(rest))
|
||||
return ".".join(shortened)
|
||||
|
||||
@classmethod
|
||||
def parse(cls, ua: str) -> 'UserAgent':
|
||||
"""Parse a user agent string into its components."""
|
||||
|
|
@ -207,6 +214,7 @@ def _format_user_agent(template: str, backend: usertypes.Backend) -> str:
|
|||
qt_version=qVersion(),
|
||||
upstream_browser_key=parsed.upstream_browser_key,
|
||||
upstream_browser_version=parsed.upstream_browser_version,
|
||||
upstream_browser_version_short=parsed.upstream_browser_version_short,
|
||||
qutebrowser_version=qutebrowser.__version__,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ li {
|
|||
the required packages for pdf.js are also installed.
|
||||
<br/>
|
||||
The package is named
|
||||
<a href="https://archlinux.org/packages/community/any/pdfjs/"><b>pdfjs</b></a> on Archlinux
|
||||
<a href="https://archlinux.org/packages/extra/any/pdfjs-legacy/"><b>pdfjs-legacy</b></a> on Archlinux
|
||||
and <a href="https://packages.debian.org/bullseye/libjs-pdf"><b>libjs-pdf</b></a> on Debian.
|
||||
</li>
|
||||
|
||||
|
|
|
|||
|
|
@ -19,4 +19,16 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||
return { promise, resolve, reject }
|
||||
}
|
||||
}
|
||||
|
||||
// Chromium 126 / QtWebEngine 6.9
|
||||
// https://caniuse.com/mdn-api_url_parse_static
|
||||
if (typeof URL.parse === "undefined") {
|
||||
URL.parse = function(url, base) {
|
||||
try {
|
||||
return new URL(url, base);
|
||||
} catch (ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -191,36 +191,51 @@ window._qutebrowser.webelem = (function() {
|
|||
}
|
||||
}
|
||||
|
||||
funcs.find_css = (selector, only_visible) => {
|
||||
let elems;
|
||||
// Recursively finds elements from DOM that have a shadowRoot
|
||||
// and returns the shadow roots in a list
|
||||
function find_shadow_roots(container = document) {
|
||||
const roots = [];
|
||||
|
||||
try {
|
||||
elems = document.querySelectorAll(selector);
|
||||
} catch (ex) {
|
||||
return {"success": false, "error": ex.toString()};
|
||||
}
|
||||
|
||||
const subelem_frames = window.frames;
|
||||
const out = [];
|
||||
|
||||
for (let i = 0; i < elems.length; ++i) {
|
||||
if (!only_visible || is_visible(elems[i])) {
|
||||
out.push(serialize_elem(elems[i]));
|
||||
for (const elem of container.querySelectorAll("*")) {
|
||||
if (elem.shadowRoot) {
|
||||
roots.push(elem.shadowRoot, ...find_shadow_roots(elem.shadowRoot));
|
||||
}
|
||||
}
|
||||
|
||||
// Recurse into frames and add them
|
||||
for (let i = 0; i < subelem_frames.length; i++) {
|
||||
if (iframe_same_domain(subelem_frames[i])) {
|
||||
const frame = subelem_frames[i];
|
||||
const subelems = frame.document.
|
||||
querySelectorAll(selector);
|
||||
for (let elem_num = 0; elem_num < subelems.length; ++elem_num) {
|
||||
if (!only_visible ||
|
||||
is_visible(subelems[elem_num], frame)) {
|
||||
out.push(serialize_elem(subelems[elem_num], frame));
|
||||
}
|
||||
return roots;
|
||||
}
|
||||
|
||||
funcs.find_css = (selector, only_visible) => {
|
||||
// Find all places where we need to look for elements:
|
||||
const containers = [[document, null]];
|
||||
// Same-domain iframes
|
||||
for (const frame of Array.from(window.frames)) {
|
||||
if (iframe_same_domain(frame)) {
|
||||
containers.push([frame.document, frame]);
|
||||
}
|
||||
}
|
||||
// Open shadow roots
|
||||
for (const root of find_shadow_roots()) {
|
||||
containers.push([root, null]);
|
||||
}
|
||||
|
||||
// Then find elements in all of them
|
||||
const elems = [];
|
||||
for (const [container, frame] of containers) {
|
||||
try {
|
||||
for (const elem of container.querySelectorAll(selector)) {
|
||||
elems.push([elem, frame]);
|
||||
}
|
||||
} catch (ex) {
|
||||
return {"success": false, "error": ex.toString()};
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, filter by visibility
|
||||
const out = [];
|
||||
for (const [elem, frame] of elems) {
|
||||
if (!only_visible || is_visible(elem, frame)) {
|
||||
out.push(serialize_elem(elem, frame));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -562,7 +562,7 @@ class MainWindow(QWidget):
|
|||
self._completion.on_clear_completion_selection)
|
||||
self.status.cmd.hide_completion.connect(
|
||||
self._completion.hide)
|
||||
self.status.cmd.hide_cmd.connect(self.tabbed_browser.on_release_focus)
|
||||
self.status.release_focus.connect(self.tabbed_browser.on_release_focus)
|
||||
|
||||
def _set_decoration(self, hidden):
|
||||
"""Set the visibility of the window decoration via Qt."""
|
||||
|
|
|
|||
|
|
@ -342,9 +342,9 @@ class PromptContainer(QWidget):
|
|||
"""Leave KEY_MODE whenever a prompt is aborted."""
|
||||
try:
|
||||
modeman.leave(self._win_id, key_mode, 'aborted', maybe=True)
|
||||
except objreg.RegistryUnavailableError:
|
||||
except (objreg.RegistryUnavailableError, RuntimeError):
|
||||
# window was deleted: ignore
|
||||
pass
|
||||
log.prompt.debug(f"Ignoring leaving {key_mode} as window was deleted")
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def _on_prompt_done(self, key_mode):
|
||||
|
|
@ -654,6 +654,12 @@ class FilenamePrompt(_BasePrompt):
|
|||
|
||||
"""A prompt for a filename."""
|
||||
|
||||
# Note: This *must* be a class variable! If it's not, for unknown reasons,
|
||||
# we get a segfault in Qt/PyQt in QFileInfoGatherer::getInfo() if we have
|
||||
# nested download prompts (i.e. trigger a download while a download prompt
|
||||
# is open already).
|
||||
_null_icon_provider = NullIconProvider()
|
||||
|
||||
def __init__(self, question, parent=None):
|
||||
super().__init__(question, parent)
|
||||
self._init_texts(question)
|
||||
|
|
@ -753,7 +759,7 @@ class FilenamePrompt(_BasePrompt):
|
|||
self._file_model = QFileSystemModel(self)
|
||||
|
||||
# avoid icon and mime type lookups, they are slow in Qt6
|
||||
self._file_model.setIconProvider(NullIconProvider())
|
||||
self._file_model.setIconProvider(self._null_icon_provider)
|
||||
|
||||
self._file_view.setModel(self._file_model)
|
||||
self._file_view.clicked.connect(self._insert_path)
|
||||
|
|
|
|||
|
|
@ -140,10 +140,12 @@ class StatusBar(QWidget):
|
|||
moved: Emitted when the statusbar has moved, so the completion widget
|
||||
can move to the right position.
|
||||
arg: The new position.
|
||||
release_focus: Emitted just before the statusbar is hidden.
|
||||
"""
|
||||
|
||||
resized = pyqtSignal('QRect')
|
||||
moved = pyqtSignal('QPoint')
|
||||
release_focus = pyqtSignal()
|
||||
|
||||
STYLESHEET = _generate_stylesheet()
|
||||
|
||||
|
|
@ -365,6 +367,7 @@ class StatusBar(QWidget):
|
|||
def _hide_cmd_widget(self):
|
||||
"""Show temporary text instead of command widget."""
|
||||
log.statusbar.debug("Hiding cmd widget")
|
||||
self.release_focus.emit()
|
||||
self._stack.setCurrentWidget(self.txt)
|
||||
self.maybe_hide()
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
"""The main tabbed browser widget."""
|
||||
|
||||
import os
|
||||
import signal
|
||||
import collections
|
||||
import functools
|
||||
import weakref
|
||||
|
|
@ -1008,7 +1010,18 @@ class TabbedBrowser(QWidget):
|
|||
browsertab.TerminationStatus.killed: "Renderer process was killed",
|
||||
browsertab.TerminationStatus.unknown: "Renderer process did not start",
|
||||
}
|
||||
msg = messages[status] + f" (status {code})"
|
||||
|
||||
sig = None
|
||||
try:
|
||||
if os.WIFSIGNALED(code):
|
||||
sig = signal.Signals(os.WTERMSIG(code))
|
||||
except (AttributeError, ValueError):
|
||||
pass
|
||||
|
||||
if sig is not None:
|
||||
msg = messages[status] + f" (status {code}: {sig.name})"
|
||||
else:
|
||||
msg = messages[status] + f" (status {code})"
|
||||
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-91715
|
||||
versions = version.qtwebengine_versions()
|
||||
|
|
|
|||
|
|
@ -422,6 +422,37 @@ class _BackendProblemChecker:
|
|||
|
||||
raise utils.Unreachable
|
||||
|
||||
def _force_wayland_hardware_acceleration(self) -> None:
|
||||
"""Set environment variable so hardware acceleration works on Wayland.
|
||||
|
||||
Set EGL_PLATFORM=wayland to force ANGLE to obtain EGL display connection
|
||||
for wayland platform. Otherwise, the display connection for
|
||||
EGL_DEFAULT_DISPLAY may belong to a platform which Nvidia's EGL driver
|
||||
doesn't support. In case of unsupported platform, EGL may fallback to
|
||||
Mesa software renderer (LLVMPipe) disabling hardware acceleration in
|
||||
Chromium.
|
||||
|
||||
Equivalent to:
|
||||
https://codereview.qt-project.org/c/qt/qtwebengine/+/663568
|
||||
"""
|
||||
if objects.qapp.platformName() != 'wayland':
|
||||
return
|
||||
|
||||
versions = version.qtwebengine_versions(avoid_init=True)
|
||||
if versions.webengine >= utils.VersionNumber(6, 10):
|
||||
# Qt workaround is active
|
||||
return
|
||||
|
||||
egl_platform_var = "EGL_PLATFORM"
|
||||
egl_platform = os.environ.get(egl_platform_var)
|
||||
if not egl_platform:
|
||||
os.environ[egl_platform_var] = "wayland"
|
||||
elif egl_platform != "wayland":
|
||||
log.init.warning(
|
||||
f"{egl_platform_var} environment variable is set to {egl_platform!r}. "
|
||||
"This may break hardware rendering on Wayland."
|
||||
)
|
||||
|
||||
def _assert_backend(self, backend: usertypes.Backend) -> None:
|
||||
assert objects.backend == backend, objects.backend
|
||||
|
||||
|
|
@ -433,6 +464,7 @@ class _BackendProblemChecker:
|
|||
self._handle_ssl_support()
|
||||
self._handle_serviceworker_nuking()
|
||||
self._check_software_rendering()
|
||||
self._force_wayland_hardware_acceleration()
|
||||
self._confirm_chromium_version_changes()
|
||||
else:
|
||||
self._assert_backend(usertypes.Backend.QtWebKit)
|
||||
|
|
|
|||
|
|
@ -48,8 +48,8 @@ def parse_fatal_stacktrace(text):
|
|||
lines = [
|
||||
r'(?P<type>Fatal Python error|Windows fatal exception): (?P<msg>.*)',
|
||||
r' *',
|
||||
r'(Current )?[Tt]hread [^ ]* \(most recent call first\): *',
|
||||
r' File ".*", line \d+ in (?P<func>.*)',
|
||||
r'(Current )?[Tt]hread .* \(most recent call first\): *',
|
||||
r' (File ".*", line \d+ in (?P<func>.*)|<no Python frame>)',
|
||||
]
|
||||
m = re.search('\n'.join(lines), text)
|
||||
if m is None:
|
||||
|
|
@ -58,7 +58,7 @@ def parse_fatal_stacktrace(text):
|
|||
else:
|
||||
msg = m.group('msg')
|
||||
typ = m.group('type')
|
||||
func = m.group('func')
|
||||
func = m.group('func') or ''
|
||||
if typ == 'Windows fatal exception':
|
||||
msg = 'Windows ' + msg
|
||||
return msg, func
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import os.path
|
|||
import sys
|
||||
import bdb
|
||||
import pdb # noqa: T002
|
||||
import types
|
||||
import signal
|
||||
import argparse
|
||||
import functools
|
||||
|
|
@ -16,7 +17,7 @@ import threading
|
|||
import faulthandler
|
||||
import dataclasses
|
||||
from typing import TYPE_CHECKING, Optional, cast
|
||||
from collections.abc import MutableMapping
|
||||
from collections.abc import Callable, MutableMapping
|
||||
|
||||
from qutebrowser.qt.core import (pyqtSlot, qInstallMessageHandler, QObject,
|
||||
QSocketNotifier, QTimer, QUrl)
|
||||
|
|
@ -324,7 +325,9 @@ class SignalHandler(QObject):
|
|||
self._activated = False
|
||||
self._orig_wakeup_fd: Optional[int] = None
|
||||
|
||||
self._handlers = {
|
||||
self._handlers: dict[
|
||||
signal.Signals, Callable[[int, Optional[types.FrameType]], None]
|
||||
] = {
|
||||
signal.SIGINT: self.interrupt,
|
||||
signal.SIGTERM: self.interrupt,
|
||||
}
|
||||
|
|
@ -332,8 +335,10 @@ class SignalHandler(QObject):
|
|||
"SIGHUP": self.reload_config,
|
||||
}
|
||||
for sig_str, handler in platform_dependant_handlers.items():
|
||||
if hasattr(signal.Signals, sig_str):
|
||||
try:
|
||||
self._handlers[signal.Signals[sig_str]] = handler
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def activate(self):
|
||||
"""Set up signal handlers.
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import datetime
|
|||
from typing import NoReturn
|
||||
try:
|
||||
import tkinter
|
||||
import tkinter.messagebox
|
||||
except ImportError:
|
||||
tkinter = None # type: ignore[assignment]
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
This entire file is a giant WORKAROUND for https://bugreports.qt.io/browse/QTBUG-114334.
|
||||
"""
|
||||
|
||||
from typing import Union, cast, Optional
|
||||
from typing import Union, Optional
|
||||
import enum
|
||||
import ctypes
|
||||
import ctypes.util
|
||||
|
|
@ -16,7 +16,7 @@ from qutebrowser.qt import sip, machinery
|
|||
from qutebrowser.qt.core import QAbstractNativeEventFilter, QByteArray, qVersion
|
||||
|
||||
from qutebrowser.misc import objects
|
||||
from qutebrowser.utils import log
|
||||
from qutebrowser.utils import log, qtutils
|
||||
|
||||
|
||||
# Needs to be saved to avoid garbage collection
|
||||
|
|
@ -104,8 +104,8 @@ class NativeEventFilter(QAbstractNativeEventFilter):
|
|||
#
|
||||
# Tuple because PyQt uses the second value as the *result out-pointer, which
|
||||
# according to the Qt documentation is only used on Windows.
|
||||
_PASS_EVENT_RET = (False, cast(_PointerRetType, 0))
|
||||
_FILTER_EVENT_RET = (True, cast(_PointerRetType, 0))
|
||||
_PASS_EVENT_RET = (False, qtutils.maybe_cast(_PointerRetType, machinery.IS_QT6, 0))
|
||||
_FILTER_EVENT_RET = (True, qtutils.maybe_cast(_PointerRetType, machinery.IS_QT6, 0))
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
|
@ -137,7 +137,9 @@ class NativeEventFilter(QAbstractNativeEventFilter):
|
|||
xcb.xcb_disconnect(conn)
|
||||
|
||||
def nativeEventFilter(
|
||||
self, evtype: Union[bytes, QByteArray], message: Optional[sip.voidptr]
|
||||
self,
|
||||
evtype: Union[QByteArray, bytes, bytearray, memoryview],
|
||||
message: Optional[sip.voidptr],
|
||||
) -> tuple[bool, _PointerRetType]:
|
||||
"""Handle XCB events."""
|
||||
# We're only installed when the platform plugin is xcb
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ instead of crashing.
|
|||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import pathlib
|
||||
import dataclasses
|
||||
|
|
@ -35,11 +36,17 @@ from collections.abc import Iterator
|
|||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.misc import binparsing, objects
|
||||
from qutebrowser.qt import core
|
||||
from qutebrowser.utils import qtutils, standarddir, version, utils, log, message
|
||||
from qutebrowser.qt.webenginecore import QWebEngineProfile
|
||||
|
||||
HANGOUTS_MARKER = b"// Extension ID: nkeimhogjdpnpccoofpliimaahmaaome"
|
||||
|
||||
HANGOUTS_EXT_ID = "nkeimhogjdpnpccoofpliimaahmaaome"
|
||||
HANGOUTS_MARKER = f"// Extension ID: {HANGOUTS_EXT_ID}".encode("utf-8")
|
||||
HANGOUTS_IDS = [
|
||||
# Linux
|
||||
47222, # QtWebEngine 6.9 Beta 3
|
||||
43932, # QtWebEngine 6.9 Beta 1
|
||||
43722, # QtWebEngine 6.8
|
||||
41262, # QtWebEngine 6.7
|
||||
36197, # QtWebEngine 6.6
|
||||
|
|
@ -55,7 +62,11 @@ PAK_VERSION = 5
|
|||
RESOURCES_ENV_VAR = "QTWEBENGINE_RESOURCES_PATH"
|
||||
DISABLE_ENV_VAR = "QUTE_DISABLE_PAKJOY"
|
||||
CACHE_DIR_NAME = "webengine_resources_pak_quirk"
|
||||
PAK_FILENAME = "qtwebengine_resources.pak"
|
||||
PAK_FILENAME = (
|
||||
"qtwebengine_resources.debug.pak"
|
||||
if core.QLibraryInfo.isDebugBuild()
|
||||
else "qtwebengine_resources.pak"
|
||||
)
|
||||
|
||||
TARGET_URL = b"https://*.google.com/*"
|
||||
REPLACEMENT_URL = b"https://qute.invalid/*"
|
||||
|
|
@ -220,7 +231,7 @@ def copy_webengine_resources() -> Optional[pathlib.Path]:
|
|||
)
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/8257
|
||||
or config.val.qt.workarounds.disable_hangouts_extension
|
||||
):
|
||||
) or hasattr(QWebEngineProfile, "extensionManager"): # Qt 6.10+
|
||||
# No patching needed
|
||||
return None
|
||||
|
||||
|
|
@ -301,3 +312,16 @@ def patch_webengine() -> Iterator[None]:
|
|||
del os.environ[RESOURCES_ENV_VAR]
|
||||
else:
|
||||
os.environ[RESOURCES_ENV_VAR] = old_value
|
||||
|
||||
|
||||
def main() -> None:
|
||||
with open(sys.argv[1], "rb") as f:
|
||||
parser = PakParser(f)
|
||||
print(parser.manifest.decode("utf-8"))
|
||||
print()
|
||||
print(f"entry: {parser.manifest_entry}")
|
||||
print(f"URL offset: {parser.find_patch_offset()}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,325 @@
|
|||
# SPDX-FileCopyrightText: Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
"""Utilities to get the name of the window manager (X11) / compositor (Wayland)."""
|
||||
|
||||
from typing import NewType
|
||||
from collections.abc import Iterator
|
||||
import ctypes
|
||||
import socket
|
||||
import struct
|
||||
import pathlib
|
||||
import dataclasses
|
||||
import contextlib
|
||||
import ctypes.util
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
"""Base class for errors in this module."""
|
||||
|
||||
|
||||
class _WaylandDisplayStruct(ctypes.Structure):
|
||||
pass
|
||||
|
||||
|
||||
_WaylandDisplay = NewType("_WaylandDisplay", "ctypes._Pointer[_WaylandDisplayStruct]")
|
||||
|
||||
|
||||
def _load_library(name: str) -> ctypes.CDLL:
|
||||
lib = ctypes.util.find_library(name)
|
||||
if lib is None:
|
||||
raise Error(f"{name} library not found")
|
||||
|
||||
try:
|
||||
return ctypes.CDLL(lib)
|
||||
except OSError as e:
|
||||
raise Error(f"Failed to load {name} library: {e}")
|
||||
|
||||
|
||||
def _pid_from_fd(fd: int) -> int:
|
||||
"""Get the process ID from a file descriptor using SO_PEERCRED.
|
||||
|
||||
https://stackoverflow.com/a/35827184
|
||||
"""
|
||||
if not hasattr(socket, "SO_PEERCRED"):
|
||||
raise Error("Missing socket.SO_PEERCRED")
|
||||
|
||||
# struct ucred {
|
||||
# pid_t pid;
|
||||
# uid_t uid;
|
||||
# gid_t gid;
|
||||
# }; // where all of those are integers
|
||||
ucred_format = "3i"
|
||||
ucred_size = struct.calcsize(ucred_format)
|
||||
|
||||
try:
|
||||
sock = socket.fromfd(fd, socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
except OSError as e:
|
||||
raise Error(f"Error creating socket for fd {fd}: {e}")
|
||||
|
||||
try:
|
||||
ucred = sock.getsockopt(socket.SOL_SOCKET, socket.SO_PEERCRED, ucred_size)
|
||||
except OSError as e:
|
||||
raise Error(f"Error getting SO_PEERCRED for fd {fd}: {e}")
|
||||
finally:
|
||||
sock.close()
|
||||
|
||||
pid, _uid, _gid = struct.unpack(ucred_format, ucred)
|
||||
return pid
|
||||
|
||||
|
||||
def _process_name_from_pid(pid: int) -> str:
|
||||
"""Get the process name from a PID by reading /proc/[pid]/cmdline."""
|
||||
proc_path = pathlib.Path(f"/proc/{pid}/cmdline")
|
||||
try:
|
||||
return proc_path.read_text(encoding="utf-8").replace("\0", " ").strip()
|
||||
except OSError as e:
|
||||
raise Error(f"Error opening {proc_path}: {e}")
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _wayland_display(wayland_client: ctypes.CDLL) -> Iterator[_WaylandDisplay]:
|
||||
"""Context manager to connect to a Wayland display."""
|
||||
wayland_client.wl_display_connect.argtypes = [ctypes.c_char_p] # name
|
||||
wayland_client.wl_display_connect.restype = ctypes.POINTER(_WaylandDisplayStruct)
|
||||
|
||||
wayland_client.wl_display_disconnect.argtypes = [
|
||||
ctypes.POINTER(_WaylandDisplayStruct)
|
||||
]
|
||||
wayland_client.wl_display_disconnect.restype = None
|
||||
|
||||
display = wayland_client.wl_display_connect(None)
|
||||
if not display:
|
||||
raise Error("Can't connect to display")
|
||||
|
||||
try:
|
||||
yield display
|
||||
finally:
|
||||
wayland_client.wl_display_disconnect(display)
|
||||
|
||||
|
||||
def _wayland_get_fd(wayland_client: ctypes.CDLL, display: _WaylandDisplay) -> int:
|
||||
"""Get the file descriptor for the Wayland display."""
|
||||
wayland_client.wl_display_get_fd.argtypes = [ctypes.POINTER(_WaylandDisplayStruct)]
|
||||
wayland_client.wl_display_get_fd.restype = ctypes.c_int
|
||||
|
||||
fd = wayland_client.wl_display_get_fd(display)
|
||||
if fd < 0:
|
||||
raise Error(f"Failed to get Wayland display file descriptor: {fd}")
|
||||
return fd
|
||||
|
||||
|
||||
def wayland_compositor_name() -> str:
|
||||
"""Get the name of the running Wayland compositor.
|
||||
|
||||
Approach based on:
|
||||
https://stackoverflow.com/questions/69302630/wayland-client-get-compositor-name
|
||||
"""
|
||||
wayland_client = _load_library("wayland-client")
|
||||
with _wayland_display(wayland_client) as display:
|
||||
fd = _wayland_get_fd(wayland_client, display)
|
||||
pid = _pid_from_fd(fd)
|
||||
process_name = _process_name_from_pid(pid)
|
||||
return process_name
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class _X11Atoms:
|
||||
NET_SUPPORTING_WM_CHECK: int
|
||||
NET_WM_NAME: int
|
||||
UTF8_STRING: int
|
||||
|
||||
|
||||
class _X11DisplayStruct(ctypes.Structure):
|
||||
pass
|
||||
|
||||
|
||||
_X11Display = NewType("_X11Display", "ctypes._Pointer[_X11DisplayStruct]")
|
||||
_X11Window = NewType("_X11Window", int)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _x11_open_display(xlib: ctypes.CDLL) -> Iterator[_X11Display]:
|
||||
"""Open a connection to the X11 display."""
|
||||
xlib.XOpenDisplay.argtypes = [ctypes.c_char_p]
|
||||
xlib.XOpenDisplay.restype = ctypes.POINTER(_X11DisplayStruct)
|
||||
|
||||
xlib.XCloseDisplay.argtypes = [ctypes.POINTER(_X11DisplayStruct)]
|
||||
xlib.XCloseDisplay.restype = None
|
||||
|
||||
display = xlib.XOpenDisplay(None)
|
||||
if not display:
|
||||
raise Error("Cannot open display")
|
||||
|
||||
try:
|
||||
yield display
|
||||
finally:
|
||||
xlib.XCloseDisplay(display)
|
||||
|
||||
|
||||
def _x11_intern_atom(
|
||||
xlib: ctypes.CDLL, display: _X11Display, name: bytes, only_if_exists: bool = True
|
||||
) -> int:
|
||||
"""Call xlib's XInternAtom function."""
|
||||
xlib.XInternAtom.argtypes = [
|
||||
ctypes.POINTER(_X11DisplayStruct), # Display
|
||||
ctypes.c_char_p, # Atom name
|
||||
ctypes.c_int, # Only if exists (bool)
|
||||
]
|
||||
xlib.XInternAtom.restype = ctypes.c_ulong
|
||||
|
||||
atom = xlib.XInternAtom(display, name, only_if_exists)
|
||||
if atom == 0:
|
||||
raise Error(f"Failed to intern atom: {name!r}")
|
||||
|
||||
return atom
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _x11_get_window_property(
|
||||
xlib: ctypes.CDLL,
|
||||
display: _X11Display,
|
||||
*,
|
||||
window: _X11Window,
|
||||
prop: int,
|
||||
req_type: int,
|
||||
length: int,
|
||||
offset: int = 0,
|
||||
delete: bool = False,
|
||||
) -> Iterator[tuple["ctypes._Pointer[ctypes.c_ubyte]", ctypes.c_ulong]]:
|
||||
"""Call xlib's XGetWindowProperty function."""
|
||||
ret_actual_type = ctypes.c_ulong()
|
||||
ret_actual_format = ctypes.c_int()
|
||||
ret_nitems = ctypes.c_ulong()
|
||||
ret_bytes_after = ctypes.c_ulong()
|
||||
ret_prop = ctypes.POINTER(ctypes.c_ubyte)()
|
||||
|
||||
xlib.XGetWindowProperty.argtypes = [
|
||||
ctypes.POINTER(_X11DisplayStruct), # Display
|
||||
ctypes.c_ulong, # Window
|
||||
ctypes.c_ulong, # Property
|
||||
ctypes.c_long, # Offset
|
||||
ctypes.c_long, # Length
|
||||
ctypes.c_int, # Delete (bool)
|
||||
ctypes.c_ulong, # Required type (Atom)
|
||||
ctypes.POINTER(ctypes.c_ulong), # return: Actual type (Atom)
|
||||
ctypes.POINTER(ctypes.c_int), # return: Actual format
|
||||
ctypes.POINTER(ctypes.c_ulong), # return: Number of items
|
||||
ctypes.POINTER(ctypes.c_ulong), # return: Bytes after
|
||||
ctypes.POINTER(ctypes.POINTER(ctypes.c_ubyte)), # return: Property value
|
||||
]
|
||||
xlib.XGetWindowProperty.restype = ctypes.c_int
|
||||
|
||||
result = xlib.XGetWindowProperty(
|
||||
display,
|
||||
window,
|
||||
prop,
|
||||
offset,
|
||||
length,
|
||||
delete,
|
||||
req_type,
|
||||
ctypes.byref(ret_actual_type),
|
||||
ctypes.byref(ret_actual_format),
|
||||
ctypes.byref(ret_nitems),
|
||||
ctypes.byref(ret_bytes_after),
|
||||
ctypes.byref(ret_prop),
|
||||
)
|
||||
if result != 0:
|
||||
raise Error(f"XGetWindowProperty for {prop} failed: {result}")
|
||||
if not ret_prop:
|
||||
raise Error(f"Property {prop} is NULL")
|
||||
if ret_actual_type.value != req_type:
|
||||
raise Error(
|
||||
f"Expected type {req_type}, got {ret_actual_type.value} for property {prop}"
|
||||
)
|
||||
if ret_bytes_after.value != 0:
|
||||
raise Error(
|
||||
f"Expected no bytes after property {prop}, got {ret_bytes_after.value}"
|
||||
)
|
||||
|
||||
try:
|
||||
yield ret_prop, ret_nitems
|
||||
finally:
|
||||
xlib.XFree(ret_prop)
|
||||
|
||||
|
||||
def _x11_get_wm_window(
|
||||
xlib: ctypes.CDLL, display: _X11Display, *, atoms: _X11Atoms
|
||||
) -> _X11Window:
|
||||
"""Get the _NET_SUPPORTING_WM_CHECK window."""
|
||||
xlib.XDefaultScreen.argtypes = [ctypes.POINTER(_X11DisplayStruct)]
|
||||
xlib.XDefaultScreen.restype = ctypes.c_int
|
||||
|
||||
xlib.XRootWindow.argtypes = [
|
||||
ctypes.POINTER(_X11DisplayStruct), # Display
|
||||
ctypes.c_int, # Screen number
|
||||
]
|
||||
xlib.XRootWindow.restype = ctypes.c_ulong
|
||||
|
||||
screen = xlib.XDefaultScreen(display)
|
||||
root_window = xlib.XRootWindow(display, screen)
|
||||
|
||||
with _x11_get_window_property(
|
||||
xlib,
|
||||
display,
|
||||
window=root_window,
|
||||
prop=atoms.NET_SUPPORTING_WM_CHECK,
|
||||
req_type=33, # XA_WINDOW
|
||||
length=1,
|
||||
) as (prop, _nitems):
|
||||
win = ctypes.cast(prop, ctypes.POINTER(ctypes.c_ulong)).contents.value
|
||||
return _X11Window(win)
|
||||
|
||||
|
||||
def _x11_get_wm_name(
|
||||
xlib: ctypes.CDLL,
|
||||
display: _X11Display,
|
||||
*,
|
||||
atoms: _X11Atoms,
|
||||
wm_window: _X11Window,
|
||||
) -> str:
|
||||
"""Get the _NET_WM_NAME property of the window manager."""
|
||||
with _x11_get_window_property(
|
||||
xlib,
|
||||
display,
|
||||
window=wm_window,
|
||||
prop=atoms.NET_WM_NAME,
|
||||
req_type=atoms.UTF8_STRING,
|
||||
length=1024, # somewhat arbitrary
|
||||
) as (prop, nitems):
|
||||
if nitems.value <= 0:
|
||||
raise Error(f"{nitems.value} items found in _NET_WM_NAME property")
|
||||
wm_name = ctypes.string_at(prop, nitems.value).decode("utf-8")
|
||||
if not wm_name:
|
||||
raise Error("Window manager name is empty")
|
||||
return wm_name
|
||||
|
||||
|
||||
def x11_wm_name() -> str:
|
||||
"""Get the name of the running X11 window manager."""
|
||||
xlib = _load_library("X11")
|
||||
with _x11_open_display(xlib) as display:
|
||||
atoms = _X11Atoms(
|
||||
NET_SUPPORTING_WM_CHECK=_x11_intern_atom(
|
||||
xlib, display, b"_NET_SUPPORTING_WM_CHECK"
|
||||
),
|
||||
NET_WM_NAME=_x11_intern_atom(xlib, display, b"_NET_WM_NAME"),
|
||||
UTF8_STRING=_x11_intern_atom(xlib, display, b"UTF8_STRING"),
|
||||
)
|
||||
wm_window = _x11_get_wm_window(xlib, display, atoms=atoms)
|
||||
return _x11_get_wm_name(xlib, display, atoms=atoms, wm_window=wm_window)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
wayland_name = wayland_compositor_name()
|
||||
print(f"Wayland compositor name: {wayland_name}")
|
||||
except Error as e:
|
||||
print(f"Wayland error: {e}")
|
||||
|
||||
try:
|
||||
x11_name = x11_wm_name()
|
||||
print(f"X11 window manager name: {x11_name}")
|
||||
except Error as e:
|
||||
print(f"X11 error: {e}")
|
||||
|
|
@ -172,12 +172,14 @@ def debug_flag_error(flag):
|
|||
werror: Turn Python warnings into errors.
|
||||
test-notification-service: Use the testing libnotify service.
|
||||
caret: Enable debug logging for caret.js.
|
||||
no-system-pdfjs: Ignore system-wide PDF.js installations
|
||||
"""
|
||||
valid_flags = ['debug-exit', 'pdb-postmortem', 'no-sql-history',
|
||||
'no-scroll-filtering', 'log-requests', 'log-cookies',
|
||||
'log-scroll-pos', 'log-sensitive-keys', 'stack', 'chromium',
|
||||
'wait-renderer-process', 'avoid-chromium-init', 'werror',
|
||||
'test-notification-service', 'log-qt-events', 'caret']
|
||||
'test-notification-service', 'log-qt-events', 'caret',
|
||||
'no-system-pdfjs']
|
||||
|
||||
if flag in valid_flags:
|
||||
return flag
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from typing import (
|
|||
from collections.abc import Mapping, MutableSequence, Sequence, Callable
|
||||
|
||||
from qutebrowser.qt.core import Qt, QEvent, QMetaMethod, QObject, pyqtBoundSignal
|
||||
from qutebrowser.qt.widgets import QApplication
|
||||
|
||||
from qutebrowser.utils import log, utils, qtutils, objreg
|
||||
from qutebrowser.misc import objects
|
||||
|
|
@ -345,9 +346,9 @@ class log_time: # noqa: N801,N806 pylint: disable=invalid-name
|
|||
return wrapped
|
||||
|
||||
|
||||
def _get_widgets() -> Sequence[str]:
|
||||
def _get_widgets(qapp: QApplication) -> Sequence[str]:
|
||||
"""Get a string list of all widgets."""
|
||||
widgets = objects.qapp.allWidgets()
|
||||
widgets = qapp.allWidgets()
|
||||
widgets.sort(key=repr)
|
||||
return [repr(w) for w in widgets]
|
||||
|
||||
|
|
@ -361,17 +362,20 @@ def _get_pyqt_objects(lines: MutableSequence[str],
|
|||
_get_pyqt_objects(lines, kid, depth + 1)
|
||||
|
||||
|
||||
def get_all_objects(start_obj: QObject = None) -> str:
|
||||
def get_all_objects(start_obj: QObject = None, *, qapp: QApplication = None) -> str:
|
||||
"""Get all children of an object recursively as a string."""
|
||||
if qapp is None:
|
||||
assert objects.qapp is not None
|
||||
qapp = objects.qapp
|
||||
output = ['']
|
||||
widget_lines = _get_widgets()
|
||||
widget_lines = _get_widgets(qapp)
|
||||
widget_lines = [' ' + e for e in widget_lines]
|
||||
widget_lines.insert(0, "Qt widgets - {} objects:".format(
|
||||
len(widget_lines)))
|
||||
output += widget_lines
|
||||
|
||||
if start_obj is None:
|
||||
start_obj = objects.qapp
|
||||
start_obj = qapp
|
||||
|
||||
pyqt_lines: list[str] = []
|
||||
_get_pyqt_objects(pyqt_lines, start_obj)
|
||||
|
|
|
|||
|
|
@ -127,6 +127,9 @@ def qt_message_handler(msg_type: qtcore.QtMsgType,
|
|||
"QCoreApplication is created.",
|
||||
# Qt 6.4 beta 1: https://bugreports.qt.io/browse/QTBUG-104741
|
||||
"GL format 0 is not supported",
|
||||
# WORKAROUND https://bugreports.qt.io/browse/QTBUG-137424
|
||||
("QObject::disconnect: wildcard call disconnects from destroyed signal of "
|
||||
"QNativeSocketEngine::unnamed"),
|
||||
]
|
||||
# not using utils.is_mac here, because we can't be sure we can successfully
|
||||
# import the utils module here.
|
||||
|
|
|
|||
|
|
@ -749,3 +749,14 @@ else:
|
|||
return obj
|
||||
|
||||
QT_NONE: None = None
|
||||
|
||||
|
||||
def maybe_cast(to_type: type[_T], do_cast: bool, value: Any) -> _T:
|
||||
"""Cast `value` to `to_type` only if `do_cast` is true."""
|
||||
if do_cast:
|
||||
return cast(
|
||||
to_type, # type: ignore[valid-type]
|
||||
value,
|
||||
)
|
||||
|
||||
return value
|
||||
|
|
|
|||
|
|
@ -11,18 +11,12 @@ import ipaddress
|
|||
import posixpath
|
||||
import urllib.parse
|
||||
import mimetypes
|
||||
from typing import Optional, Union, cast, TYPE_CHECKING
|
||||
from typing import Optional, Union, cast
|
||||
from collections.abc import Iterable
|
||||
|
||||
from qutebrowser.qt import machinery
|
||||
from qutebrowser.qt.core import QUrl, QUrlQuery
|
||||
from qutebrowser.qt.network import QHostInfo, QHostAddress, QNetworkProxy
|
||||
# WORKAROUND for
|
||||
# https://www.riverbankcomputing.com/pipermail/pyqt/2024-December/046096.html
|
||||
if TYPE_CHECKING and machinery.IS_QT6:
|
||||
from qutebrowser.qt.core import QChar
|
||||
else:
|
||||
QChar = str
|
||||
|
||||
from qutebrowser.api import cmdutils
|
||||
from qutebrowser.config import config
|
||||
|
|
@ -704,7 +698,7 @@ def get_url_yank_text(url: QUrl, *, pretty: bool) -> str:
|
|||
url_query = QUrlQuery()
|
||||
url_query_str = url.query()
|
||||
if '&' not in url_query_str and ';' in url_query_str:
|
||||
url_query.setQueryDelimiters(cast(QChar, '='), cast(QChar, ';'))
|
||||
url_query.setQueryDelimiters('=', ';')
|
||||
url_query.setQuery(url_query_str)
|
||||
for key in dict(url_query.queryItems()):
|
||||
if key in config.val.url.yank_ignored_parameters:
|
||||
|
|
|
|||
|
|
@ -722,6 +722,11 @@ def guess_mimetype(filename: str, fallback: bool = False) -> str:
|
|||
fallback: Fall back to application/octet-stream if unknown.
|
||||
"""
|
||||
mimetype, _encoding = mimetypes.guess_type(filename)
|
||||
if os.path.splitext(filename)[1] == '.mjs' and mimetype == "text/plain":
|
||||
# Windows can sometimes have .mjs registered wrongly as text/plain:
|
||||
# https://github.com/golang/go/issues/68591
|
||||
return "text/javascript"
|
||||
|
||||
if mimetype is None:
|
||||
if fallback:
|
||||
return 'application/octet-stream'
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ except ImportError: # pragma: no cover
|
|||
import qutebrowser
|
||||
from qutebrowser.utils import (log, utils, standarddir, usertypes, message, resources,
|
||||
qtutils)
|
||||
from qutebrowser.misc import objects, earlyinit, sql, httpclient, pastebin, elf
|
||||
from qutebrowser.misc import objects, earlyinit, sql, httpclient, pastebin, elf, wmname
|
||||
from qutebrowser.browser import pdfjs
|
||||
from qutebrowser.config import config
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -322,8 +322,8 @@ class ModuleInfo:
|
|||
except (ImportError, ValueError):
|
||||
self._installed = False
|
||||
return
|
||||
else:
|
||||
self._installed = True
|
||||
|
||||
self._installed = True
|
||||
|
||||
for attribute_name in self._version_attributes:
|
||||
if hasattr(module, attribute_name):
|
||||
|
|
@ -332,6 +332,13 @@ class ModuleInfo:
|
|||
self._version = str(version)
|
||||
break
|
||||
|
||||
if self._version is None:
|
||||
try:
|
||||
self._version = importlib.metadata.version(self.name)
|
||||
except importlib.metadata.PackageNotFoundError:
|
||||
log.misc.debug(f"{self.name} not found")
|
||||
self._version = None
|
||||
|
||||
self._initialized = True
|
||||
|
||||
def get_version(self) -> Optional[str]:
|
||||
|
|
@ -372,7 +379,7 @@ class ModuleInfo:
|
|||
|
||||
version = self.get_version()
|
||||
if version is None:
|
||||
return f'{self.name}: yes'
|
||||
return f'{self.name}: unknown'
|
||||
|
||||
text = f'{self.name}: {version}'
|
||||
if self.is_outdated():
|
||||
|
|
@ -383,7 +390,7 @@ class ModuleInfo:
|
|||
def _create_module_info() -> dict[str, ModuleInfo]:
|
||||
packages = [
|
||||
('colorama', ['VERSION', '__version__']),
|
||||
('jinja2', ['__version__']),
|
||||
('jinja2', []),
|
||||
('pygments', ['__version__']),
|
||||
('yaml', ['__version__']),
|
||||
('adblock', ['__version__'], "0.3.2"),
|
||||
|
|
@ -487,7 +494,7 @@ def _pdfjs_version() -> str:
|
|||
else:
|
||||
pdfjs_file = pdfjs_file.decode('utf-8')
|
||||
version_re = re.compile(
|
||||
r"""^ *(PDFJS\.version|(var|const) pdfjsVersion) = ['"](?P<version>[^'"]+)['"];$""",
|
||||
r"""^ *(PDFJS\.version|(var|const|\*) pdfjsVersion) = ['"]?(?P<version>[^'"\n]+)['"]?;?$""",
|
||||
re.MULTILINE)
|
||||
|
||||
match = version_re.search(pdfjs_file)
|
||||
|
|
@ -551,6 +558,8 @@ class WebEngineVersions:
|
|||
112: '112.0.5615.213', # 2023-05-24, Qt 6.6
|
||||
118: '118.0.5993.220', # 2024-01-25, Qt 6.7
|
||||
122: '122.0.6261.171', # 2024-04-15, Qt 6.8
|
||||
130: '130.0.6723.192', # 2025-01-06, Qt 6.9
|
||||
134: '134.0.6998.208', # 2025-04-16, Qt 6.10
|
||||
}
|
||||
|
||||
# Dates based on https://chromereleases.googleblog.com/
|
||||
|
|
@ -593,6 +602,7 @@ class WebEngineVersions:
|
|||
utils.VersionNumber(5, 15, 16): (_BASES[87], '119.0.6045.123'), # 2023-11-07
|
||||
utils.VersionNumber(5, 15, 17): (_BASES[87], '123.0.6312.58'), # 2024-03-19
|
||||
utils.VersionNumber(5, 15, 18): (_BASES[87], '130.0.6723.59'), # 2024-10-14
|
||||
utils.VersionNumber(5, 15, 19): (_BASES[87], '135.0.7049.95'), # 2025-04-14
|
||||
|
||||
|
||||
## Qt 6.2
|
||||
|
|
@ -637,6 +647,18 @@ class WebEngineVersions:
|
|||
## Qt 6.8
|
||||
utils.VersionNumber(6, 8): (_BASES[122], '129.0.6668.70'), # 2024-09-24
|
||||
utils.VersionNumber(6, 8, 1): (_BASES[122], '131.0.6778.70'), # 2024-11-12
|
||||
utils.VersionNumber(6, 8, 2): (_BASES[122], '132.0.6834.111'), # 2025-01-22
|
||||
utils.VersionNumber(6, 8, 3): (_BASES[122], '134.0.6998.89'), # 2025-03-10
|
||||
|
||||
## Qt 6.9
|
||||
utils.VersionNumber(6, 9): (_BASES[130], '133.0.6943.141'), # 2025-02-25
|
||||
utils.VersionNumber(6, 9, 1): (_BASES[130], '136.0.7103.114'), # 2025-05-13
|
||||
utils.VersionNumber(6, 9, 2): (_BASES[130], '139.0.7258.67'), # 2025-07-29
|
||||
utils.VersionNumber(6, 9, 3): (_BASES[130], '140.0.7339.207'), # 2025-09-22
|
||||
|
||||
## Qt 6.10
|
||||
utils.VersionNumber(6, 10): (_BASES[134], '140.0.7339.207'), # 2025-09-22
|
||||
utils.VersionNumber(6, 10, 1): (_BASES[134], '142.0.7444.162'), # 2025-11-11
|
||||
}
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
|
|
@ -905,6 +927,51 @@ def _backend() -> str:
|
|||
raise utils.Unreachable(objects.backend)
|
||||
|
||||
|
||||
def _webengine_extensions() -> Sequence[str]:
|
||||
"""Get a list of WebExtensions enabled in QtWebEngine."""
|
||||
lines: list[str] = []
|
||||
if (
|
||||
objects.backend == usertypes.Backend.QtWebEngine
|
||||
and machinery.IS_QT6 # mypy; TODO early return once Qt 5 is dropped
|
||||
):
|
||||
from qutebrowser.browser.webengine import webenginesettings
|
||||
lines.append("WebExtensions:")
|
||||
|
||||
if webenginesettings.default_profile:
|
||||
profile = webenginesettings.default_profile
|
||||
elif "avoid-chromium-init" in objects.debug_flags:
|
||||
lines[0] += " unknown (avoiding init)"
|
||||
return lines
|
||||
else:
|
||||
profile = webenginesettings.default_qt_profile()
|
||||
|
||||
try:
|
||||
ext_manager = profile.extensionManager()
|
||||
except AttributeError:
|
||||
# Added in QtWebEngine 6.10
|
||||
return []
|
||||
assert ext_manager is not None # mypy
|
||||
|
||||
if not ext_manager.extensions():
|
||||
lines[0] += " none"
|
||||
|
||||
for info in ext_manager.extensions():
|
||||
tags = [
|
||||
("[x]" if info.isEnabled() else "[ ]") + " enabled",
|
||||
("[x]" if info.isLoaded() else "[ ]") + " loaded",
|
||||
("[x]" if info.isInstalled() else "[ ]") + " installed",
|
||||
]
|
||||
lines.append(f" {info.name()} ({info.id()})")
|
||||
lines.append(f" {' '.join(tags)}")
|
||||
lines.append(f" {info.path()}")
|
||||
url = info.actionPopupUrl()
|
||||
if url.isValid():
|
||||
lines.append(f" {url.toDisplayString()}")
|
||||
lines.append("")
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
def _uptime() -> datetime.timedelta:
|
||||
time_delta = datetime.datetime.now() - objects.qapp.launch_time
|
||||
# Round off microseconds
|
||||
|
|
@ -954,13 +1021,15 @@ def version_info() -> str:
|
|||
if QSslSocket.supportsSsl() else 'no'),
|
||||
]
|
||||
|
||||
lines += _webengine_extensions()
|
||||
|
||||
if objects.qapp:
|
||||
style = objects.qapp.style()
|
||||
assert style is not None
|
||||
metaobj = style.metaObject()
|
||||
assert metaobj is not None
|
||||
lines.append('Style: {}'.format(metaobj.className()))
|
||||
lines.append('Platform plugin: {}'.format(objects.qapp.platformName()))
|
||||
lines.append('Qt Platform: {}'.format(gui_platform_info()))
|
||||
lines.append('OpenGL: {}'.format(opengl_info()))
|
||||
|
||||
importpath = os.path.dirname(os.path.abspath(qutebrowser.__file__))
|
||||
|
|
@ -1129,6 +1198,19 @@ def opengl_info() -> Optional[OpenGLInfo]: # pragma: no cover
|
|||
old_context.makeCurrent(old_surface)
|
||||
|
||||
|
||||
def gui_platform_info() -> str:
|
||||
"""Get the Qt GUI platform name, optionally with the WM/compositor name."""
|
||||
info = objects.qapp.platformName()
|
||||
try:
|
||||
if info == "xcb":
|
||||
info += f" ({wmname.x11_wm_name()})"
|
||||
elif info in ["wayland", "wayland-egl"]:
|
||||
info += f" ({wmname.wayland_compositor_name()})"
|
||||
except wmname.Error as e:
|
||||
info += f" (Error: {e})"
|
||||
return info
|
||||
|
||||
|
||||
def pastebin_version(pbclient: pastebin.PastebinClient = None) -> None:
|
||||
"""Pastebin the version and log the url to messages."""
|
||||
def _yank_url(url: str) -> None:
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue