Compare commits
271 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 | |
|
|
9a23ce6c3c |
|
|
@ -1,5 +1,5 @@
|
||||||
[tool.bumpversion]
|
[tool.bumpversion]
|
||||||
current_version = "3.5.0"
|
current_version = "3.6.3"
|
||||||
commit = true
|
commit = true
|
||||||
message = "Release v{new_version}"
|
message = "Release v{new_version}"
|
||||||
tag = true
|
tag = true
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ include =
|
||||||
tests/*
|
tests/*
|
||||||
scripts/*
|
scripts/*
|
||||||
branch = true
|
branch = true
|
||||||
|
patch = subprocess
|
||||||
omit =
|
omit =
|
||||||
qutebrowser/__main__.py
|
qutebrowser/__main__.py
|
||||||
*/__init__.py
|
*/__init__.py
|
||||||
|
|
|
||||||
3
.flake8
3
.flake8
|
|
@ -42,6 +42,7 @@ exclude = .*,__pycache__,resources.py
|
||||||
# W503: like break before binary operator
|
# W503: like break before binary operator
|
||||||
# W504: line break after binary operator
|
# W504: line break after binary operator
|
||||||
# FI18: __future__ import "annotations" missing
|
# FI18: __future__ import "annotations" missing
|
||||||
|
# FI58: __future__ import "annotations" present
|
||||||
# PT004: fixture '{name}' does not return anything, add leading underscore
|
# 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
|
# 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
|
# 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,
|
D102,D103,D106,D107,D104,D105,D209,D211,D401,D402,D403,D412,D413,
|
||||||
A003,
|
A003,
|
||||||
W503, W504,
|
W503, W504,
|
||||||
FI18,
|
FI18,FI58,
|
||||||
PT004,
|
PT004,
|
||||||
PT011,
|
PT011,
|
||||||
PT012
|
PT012
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,6 @@ jobs:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- testenv: bleeding
|
- testenv: bleeding
|
||||||
image: "archlinux-webengine-unstable-qt6"
|
|
||||||
- testenv: bleeding-qt5
|
|
||||||
image: "archlinux-webengine-unstable"
|
image: "archlinux-webengine-unstable"
|
||||||
container:
|
container:
|
||||||
image: "qutebrowser/ci:${{ matrix.image }}"
|
image: "qutebrowser/ci:${{ matrix.image }}"
|
||||||
|
|
@ -33,14 +31,13 @@ jobs:
|
||||||
- /home/runner/work/_temp/:/home/runner/work/_temp/
|
- /home/runner/work/_temp/:/home/runner/work/_temp/
|
||||||
options: --privileged --tty
|
options: --privileged --tty
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Set up problem matchers
|
- name: Set up problem matchers
|
||||||
run: "python scripts/dev/ci/problemmatchers.py py3 ${{ runner.temp }}"
|
run: "python scripts/dev/ci/problemmatchers.py py3 ${{ runner.temp }}"
|
||||||
- name: Upgrade 3rd party assets
|
- name: Upgrade 3rd party assets
|
||||||
run: "tox exec -e ${{ matrix.testenv }} -- python scripts/dev/update_3rdparty.py --gh-token ${{ secrets.GITHUB_TOKEN }} --modern-pdfjs"
|
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
|
- name: Run tox
|
||||||
run: dbus-run-session tox -e ${{ matrix.testenv }}
|
run: dbus-run-session tox -e ${{ matrix.testenv }}
|
||||||
- name: Gather info
|
- name: Gather info
|
||||||
|
|
@ -51,7 +48,7 @@ jobs:
|
||||||
shell: bash
|
shell: bash
|
||||||
if: failure()
|
if: failure()
|
||||||
- name: Upload screenshots
|
- name: Upload screenshots
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: "end2end-screenshots-${{ steps.info.outputs.date }}-${{ steps.info.outputs.sha_short }}-${{ matrix.image }}"
|
name: "end2end-screenshots-${{ steps.info.outputs.date }}-${{ steps.info.outputs.sha_short }}-${{ matrix.image }}"
|
||||||
path: |
|
path: |
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ jobs:
|
||||||
- testenv: vulture
|
- testenv: vulture
|
||||||
- testenv: misc
|
- testenv: misc
|
||||||
- testenv: pyroma
|
- testenv: pyroma
|
||||||
- testenv: check-manifest
|
|
||||||
- testenv: eslint
|
- testenv: eslint
|
||||||
- testenv: shellcheck
|
- testenv: shellcheck
|
||||||
args: "-f gcc" # For problem matchers
|
args: "-f gcc" # For problem matchers
|
||||||
|
|
@ -35,20 +34,20 @@ jobs:
|
||||||
- testenv: actionlint
|
- testenv: actionlint
|
||||||
- testenv: package
|
- testenv: package
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v5
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
.mypy_cache
|
.mypy_cache
|
||||||
.tox
|
.tox
|
||||||
~/.cache/pip
|
~/.cache/pip
|
||||||
key: "${{ matrix.testenv }}-${{ hashFiles('misc/requirements/requirements-*.txt') }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('scripts/dev/pylint_checkers/qute_pylint/*.py') }}"
|
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:
|
with:
|
||||||
python-version: '3.10'
|
python-version: '3.10'
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: '22.x'
|
node-version: '22.x'
|
||||||
if: "matrix.testenv == 'eslint'"
|
if: "matrix.testenv == 'eslint'"
|
||||||
|
|
@ -91,16 +90,10 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- testenv: py-qt5
|
- testenv: py
|
||||||
image: archlinux-webkit
|
|
||||||
- testenv: py-qt5
|
|
||||||
image: archlinux-webengine
|
image: archlinux-webengine
|
||||||
- testenv: py-qt5
|
- testenv: py
|
||||||
image: archlinux-webengine-unstable
|
image: archlinux-webengine-unstable
|
||||||
- testenv: py
|
|
||||||
image: archlinux-webengine-qt6
|
|
||||||
- testenv: py
|
|
||||||
image: archlinux-webengine-unstable-qt6
|
|
||||||
container:
|
container:
|
||||||
image: "qutebrowser/ci:${{ matrix.image }}"
|
image: "qutebrowser/ci:${{ matrix.image }}"
|
||||||
env:
|
env:
|
||||||
|
|
@ -113,7 +106,7 @@ jobs:
|
||||||
- /home/runner/work/_temp/:/home/runner/work/_temp/
|
- /home/runner/work/_temp/:/home/runner/work/_temp/
|
||||||
options: --privileged --tty
|
options: --privileged --tty
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Set up problem matchers
|
- name: Set up problem matchers
|
||||||
|
|
@ -128,7 +121,7 @@ jobs:
|
||||||
shell: bash
|
shell: bash
|
||||||
if: failure()
|
if: failure()
|
||||||
- name: Upload screenshots
|
- name: Upload screenshots
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: "end2end-screenshots-${{ steps.info.outputs.date }}-${{ steps.info.outputs.sha_short }}-${{ matrix.image }}"
|
name: "end2end-screenshots-${{ steps.info.outputs.date }}-${{ steps.info.outputs.sha_short }}-${{ matrix.image }}"
|
||||||
path: |
|
path: |
|
||||||
|
|
@ -193,36 +186,41 @@ jobs:
|
||||||
- testenv: py313-pyqt68
|
- testenv: py313-pyqt68
|
||||||
os: ubuntu-24.04
|
os: ubuntu-24.04
|
||||||
python: "3.13"
|
python: "3.13"
|
||||||
### PyQt 6.8 (Python 3.14)
|
### PyQt 6.8 (Python 3.13)
|
||||||
# Pinned to Alpha 6:
|
|
||||||
# https://www.riverbankcomputing.com/pipermail/pyqt/2025-April/046210.html
|
|
||||||
- testenv: py314-pyqt68
|
|
||||||
os: ubuntu-24.04
|
|
||||||
python: "3.14.0-alpha.6"
|
|
||||||
### PyQt 6.9 (Python 3.13)
|
|
||||||
- testenv: py313-pyqt69
|
|
||||||
os: ubuntu-24.04
|
|
||||||
python: "3.13"
|
|
||||||
### macOS Ventura
|
|
||||||
- testenv: py313-pyqt68
|
- testenv: py313-pyqt68
|
||||||
os: macos-13
|
os: ubuntu-24.04
|
||||||
python: "3.13"
|
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)
|
### macOS Sonoma (M1 runner)
|
||||||
- testenv: py313-pyqt68
|
- testenv: py314-pyqt610
|
||||||
os: macos-14
|
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
|
args: "tests/unit" # Only run unit tests on macOS
|
||||||
### Windows
|
### Windows
|
||||||
- testenv: py313-pyqt68
|
- testenv: py314-pyqt610
|
||||||
os: windows-2019
|
os: windows-2022
|
||||||
python: "3.13"
|
python: "3.14"
|
||||||
|
- testenv: py314-pyqt610
|
||||||
|
os: windows-2025
|
||||||
|
python: "3.14"
|
||||||
runs-on: "${{ matrix.os }}"
|
runs-on: "${{ matrix.os }}"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v5
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
.mypy_cache
|
.mypy_cache
|
||||||
|
|
@ -230,7 +228,7 @@ jobs:
|
||||||
~/.cache/pip
|
~/.cache/pip
|
||||||
key: "${{ matrix.testenv }}-${{ matrix.os }}-${{ matrix.python }}-${{ hashFiles('misc/requirements/requirements-*.txt') }}-${{ hashFiles('requirements.txt') }}"
|
key: "${{ matrix.testenv }}-${{ matrix.os }}-${{ matrix.python }}-${{ hashFiles('misc/requirements/requirements-*.txt') }}-${{ hashFiles('requirements.txt') }}"
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: "${{ matrix.python }}"
|
python-version: "${{ matrix.python }}"
|
||||||
- name: Set up problem matchers
|
- name: Set up problem matchers
|
||||||
|
|
@ -271,7 +269,7 @@ jobs:
|
||||||
shell: bash
|
shell: bash
|
||||||
if: failure()
|
if: failure()
|
||||||
- name: Upload screenshots
|
- name: Upload screenshots
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: "end2end-screenshots-${{ steps.info.outputs.date }}-${{ steps.info.outputs.sha_short }}-${{ matrix.testenv }}-${{ matrix.os }}"
|
name: "end2end-screenshots-${{ steps.info.outputs.date }}-${{ steps.info.outputs.sha_short }}-${{ matrix.testenv }}-${{ matrix.os }}"
|
||||||
path: |
|
path: |
|
||||||
|
|
@ -287,16 +285,16 @@ jobs:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v3
|
uses: github/codeql-action/init@v4
|
||||||
with:
|
with:
|
||||||
languages: javascript, python
|
languages: javascript, python
|
||||||
queries: +security-extended
|
queries: +security-extended
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v3
|
uses: github/codeql-action/analyze@v4
|
||||||
|
|
||||||
irc:
|
irc:
|
||||||
timeout-minutes: 2
|
timeout-minutes: 2
|
||||||
|
|
|
||||||
|
|
@ -13,14 +13,11 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
image:
|
image:
|
||||||
- archlinux-webkit
|
|
||||||
- archlinux-webengine
|
- archlinux-webengine
|
||||||
- archlinux-webengine-unstable
|
- archlinux-webengine-unstable
|
||||||
- archlinux-webengine-unstable-qt6
|
|
||||||
- archlinux-webengine-qt6
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: '3.x'
|
||||||
- run: pip install jinja2
|
- run: pip install jinja2
|
||||||
|
|
|
||||||
|
|
@ -14,36 +14,45 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: macos-13
|
- os: macos-15-intel
|
||||||
toxenv: build-release
|
toxenv: build-release
|
||||||
name: macos-intel
|
name: macos-intel
|
||||||
- os: macos-14
|
- os: macos-14
|
||||||
toxenv: build-release
|
toxenv: build-release
|
||||||
name: macos-apple-silicon
|
name: macos-apple-silicon
|
||||||
- os: windows-2019
|
- os: windows-latest
|
||||||
toxenv: build-release
|
toxenv: build-release
|
||||||
name: windows
|
name: windows
|
||||||
- os: macos-13
|
- os: macos-15-intel
|
||||||
args: --debug
|
args: --debug
|
||||||
toxenv: build-release
|
toxenv: build-release
|
||||||
name: macos-debug-intel
|
name: macos-debug-intel
|
||||||
- os: macos-14
|
- os: macos-14
|
||||||
toxenv: build-release
|
toxenv: build-release
|
||||||
name: macos-debug-apple-silicon
|
name: macos-debug-apple-silicon
|
||||||
- os: windows-2019
|
- os: windows-latest
|
||||||
args: --debug
|
args: --debug
|
||||||
toxenv: build-release
|
toxenv: build-release
|
||||||
name: windows-debug
|
name: windows-debug
|
||||||
runs-on: "${{ matrix.os }}"
|
runs-on: "${{ matrix.os }}"
|
||||||
timeout-minutes: 45
|
timeout-minutes: 45
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: "3.13"
|
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
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install -U pip
|
python -m pip install -U pip
|
||||||
|
|
@ -63,7 +72,7 @@ jobs:
|
||||||
echo "sha_short=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"
|
echo "sha_short=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"
|
||||||
shell: bash
|
shell: bash
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: "qutebrowser-nightly-${{ steps.info.outputs.date }}-${{ steps.info.outputs.sha_short }}-${{ matrix.name }}"
|
name: "qutebrowser-nightly-${{ steps.info.outputs.date }}-${{ steps.info.outputs.sha_short }}-${{ matrix.name }}"
|
||||||
path: |
|
path: |
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,11 @@ jobs:
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Set up Python 3.9
|
- name: Set up Python 3.9
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: '3.9'
|
python-version: '3.9'
|
||||||
- name: Recompile requirements
|
- name: Recompile requirements
|
||||||
|
|
@ -41,7 +41,7 @@ jobs:
|
||||||
- name: Run qutebrowser smoke test
|
- name: Run qutebrowser smoke test
|
||||||
run: "xvfb-run .venv/bin/python3 -m qutebrowser --no-err-windows --nowindow --temp-basedir about:blank ':later 500 quit'"
|
run: "xvfb-run .venv/bin/python3 -m qutebrowser --no-err-windows --nowindow --temp-basedir about:blank ':later 500 quit'"
|
||||||
- name: Create pull request
|
- name: Create pull request
|
||||||
uses: peter-evans/create-pull-request@v7
|
uses: peter-evans/create-pull-request@v8
|
||||||
with:
|
with:
|
||||||
committer: qutebrowser bot <bot@qutebrowser.org>
|
committer: qutebrowser bot <bot@qutebrowser.org>
|
||||||
author: qutebrowser bot <bot@qutebrowser.org>
|
author: qutebrowser bot <bot@qutebrowser.org>
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,12 @@ on:
|
||||||
- 'patch'
|
- 'patch'
|
||||||
- 'minor'
|
- 'minor'
|
||||||
- 'major'
|
- 'major'
|
||||||
|
- 'reupload' # reupload last release
|
||||||
# FIXME do we want a possibility to do prereleases here?
|
# FIXME do we want a possibility to do prereleases here?
|
||||||
python_version:
|
python_version:
|
||||||
description: 'Python version'
|
description: 'Python version'
|
||||||
required: true
|
required: true
|
||||||
default: '3.13'
|
default: '3.14'
|
||||||
type: choice
|
type: choice
|
||||||
options:
|
options:
|
||||||
- '3.9'
|
- '3.9'
|
||||||
|
|
@ -24,18 +25,20 @@ on:
|
||||||
- '3.11'
|
- '3.11'
|
||||||
- '3.12'
|
- '3.12'
|
||||||
- '3.13'
|
- '3.13'
|
||||||
|
- '3.14'
|
||||||
jobs:
|
jobs:
|
||||||
prepare:
|
prepare:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
timeout-minutes: 5
|
timeout-minutes: 5
|
||||||
outputs:
|
outputs:
|
||||||
version: ${{ steps.bump.outputs.version }}
|
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:
|
permissions:
|
||||||
contents: write # To push release commit/tag
|
contents: write # To push release commit/tag
|
||||||
steps:
|
steps:
|
||||||
- name: Find release branch
|
- name: Find release branch
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v8
|
||||||
id: find-branch
|
id: find-branch
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
|
|
@ -59,9 +62,9 @@ jobs:
|
||||||
console.log(`sorted: ${sorted}`);
|
console.log(`sorted: ${sorted}`);
|
||||||
return sorted.at(-1);
|
return sorted.at(-1);
|
||||||
result-encoding: string
|
result-encoding: string
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
# Doesn't really matter what we prepare the release with, but let's
|
# Doesn't really matter what we prepare the release with, but let's
|
||||||
# use the same version for consistency.
|
# use the same version for consistency.
|
||||||
|
|
@ -75,7 +78,7 @@ jobs:
|
||||||
git config --global user.name "qutebrowser bot"
|
git config --global user.name "qutebrowser bot"
|
||||||
git config --global user.email "bot@qutebrowser.org"
|
git config --global user.email "bot@qutebrowser.org"
|
||||||
- name: Switch to release branch
|
- name: Switch to release branch
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: ${{ steps.find-branch.outputs.result }}
|
ref: ${{ steps.find-branch.outputs.result }}
|
||||||
- name: Import GPG Key
|
- name: Import GPG Key
|
||||||
|
|
@ -83,9 +86,9 @@ jobs:
|
||||||
gpg --import <<< "${{ secrets.QUTEBROWSER_BOT_GPGKEY }}"
|
gpg --import <<< "${{ secrets.QUTEBROWSER_BOT_GPGKEY }}"
|
||||||
- name: Bump version
|
- name: Bump version
|
||||||
id: bump
|
id: bump
|
||||||
run: "tox -e update-version -- ${{ github.event.inputs.release_type }}"
|
run: "tox -e update-version -- ${{ inputs.release_type }}"
|
||||||
- name: Check milestone
|
- name: Check milestone
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v8
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const milestones = await github.paginate(github.rest.issues.listMilestones, {
|
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!`);
|
core.setFailed(`Found open milestone ${milestone.title} with ${milestone.open_issues} open and ${milestone.closed_issues} closed issues!`);
|
||||||
}
|
}
|
||||||
- name: Push release commit/tag
|
- name: Push release commit/tag
|
||||||
|
if: ${{ inputs.release_type != 'reupload' }}
|
||||||
run: |
|
run: |
|
||||||
git push origin ${{ steps.find-branch.outputs.result }}
|
git push origin ${{ steps.find-branch.outputs.result }}
|
||||||
git push origin v${{ steps.bump.outputs.version }}
|
git push origin v${{ steps.bump.outputs.version }}
|
||||||
- name: Cherry-pick release commit
|
- name: Cherry-pick release commit
|
||||||
if: ${{ github.event.inputs.release_type == 'patch' }}
|
if: ${{ inputs.release_type == 'patch' }}
|
||||||
run: |
|
run: |
|
||||||
|
git fetch origin main
|
||||||
git checkout main
|
git checkout main
|
||||||
git cherry-pick -x v${{ steps.bump.outputs.version }}
|
git cherry-pick -x v${{ steps.bump.outputs.version }}
|
||||||
git push origin main
|
git push origin main
|
||||||
git checkout v${{ steps.bump.outputs.version_x }}
|
git checkout v${{ steps.bump.outputs.version_x }}
|
||||||
- name: Create release branch
|
- name: Create release branch
|
||||||
if: ${{ github.event.inputs.release_type != 'patch' }}
|
if: ${{ inputs.release_type == 'minor' || inputs.release_type == 'major' }}
|
||||||
run: |
|
run: |
|
||||||
git checkout -b v${{ steps.bump.outputs.version_x }}
|
git checkout -b v${{ steps.bump.outputs.version_x }}
|
||||||
git push --set-upstream origin v${{ steps.bump.outputs.version_x }}
|
git push --set-upstream origin v${{ steps.bump.outputs.version_x }}
|
||||||
- name: Create GitHub draft release
|
- name: Create GitHub draft release
|
||||||
|
if: ${{ inputs.release_type != 'reupload' }}
|
||||||
id: create-release
|
id: create-release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
tag_name: v${{ steps.bump.outputs.version }}
|
tag_name: v${{ steps.bump.outputs.version }}
|
||||||
draft: true
|
draft: true
|
||||||
body: "*Release artifacts for this release are currently being uploaded...*"
|
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:
|
release:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: macos-13
|
- os: macos-14-large # Intel
|
||||||
- os: macos-14
|
- os: macos-14 # Apple Silicon
|
||||||
- os: windows-2019
|
- os: windows-2022
|
||||||
- os: ubuntu-24.04
|
- os: ubuntu-24.04
|
||||||
runs-on: "${{ matrix.os }}"
|
runs-on: "${{ matrix.os }}"
|
||||||
timeout-minutes: 45
|
timeout-minutes: 45
|
||||||
|
|
@ -136,13 +164,13 @@ jobs:
|
||||||
permissions:
|
permissions:
|
||||||
contents: write # To upload release artifacts
|
contents: write # To upload release artifacts
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
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
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: ${{ github.event.inputs.python_version }}
|
python-version: ${{ inputs.python_version }}
|
||||||
- name: Import GPG Key
|
- name: Import GPG Key
|
||||||
if: ${{ startsWith(matrix.os, 'ubuntu-') }}
|
if: ${{ startsWith(matrix.os, 'ubuntu-') }}
|
||||||
run: |
|
run: |
|
||||||
|
|
@ -167,7 +195,7 @@ jobs:
|
||||||
# FIXME consider switching to trusted publishers:
|
# FIXME consider switching to trusted publishers:
|
||||||
# https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/
|
# https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/
|
||||||
- name: Build and upload release
|
- 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:
|
env:
|
||||||
TWINE_USERNAME: __token__
|
TWINE_USERNAME: __token__
|
||||||
TWINE_PASSWORD: ${{ secrets.QUTEBROWSER_BOT_PYPI_TOKEN }}
|
TWINE_PASSWORD: ${{ secrets.QUTEBROWSER_BOT_PYPI_TOKEN }}
|
||||||
|
|
@ -180,7 +208,7 @@ jobs:
|
||||||
contents: write # To change release
|
contents: write # To change release
|
||||||
steps:
|
steps:
|
||||||
- name: Publish final release
|
- name: Publish final release
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v8
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
await github.rest.repos.updateRelease({
|
await github.rest.repos.updateRelease({
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,161 @@ breaking changes (such as renamed commands) can happen in minor releases.
|
||||||
// `Fixed` for any bug fixes.
|
// `Fixed` for any bug fixes.
|
||||||
// `Security` to invite users to upgrade in case of vulnerabilities.
|
// `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]]
|
||||||
v3.5.0 (2025-04-12)
|
v3.5.0 (2025-04-12)
|
||||||
-------------------
|
-------------------
|
||||||
|
|
@ -25,6 +180,7 @@ Changed
|
||||||
- Windows/macOS releases are now built with Qt 6.9.0
|
- Windows/macOS releases are now built with Qt 6.9.0
|
||||||
* Based on Chromium 130.0.6723.192
|
* Based on Chromium 130.0.6723.192
|
||||||
* Security fixes up to Chromium 133.0.6943.141
|
* 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
|
- The `content.headers.user_agent` setting now has a new
|
||||||
`{upstream_browser_version_short}` template field, which is the
|
`{upstream_browser_version_short}` template field, which is the
|
||||||
upstream/Chromium version but shortened to only major version.
|
upstream/Chromium version but shortened to only major version.
|
||||||
|
|
|
||||||
|
|
@ -121,8 +121,6 @@ Currently, the following tox environments are available:
|
||||||
* `pyroma`: Check packaging practices with
|
* `pyroma`: Check packaging practices with
|
||||||
https://pypi.python.org/pypi/pyroma/[pyroma].
|
https://pypi.python.org/pypi/pyroma/[pyroma].
|
||||||
* `eslint`: Run https://eslint.org/[ESLint] javascript checker.
|
* `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.
|
* `mkvenv`: Bootstrap a virtualenv for testing.
|
||||||
* `misc`: Run `scripts/misc_checks.py` to check for:
|
* `misc`: Run `scripts/misc_checks.py` to check for:
|
||||||
- untracked git files
|
- untracked git files
|
||||||
|
|
@ -604,6 +602,7 @@ Info pages:
|
||||||
- chrome://device-log/ (QtWebEngine >= 6.3)
|
- chrome://device-log/ (QtWebEngine >= 6.3)
|
||||||
- chrome://gpu/
|
- chrome://gpu/
|
||||||
- chrome://sandbox/ (Linux only)
|
- chrome://sandbox/ (Linux only)
|
||||||
|
- chrome://qt/ (QtWebEngine >= 6.7)
|
||||||
|
|
||||||
Misc. / Debugging pages:
|
Misc. / Debugging pages:
|
||||||
|
|
||||||
|
|
@ -614,6 +613,7 @@ Misc. / Debugging pages:
|
||||||
- chrome://ukm/ (QtWebEngine >= 5.15.3)
|
- chrome://ukm/ (QtWebEngine >= 5.15.3)
|
||||||
- chrome://user-actions/ (QtWebEngine >= 5.15.3)
|
- chrome://user-actions/ (QtWebEngine >= 5.15.3)
|
||||||
- chrome://webrtc-logs/ (QtWebEngine >= 5.15.3)
|
- chrome://webrtc-logs/ (QtWebEngine >= 5.15.3)
|
||||||
|
- chrome://extensions/ (QtWebEngine >= 6.10)
|
||||||
|
|
||||||
Internals pages:
|
Internals pages:
|
||||||
|
|
||||||
|
|
@ -804,7 +804,8 @@ qutebrowser release
|
||||||
**Automatic release via GitHub Actions (starting with v3.0.0):**
|
**Automatic release via GitHub Actions (starting with v3.0.0):**
|
||||||
|
|
||||||
* Double check Python version in `.github/workflows/release.yml`
|
* 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:**
|
**Manual release:**
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ Why Python?::
|
||||||
point, I wasn't comfortable with C++ so that wasn't an alternative.
|
point, I wasn't comfortable with C++ so that wasn't an alternative.
|
||||||
|
|
||||||
But isn't Python too slow for a browser?::
|
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
|
Most of the heavy lifting of qutebrowser is done by Qt and
|
||||||
QtWebKit/QtWebEngine in C++, with the
|
QtWebKit/QtWebEngine in C++, with the
|
||||||
https://wiki.python.org/moin/GlobalInterpreterLock[GIL] released.
|
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.
|
the built-in ones.
|
||||||
+
|
+
|
||||||
Note that you might need an additional package (e.g.
|
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.
|
Archlinux) to play web videos with mpv.
|
||||||
+
|
+
|
||||||
There is a very useful script for mpv, which emulates "unique application"
|
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]
|
[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
|
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]
|
[source,python]
|
||||||
----
|
----
|
||||||
with config.pattern('*://example.com/') as p:
|
with config.pattern('*://example.com/*') as p:
|
||||||
p.content.images = False
|
p.content.images = False
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|
@ -417,6 +417,7 @@ Pre-built colorschemes
|
||||||
- https://github.com/catppuccin/qutebrowser[Catppuccin]
|
- https://github.com/catppuccin/qutebrowser[Catppuccin]
|
||||||
- https://github.com/iruzo/matrix-qutebrowser[Matrix]
|
- https://github.com/iruzo/matrix-qutebrowser[Matrix]
|
||||||
- https://github.com/harmtemolder/qutebrowser-solarized[Solarized]
|
- https://github.com/harmtemolder/qutebrowser-solarized[Solarized]
|
||||||
|
- https://github.com/Rehpotsirhc-z/qutebrowser-doom-one[Doom One]
|
||||||
|
|
||||||
Avoiding flake8 errors
|
Avoiding flake8 errors
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
|
@ -302,6 +302,7 @@
|
||||||
|<<qt.force_software_rendering,qt.force_software_rendering>>|Force software rendering for QtWebEngine.
|
|<<qt.force_software_rendering,qt.force_software_rendering>>|Force software rendering for QtWebEngine.
|
||||||
|<<qt.highdpi,qt.highdpi>>|Turn on Qt HighDPI scaling.
|
|<<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_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.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.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.
|
|<<qt.workarounds.remove_service_workers,qt.workarounds.remove_service_workers>>|Delete the QtWebEngine Service Worker directory on every start.
|
||||||
|
|
@ -2770,6 +2771,7 @@ Valid values:
|
||||||
|
|
||||||
* +ua-google+
|
* +ua-google+
|
||||||
* +ua-googledocs+
|
* +ua-googledocs+
|
||||||
|
* +ua-gnome-gitlab+
|
||||||
* +js-whatsapp-web+
|
* +js-whatsapp-web+
|
||||||
* +js-discord+
|
* +js-discord+
|
||||||
* +js-string-replaceall+
|
* +js-string-replaceall+
|
||||||
|
|
@ -3990,11 +3992,29 @@ Type: <<types,String>>
|
||||||
Valid values:
|
Valid values:
|
||||||
|
|
||||||
* +always+: Disable accelerated 2d canvas
|
* +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
|
* +never+: Enable accelerated 2d canvas
|
||||||
|
|
||||||
Default: +pass:[auto]+
|
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]]
|
||||||
=== qt.workarounds.disable_hangouts_extension
|
=== qt.workarounds.disable_hangouts_extension
|
||||||
Disable the Hangouts extension.
|
Disable the Hangouts extension.
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,11 @@
|
||||||
</content_rating>
|
</content_rating>
|
||||||
<releases>
|
<releases>
|
||||||
<!-- Add new releases here -->
|
<!-- 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.5.0' date='2025-04-12'/>
|
||||||
<release version="3.4.0" date="2024-12-14"/>
|
<release version="3.4.0" date="2024-12-14"/>
|
||||||
<release version="3.3.1" date="2024-10-12"/>
|
<release version="3.3.1" 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.6.1
|
|
||||||
packaging==24.2
|
|
||||||
pyproject_hooks==1.2.0
|
|
||||||
tomli==2.2.1
|
|
||||||
zipp==3.21.0
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
check-manifest
|
|
||||||
|
|
@ -1,74 +1,73 @@
|
||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
annotated-types==0.7.0
|
annotated-types==0.7.0
|
||||||
anyio==4.9.0
|
anyio==4.12.0
|
||||||
autocommand==2.2.2
|
autocommand==2.2.2
|
||||||
backports.tarfile==1.2.0
|
backports.tarfile==1.2.0
|
||||||
bracex==2.5.post1
|
bracex==2.6
|
||||||
build==1.2.2.post1
|
build==1.3.0
|
||||||
bump-my-version==1.1.1
|
bump-my-version==1.2.5
|
||||||
certifi==2025.1.31
|
certifi==2025.11.12
|
||||||
cffi==1.17.1
|
cffi==2.0.0
|
||||||
charset-normalizer==3.4.1
|
charset-normalizer==3.4.4
|
||||||
click==8.1.8
|
click==8.1.8
|
||||||
cryptography==44.0.2
|
cryptography==46.0.3
|
||||||
docutils==0.21.2
|
docutils==0.22.3
|
||||||
exceptiongroup==1.2.2
|
exceptiongroup==1.3.1
|
||||||
github3.py==4.0.1
|
github3.py==4.0.1
|
||||||
h11==0.14.0
|
h11==0.16.0
|
||||||
httpcore==1.0.7
|
httpcore==1.0.9
|
||||||
httpx==0.28.1
|
httpx==0.28.1
|
||||||
hunter==3.7.0
|
hunter==3.9.0
|
||||||
id==1.5.0
|
id==1.5.0
|
||||||
idna==3.10
|
idna==3.11
|
||||||
importlib_metadata==8.6.1
|
importlib_metadata==8.7.0
|
||||||
importlib_resources==6.5.2
|
importlib_resources==6.5.2
|
||||||
inflect==7.3.1
|
inflect==7.3.1
|
||||||
jaraco.classes==3.4.0
|
jaraco.classes==3.4.0
|
||||||
jaraco.collections==5.1.0
|
jaraco.collections==5.1.0
|
||||||
jaraco.context==6.0.1
|
jaraco.context==6.0.1
|
||||||
jaraco.functools==4.1.0
|
jaraco.functools==4.0.1
|
||||||
jaraco.text==3.12.1
|
jaraco.text==3.12.1
|
||||||
jeepney==0.9.0
|
jeepney==0.9.0
|
||||||
keyring==25.6.0
|
keyring==25.7.0
|
||||||
manhole==1.8.1
|
manhole==1.8.1
|
||||||
markdown-it-py==3.0.0
|
markdown-it-py==3.0.0
|
||||||
mdurl==0.1.2
|
mdurl==0.1.2
|
||||||
more-itertools==10.6.0
|
more-itertools==10.8.0
|
||||||
nh3==0.2.21
|
nh3==0.3.2
|
||||||
packaging==24.2
|
packaging==25.0
|
||||||
platformdirs==4.3.7
|
platformdirs==4.4.0
|
||||||
prompt_toolkit==3.0.50
|
prompt_toolkit==3.0.52
|
||||||
pycparser==2.22
|
pycparser==2.23
|
||||||
pydantic==2.11.1
|
pydantic==2.12.5
|
||||||
pydantic-settings==2.8.1
|
pydantic-settings==2.11.0
|
||||||
pydantic_core==2.33.0
|
pydantic_core==2.41.5
|
||||||
Pygments==2.19.1
|
Pygments==2.19.2
|
||||||
PyJWT==2.10.1
|
PyJWT==2.10.1
|
||||||
Pympler==1.1
|
Pympler==1.1
|
||||||
pyproject_hooks==1.2.0
|
pyproject_hooks==1.2.0
|
||||||
PyQt-builder==1.18.1
|
PyQt-builder==1.19.1
|
||||||
python-dateutil==2.9.0.post0
|
python-dateutil==2.9.0.post0
|
||||||
python-dotenv==1.1.0
|
python-dotenv==1.2.1
|
||||||
questionary==2.1.0
|
questionary==2.1.1
|
||||||
readme_renderer==44.0
|
readme_renderer==44.0
|
||||||
requests==2.32.3
|
requests==2.32.5
|
||||||
requests-toolbelt==1.0.0
|
requests-toolbelt==1.0.0
|
||||||
rfc3986==2.0.0
|
rfc3986==2.0.0
|
||||||
rich==14.0.0
|
rich==14.2.0
|
||||||
rich-click==1.8.8
|
rich-click==1.9.4
|
||||||
SecretStorage==3.3.3
|
SecretStorage==3.3.3
|
||||||
sip==6.10.0
|
sip==6.14.0
|
||||||
six==1.17.0
|
six==1.17.0
|
||||||
sniffio==1.3.1
|
tomli==2.3.0
|
||||||
tomli==2.2.1
|
tomlkit==0.13.3
|
||||||
tomlkit==0.13.2
|
twine==6.2.0
|
||||||
twine==6.1.0
|
|
||||||
typeguard==4.3.0
|
typeguard==4.3.0
|
||||||
typing-inspection==0.4.0
|
typing-inspection==0.4.2
|
||||||
typing_extensions==4.13.0
|
typing_extensions==4.15.0
|
||||||
uritemplate==4.1.1
|
uritemplate==4.2.0
|
||||||
# urllib3==2.3.0
|
# urllib3==2.6.2
|
||||||
wcmatch==10.0
|
wcmatch==10.1
|
||||||
wcwidth==0.2.13
|
wcwidth==0.2.14
|
||||||
zipp==3.21.0
|
zipp==3.23.0
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
attrs==25.3.0
|
attrs==25.4.0
|
||||||
flake8==7.2.0
|
flake8==7.3.0
|
||||||
flake8-bugbear==24.12.12
|
flake8-bugbear==24.12.12
|
||||||
flake8-builtins==2.5.0
|
flake8-builtins==3.0.0
|
||||||
flake8-comprehensions==3.16.0
|
flake8-comprehensions==3.17.0
|
||||||
flake8-debugger==4.1.2
|
flake8-debugger==4.1.2
|
||||||
flake8-deprecated==2.2.1
|
flake8-deprecated==2.2.1
|
||||||
flake8-docstrings==1.7.0
|
flake8-docstrings==1.7.0
|
||||||
|
|
@ -12,12 +12,12 @@ flake8-future-import==0.4.7
|
||||||
flake8-plugin-utils==1.3.3
|
flake8-plugin-utils==1.3.3
|
||||||
flake8-pytest-style==2.1.0
|
flake8-pytest-style==2.1.0
|
||||||
flake8-string-format==0.3.0
|
flake8-string-format==0.3.0
|
||||||
flake8-tidy-imports==4.11.0
|
flake8-tidy-imports==4.12.0
|
||||||
flake8-tuple==0.4.1
|
flake8-tuple==0.4.1
|
||||||
mccabe==0.7.0
|
mccabe==0.7.0
|
||||||
pep8-naming==0.14.1
|
pep8-naming==0.15.1
|
||||||
pycodestyle==2.13.0
|
pycodestyle==2.14.0
|
||||||
pydocstyle==6.3.0
|
pydocstyle==6.3.0
|
||||||
pyflakes==3.3.1
|
pyflakes==3.4.0
|
||||||
six==1.17.0
|
six==1.17.0
|
||||||
snowballstemmer==2.2.0
|
snowballstemmer==3.0.1
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,20 @@
|
||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
chardet==5.2.0
|
chardet==5.2.0
|
||||||
diff_cover==9.2.4
|
diff_cover==10.0.0
|
||||||
Jinja2==3.1.6
|
Jinja2==3.1.6
|
||||||
lxml==5.3.1
|
librt==0.7.3
|
||||||
MarkupSafe==3.0.2
|
lxml==6.0.2
|
||||||
mypy==1.15.0
|
MarkupSafe==3.0.3
|
||||||
mypy-extensions==1.0.0
|
mypy==1.19.0
|
||||||
pluggy==1.5.0
|
mypy_extensions==1.1.0
|
||||||
Pygments==2.19.1
|
pathspec==0.12.1
|
||||||
|
pluggy==1.6.0
|
||||||
|
Pygments==2.19.2
|
||||||
PyQt5-stubs==5.15.6.0
|
PyQt5-stubs==5.15.6.0
|
||||||
tomli==2.2.1
|
tomli==2.3.0
|
||||||
types-colorama==0.4.15.20240311
|
types-colorama==0.4.15.20250801
|
||||||
types-docutils==0.21.0.20241128
|
types-docutils==0.22.3.20251115
|
||||||
types-Pygments==2.19.0.20250305
|
types-Pygments==2.19.0.20251121
|
||||||
types-PyYAML==6.0.12.20250326
|
types-PyYAML==6.0.12.20250915
|
||||||
typing_extensions==4.13.0
|
typing_extensions==4.15.0
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
altgraph==0.17.4
|
altgraph==0.17.5
|
||||||
importlib_metadata==8.6.1
|
importlib_metadata==8.7.0
|
||||||
packaging==24.2
|
packaging==25.0
|
||||||
pyinstaller==6.12.0
|
pyinstaller==6.17.0
|
||||||
pyinstaller-hooks-contrib==2025.2
|
pyinstaller-hooks-contrib==2025.10
|
||||||
zipp==3.21.0
|
zipp==3.23.0
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,28 @@
|
||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
astroid==3.3.9
|
astroid==3.3.11
|
||||||
certifi==2025.1.31
|
certifi==2025.11.12
|
||||||
cffi==1.17.1
|
cffi==2.0.0
|
||||||
charset-normalizer==3.4.1
|
charset-normalizer==3.4.4
|
||||||
cryptography==44.0.2
|
cryptography==46.0.3
|
||||||
dill==0.3.9
|
dill==0.4.0
|
||||||
github3.py==4.0.1
|
github3.py==4.0.1
|
||||||
idna==3.10
|
idna==3.11
|
||||||
isort==6.0.1
|
importlib_metadata==8.7.0
|
||||||
|
isort==6.1.0
|
||||||
mccabe==0.7.0
|
mccabe==0.7.0
|
||||||
pefile==2024.8.26
|
pefile==2024.8.26
|
||||||
platformdirs==4.3.7
|
platformdirs==4.4.0
|
||||||
pycparser==2.22
|
pycparser==2.23
|
||||||
PyJWT==2.10.1
|
PyJWT==2.10.1
|
||||||
pylint==3.3.6
|
pylint==3.3.9
|
||||||
python-dateutil==2.9.0.post0
|
python-dateutil==2.9.0.post0
|
||||||
./scripts/dev/pylint_checkers
|
./scripts/dev/pylint_checkers
|
||||||
requests==2.32.3
|
requests==2.32.5
|
||||||
six==1.17.0
|
six==1.17.0
|
||||||
tomli==2.2.1
|
tomli==2.3.0
|
||||||
tomlkit==0.13.2
|
tomlkit==0.13.3
|
||||||
typing_extensions==4.13.0
|
typing_extensions==4.15.0
|
||||||
uritemplate==4.1.1
|
uritemplate==4.2.0
|
||||||
# urllib3==2.3.0
|
# urllib3==2.6.2
|
||||||
|
zipp==3.23.0
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
PyQt5==5.15.2 # rq.filter: == 5.15.2
|
PyQt5==5.15.2 # rq.filter: == 5.15.2
|
||||||
PyQt5_sip==12.17.0
|
PyQt5_sip==12.17.1
|
||||||
PyQtWebEngine==5.15.2 # rq.filter: == 5.15.2
|
PyQtWebEngine==5.15.2 # rq.filter: == 5.15.2
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
PyQt5==5.15.11 # rq.filter: < 5.16
|
PyQt5==5.15.11 # rq.filter: < 5.16
|
||||||
PyQt5-Qt5==5.15.16
|
PyQt5-Qt5==5.15.18
|
||||||
PyQt5_sip==12.17.0
|
PyQt5_sip==12.17.1
|
||||||
PyQtWebEngine==5.15.7 # rq.filter: < 5.16
|
PyQtWebEngine==5.15.7 # rq.filter: < 5.16
|
||||||
PyQtWebEngine-Qt5==5.15.16
|
PyQtWebEngine-Qt5==5.15.18
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
PyQt5==5.15.11
|
PyQt5==5.15.11
|
||||||
PyQt5-Qt5==5.15.16
|
PyQt5-Qt5==5.15.18
|
||||||
PyQt5_sip==12.17.0
|
PyQt5_sip==12.17.1
|
||||||
PyQtWebEngine==5.15.7
|
PyQtWebEngine==5.15.7
|
||||||
PyQtWebEngine-Qt5==5.15.16
|
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-Qt6==6.2.4
|
||||||
PyQt6-WebEngine==6.2.1
|
PyQt6-WebEngine==6.2.1
|
||||||
PyQt6-WebEngine-Qt6==6.2.4
|
PyQt6-WebEngine-Qt6==6.2.4
|
||||||
PyQt6_sip==13.10.0
|
PyQt6_sip==13.10.2
|
||||||
|
|
|
||||||
|
|
@ -4,4 +4,4 @@ PyQt6==6.3.1
|
||||||
PyQt6-Qt6==6.3.2
|
PyQt6-Qt6==6.3.2
|
||||||
PyQt6-WebEngine==6.3.1
|
PyQt6-WebEngine==6.3.1
|
||||||
PyQt6-WebEngine-Qt6==6.3.2
|
PyQt6-WebEngine-Qt6==6.3.2
|
||||||
PyQt6_sip==13.10.0
|
PyQt6_sip==13.10.2
|
||||||
|
|
|
||||||
|
|
@ -4,4 +4,4 @@ PyQt6==6.4.2
|
||||||
PyQt6-Qt6==6.4.3
|
PyQt6-Qt6==6.4.3
|
||||||
PyQt6-WebEngine==6.4.0
|
PyQt6-WebEngine==6.4.0
|
||||||
PyQt6-WebEngine-Qt6==6.4.3
|
PyQt6-WebEngine-Qt6==6.4.3
|
||||||
PyQt6_sip==13.10.0
|
PyQt6_sip==13.10.2
|
||||||
|
|
|
||||||
|
|
@ -4,4 +4,4 @@ PyQt6==6.5.3
|
||||||
PyQt6-Qt6==6.5.3
|
PyQt6-Qt6==6.5.3
|
||||||
PyQt6-WebEngine==6.5.0
|
PyQt6-WebEngine==6.5.0
|
||||||
PyQt6-WebEngine-Qt6==6.5.3
|
PyQt6-WebEngine-Qt6==6.5.3
|
||||||
PyQt6_sip==13.10.0
|
PyQt6_sip==13.10.2
|
||||||
|
|
|
||||||
|
|
@ -4,4 +4,4 @@ PyQt6==6.6.1
|
||||||
PyQt6-Qt6==6.6.3
|
PyQt6-Qt6==6.6.3
|
||||||
PyQt6-WebEngine==6.6.0
|
PyQt6-WebEngine==6.6.0
|
||||||
PyQt6-WebEngine-Qt6==6.6.3
|
PyQt6-WebEngine-Qt6==6.6.3
|
||||||
PyQt6_sip==13.10.0
|
PyQt6_sip==13.10.2
|
||||||
|
|
|
||||||
|
|
@ -5,4 +5,4 @@ PyQt6-Qt6==6.7.3
|
||||||
PyQt6-WebEngine==6.7.0
|
PyQt6-WebEngine==6.7.0
|
||||||
PyQt6-WebEngine-Qt6==6.7.3
|
PyQt6-WebEngine-Qt6==6.7.3
|
||||||
PyQt6-WebEngineSubwheel-Qt6==6.7.3
|
PyQt6-WebEngineSubwheel-Qt6==6.7.3
|
||||||
PyQt6_sip==13.10.0
|
PyQt6_sip==13.10.2
|
||||||
|
|
|
||||||
|
|
@ -4,4 +4,4 @@ PyQt6==6.8.1
|
||||||
PyQt6-Qt6==6.8.2
|
PyQt6-Qt6==6.8.2
|
||||||
PyQt6-WebEngine==6.8.0
|
PyQt6-WebEngine==6.8.0
|
||||||
PyQt6-WebEngine-Qt6==6.8.2
|
PyQt6-WebEngine-Qt6==6.8.2
|
||||||
PyQt6_sip==13.10.0
|
PyQt6_sip==13.10.2
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
PyQt6==6.9.0
|
PyQt6==6.9.1
|
||||||
PyQt6-Qt6==6.9.0
|
PyQt6-Qt6==6.9.2
|
||||||
PyQt6-WebEngine==6.9.0
|
PyQt6-WebEngine==6.9.0
|
||||||
PyQt6-WebEngine-Qt6==6.9.0
|
PyQt6-WebEngine-Qt6==6.9.2
|
||||||
PyQt6_sip==13.10.0
|
PyQt6_sip==13.10.2
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
PyQt6==6.9.0
|
PyQt6==6.10.1
|
||||||
PyQt6-Qt6==6.9.0
|
PyQt6-Qt6==6.10.1
|
||||||
PyQt6-WebEngine==6.9.0
|
PyQt6-WebEngine==6.10.0
|
||||||
PyQt6-WebEngine-Qt6==6.9.0
|
PyQt6-WebEngine-Qt6==6.10.1
|
||||||
PyQt6_sip==13.10.0
|
PyQt6_sip==13.10.2
|
||||||
|
--extra-index-url https://www.riverbankcomputing.com/pypi/simple/
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,7 @@ PyQt6
|
||||||
PyQt6-Qt6
|
PyQt6-Qt6
|
||||||
PyQt6-WebEngine
|
PyQt6-WebEngine
|
||||||
PyQt6-WebEngine-Qt6
|
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
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
PyQt6==6.9.0
|
PyQt6==6.10.1
|
||||||
PyQt6-Qt6==6.9.0
|
PyQt6-Qt6==6.10.1
|
||||||
PyQt6-WebEngine==6.9.0
|
PyQt6-WebEngine==6.10.0
|
||||||
PyQt6-WebEngine-Qt6==6.9.0
|
PyQt6-WebEngine-Qt6==6.10.1
|
||||||
PyQt6_sip==13.10.0
|
PyQt6_sip==13.10.2
|
||||||
|
--extra-index-url https://www.riverbankcomputing.com/pypi/simple/
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,7 @@ PyQt6
|
||||||
PyQt6-Qt6
|
PyQt6-Qt6
|
||||||
PyQt6-WebEngine
|
PyQt6-WebEngine
|
||||||
PyQt6-WebEngine-Qt6
|
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
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
build==1.2.2.post1
|
build==1.3.0
|
||||||
certifi==2025.1.31
|
certifi==2025.11.12
|
||||||
charset-normalizer==3.4.1
|
charset-normalizer==3.4.4
|
||||||
docutils==0.21.2
|
check-manifest==0.51
|
||||||
idna==3.10
|
docutils==0.22.3
|
||||||
importlib_metadata==8.6.1
|
idna==3.11
|
||||||
packaging==24.2
|
importlib_metadata==8.7.0
|
||||||
Pygments==2.19.1
|
packaging==25.0
|
||||||
|
Pygments==2.19.2
|
||||||
pyproject_hooks==1.2.0
|
pyproject_hooks==1.2.0
|
||||||
pyroma==4.2
|
pyroma==5.0.1
|
||||||
requests==2.32.3
|
requests==2.32.5
|
||||||
tomli==2.2.1
|
tomli==2.3.0
|
||||||
trove-classifiers==2025.3.19.19
|
trove-classifiers==2025.12.1.14
|
||||||
urllib3==2.3.0
|
urllib3==2.6.2
|
||||||
zipp==3.21.0
|
zipp==3.23.0
|
||||||
|
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
pyroma
|
pyroma
|
||||||
|
check-manifest
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,18 @@
|
||||||
|
|
||||||
alabaster==0.7.16
|
alabaster==0.7.16
|
||||||
babel==2.17.0
|
babel==2.17.0
|
||||||
certifi==2025.1.31
|
certifi==2025.11.12
|
||||||
charset-normalizer==3.4.1
|
charset-normalizer==3.4.4
|
||||||
docutils==0.21.2
|
docutils==0.21.2
|
||||||
idna==3.10
|
idna==3.11
|
||||||
imagesize==1.4.1
|
imagesize==1.4.1
|
||||||
importlib_metadata==8.6.1
|
importlib_metadata==8.7.0
|
||||||
Jinja2==3.1.6
|
Jinja2==3.1.6
|
||||||
MarkupSafe==3.0.2
|
MarkupSafe==3.0.3
|
||||||
packaging==24.2
|
packaging==25.0
|
||||||
Pygments==2.19.1
|
Pygments==2.19.2
|
||||||
requests==2.32.3
|
requests==2.32.5
|
||||||
snowballstemmer==2.2.0
|
snowballstemmer==3.0.1
|
||||||
Sphinx==7.4.7
|
Sphinx==7.4.7
|
||||||
sphinxcontrib-applehelp==2.0.0
|
sphinxcontrib-applehelp==2.0.0
|
||||||
sphinxcontrib-devhelp==2.0.0
|
sphinxcontrib-devhelp==2.0.0
|
||||||
|
|
@ -21,6 +21,6 @@ sphinxcontrib-htmlhelp==2.1.0
|
||||||
sphinxcontrib-jsmath==1.0.1
|
sphinxcontrib-jsmath==1.0.1
|
||||||
sphinxcontrib-qthelp==2.0.0
|
sphinxcontrib-qthelp==2.0.0
|
||||||
sphinxcontrib-serializinghtml==2.0.0
|
sphinxcontrib-serializinghtml==2.0.0
|
||||||
tomli==2.2.1
|
tomli==2.3.0
|
||||||
urllib3==2.3.0
|
urllib3==2.6.2
|
||||||
zipp==3.21.0
|
zipp==3.23.0
|
||||||
|
|
|
||||||
|
|
@ -1,67 +1,67 @@
|
||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
attrs==25.3.0
|
attrs==25.4.0
|
||||||
autocommand==2.2.2
|
autocommand==2.2.2
|
||||||
backports.tarfile==1.2.0
|
backports.tarfile==1.2.0
|
||||||
beautifulsoup4==4.13.3
|
beautifulsoup4==4.14.3
|
||||||
blinker==1.9.0
|
blinker==1.9.0
|
||||||
certifi==2025.1.31
|
certifi==2025.11.12
|
||||||
charset-normalizer==3.4.1
|
charset-normalizer==3.4.4
|
||||||
cheroot==10.0.1
|
cheroot==11.1.2
|
||||||
click==8.1.8
|
click==8.1.8
|
||||||
coverage==7.8.0
|
coverage==7.10.7
|
||||||
exceptiongroup==1.2.2
|
exceptiongroup==1.3.1
|
||||||
execnet==2.1.1
|
execnet==2.1.2
|
||||||
filelock==3.18.0
|
filelock==3.19.1
|
||||||
Flask==3.1.0
|
Flask==3.1.2
|
||||||
gherkin-official==29.0.0
|
gherkin-official==29.0.0
|
||||||
hunter==3.7.0
|
hunter==3.9.0
|
||||||
hypothesis==6.130.5
|
hypothesis==6.141.1
|
||||||
idna==3.10
|
idna==3.11
|
||||||
importlib_metadata==8.6.1
|
importlib_metadata==8.7.0
|
||||||
importlib_resources==6.5.2
|
importlib_resources==6.5.2
|
||||||
inflect==7.3.1
|
inflect==7.3.1
|
||||||
iniconfig==2.1.0
|
iniconfig==2.1.0
|
||||||
itsdangerous==2.2.0
|
itsdangerous==2.2.0
|
||||||
jaraco.collections==5.1.0
|
jaraco.collections==5.1.0
|
||||||
jaraco.context==6.0.1
|
jaraco.context==6.0.1
|
||||||
jaraco.functools==4.1.0
|
jaraco.functools==4.0.1
|
||||||
jaraco.text==3.12.1
|
jaraco.text==3.12.1
|
||||||
# Jinja2==3.1.6
|
# Jinja2==3.1.6
|
||||||
Mako==1.3.9
|
Mako==1.3.10
|
||||||
manhole==1.8.1
|
manhole==1.8.1
|
||||||
# MarkupSafe==3.0.2
|
# MarkupSafe==3.0.3
|
||||||
more-itertools==10.6.0
|
more-itertools==10.8.0
|
||||||
packaging==24.2
|
packaging==25.0
|
||||||
parse==1.20.2
|
parse==1.20.2
|
||||||
parse_type==0.6.4
|
parse_type==0.6.6
|
||||||
pillow==11.1.0
|
pillow==11.3.0
|
||||||
platformdirs==4.3.7
|
platformdirs==4.4.0
|
||||||
pluggy==1.5.0
|
pluggy==1.6.0
|
||||||
py-cpuinfo==9.0.0
|
py-cpuinfo==9.0.0
|
||||||
Pygments==2.19.1
|
Pygments==2.19.2
|
||||||
pytest==8.3.5
|
pytest==8.4.2
|
||||||
pytest-bdd==8.1.0
|
pytest-bdd==8.1.0
|
||||||
pytest-benchmark==5.1.0
|
pytest-benchmark==5.2.3
|
||||||
pytest-cov==6.0.0
|
pytest-cov==7.0.0
|
||||||
pytest-instafail==0.5.0
|
pytest-instafail==0.5.0
|
||||||
pytest-mock==3.14.0
|
pytest-mock==3.15.1
|
||||||
pytest-qt==4.4.0
|
pytest-qt==4.5.0
|
||||||
pytest-repeat==0.9.3
|
pytest-repeat==0.9.4
|
||||||
pytest-rerunfailures==15.0
|
pytest-rerunfailures==16.0.1
|
||||||
pytest-xdist==3.6.1
|
pytest-xdist==3.8.0
|
||||||
pytest-xvfb==3.1.1
|
pytest-xvfb==3.1.1
|
||||||
PyVirtualDisplay==3.0
|
PyVirtualDisplay==3.0
|
||||||
requests==2.32.3
|
requests==2.32.5
|
||||||
requests-file==2.1.0
|
requests-file==3.0.1
|
||||||
six==1.17.0
|
six==1.17.0
|
||||||
sortedcontainers==2.4.0
|
sortedcontainers==2.4.0
|
||||||
soupsieve==2.6
|
soupsieve==2.8
|
||||||
tldextract==5.1.3
|
tldextract==5.3.0
|
||||||
tomli==2.2.1
|
tomli==2.3.0
|
||||||
typeguard==4.3.0
|
typeguard==4.3.0
|
||||||
typing_extensions==4.13.0
|
typing_extensions==4.15.0
|
||||||
urllib3==2.3.0
|
urllib3==2.6.2
|
||||||
vulture==2.14
|
vulture==2.14
|
||||||
Werkzeug==3.1.3
|
Werkzeug==3.1.4
|
||||||
zipp==3.21.0
|
zipp==3.23.0
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,19 @@
|
||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
cachetools==5.5.2
|
cachetools==6.2.3
|
||||||
chardet==5.2.0
|
chardet==5.2.0
|
||||||
colorama==0.4.6
|
colorama==0.4.6
|
||||||
distlib==0.3.9
|
distlib==0.4.0
|
||||||
filelock==3.18.0
|
filelock==3.19.1
|
||||||
packaging==24.2
|
packaging==25.0
|
||||||
pip==25.0.1
|
pip==25.3
|
||||||
platformdirs==4.3.7
|
platformdirs==4.4.0
|
||||||
pluggy==1.5.0
|
pluggy==1.6.0
|
||||||
pyproject-api==1.9.0
|
pyproject-api==1.9.1
|
||||||
setuptools==78.1.0
|
setuptools==80.9.0
|
||||||
tomli==2.2.1
|
tomli==2.3.0
|
||||||
tox==4.25.0
|
tox==4.30.3 ; python_full_version!="3.14.0b1"
|
||||||
typing_extensions==4.13.0
|
typing_extensions==4.15.0
|
||||||
virtualenv==20.29.3
|
virtualenv==20.35.4
|
||||||
wheel==0.45.1
|
wheel==0.45.1
|
||||||
|
tox @ git+https://github.com/tox-dev/tox ; python_full_version=="3.14.0b1"
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,5 @@
|
||||||
tox
|
tox
|
||||||
wheel
|
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
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
tomli==2.2.1
|
tomli==2.3.0
|
||||||
vulture==2.14
|
vulture==2.14
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
pathspec==0.12.1
|
pathspec==0.12.1
|
||||||
PyYAML==6.0.2
|
PyYAML==6.0.3
|
||||||
yamllint==1.37.0
|
yamllint==1.37.1
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,7 @@ no_entries_found() {
|
||||||
# expected to write the username of that entry to the $username variable and
|
# expected to write the username of that entry to the $username variable and
|
||||||
# the corresponding password to $password
|
# the corresponding password to $password
|
||||||
|
|
||||||
# shellcheck disable=SC2317
|
# shellcheck disable=SC2329
|
||||||
reset_backend() {
|
reset_backend() {
|
||||||
init() { true ; }
|
init() { true ; }
|
||||||
query_entries() { true ; }
|
query_entries() { true ; }
|
||||||
|
|
@ -199,7 +199,8 @@ choose_entry_zenity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
choose_entry_zenity_radio() {
|
choose_entry_zenity_radio() {
|
||||||
zenity_helper() { # shellcheck disable=SC2317
|
# shellcheck disable=SC2329
|
||||||
|
zenity_helper() {
|
||||||
awk '{ print $0 ; print $0 }' \
|
awk '{ print $0 ; print $0 }' \
|
||||||
| zenity --list --radiolist \
|
| zenity --list --radiolist \
|
||||||
--title "qutebrowser password fill" \
|
--title "qutebrowser password fill" \
|
||||||
|
|
@ -279,7 +280,7 @@ pass_backend() {
|
||||||
|
|
||||||
# =======================================================
|
# =======================================================
|
||||||
# backend: secret
|
# backend: secret
|
||||||
# shellcheck disable=SC2317
|
# shellcheck disable=SC2329
|
||||||
secret_backend() {
|
secret_backend() {
|
||||||
init() {
|
init() {
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -208,10 +208,10 @@ def main(arguments):
|
||||||
None,
|
None,
|
||||||
[
|
[
|
||||||
extract_result.fqdn,
|
extract_result.fqdn,
|
||||||
getattr(
|
(
|
||||||
extract_result,
|
extract_result.top_domain_under_public_suffix
|
||||||
"top_domain_under_public_suffix",
|
if hasattr(extract_result, "top_domain_under_public_suffix")
|
||||||
getattr(extract_result, "registered_domain"),
|
else extract_result.registered_domain
|
||||||
),
|
),
|
||||||
extract_result.subdomain + "." + extract_result.domain,
|
extract_result.subdomain + "." + extract_result.domain,
|
||||||
extract_result.domain,
|
extract_result.domain,
|
||||||
|
|
|
||||||
|
|
@ -121,10 +121,10 @@ def main(arguments):
|
||||||
None,
|
None,
|
||||||
[
|
[
|
||||||
extract_result.fqdn,
|
extract_result.fqdn,
|
||||||
getattr(
|
(
|
||||||
extract_result,
|
extract_result.top_domain_under_public_suffix
|
||||||
"top_domain_under_public_suffix",
|
if hasattr(extract_result, "top_domain_under_public_suffix")
|
||||||
getattr(extract_result, "registered_domain"),
|
else extract_result.registered_domain
|
||||||
),
|
),
|
||||||
extract_result.subdomain + extract_result.domain,
|
extract_result.subdomain + extract_result.domain,
|
||||||
extract_result.domain,
|
extract_result.domain,
|
||||||
|
|
|
||||||
|
|
@ -247,10 +247,10 @@ def main(arguments):
|
||||||
None,
|
None,
|
||||||
[
|
[
|
||||||
extract_result.fqdn,
|
extract_result.fqdn,
|
||||||
getattr(
|
(
|
||||||
extract_result,
|
extract_result.top_domain_under_public_suffix
|
||||||
"top_domain_under_public_suffix",
|
if hasattr(extract_result, "top_domain_under_public_suffix")
|
||||||
getattr(extract_result, "registered_domain"),
|
else extract_result.registered_domain
|
||||||
),
|
),
|
||||||
extract_result.ipv4,
|
extract_result.ipv4,
|
||||||
private_domain,
|
private_domain,
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ create_menu() {
|
||||||
done < "$QUTE_CONFIG_DIR"/bookmarks/urls
|
done < "$QUTE_CONFIG_DIR"/bookmarks/urls
|
||||||
|
|
||||||
# Finally history
|
# 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() {
|
get_selection() {
|
||||||
|
|
|
||||||
|
|
@ -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__",
|
||||||
|
]
|
||||||
12
pytest.ini
12
pytest.ini
|
|
@ -1,4 +1,5 @@
|
||||||
[pytest]
|
[pytest]
|
||||||
|
pythonpath = .
|
||||||
log_level = NOTSET
|
log_level = NOTSET
|
||||||
addopts = --strict-markers --strict-config --instafail --benchmark-columns=Min,Max,Median
|
addopts = --strict-markers --strict-config --instafail --benchmark-columns=Min,Max,Median
|
||||||
testpaths = tests
|
testpaths = tests
|
||||||
|
|
@ -42,8 +43,8 @@ markers =
|
||||||
qt6_only: Tests which should only run with Qt 6
|
qt6_only: Tests which should only run with Qt 6
|
||||||
qt5_xfail: Tests which fail with Qt 5
|
qt5_xfail: Tests which fail with Qt 5
|
||||||
qt6_xfail: Tests which fail with Qt 6
|
qt6_xfail: Tests which fail with Qt 6
|
||||||
qt69_ci_flaky: Tests which are flaky with Qt 6.9 on CI
|
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
|
qt69_ci_skip: Tests which should be skipped with Qt 6.9+ on CI
|
||||||
qt_log_level_fail = WARNING
|
qt_log_level_fail = WARNING
|
||||||
qt_log_ignore =
|
qt_log_ignore =
|
||||||
# GitHub Actions
|
# GitHub Actions
|
||||||
|
|
@ -89,6 +90,11 @@ qt_log_ignore =
|
||||||
^QRhiGles2: Failed to create (temporary )?context$
|
^QRhiGles2: Failed to create (temporary )?context$
|
||||||
^QVulkanInstance: Failed to initialize Vulkan$
|
^QVulkanInstance: Failed to initialize Vulkan$
|
||||||
^Unable to detect GPU vendor\.$
|
^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
|
xfail_strict = true
|
||||||
filterwarnings =
|
filterwarnings =
|
||||||
error
|
error
|
||||||
|
|
@ -96,8 +102,6 @@ filterwarnings =
|
||||||
# https://github.com/cucumber/gherkin/commit/2f4830093149eae7ff7bd82f683b3d3bb7320d39
|
# https://github.com/cucumber/gherkin/commit/2f4830093149eae7ff7bd82f683b3d3bb7320d39
|
||||||
# https://github.com/pytest-dev/pytest-bdd/issues/752
|
# https://github.com/pytest-dev/pytest-bdd/issues/752
|
||||||
ignore:'maxsplit' is passed as positional argument:DeprecationWarning:gherkin.gherkin_line
|
ignore:'maxsplit' is passed as positional argument:DeprecationWarning:gherkin.gherkin_line
|
||||||
# https://github.com/pytest-dev/pytest-mock/issues/468
|
|
||||||
ignore:'asyncio\.iscoroutinefunction' is deprecated and slated for removal:DeprecationWarning:pytest_mock.plugin
|
|
||||||
# https://github.com/ionelmc/pytest-benchmark/issues/283
|
# https://github.com/ionelmc/pytest-benchmark/issues/283
|
||||||
ignore:FileType is deprecated\. Simply open files after parsing arguments\.:PendingDeprecationWarning:pytest_benchmark.plugin
|
ignore:FileType is deprecated\. Simply open files after parsing arguments\.:PendingDeprecationWarning:pytest_benchmark.plugin
|
||||||
faulthandler_timeout = 90
|
faulthandler_timeout = 90
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ __copyright__ = "Copyright 2013-{} Florian Bruhin (The Compiler)".format(_year)
|
||||||
__license__ = "GPL-3.0-or-later"
|
__license__ = "GPL-3.0-or-later"
|
||||||
__maintainer__ = __author__
|
__maintainer__ = __author__
|
||||||
__email__ = "mail@qutebrowser.org"
|
__email__ = "mail@qutebrowser.org"
|
||||||
__version__ = "3.5.0"
|
__version__ = "3.6.3"
|
||||||
__version_info__ = tuple(int(part) for part in __version__.split('.'))
|
__version_info__ = tuple(int(part) for part in __version__.split('.'))
|
||||||
__description__ = "A keyboard-driven, vim-like browser based on Python and Qt."
|
__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.keyinput import modeman
|
||||||
from qutebrowser.config import config, websettings
|
from qutebrowser.config import config, websettings
|
||||||
from qutebrowser.utils import (utils, objreg, usertypes, log, qtutils,
|
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.misc import miscwidgets, objects, sessions
|
||||||
from qutebrowser.browser import eventfilter, inspector
|
from qutebrowser.browser import eventfilter, inspector
|
||||||
from qutebrowser.qt import sip
|
from qutebrowser.qt import sip
|
||||||
|
|
@ -1177,37 +1177,6 @@ class AbstractTab(QWidget):
|
||||||
navigation.url.errorString()))
|
navigation.url.errorString()))
|
||||||
navigation.accepted = False
|
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)
|
@pyqtSlot(bool)
|
||||||
def _on_load_finished(self, ok: bool) -> None:
|
def _on_load_finished(self, ok: bool) -> None:
|
||||||
assert self._widget is not None
|
assert self._widget is not None
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,12 @@
|
||||||
|
|
||||||
from qutebrowser.qt import machinery
|
from qutebrowser.qt import machinery
|
||||||
from qutebrowser.qt.core import QObject, QEvent, Qt, QTimer
|
from qutebrowser.qt.core import QObject, QEvent, Qt, QTimer
|
||||||
|
from qutebrowser.qt.gui import QKeyEvent
|
||||||
from qutebrowser.qt.widgets import QWidget
|
from qutebrowser.qt.widgets import QWidget
|
||||||
|
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.utils import log, message, usertypes, qtutils
|
from qutebrowser.utils import log, message, usertypes, qtutils, version, utils
|
||||||
from qutebrowser.keyinput import modeman
|
from qutebrowser.keyinput import modeman, keyutils
|
||||||
|
|
||||||
|
|
||||||
class ChildEventFilter(QObject):
|
class ChildEventFilter(QObject):
|
||||||
|
|
@ -37,8 +38,8 @@ class ChildEventFilter(QObject):
|
||||||
if event.type() == QEvent.Type.ChildAdded:
|
if event.type() == QEvent.Type.ChildAdded:
|
||||||
child = event.child()
|
child = event.child()
|
||||||
if not isinstance(child, QWidget):
|
if not isinstance(child, QWidget):
|
||||||
# Can e.g. happen when dragging text
|
# Can e.g. happen when dragging text, or accessibility tree
|
||||||
log.misc.debug(f"Ignoring new child {qtutils.qobj_repr(child)}")
|
# nodes since Qt 6.9
|
||||||
return False
|
return False
|
||||||
|
|
||||||
log.misc.debug(
|
log.misc.debug(
|
||||||
|
|
@ -54,21 +55,30 @@ class ChildEventFilter(QObject):
|
||||||
# - This is a child event filter on a tab (self._widget is not None)
|
# - 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
|
# - We find an old existing child which is a QQuickWidget and is
|
||||||
# currently focused.
|
# currently focused.
|
||||||
# - We're using QtWebEngine >= 6.4 (older versions are not affected)
|
# - We're using an affected QtWebEngine version
|
||||||
children = [
|
children = [
|
||||||
c for c in self._widget.findChildren(
|
c for c in self._widget.findChildren(
|
||||||
QWidget, "", Qt.FindChildOption.FindDirectChildrenOnly)
|
QWidget, "", Qt.FindChildOption.FindDirectChildrenOnly)
|
||||||
if c is not child and
|
if c is not child and
|
||||||
c.hasFocus() and
|
c.hasFocus() and
|
||||||
c.metaObject() is not None 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")
|
log.misc.debug("Focusing new child")
|
||||||
child.setFocus()
|
child.setFocus()
|
||||||
|
|
||||||
child.installEventFilter(self._filter)
|
child.installEventFilter(self._filter)
|
||||||
elif event.type() == QEvent.Type.ChildRemoved:
|
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()
|
child = event.child()
|
||||||
log.misc.debug(
|
log.misc.debug(
|
||||||
f"{qtutils.qobj_repr(obj)}: removed child {qtutils.qobj_repr(child)}")
|
f"{qtutils.qobj_repr(obj)}: removed child {qtutils.qobj_repr(child)}")
|
||||||
|
|
|
||||||
|
|
@ -123,25 +123,24 @@ def data_for_url(url: QUrl) -> tuple[str, bytes]:
|
||||||
|
|
||||||
path = url.path()
|
path = url.path()
|
||||||
host = url.host()
|
host = url.host()
|
||||||
query = url.query()
|
|
||||||
# A url like "qute:foo" is split as "scheme:path", not "scheme:host".
|
# A url like "qute:foo" is split as "scheme:path", not "scheme:host".
|
||||||
log.misc.debug("url: {}, path: {}, host {}".format(
|
log.misc.debug("url: {}, path: {}, host {}".format(
|
||||||
url.toDisplayString(), path, host))
|
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('/')
|
new_url.setPath('/')
|
||||||
if query:
|
if not new_url.host(): # Valid path but not valid host
|
||||||
new_url.setQuery(query)
|
raise UrlInvalidError(f"Invalid host (from path): {path!r}")
|
||||||
if new_url.host(): # path was a valid host
|
raise Redirect(new_url)
|
||||||
raise Redirect(new_url)
|
|
||||||
|
if not path:
|
||||||
|
# Redirect qute://help -> qute://help/
|
||||||
|
new_url = QUrl(url)
|
||||||
|
new_url.setPath('/')
|
||||||
|
raise Redirect(new_url)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
handler = _HANDLERS[host]
|
handler = _HANDLERS[host]
|
||||||
|
|
|
||||||
|
|
@ -417,6 +417,37 @@ def _init_profile(profile: QWebEngineProfile) -> None:
|
||||||
lambda url: profile.clearVisitedLinks([url]))
|
lambda url: profile.clearVisitedLinks([url]))
|
||||||
|
|
||||||
_global_settings.init_settings()
|
_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():
|
def _clear_webengine_permissions_json():
|
||||||
|
|
@ -438,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():
|
def _init_default_profile():
|
||||||
"""Init the default QWebEngineProfile."""
|
"""Init the default QWebEngineProfile."""
|
||||||
global default_profile
|
global default_profile
|
||||||
|
default_profile = default_qt_profile()
|
||||||
if machinery.IS_QT6:
|
|
||||||
default_profile = QWebEngineProfile("Default")
|
|
||||||
else:
|
|
||||||
default_profile = QWebEngineProfile.defaultProfile()
|
|
||||||
assert not default_profile.isOffTheRecord()
|
assert not default_profile.isOffTheRecord()
|
||||||
|
|
||||||
assert parsed_user_agent is None # avoid earlier profile initialization
|
assert parsed_user_agent is None # avoid earlier profile initialization
|
||||||
|
|
@ -453,8 +488,19 @@ def _init_default_profile():
|
||||||
|
|
||||||
init_user_agent()
|
init_user_agent()
|
||||||
ua_version = version.qtwebengine_versions()
|
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:
|
if ua_version.webengine != non_ua_version.webengine:
|
||||||
log.init.warning(
|
logger(
|
||||||
"QtWebEngine version mismatch - unexpected behavior might occur, "
|
"QtWebEngine version mismatch - unexpected behavior might occur, "
|
||||||
"please open a bug about this.\n"
|
"please open a bug about this.\n"
|
||||||
f" Early version: {non_ua_version}\n"
|
f" Early version: {non_ua_version}\n"
|
||||||
|
|
@ -495,7 +541,21 @@ def _init_site_specific_quirks():
|
||||||
# "{qt_key}/{qt_version} "
|
# "{qt_key}/{qt_version} "
|
||||||
# "{upstream_browser_key}/{upstream_browser_version_short} "
|
# "{upstream_browser_key}/{upstream_browser_version_short} "
|
||||||
# "Safari/{webkit_version}")
|
# "Safari/{webkit_version}")
|
||||||
firefox_ua = "Mozilla/5.0 ({os_info}; rv:136.0) Gecko/20100101 Firefox/136.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):
|
def maybe_newer_chrome_ua(at_least_version):
|
||||||
"""Return a new UA if our current chrome version isn't at least at_least_version."""
|
"""Return a new UA if our current chrome version isn't at least at_least_version."""
|
||||||
|
|
@ -517,6 +577,7 @@ def _init_site_specific_quirks():
|
||||||
# to keep your account secure" error.
|
# to keep your account secure" error.
|
||||||
# https://github.com/qutebrowser/qutebrowser/issues/5182
|
# https://github.com/qutebrowser/qutebrowser/issues/5182
|
||||||
("ua-google", "https://accounts.google.com/*", firefox_ua),
|
("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:
|
for name, pattern, ua in user_agents:
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import html as html_utils
|
||||||
from typing import cast, Union, Optional
|
from typing import cast, Union, Optional
|
||||||
|
|
||||||
from qutebrowser.qt.core import (pyqtSignal, pyqtSlot, Qt, QPoint, QPointF, QUrl,
|
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.network import QAuthenticator
|
||||||
from qutebrowser.qt.webenginecore import QWebEnginePage, QWebEngineScript, QWebEngineHistory
|
from qutebrowser.qt.webenginecore import QWebEnginePage, QWebEngineScript, QWebEngineHistory
|
||||||
|
|
||||||
|
|
@ -626,7 +626,14 @@ class WebEngineHistoryPrivate(browsertab.AbstractHistoryPrivate):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def deserialize(self, 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):
|
def _load_items_workaround(self, items):
|
||||||
"""WORKAROUND for session loading not working on Qt 5.15.
|
"""WORKAROUND for session loading not working on Qt 5.15.
|
||||||
|
|
@ -933,6 +940,10 @@ class _WebEnginePermissions(QObject):
|
||||||
notif = miscwidgets.FullscreenNotification(self._widget)
|
notif = miscwidgets.FullscreenNotification(self._widget)
|
||||||
notif.set_timeout(timeout)
|
notif.set_timeout(timeout)
|
||||||
notif.show()
|
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')
|
@pyqtSlot(QUrl, 'QWebEnginePage::Feature')
|
||||||
def _on_feature_permission_requested(self, url, feature):
|
def _on_feature_permission_requested(self, url, feature):
|
||||||
|
|
@ -1612,6 +1623,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||||
def _on_navigation_request(self, navigation):
|
def _on_navigation_request(self, navigation):
|
||||||
super()._on_navigation_request(navigation)
|
super()._on_navigation_request(navigation)
|
||||||
|
|
||||||
|
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-103778
|
||||||
local_schemes = {"qute", "file"}
|
local_schemes = {"qute", "file"}
|
||||||
qtwe_ver = version.qtwebengine_versions().webengine
|
qtwe_ver = version.qtwebengine_versions().webengine
|
||||||
if (
|
if (
|
||||||
|
|
@ -1624,7 +1636,6 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||||
(utils.VersionNumber(6, 2) <= qtwe_ver < utils.VersionNumber(6, 2, 5) or
|
(utils.VersionNumber(6, 2) <= qtwe_ver < utils.VersionNumber(6, 2, 5) or
|
||||||
utils.VersionNumber(6, 3) <= qtwe_ver < utils.VersionNumber(6, 3, 1))
|
utils.VersionNumber(6, 3) <= qtwe_ver < utils.VersionNumber(6, 3, 1))
|
||||||
):
|
):
|
||||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-103778
|
|
||||||
log.webview.debug(
|
log.webview.debug(
|
||||||
"Working around blocked request from local page "
|
"Working around blocked request from local page "
|
||||||
f"{self.url().toDisplayString()}"
|
f"{self.url().toDisplayString()}"
|
||||||
|
|
@ -1632,6 +1643,51 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||||
navigation.accepted = False
|
navigation.accepted = False
|
||||||
self.load_url(navigation.url)
|
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:
|
if not navigation.accepted or not navigation.is_main_frame:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
"""A model that proxies access to one or more completion categories."""
|
"""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 collections.abc import MutableSequence
|
||||||
|
|
||||||
from qutebrowser.qt import machinery
|
from qutebrowser.qt import machinery
|
||||||
|
|
@ -91,14 +91,14 @@ class CompletionModel(QAbstractItemModel):
|
||||||
Return: The item flags, or Qt.ItemFlag.NoItemFlags on error.
|
Return: The item flags, or Qt.ItemFlag.NoItemFlags on error.
|
||||||
"""
|
"""
|
||||||
if not index.isValid():
|
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():
|
if index.parent().isValid():
|
||||||
# item
|
# item
|
||||||
return (Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable |
|
return (Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable |
|
||||||
Qt.ItemFlag.ItemNeverHasChildren)
|
Qt.ItemFlag.ItemNeverHasChildren)
|
||||||
else:
|
else:
|
||||||
# category
|
# 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:
|
def index(self, row: int, col: int, parent: QModelIndex = QModelIndex()) -> QModelIndex:
|
||||||
"""Get an index into the model.
|
"""Get an index into the model.
|
||||||
|
|
|
||||||
|
|
@ -333,8 +333,13 @@ class Config(QObject):
|
||||||
pattern, hide_userconfig=hide_userconfig)
|
pattern, hide_userconfig=hide_userconfig)
|
||||||
|
|
||||||
self.changed.emit(opt.name)
|
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:
|
def _check_yaml(self, opt: 'configdata.Option', save_yaml: bool) -> None:
|
||||||
"""Make sure the given option may be set in autoconfig.yml."""
|
"""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 "
|
raise cmdutils.CommandError(":config-list-remove can only be used "
|
||||||
"for lists")
|
"for lists")
|
||||||
|
|
||||||
converted = opt.typ.valtype.from_str(value)
|
|
||||||
|
|
||||||
with self._handle_config_error():
|
with self._handle_config_error():
|
||||||
|
converted = opt.typ.valtype.from_str(value)
|
||||||
option_value = self._config.get_mutable_obj(option)
|
option_value = self._config.get_mutable_obj(option)
|
||||||
|
|
||||||
if converted not in option_value:
|
if converted not in option_value:
|
||||||
|
|
|
||||||
|
|
@ -391,7 +391,7 @@ qt.workarounds.disable_accelerated_2d_canvas:
|
||||||
name: String
|
name: String
|
||||||
valid_values:
|
valid_values:
|
||||||
- always: Disable accelerated 2d canvas
|
- 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
|
- never: Enable accelerated 2d canvas
|
||||||
default: auto
|
default: auto
|
||||||
backend: QtWebEngine
|
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,
|
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.
|
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
|
||||||
|
|
||||||
auto_save.interval:
|
auto_save.interval:
|
||||||
|
|
@ -658,6 +671,7 @@ content.site_specific_quirks.skip:
|
||||||
valid_values:
|
valid_values:
|
||||||
- ua-google
|
- ua-google
|
||||||
- ua-googledocs
|
- ua-googledocs
|
||||||
|
- ua-gnome-gitlab
|
||||||
- js-whatsapp-web
|
- js-whatsapp-web
|
||||||
- js-discord
|
- js-discord
|
||||||
- js-string-replaceall
|
- js-string-replaceall
|
||||||
|
|
@ -772,14 +786,14 @@ content.headers.user_agent:
|
||||||
# Vim-protip: Place your cursor below this comment and run
|
# Vim-protip: Place your cursor below this comment and run
|
||||||
# :r!python scripts/dev/ua_fetch.py
|
# :r!python scripts/dev/ua_fetch.py
|
||||||
- - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36
|
- - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36
|
||||||
(KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36"
|
(KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36"
|
||||||
- Chrome 135 macOS
|
- Chrome 142 macOS
|
||||||
- - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,
|
- - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,
|
||||||
like Gecko) Chrome/135.0.0.0 Safari/537.36"
|
like Gecko) Chrome/142.0.0.0 Safari/537.36"
|
||||||
- Chrome 135 Win10
|
- Chrome 142 Win10
|
||||||
- - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like
|
- - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like
|
||||||
Gecko) Chrome/135.0.0.0 Safari/537.36"
|
Gecko) Chrome/142.0.0.0 Safari/537.36"
|
||||||
- Chrome 135 Linux
|
- Chrome 142 Linux
|
||||||
supports_pattern: true
|
supports_pattern: true
|
||||||
desc: |
|
desc: |
|
||||||
User agent to send.
|
User agent to send.
|
||||||
|
|
@ -1602,6 +1616,7 @@ fileselect.single_file.command:
|
||||||
- ['["xterm", "-e", "nnn", "-p", "{}"]', "nnn in xterm"]
|
- ['["xterm", "-e", "nnn", "-p", "{}"]', "nnn in xterm"]
|
||||||
- ['["xterm", "-e", "fff", "-p", "{}"]', "fff in xterm"]
|
- ['["xterm", "-e", "fff", "-p", "{}"]', "fff in xterm"]
|
||||||
- ['["xterm", "-e", "lf", "-selection-path", "{}"]', "lf in xterm"]
|
- ['["xterm", "-e", "lf", "-selection-path", "{}"]', "lf in xterm"]
|
||||||
|
- ['["xterm", "-e", "yazi", "--chooser-file", "{}"]', "yazi in xterm"]
|
||||||
default: ['xterm', '-e', 'ranger', '--choosefile={}']
|
default: ['xterm', '-e', 'ranger', '--choosefile={}']
|
||||||
desc: >-
|
desc: >-
|
||||||
Command (and arguments) to use for selecting a single file in forms.
|
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", "nnn", "-p", "{}"]', "nnn in xterm"]
|
||||||
- ['["xterm", "-e", "fff", "-p", "{}"]', "fff in xterm"]
|
- ['["xterm", "-e", "fff", "-p", "{}"]', "fff in xterm"]
|
||||||
- ['["xterm", "-e", "lf", "-selection-path", "{}"]', "lf in xterm"]
|
- ['["xterm", "-e", "lf", "-selection-path", "{}"]', "lf in xterm"]
|
||||||
|
- ['["xterm", "-e", "yazi", "--chooser-file", "{}"]', "yazi in xterm"]
|
||||||
default: ['xterm', '-e', 'ranger', '--choosefiles={}']
|
default: ['xterm', '-e', 'ranger', '--choosefiles={}']
|
||||||
desc: >-
|
desc: >-
|
||||||
Command (and arguments) to use for selecting multiple files in forms.
|
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", "ranger", "--choosedir={}"]', "Ranger in xterm"]
|
||||||
- ['["xterm", "-e", "vifm", "--choose-dir", "{}"]', "vifm in xterm"]
|
- ['["xterm", "-e", "vifm", "--choose-dir", "{}"]', "vifm in xterm"]
|
||||||
- ['["xterm", "-e", "nnn", "-p", "{}"]', "nnn in xterm"]
|
- ['["xterm", "-e", "nnn", "-p", "{}"]', "nnn in xterm"]
|
||||||
|
- ['["xterm", "-e", "yazi", "--cwd-file", "{}"]', "yazi in xterm"]
|
||||||
default: ['xterm', '-e', 'ranger', '--choosedir={}']
|
default: ['xterm', '-e', 'ranger', '--choosedir={}']
|
||||||
desc: >-
|
desc: >-
|
||||||
Command (and arguments) to use for selecting a single folder in forms.
|
Command (and arguments) to use for selecting a single folder in forms.
|
||||||
|
|
|
||||||
|
|
@ -156,12 +156,12 @@ def _qtwebengine_features( # noqa: C901
|
||||||
|
|
||||||
if versions.webengine >= utils.VersionNumber(6, 7):
|
if versions.webengine >= utils.VersionNumber(6, 7):
|
||||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-132681
|
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-132681
|
||||||
# TODO adjust if fixed in Qt 6.8.2/.3 or 6.9.0/.1
|
# TODO adjust if fixed in Qt 6.9.2+
|
||||||
disabled_features.append('DocumentPictureInPictureAPI')
|
disabled_features.append('DocumentPictureInPictureAPI')
|
||||||
|
|
||||||
if versions.webengine == utils.VersionNumber(6, 9):
|
if utils.VersionNumber(6, 9) <= versions.webengine < utils.VersionNumber(6, 10, 1):
|
||||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-135787
|
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-135787
|
||||||
# TODO adjust if still present in 6.9.1
|
# and https://bugreports.qt.io/browse/QTBUG-141096
|
||||||
disabled_features.append('PermissionElement')
|
disabled_features.append('PermissionElement')
|
||||||
|
|
||||||
if not config.val.input.media_keys:
|
if not config.val.input.media_keys:
|
||||||
|
|
@ -356,7 +356,21 @@ _WEBENGINE_SETTINGS: dict[str, dict[Any, Optional[_SettingValueType]]] = {
|
||||||
'qt.workarounds.disable_accelerated_2d_canvas': {
|
'qt.workarounds.disable_accelerated_2d_canvas': {
|
||||||
'always': '--disable-accelerated-2d-canvas',
|
'always': '--disable-accelerated-2d-canvas',
|
||||||
'never': None,
|
'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,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ li {
|
||||||
the required packages for pdf.js are also installed.
|
the required packages for pdf.js are also installed.
|
||||||
<br/>
|
<br/>
|
||||||
The package is named
|
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.
|
and <a href="https://packages.debian.org/bullseye/libjs-pdf"><b>libjs-pdf</b></a> on Debian.
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -191,36 +191,51 @@ window._qutebrowser.webelem = (function() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
funcs.find_css = (selector, only_visible) => {
|
// Recursively finds elements from DOM that have a shadowRoot
|
||||||
let elems;
|
// and returns the shadow roots in a list
|
||||||
|
function find_shadow_roots(container = document) {
|
||||||
|
const roots = [];
|
||||||
|
|
||||||
try {
|
for (const elem of container.querySelectorAll("*")) {
|
||||||
elems = document.querySelectorAll(selector);
|
if (elem.shadowRoot) {
|
||||||
} catch (ex) {
|
roots.push(elem.shadowRoot, ...find_shadow_roots(elem.shadowRoot));
|
||||||
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]));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recurse into frames and add them
|
return roots;
|
||||||
for (let i = 0; i < subelem_frames.length; i++) {
|
}
|
||||||
if (iframe_same_domain(subelem_frames[i])) {
|
|
||||||
const frame = subelem_frames[i];
|
funcs.find_css = (selector, only_visible) => {
|
||||||
const subelems = frame.document.
|
// Find all places where we need to look for elements:
|
||||||
querySelectorAll(selector);
|
const containers = [[document, null]];
|
||||||
for (let elem_num = 0; elem_num < subelems.length; ++elem_num) {
|
// Same-domain iframes
|
||||||
if (!only_visible ||
|
for (const frame of Array.from(window.frames)) {
|
||||||
is_visible(subelems[elem_num], frame)) {
|
if (iframe_same_domain(frame)) {
|
||||||
out.push(serialize_elem(subelems[elem_num], 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._completion.on_clear_completion_selection)
|
||||||
self.status.cmd.hide_completion.connect(
|
self.status.cmd.hide_completion.connect(
|
||||||
self._completion.hide)
|
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):
|
def _set_decoration(self, hidden):
|
||||||
"""Set the visibility of the window decoration via Qt."""
|
"""Set the visibility of the window decoration via Qt."""
|
||||||
|
|
|
||||||
|
|
@ -342,9 +342,9 @@ class PromptContainer(QWidget):
|
||||||
"""Leave KEY_MODE whenever a prompt is aborted."""
|
"""Leave KEY_MODE whenever a prompt is aborted."""
|
||||||
try:
|
try:
|
||||||
modeman.leave(self._win_id, key_mode, 'aborted', maybe=True)
|
modeman.leave(self._win_id, key_mode, 'aborted', maybe=True)
|
||||||
except objreg.RegistryUnavailableError:
|
except (objreg.RegistryUnavailableError, RuntimeError):
|
||||||
# window was deleted: ignore
|
# window was deleted: ignore
|
||||||
pass
|
log.prompt.debug(f"Ignoring leaving {key_mode} as window was deleted")
|
||||||
|
|
||||||
@pyqtSlot(usertypes.KeyMode)
|
@pyqtSlot(usertypes.KeyMode)
|
||||||
def _on_prompt_done(self, key_mode):
|
def _on_prompt_done(self, key_mode):
|
||||||
|
|
@ -654,6 +654,12 @@ class FilenamePrompt(_BasePrompt):
|
||||||
|
|
||||||
"""A prompt for a filename."""
|
"""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):
|
def __init__(self, question, parent=None):
|
||||||
super().__init__(question, parent)
|
super().__init__(question, parent)
|
||||||
self._init_texts(question)
|
self._init_texts(question)
|
||||||
|
|
@ -753,7 +759,7 @@ class FilenamePrompt(_BasePrompt):
|
||||||
self._file_model = QFileSystemModel(self)
|
self._file_model = QFileSystemModel(self)
|
||||||
|
|
||||||
# avoid icon and mime type lookups, they are slow in Qt6
|
# 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.setModel(self._file_model)
|
||||||
self._file_view.clicked.connect(self._insert_path)
|
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
|
moved: Emitted when the statusbar has moved, so the completion widget
|
||||||
can move to the right position.
|
can move to the right position.
|
||||||
arg: The new position.
|
arg: The new position.
|
||||||
|
release_focus: Emitted just before the statusbar is hidden.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
resized = pyqtSignal('QRect')
|
resized = pyqtSignal('QRect')
|
||||||
moved = pyqtSignal('QPoint')
|
moved = pyqtSignal('QPoint')
|
||||||
|
release_focus = pyqtSignal()
|
||||||
|
|
||||||
STYLESHEET = _generate_stylesheet()
|
STYLESHEET = _generate_stylesheet()
|
||||||
|
|
||||||
|
|
@ -365,6 +367,7 @@ class StatusBar(QWidget):
|
||||||
def _hide_cmd_widget(self):
|
def _hide_cmd_widget(self):
|
||||||
"""Show temporary text instead of command widget."""
|
"""Show temporary text instead of command widget."""
|
||||||
log.statusbar.debug("Hiding cmd widget")
|
log.statusbar.debug("Hiding cmd widget")
|
||||||
|
self.release_focus.emit()
|
||||||
self._stack.setCurrentWidget(self.txt)
|
self._stack.setCurrentWidget(self.txt)
|
||||||
self.maybe_hide()
|
self.maybe_hide()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -422,6 +422,37 @@ class _BackendProblemChecker:
|
||||||
|
|
||||||
raise utils.Unreachable
|
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:
|
def _assert_backend(self, backend: usertypes.Backend) -> None:
|
||||||
assert objects.backend == backend, objects.backend
|
assert objects.backend == backend, objects.backend
|
||||||
|
|
||||||
|
|
@ -433,6 +464,7 @@ class _BackendProblemChecker:
|
||||||
self._handle_ssl_support()
|
self._handle_ssl_support()
|
||||||
self._handle_serviceworker_nuking()
|
self._handle_serviceworker_nuking()
|
||||||
self._check_software_rendering()
|
self._check_software_rendering()
|
||||||
|
self._force_wayland_hardware_acceleration()
|
||||||
self._confirm_chromium_version_changes()
|
self._confirm_chromium_version_changes()
|
||||||
else:
|
else:
|
||||||
self._assert_backend(usertypes.Backend.QtWebKit)
|
self._assert_backend(usertypes.Backend.QtWebKit)
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,8 @@ def parse_fatal_stacktrace(text):
|
||||||
lines = [
|
lines = [
|
||||||
r'(?P<type>Fatal Python error|Windows fatal exception): (?P<msg>.*)',
|
r'(?P<type>Fatal Python error|Windows fatal exception): (?P<msg>.*)',
|
||||||
r' *',
|
r' *',
|
||||||
r'(Current )?[Tt]hread [^ ]* \(most recent call first\): *',
|
r'(Current )?[Tt]hread .* \(most recent call first\): *',
|
||||||
r' File ".*", line \d+ in (?P<func>.*)',
|
r' (File ".*", line \d+ in (?P<func>.*)|<no Python frame>)',
|
||||||
]
|
]
|
||||||
m = re.search('\n'.join(lines), text)
|
m = re.search('\n'.join(lines), text)
|
||||||
if m is None:
|
if m is None:
|
||||||
|
|
@ -58,7 +58,7 @@ def parse_fatal_stacktrace(text):
|
||||||
else:
|
else:
|
||||||
msg = m.group('msg')
|
msg = m.group('msg')
|
||||||
typ = m.group('type')
|
typ = m.group('type')
|
||||||
func = m.group('func')
|
func = m.group('func') or ''
|
||||||
if typ == 'Windows fatal exception':
|
if typ == 'Windows fatal exception':
|
||||||
msg = 'Windows ' + msg
|
msg = 'Windows ' + msg
|
||||||
return msg, func
|
return msg, func
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import datetime
|
||||||
from typing import NoReturn
|
from typing import NoReturn
|
||||||
try:
|
try:
|
||||||
import tkinter
|
import tkinter
|
||||||
|
import tkinter.messagebox
|
||||||
except ImportError:
|
except ImportError:
|
||||||
tkinter = None # type: ignore[assignment]
|
tkinter = None # type: ignore[assignment]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
This entire file is a giant WORKAROUND for https://bugreports.qt.io/browse/QTBUG-114334.
|
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 enum
|
||||||
import ctypes
|
import ctypes
|
||||||
import ctypes.util
|
import ctypes.util
|
||||||
|
|
@ -16,7 +16,7 @@ from qutebrowser.qt import sip, machinery
|
||||||
from qutebrowser.qt.core import QAbstractNativeEventFilter, QByteArray, qVersion
|
from qutebrowser.qt.core import QAbstractNativeEventFilter, QByteArray, qVersion
|
||||||
|
|
||||||
from qutebrowser.misc import objects
|
from qutebrowser.misc import objects
|
||||||
from qutebrowser.utils import log
|
from qutebrowser.utils import log, qtutils
|
||||||
|
|
||||||
|
|
||||||
# Needs to be saved to avoid garbage collection
|
# 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
|
# Tuple because PyQt uses the second value as the *result out-pointer, which
|
||||||
# according to the Qt documentation is only used on Windows.
|
# according to the Qt documentation is only used on Windows.
|
||||||
_PASS_EVENT_RET = (False, cast(_PointerRetType, 0))
|
_PASS_EVENT_RET = (False, qtutils.maybe_cast(_PointerRetType, machinery.IS_QT6, 0))
|
||||||
_FILTER_EVENT_RET = (True, cast(_PointerRetType, 0))
|
_FILTER_EVENT_RET = (True, qtutils.maybe_cast(_PointerRetType, machinery.IS_QT6, 0))
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ instead of crashing.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
import shutil
|
import shutil
|
||||||
import pathlib
|
import pathlib
|
||||||
import dataclasses
|
import dataclasses
|
||||||
|
|
@ -35,9 +36,13 @@ from collections.abc import Iterator
|
||||||
|
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.misc import binparsing, objects
|
from qutebrowser.misc import binparsing, objects
|
||||||
|
from qutebrowser.qt import core
|
||||||
from qutebrowser.utils import qtutils, standarddir, version, utils, log, message
|
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 = [
|
HANGOUTS_IDS = [
|
||||||
# Linux
|
# Linux
|
||||||
47222, # QtWebEngine 6.9 Beta 3
|
47222, # QtWebEngine 6.9 Beta 3
|
||||||
|
|
@ -57,7 +62,11 @@ PAK_VERSION = 5
|
||||||
RESOURCES_ENV_VAR = "QTWEBENGINE_RESOURCES_PATH"
|
RESOURCES_ENV_VAR = "QTWEBENGINE_RESOURCES_PATH"
|
||||||
DISABLE_ENV_VAR = "QUTE_DISABLE_PAKJOY"
|
DISABLE_ENV_VAR = "QUTE_DISABLE_PAKJOY"
|
||||||
CACHE_DIR_NAME = "webengine_resources_pak_quirk"
|
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/*"
|
TARGET_URL = b"https://*.google.com/*"
|
||||||
REPLACEMENT_URL = b"https://qute.invalid/*"
|
REPLACEMENT_URL = b"https://qute.invalid/*"
|
||||||
|
|
@ -222,7 +231,7 @@ def copy_webengine_resources() -> Optional[pathlib.Path]:
|
||||||
)
|
)
|
||||||
# https://github.com/qutebrowser/qutebrowser/issues/8257
|
# https://github.com/qutebrowser/qutebrowser/issues/8257
|
||||||
or config.val.qt.workarounds.disable_hangouts_extension
|
or config.val.qt.workarounds.disable_hangouts_extension
|
||||||
):
|
) or hasattr(QWebEngineProfile, "extensionManager"): # Qt 6.10+
|
||||||
# No patching needed
|
# No patching needed
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
@ -303,3 +312,16 @@ def patch_webengine() -> Iterator[None]:
|
||||||
del os.environ[RESOURCES_ENV_VAR]
|
del os.environ[RESOURCES_ENV_VAR]
|
||||||
else:
|
else:
|
||||||
os.environ[RESOURCES_ENV_VAR] = old_value
|
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}")
|
||||||
|
|
@ -16,6 +16,7 @@ from typing import (
|
||||||
from collections.abc import Mapping, MutableSequence, Sequence, Callable
|
from collections.abc import Mapping, MutableSequence, Sequence, Callable
|
||||||
|
|
||||||
from qutebrowser.qt.core import Qt, QEvent, QMetaMethod, QObject, pyqtBoundSignal
|
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.utils import log, utils, qtutils, objreg
|
||||||
from qutebrowser.misc import objects
|
from qutebrowser.misc import objects
|
||||||
|
|
@ -345,9 +346,9 @@ class log_time: # noqa: N801,N806 pylint: disable=invalid-name
|
||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
def _get_widgets() -> Sequence[str]:
|
def _get_widgets(qapp: QApplication) -> Sequence[str]:
|
||||||
"""Get a string list of all widgets."""
|
"""Get a string list of all widgets."""
|
||||||
widgets = objects.qapp.allWidgets()
|
widgets = qapp.allWidgets()
|
||||||
widgets.sort(key=repr)
|
widgets.sort(key=repr)
|
||||||
return [repr(w) for w in widgets]
|
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)
|
_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."""
|
"""Get all children of an object recursively as a string."""
|
||||||
|
if qapp is None:
|
||||||
|
assert objects.qapp is not None
|
||||||
|
qapp = objects.qapp
|
||||||
output = ['']
|
output = ['']
|
||||||
widget_lines = _get_widgets()
|
widget_lines = _get_widgets(qapp)
|
||||||
widget_lines = [' ' + e for e in widget_lines]
|
widget_lines = [' ' + e for e in widget_lines]
|
||||||
widget_lines.insert(0, "Qt widgets - {} objects:".format(
|
widget_lines.insert(0, "Qt widgets - {} objects:".format(
|
||||||
len(widget_lines)))
|
len(widget_lines)))
|
||||||
output += widget_lines
|
output += widget_lines
|
||||||
|
|
||||||
if start_obj is None:
|
if start_obj is None:
|
||||||
start_obj = objects.qapp
|
start_obj = qapp
|
||||||
|
|
||||||
pyqt_lines: list[str] = []
|
pyqt_lines: list[str] = []
|
||||||
_get_pyqt_objects(pyqt_lines, start_obj)
|
_get_pyqt_objects(pyqt_lines, start_obj)
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,9 @@ def qt_message_handler(msg_type: qtcore.QtMsgType,
|
||||||
"QCoreApplication is created.",
|
"QCoreApplication is created.",
|
||||||
# Qt 6.4 beta 1: https://bugreports.qt.io/browse/QTBUG-104741
|
# Qt 6.4 beta 1: https://bugreports.qt.io/browse/QTBUG-104741
|
||||||
"GL format 0 is not supported",
|
"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
|
# not using utils.is_mac here, because we can't be sure we can successfully
|
||||||
# import the utils module here.
|
# import the utils module here.
|
||||||
|
|
|
||||||
|
|
@ -749,3 +749,14 @@ else:
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
QT_NONE: None = None
|
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
|
||||||
|
|
|
||||||
|
|
@ -722,6 +722,11 @@ def guess_mimetype(filename: str, fallback: bool = False) -> str:
|
||||||
fallback: Fall back to application/octet-stream if unknown.
|
fallback: Fall back to application/octet-stream if unknown.
|
||||||
"""
|
"""
|
||||||
mimetype, _encoding = mimetypes.guess_type(filename)
|
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 mimetype is None:
|
||||||
if fallback:
|
if fallback:
|
||||||
return 'application/octet-stream'
|
return 'application/octet-stream'
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ except ImportError: # pragma: no cover
|
||||||
import qutebrowser
|
import qutebrowser
|
||||||
from qutebrowser.utils import (log, utils, standarddir, usertypes, message, resources,
|
from qutebrowser.utils import (log, utils, standarddir, usertypes, message, resources,
|
||||||
qtutils)
|
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.browser import pdfjs
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
@ -322,8 +322,8 @@ class ModuleInfo:
|
||||||
except (ImportError, ValueError):
|
except (ImportError, ValueError):
|
||||||
self._installed = False
|
self._installed = False
|
||||||
return
|
return
|
||||||
else:
|
|
||||||
self._installed = True
|
self._installed = True
|
||||||
|
|
||||||
for attribute_name in self._version_attributes:
|
for attribute_name in self._version_attributes:
|
||||||
if hasattr(module, attribute_name):
|
if hasattr(module, attribute_name):
|
||||||
|
|
@ -332,6 +332,13 @@ class ModuleInfo:
|
||||||
self._version = str(version)
|
self._version = str(version)
|
||||||
break
|
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
|
self._initialized = True
|
||||||
|
|
||||||
def get_version(self) -> Optional[str]:
|
def get_version(self) -> Optional[str]:
|
||||||
|
|
@ -372,7 +379,7 @@ class ModuleInfo:
|
||||||
|
|
||||||
version = self.get_version()
|
version = self.get_version()
|
||||||
if version is None:
|
if version is None:
|
||||||
return f'{self.name}: yes'
|
return f'{self.name}: unknown'
|
||||||
|
|
||||||
text = f'{self.name}: {version}'
|
text = f'{self.name}: {version}'
|
||||||
if self.is_outdated():
|
if self.is_outdated():
|
||||||
|
|
@ -383,7 +390,7 @@ class ModuleInfo:
|
||||||
def _create_module_info() -> dict[str, ModuleInfo]:
|
def _create_module_info() -> dict[str, ModuleInfo]:
|
||||||
packages = [
|
packages = [
|
||||||
('colorama', ['VERSION', '__version__']),
|
('colorama', ['VERSION', '__version__']),
|
||||||
('jinja2', ['__version__']),
|
('jinja2', []),
|
||||||
('pygments', ['__version__']),
|
('pygments', ['__version__']),
|
||||||
('yaml', ['__version__']),
|
('yaml', ['__version__']),
|
||||||
('adblock', ['__version__'], "0.3.2"),
|
('adblock', ['__version__'], "0.3.2"),
|
||||||
|
|
@ -487,7 +494,7 @@ def _pdfjs_version() -> str:
|
||||||
else:
|
else:
|
||||||
pdfjs_file = pdfjs_file.decode('utf-8')
|
pdfjs_file = pdfjs_file.decode('utf-8')
|
||||||
version_re = re.compile(
|
version_re = re.compile(
|
||||||
r"""^ *(PDFJS\.version|(var|const) pdfjsVersion) = ['"](?P<version>[^'"]+)['"];$""",
|
r"""^ *(PDFJS\.version|(var|const|\*) pdfjsVersion) = ['"]?(?P<version>[^'"\n]+)['"]?;?$""",
|
||||||
re.MULTILINE)
|
re.MULTILINE)
|
||||||
|
|
||||||
match = version_re.search(pdfjs_file)
|
match = version_re.search(pdfjs_file)
|
||||||
|
|
@ -552,6 +559,7 @@ class WebEngineVersions:
|
||||||
118: '118.0.5993.220', # 2024-01-25, Qt 6.7
|
118: '118.0.5993.220', # 2024-01-25, Qt 6.7
|
||||||
122: '122.0.6261.171', # 2024-04-15, Qt 6.8
|
122: '122.0.6261.171', # 2024-04-15, Qt 6.8
|
||||||
130: '130.0.6723.192', # 2025-01-06, Qt 6.9
|
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/
|
# Dates based on https://chromereleases.googleblog.com/
|
||||||
|
|
@ -594,6 +602,7 @@ class WebEngineVersions:
|
||||||
utils.VersionNumber(5, 15, 16): (_BASES[87], '119.0.6045.123'), # 2023-11-07
|
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, 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, 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
|
## Qt 6.2
|
||||||
|
|
@ -643,6 +652,13 @@ class WebEngineVersions:
|
||||||
|
|
||||||
## Qt 6.9
|
## Qt 6.9
|
||||||
utils.VersionNumber(6, 9): (_BASES[130], '133.0.6943.141'), # 2025-02-25
|
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:
|
def __post_init__(self) -> None:
|
||||||
|
|
@ -911,6 +927,51 @@ def _backend() -> str:
|
||||||
raise utils.Unreachable(objects.backend)
|
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:
|
def _uptime() -> datetime.timedelta:
|
||||||
time_delta = datetime.datetime.now() - objects.qapp.launch_time
|
time_delta = datetime.datetime.now() - objects.qapp.launch_time
|
||||||
# Round off microseconds
|
# Round off microseconds
|
||||||
|
|
@ -960,13 +1021,15 @@ def version_info() -> str:
|
||||||
if QSslSocket.supportsSsl() else 'no'),
|
if QSslSocket.supportsSsl() else 'no'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
lines += _webengine_extensions()
|
||||||
|
|
||||||
if objects.qapp:
|
if objects.qapp:
|
||||||
style = objects.qapp.style()
|
style = objects.qapp.style()
|
||||||
assert style is not None
|
assert style is not None
|
||||||
metaobj = style.metaObject()
|
metaobj = style.metaObject()
|
||||||
assert metaobj is not None
|
assert metaobj is not None
|
||||||
lines.append('Style: {}'.format(metaobj.className()))
|
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()))
|
lines.append('OpenGL: {}'.format(opengl_info()))
|
||||||
|
|
||||||
importpath = os.path.dirname(os.path.abspath(qutebrowser.__file__))
|
importpath = os.path.dirname(os.path.abspath(qutebrowser.__file__))
|
||||||
|
|
@ -1135,6 +1198,19 @@ def opengl_info() -> Optional[OpenGLInfo]: # pragma: no cover
|
||||||
old_context.makeCurrent(old_surface)
|
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:
|
def pastebin_version(pbclient: pastebin.PastebinClient = None) -> None:
|
||||||
"""Pastebin the version and log the url to messages."""
|
"""Pastebin the version and log the url to messages."""
|
||||||
def _yank_url(url: str) -> None:
|
def _yank_url(url: str) -> None:
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@
|
||||||
adblock==0.6.0
|
adblock==0.6.0
|
||||||
colorama==0.4.6
|
colorama==0.4.6
|
||||||
Jinja2==3.1.6
|
Jinja2==3.1.6
|
||||||
MarkupSafe==3.0.2
|
MarkupSafe==3.0.3
|
||||||
Pygments==2.19.1
|
Pygments==2.19.2
|
||||||
PyYAML==6.0.2
|
PyYAML==6.0.3
|
||||||
# Unpinned due to recompile_requirements.py limitations
|
# Unpinned due to recompile_requirements.py limitations
|
||||||
pyobjc-core ; sys_platform=="darwin"
|
pyobjc-core ; sys_platform=="darwin"
|
||||||
pyobjc-framework-Cocoa ; sys_platform=="darwin"
|
pyobjc-framework-Cocoa ; sys_platform=="darwin"
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
"""Build a new release."""
|
"""Build a new release."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
@ -20,7 +21,8 @@ import platform
|
||||||
import collections
|
import collections
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import re
|
import re
|
||||||
from typing import Optional
|
import http
|
||||||
|
from typing import Optional, TYPE_CHECKING
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -28,6 +30,12 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
import github3
|
||||||
|
import github3.repos.release
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
REPO_ROOT = pathlib.Path(__file__).resolve().parents[2]
|
REPO_ROOT = pathlib.Path(__file__).resolve().parents[2]
|
||||||
sys.path.insert(0, str(REPO_ROOT))
|
sys.path.insert(0, str(REPO_ROOT))
|
||||||
|
|
||||||
|
|
@ -126,7 +134,7 @@ def _smoke_test_run(
|
||||||
return subprocess.run(argv, check=True, capture_output=True)
|
return subprocess.run(argv, check=True, capture_output=True)
|
||||||
|
|
||||||
|
|
||||||
def smoke_test(executable: pathlib.Path, debug: bool) -> None:
|
def smoke_test(executable: pathlib.Path, debug_build: bool) -> None:
|
||||||
"""Try starting the given qutebrowser executable."""
|
"""Try starting the given qutebrowser executable."""
|
||||||
stdout_whitelist = []
|
stdout_whitelist = []
|
||||||
stderr_whitelist = [
|
stderr_whitelist = [
|
||||||
|
|
@ -173,6 +181,13 @@ def smoke_test(executable: pathlib.Path, debug: bool) -> None:
|
||||||
# Qt 6.7, only seen on macos for some reason
|
# Qt 6.7, only seen on macos for some reason
|
||||||
(r'.*Path override failed for key base::DIR_APP_DICTIONARIES '
|
(r'.*Path override failed for key base::DIR_APP_DICTIONARIES '
|
||||||
r"and path '.*/qtwebengine_dictionaries'"),
|
r"and path '.*/qtwebengine_dictionaries'"),
|
||||||
|
|
||||||
|
# Qt 6.9 on macOS
|
||||||
|
r'Compositor returned null texture',
|
||||||
|
|
||||||
|
# Qt 6.10
|
||||||
|
(r'\[.*:ERROR:service_utils.cc\([0-9]*\)\] '
|
||||||
|
r'Skia Graphite backend = "" not found - falling back to Ganesh!'),
|
||||||
])
|
])
|
||||||
elif IS_WINDOWS:
|
elif IS_WINDOWS:
|
||||||
stderr_whitelist.extend([
|
stderr_whitelist.extend([
|
||||||
|
|
@ -181,10 +196,27 @@ def smoke_test(executable: pathlib.Path, debug: bool) -> None:
|
||||||
(r'\[.*:ERROR:dxva_video_decode_accelerator_win.cc\(\d+\)\] '
|
(r'\[.*:ERROR:dxva_video_decode_accelerator_win.cc\(\d+\)\] '
|
||||||
r'DXVAVDA fatal error: could not LoadLibrary: .*: The specified '
|
r'DXVAVDA fatal error: could not LoadLibrary: .*: The specified '
|
||||||
r'module could not be found. \(0x7E\)'),
|
r'module could not be found. \(0x7E\)'),
|
||||||
|
# Qt 6.10
|
||||||
|
(r'\[.*:ERROR:direct_composition_support.cc\([0-9]*\)\] '
|
||||||
|
r'GetGpuDriverOverlayInfo: Failed to retrieve video device'),
|
||||||
|
(r'\[.*:ERROR:direct_composition_support.cc\([0-9]*\)\] QueryInterface '
|
||||||
|
r'to IDCompositionDevice4 failed: No such interface supported '
|
||||||
|
r'\(0x80004002\)'),
|
||||||
])
|
])
|
||||||
|
|
||||||
proc = _smoke_test_run(executable)
|
try:
|
||||||
if debug:
|
proc = _smoke_test_run(executable)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"Smoke test failed: {e}, running with --debug")
|
||||||
|
smoke_test_debug(
|
||||||
|
executable,
|
||||||
|
original_stdout=e.stdout.decode("utf-8"),
|
||||||
|
original_stderr=e.stderr.decode("utf-8"),
|
||||||
|
issue_description=str(e),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if debug_build:
|
||||||
print("Skipping output check for debug build")
|
print("Skipping output check for debug build")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -193,48 +225,64 @@ def smoke_test(executable: pathlib.Path, debug: bool) -> None:
|
||||||
|
|
||||||
if stdout or stderr:
|
if stdout or stderr:
|
||||||
print("Unexpected output, running with --debug")
|
print("Unexpected output, running with --debug")
|
||||||
proc = _smoke_test_run(executable, '--debug')
|
smoke_test_debug(
|
||||||
debug_stdout = proc.stdout.decode('utf-8')
|
executable,
|
||||||
debug_stderr = proc.stderr.decode('utf-8')
|
original_stdout=stdout,
|
||||||
|
original_stderr=stderr,
|
||||||
|
issue_description="Unexpected output",
|
||||||
|
)
|
||||||
|
|
||||||
lines = [
|
|
||||||
"Unexpected output!",
|
def smoke_test_debug(
|
||||||
|
executable: pathlib.Path,
|
||||||
|
*,
|
||||||
|
original_stdout: str,
|
||||||
|
original_stderr: str,
|
||||||
|
issue_description: str,
|
||||||
|
) -> None:
|
||||||
|
"""Run smoke test in debug mode to get more output."""
|
||||||
|
proc = _smoke_test_run(executable, '--debug')
|
||||||
|
debug_stdout = proc.stdout.decode('utf-8')
|
||||||
|
debug_stderr = proc.stderr.decode('utf-8')
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
issue_description,
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
if original_stdout:
|
||||||
|
lines += [
|
||||||
|
"stdout",
|
||||||
|
"------",
|
||||||
|
"",
|
||||||
|
original_stdout,
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
if original_stderr:
|
||||||
|
lines += [
|
||||||
|
"stderr",
|
||||||
|
"------",
|
||||||
|
"",
|
||||||
|
original_stderr,
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
if debug_stdout:
|
||||||
|
lines += [
|
||||||
|
"debug rerun stdout",
|
||||||
|
"------------------",
|
||||||
|
"",
|
||||||
|
debug_stdout,
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
if debug_stderr:
|
||||||
|
lines += [
|
||||||
|
"debug rerun stderr",
|
||||||
|
"------------------",
|
||||||
|
"",
|
||||||
|
debug_stderr,
|
||||||
"",
|
"",
|
||||||
]
|
]
|
||||||
if stdout:
|
|
||||||
lines += [
|
|
||||||
"stdout",
|
|
||||||
"------",
|
|
||||||
"",
|
|
||||||
stdout,
|
|
||||||
"",
|
|
||||||
]
|
|
||||||
if stderr:
|
|
||||||
lines += [
|
|
||||||
"stderr",
|
|
||||||
"------",
|
|
||||||
"",
|
|
||||||
stderr,
|
|
||||||
"",
|
|
||||||
]
|
|
||||||
if debug_stdout:
|
|
||||||
lines += [
|
|
||||||
"debug rerun stdout",
|
|
||||||
"------------------",
|
|
||||||
"",
|
|
||||||
debug_stdout,
|
|
||||||
"",
|
|
||||||
]
|
|
||||||
if debug_stderr:
|
|
||||||
lines += [
|
|
||||||
"debug rerun stderr",
|
|
||||||
"------------------",
|
|
||||||
"",
|
|
||||||
debug_stderr,
|
|
||||||
"",
|
|
||||||
]
|
|
||||||
|
|
||||||
raise Exception("\n".join(lines)) # pylint: disable=broad-exception-raised
|
raise Exception("\n".join(lines)) # pylint: disable=broad-exception-raised
|
||||||
|
|
||||||
|
|
||||||
def verify_windows_exe(exe_path: pathlib.Path) -> None:
|
def verify_windows_exe(exe_path: pathlib.Path) -> None:
|
||||||
|
|
@ -290,7 +338,7 @@ def build_mac(
|
||||||
dist_path = pathlib.Path("dist")
|
dist_path = pathlib.Path("dist")
|
||||||
|
|
||||||
utils.print_title("Running pre-dmg smoke test")
|
utils.print_title("Running pre-dmg smoke test")
|
||||||
smoke_test(_mac_bin_path(dist_path), debug=debug)
|
smoke_test(_mac_bin_path(dist_path), debug_build=debug)
|
||||||
|
|
||||||
if skip_packaging:
|
if skip_packaging:
|
||||||
return []
|
return []
|
||||||
|
|
@ -313,7 +361,7 @@ def build_mac(
|
||||||
subprocess.run(['hdiutil', 'attach', dmg_path,
|
subprocess.run(['hdiutil', 'attach', dmg_path,
|
||||||
'-mountpoint', tmp_path], check=True)
|
'-mountpoint', tmp_path], check=True)
|
||||||
try:
|
try:
|
||||||
smoke_test(_mac_bin_path(tmp_path), debug=debug)
|
smoke_test(_mac_bin_path(tmp_path), debug_build=debug)
|
||||||
finally:
|
finally:
|
||||||
print("Waiting 10s for dmg to be detachable...")
|
print("Waiting 10s for dmg to be detachable...")
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
|
|
@ -372,7 +420,7 @@ def _build_windows_single(
|
||||||
verify_windows_exe(exe_path)
|
verify_windows_exe(exe_path)
|
||||||
|
|
||||||
utils.print_title("Running smoke test")
|
utils.print_title("Running smoke test")
|
||||||
smoke_test(exe_path, debug=debug)
|
smoke_test(exe_path, debug_build=debug)
|
||||||
|
|
||||||
if skip_packaging:
|
if skip_packaging:
|
||||||
return []
|
return []
|
||||||
|
|
@ -548,11 +596,36 @@ def read_github_token(
|
||||||
return token
|
return token
|
||||||
|
|
||||||
|
|
||||||
|
def _github_find_release(
|
||||||
|
gh: github3.GitHub, tag: str, experimental: bool
|
||||||
|
) -> github3.repos.release.Release:
|
||||||
|
if experimental:
|
||||||
|
repo = gh.repository('qutebrowser', 'experiments')
|
||||||
|
else:
|
||||||
|
repo = gh.repository('qutebrowser', 'qutebrowser')
|
||||||
|
assert repo is not None
|
||||||
|
|
||||||
|
for release in repo.releases():
|
||||||
|
if release.tag_name == tag:
|
||||||
|
return release
|
||||||
|
|
||||||
|
releases = ", ".join(r.tag_name for r in repo.releases())
|
||||||
|
raise Exception( # pylint: disable=broad-exception-raised
|
||||||
|
f"No release found for {tag!r} in {repo.full_name}, found: {releases}")
|
||||||
|
|
||||||
|
|
||||||
|
def _github_assets(
|
||||||
|
release: github3.repos.release.Release, artifact: Artifact
|
||||||
|
) -> list[github3.repos.release.Asset]:
|
||||||
|
return [asset for asset in release.assets() if asset.name == artifact.path.name]
|
||||||
|
|
||||||
|
|
||||||
def github_upload(
|
def github_upload(
|
||||||
artifacts: list[Artifact],
|
artifacts: list[Artifact],
|
||||||
tag: str,
|
tag: str,
|
||||||
gh_token: str,
|
gh_token: str,
|
||||||
experimental: bool,
|
experimental: bool,
|
||||||
|
skip_if_exists: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Upload the given artifacts to GitHub.
|
"""Upload the given artifacts to GitHub.
|
||||||
|
|
||||||
|
|
@ -561,35 +634,25 @@ def github_upload(
|
||||||
tag: The name of the release tag
|
tag: The name of the release tag
|
||||||
gh_token: The GitHub token to use
|
gh_token: The GitHub token to use
|
||||||
experimental: Upload to the experiments repo
|
experimental: Upload to the experiments repo
|
||||||
|
skip_if_exists: Skip uploading artifacts that already exist
|
||||||
"""
|
"""
|
||||||
# pylint: disable=broad-exception-raised
|
|
||||||
import github3
|
import github3
|
||||||
import github3.exceptions
|
import github3.exceptions
|
||||||
utils.print_title("Uploading to github...")
|
utils.print_title("Uploading to github...")
|
||||||
|
|
||||||
gh = github3.login(token=gh_token)
|
gh = github3.login(token=gh_token)
|
||||||
|
assert gh is not None
|
||||||
if experimental:
|
release = _github_find_release(gh=gh, tag=tag, experimental=experimental)
|
||||||
repo = gh.repository('qutebrowser', 'experiments')
|
|
||||||
else:
|
|
||||||
repo = gh.repository('qutebrowser', 'qutebrowser')
|
|
||||||
|
|
||||||
release = None # to satisfy pylint
|
|
||||||
for release in repo.releases():
|
|
||||||
if release.tag_name == tag:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
releases = ", ".join(r.tag_name for r in repo.releases())
|
|
||||||
raise Exception(
|
|
||||||
f"No release found for {tag!r} in {repo.full_name}, found: {releases}")
|
|
||||||
|
|
||||||
for artifact in artifacts:
|
for artifact in artifacts:
|
||||||
|
if _github_assets(release, artifact) and skip_if_exists:
|
||||||
|
print(f"Artifact {artifact.path.name} already exists, skipping")
|
||||||
|
continue
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
print(f"Uploading {artifact.path}")
|
print(f"Uploading {artifact.path}")
|
||||||
|
|
||||||
assets = [asset for asset in release.assets()
|
if (assets := _github_assets(release, artifact)):
|
||||||
if asset.name == artifact.path.name]
|
|
||||||
if assets:
|
|
||||||
print(f"Assets already exist: {assets}")
|
print(f"Assets already exist: {assets}")
|
||||||
|
|
||||||
if utils.ON_CI:
|
if utils.ON_CI:
|
||||||
|
|
@ -617,9 +680,7 @@ def github_upload(
|
||||||
|
|
||||||
print("Retrying!")
|
print("Retrying!")
|
||||||
|
|
||||||
assets = [asset for asset in release.assets()
|
if (assets := _github_assets(release, artifact)):
|
||||||
if asset.name == artifact.path.name]
|
|
||||||
if assets:
|
|
||||||
stray_asset = assets[0]
|
stray_asset = assets[0]
|
||||||
print(f"Deleting stray asset {stray_asset.name}")
|
print(f"Deleting stray asset {stray_asset.name}")
|
||||||
stray_asset.delete()
|
stray_asset.delete()
|
||||||
|
|
@ -627,12 +688,29 @@ def github_upload(
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def pypi_upload(artifacts: list[Artifact], experimental: bool) -> None:
|
def check_pypi_exists(version: str) -> bool:
|
||||||
|
"""Check whether the given version exists on PyPI."""
|
||||||
|
response = requests.get(
|
||||||
|
f"https://pypi.org/pypi/qutebrowser/{version}/json", timeout=30
|
||||||
|
)
|
||||||
|
if response.status_code == http.HTTPStatus.NOT_FOUND:
|
||||||
|
return False
|
||||||
|
response.raise_for_status()
|
||||||
|
return bool(response.json()["urls"])
|
||||||
|
|
||||||
|
|
||||||
|
def pypi_upload(
|
||||||
|
artifacts: list[Artifact], experimental: bool, skip_if_exists: bool
|
||||||
|
) -> None:
|
||||||
"""Upload the given artifacts to PyPI using twine."""
|
"""Upload the given artifacts to PyPI using twine."""
|
||||||
|
utils.print_title("Uploading to PyPI...")
|
||||||
|
if skip_if_exists and check_pypi_exists(qutebrowser.__version__):
|
||||||
|
print(f"Version {qutebrowser.__version__} already exists on PyPI, skipping")
|
||||||
|
return
|
||||||
|
|
||||||
# https://blog.pypi.org/posts/2023-05-23-removing-pgp/
|
# https://blog.pypi.org/posts/2023-05-23-removing-pgp/
|
||||||
artifacts = [a for a in artifacts if a.mimetype != 'application/pgp-signature']
|
artifacts = [a for a in artifacts if a.mimetype != 'application/pgp-signature']
|
||||||
|
|
||||||
utils.print_title("Uploading to PyPI...")
|
|
||||||
if experimental:
|
if experimental:
|
||||||
run_twine('upload', artifacts, "-r", "testpypi")
|
run_twine('upload', artifacts, "-r", "testpypi")
|
||||||
else:
|
else:
|
||||||
|
|
@ -658,6 +736,8 @@ def main() -> None:
|
||||||
nargs='?')
|
nargs='?')
|
||||||
parser.add_argument('--upload', action='store_true', required=False,
|
parser.add_argument('--upload', action='store_true', required=False,
|
||||||
help="Toggle to upload the release to GitHub.")
|
help="Toggle to upload the release to GitHub.")
|
||||||
|
parser.add_argument('--reupload', action='store_true', required=False,
|
||||||
|
help="Skip uploading artifacts that already exist.")
|
||||||
parser.add_argument('--no-confirm', action='store_true', required=False,
|
parser.add_argument('--no-confirm', action='store_true', required=False,
|
||||||
help="Skip confirmation before uploading.")
|
help="Skip confirmation before uploading.")
|
||||||
parser.add_argument('--skip-packaging', action='store_true', required=False,
|
parser.add_argument('--skip-packaging', action='store_true', required=False,
|
||||||
|
|
@ -717,9 +797,16 @@ def main() -> None:
|
||||||
|
|
||||||
assert gh_token is not None
|
assert gh_token is not None
|
||||||
github_upload(
|
github_upload(
|
||||||
artifacts, version_tag, gh_token=gh_token, experimental=args.experimental)
|
artifacts,
|
||||||
|
version_tag,
|
||||||
|
gh_token=gh_token,
|
||||||
|
experimental=args.experimental,
|
||||||
|
skip_if_exists=args.reupload,
|
||||||
|
)
|
||||||
if upload_to_pypi:
|
if upload_to_pypi:
|
||||||
pypi_upload(artifacts, experimental=args.experimental)
|
pypi_upload(
|
||||||
|
artifacts, experimental=args.experimental, skip_if_exists=args.reupload
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
print()
|
print()
|
||||||
utils.print_title("Artifacts")
|
utils.print_title("Artifacts")
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@
|
||||||
"Mako": "https://docs.makotemplates.org/en/latest/changelog.html",
|
"Mako": "https://docs.makotemplates.org/en/latest/changelog.html",
|
||||||
"hypothesis": "https://hypothesis.readthedocs.io/en/latest/changes.html",
|
"hypothesis": "https://hypothesis.readthedocs.io/en/latest/changes.html",
|
||||||
"mypy": "https://github.com/python/mypy/blob/master/CHANGELOG.md",
|
"mypy": "https://github.com/python/mypy/blob/master/CHANGELOG.md",
|
||||||
|
"librt": "https://github.com/mypyc/librt/commits/master/",
|
||||||
"types-PyYAML": "https://github.com/python/typeshed/commits/main/stubs/PyYAML",
|
"types-PyYAML": "https://github.com/python/typeshed/commits/main/stubs/PyYAML",
|
||||||
"types-colorama": "https://github.com/python/typeshed/commits/main/stubs/colorama",
|
"types-colorama": "https://github.com/python/typeshed/commits/main/stubs/colorama",
|
||||||
"types-docutils": "https://github.com/python/typeshed/commits/main/stubs/docutils",
|
"types-docutils": "https://github.com/python/typeshed/commits/main/stubs/docutils",
|
||||||
|
|
@ -101,7 +102,6 @@
|
||||||
"h11": "https://h11.readthedocs.io/en/latest/changes.html",
|
"h11": "https://h11.readthedocs.io/en/latest/changes.html",
|
||||||
"httpcore": "https://github.com/encode/httpcore/blob/master/CHANGELOG.md",
|
"httpcore": "https://github.com/encode/httpcore/blob/master/CHANGELOG.md",
|
||||||
"httpx": "https://github.com/encode/httpx/blob/master/CHANGELOG.md",
|
"httpx": "https://github.com/encode/httpx/blob/master/CHANGELOG.md",
|
||||||
"sniffio": "https://sniffio.readthedocs.io/en/latest/history.html",
|
|
||||||
"six": "https://github.com/benjaminp/six/blob/master/CHANGES",
|
"six": "https://github.com/benjaminp/six/blob/master/CHANGES",
|
||||||
"altgraph": "https://github.com/ronaldoussoren/altgraph/blob/master/doc/changelog.rst",
|
"altgraph": "https://github.com/ronaldoussoren/altgraph/blob/master/doc/changelog.rst",
|
||||||
"urllib3": "https://github.com/urllib3/urllib3/blob/main/CHANGES.rst",
|
"urllib3": "https://github.com/urllib3/urllib3/blob/main/CHANGES.rst",
|
||||||
|
|
@ -127,7 +127,7 @@
|
||||||
"distlib": "https://github.com/pypa/distlib/blob/master/CHANGES.rst",
|
"distlib": "https://github.com/pypa/distlib/blob/master/CHANGES.rst",
|
||||||
"py-cpuinfo": "https://github.com/workhorsy/py-cpuinfo/blob/master/ChangeLog",
|
"py-cpuinfo": "https://github.com/workhorsy/py-cpuinfo/blob/master/ChangeLog",
|
||||||
"cheroot": "https://cheroot.cherrypy.dev/en/latest/history.html",
|
"cheroot": "https://cheroot.cherrypy.dev/en/latest/history.html",
|
||||||
"certifi": "https://ccadb-public.secure.force.com/mozilla/IncludedCACertificateReport",
|
"certifi": "https://ccadb.my.salesforce-sites.com/mozilla/IncludedCACertificateReport",
|
||||||
"chardet": "https://github.com/chardet/chardet/releases",
|
"chardet": "https://github.com/chardet/chardet/releases",
|
||||||
"charset-normalizer": "https://github.com/Ousret/charset_normalizer/blob/master/CHANGELOG.md",
|
"charset-normalizer": "https://github.com/Ousret/charset_normalizer/blob/master/CHANGELOG.md",
|
||||||
"idna": "https://github.com/kjd/idna/blob/master/HISTORY.rst",
|
"idna": "https://github.com/kjd/idna/blob/master/HISTORY.rst",
|
||||||
|
|
@ -146,7 +146,7 @@
|
||||||
"python-dateutil": "https://dateutil.readthedocs.io/en/stable/changelog.html",
|
"python-dateutil": "https://dateutil.readthedocs.io/en/stable/changelog.html",
|
||||||
"platformdirs": "https://github.com/platformdirs/platformdirs/releases",
|
"platformdirs": "https://github.com/platformdirs/platformdirs/releases",
|
||||||
"pluggy": "https://github.com/pytest-dev/pluggy/blob/main/CHANGELOG.rst",
|
"pluggy": "https://github.com/pytest-dev/pluggy/blob/main/CHANGELOG.rst",
|
||||||
"mypy-extensions": "https://github.com/python/mypy_extensions/commits/master",
|
"mypy_extensions": "https://github.com/python/mypy_extensions/commits/master",
|
||||||
"pyroma": "https://github.com/regebro/pyroma/blob/master/CHANGES.txt",
|
"pyroma": "https://github.com/regebro/pyroma/blob/master/CHANGES.txt",
|
||||||
"adblock": "https://github.com/ArniDagur/python-adblock/blob/master/CHANGELOG.md",
|
"adblock": "https://github.com/ArniDagur/python-adblock/blob/master/CHANGELOG.md",
|
||||||
"importlib_resources": "https://importlib-resources.readthedocs.io/en/latest/history.html",
|
"importlib_resources": "https://importlib-resources.readthedocs.io/en/latest/history.html",
|
||||||
|
|
|
||||||
|
|
@ -7,28 +7,15 @@ RUN sed -i '/^# after the header/a[kde-unstable]\nInclude = /etc/pacman.d/mirror
|
||||||
RUN pacman -Sy --noconfirm archlinux-keyring
|
RUN pacman -Sy --noconfirm archlinux-keyring
|
||||||
RUN pacman -Su --noconfirm \
|
RUN pacman -Su --noconfirm \
|
||||||
git \
|
git \
|
||||||
{% if webengine %}
|
|
||||||
python-tox \
|
python-tox \
|
||||||
python-distlib \
|
python-distlib \
|
||||||
{% endif %}
|
libxml2-legacy \
|
||||||
{% if qt6 %}
|
qt6-base \
|
||||||
qt6-base \
|
qt6-declarative \
|
||||||
qt6-declarative \
|
qt6-webengine \
|
||||||
{% if webengine %}
|
python-pyqt6-webengine \
|
||||||
qt6-webengine python-pyqt6-webengine \
|
pdfjs \
|
||||||
pdfjs \
|
python-pyqt6 \
|
||||||
{% else %}{{ 1/0 }}{% endif %}
|
|
||||||
python-pyqt6 \
|
|
||||||
{% else %}
|
|
||||||
qt5-base \
|
|
||||||
qt5-declarative \
|
|
||||||
openssl-1.1 \
|
|
||||||
{% if webengine %}
|
|
||||||
qt5-webengine \
|
|
||||||
python-pyqtwebengine \
|
|
||||||
python-pyqt5 \
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
xorg-xinit \
|
xorg-xinit \
|
||||||
xorg-server-xvfb \
|
xorg-server-xvfb \
|
||||||
ttf-bitstream-vera \
|
ttf-bitstream-vera \
|
||||||
|
|
@ -40,44 +27,7 @@ RUN useradd user -u 1001 && \
|
||||||
mkdir /home/user && \
|
mkdir /home/user && \
|
||||||
chown user:users /home/user
|
chown user:users /home/user
|
||||||
|
|
||||||
{% if not webengine %}
|
RUN python3 -c "from PyQt6 import QtWebEngineCore, QtWebEngineWidgets"
|
||||||
RUN pacman -U --noconfirm \
|
|
||||||
https://archive.archlinux.org/packages/q/qt5-webkit/qt5-webkit-5.212.0alpha4-18-x86_64.pkg.tar.zst \
|
|
||||||
https://archive.archlinux.org/packages/p/python-pyqt5/python-pyqt5-5.15.7-2-x86_64.pkg.tar.zst \
|
|
||||||
https://archive.archlinux.org/packages/i/icu/icu-72.1-2-x86_64.pkg.tar.zst \
|
|
||||||
https://archive.archlinux.org/packages/l/libxml2/libxml2-2.10.4-4-x86_64.pkg.tar.zst \
|
|
||||||
https://archive.archlinux.org/packages/l/libxslt/libxslt-1.1.42-2-x86_64.pkg.tar.zst \
|
|
||||||
https://archive.archlinux.org/packages/q/qt5-base/qt5-base-5.15.10%2Bkde%2Br129-3-x86_64.pkg.tar.zst \
|
|
||||||
https://archive.archlinux.org/packages/q/qt5-declarative/qt5-declarative-5.15.10%2Bkde%2Br31-1-x86_64.pkg.tar.zst \
|
|
||||||
https://archive.archlinux.org/packages/q/qt5-translations/qt5-translations-5.15.10-1-any.pkg.tar.zst \
|
|
||||||
https://archive.archlinux.org/packages/q/qt5-sensors/qt5-sensors-5.15.10-1-x86_64.pkg.tar.zst \
|
|
||||||
https://archive.archlinux.org/packages/q/qt5-location/qt5-location-5.15.10%2Bkde%2Br5-1-x86_64.pkg.tar.zst \
|
|
||||||
https://archive.archlinux.org/packages/q/qt5-webchannel/qt5-webchannel-5.15.10%2Bkde%2Br3-1-x86_64.pkg.tar.zst
|
|
||||||
RUN pacman -S --noconfirm base-devel
|
|
||||||
|
|
||||||
RUN echo 'user ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers # needed for makepkg
|
|
||||||
USER user
|
|
||||||
RUN cd ~ && \
|
|
||||||
git clone https://aur.archlinux.org/python310.git && \
|
|
||||||
cd python310 && \
|
|
||||||
makepkg -si --noconfirm
|
|
||||||
USER root
|
|
||||||
RUN python3.10 -m ensurepip
|
|
||||||
RUN python3.10 -m pip install tox pyqt5-sip
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if qt6 %}
|
|
||||||
{% set pyqt_module = 'PyQt6' %}
|
|
||||||
{% else %}
|
|
||||||
{% set pyqt_module = 'PyQt5' %}
|
|
||||||
{% endif %}
|
|
||||||
{% if webengine %}
|
|
||||||
{% set python = 'python3' %}
|
|
||||||
RUN {{ python }} -c "from {{ pyqt_module }} import QtWebEngineCore, QtWebEngineWidgets"
|
|
||||||
{% else %}
|
|
||||||
{% set python = 'python3.10' %}
|
|
||||||
RUN {{ python }} -c "from {{ pyqt_module }} import QtWebKit, QtWebKitWidgets"
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
USER user
|
USER user
|
||||||
WORKDIR /home/user
|
WORKDIR /home/user
|
||||||
|
|
@ -85,4 +35,4 @@ RUN git config --global --add safe.directory /outside/.git
|
||||||
|
|
||||||
CMD git clone /outside qutebrowser.git && \
|
CMD git clone /outside qutebrowser.git && \
|
||||||
cd qutebrowser.git && \
|
cd qutebrowser.git && \
|
||||||
{{ python }} -m tox -e {% if qt6 %}py-qt6{% else %}py-qt5{% endif %}
|
{{ python }} -m tox -e py-qt6
|
||||||
|
|
|
||||||
|
|
@ -7,18 +7,14 @@
|
||||||
|
|
||||||
"""Generate Dockerfiles for qutebrowser's CI."""
|
"""Generate Dockerfiles for qutebrowser's CI."""
|
||||||
|
|
||||||
import sys
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
import jinja2
|
import jinja2
|
||||||
|
|
||||||
|
|
||||||
CONFIGS = {
|
CONFIGS = {
|
||||||
'archlinux-webkit': {'webengine': False, 'unstable': False, 'qt6': False},
|
'archlinux-webengine': {'unstable': False},
|
||||||
'archlinux-webengine': {'webengine': True, 'unstable': False, 'qt6': False},
|
'archlinux-webengine-unstable': {'unstable': True},
|
||||||
'archlinux-webengine-qt6': {'webengine': True, 'unstable': False, 'qt6': True},
|
|
||||||
'archlinux-webengine-unstable': {'webengine': True, 'unstable': True, 'qt6': False},
|
|
||||||
'archlinux-webengine-unstable-qt6': {'webengine': True, 'unstable': True, 'qt6': True},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,13 +54,14 @@ def show_commit():
|
||||||
git_args = ['git', 'show']
|
git_args = ['git', 'show']
|
||||||
if utils.ON_CI:
|
if utils.ON_CI:
|
||||||
git_args.append("--color")
|
git_args.append("--color")
|
||||||
|
git_args.append("--no-patch") # shows entire git tree on CI (shallow clone)
|
||||||
subprocess.run(git_args, check=True)
|
subprocess.run(git_args, check=True)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser(description="Update release version.")
|
parser = argparse.ArgumentParser(description="Update release version.")
|
||||||
parser.add_argument('bump', action="store",
|
parser.add_argument('bump', action="store",
|
||||||
choices=["major", "minor", "patch"],
|
choices=["major", "minor", "patch", "reupload"],
|
||||||
help="Update release version")
|
help="Update release version")
|
||||||
parser.add_argument('--commands', action="store_true",
|
parser.add_argument('--commands', action="store_true",
|
||||||
help="Only show commands to run post-release.")
|
help="Only show commands to run post-release.")
|
||||||
|
|
@ -70,7 +71,8 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
if not args.commands:
|
if not args.commands:
|
||||||
verify_branch(args.bump)
|
verify_branch(args.bump)
|
||||||
bump_version(args.bump)
|
if args.bump != "reupload":
|
||||||
|
bump_version(args.bump)
|
||||||
show_commit()
|
show_commit()
|
||||||
|
|
||||||
import qutebrowser
|
import qutebrowser
|
||||||
|
|
@ -87,15 +89,16 @@ if __name__ == "__main__":
|
||||||
print(f"Outputs for {version} written to GitHub Actions output file")
|
print(f"Outputs for {version} written to GitHub Actions output file")
|
||||||
else:
|
else:
|
||||||
print("Run the following commands to create a new release:")
|
print("Run the following commands to create a new release:")
|
||||||
print("* git push origin; git push origin v{v}".format(v=version))
|
if args.bump != 'reupload':
|
||||||
if args.bump == 'patch':
|
print("* git push origin; git push origin v{v}".format(v=version))
|
||||||
print("* git checkout main && git cherry-pick -x v{v} && "
|
if args.bump == 'patch':
|
||||||
"git push origin".format(v=version))
|
print("* git checkout main && git cherry-pick -x v{v} && "
|
||||||
else:
|
"git push origin".format(v=version))
|
||||||
print("* git branch v{x} v{v} && git push --set-upstream origin v{x}"
|
else:
|
||||||
.format(v=version, x=version_x))
|
print("* git branch v{x} v{v} && git push --set-upstream origin v{x}"
|
||||||
print("* Create new release via GitHub (required to upload release "
|
.format(v=version, x=version_x))
|
||||||
"artifacts)")
|
print("* Create new release via GitHub (required to upload release "
|
||||||
|
"artifacts)")
|
||||||
print("* Linux: git fetch && git checkout v{v} && "
|
print("* Linux: git fetch && git checkout v{v} && "
|
||||||
"tox -e build-release -- --upload"
|
"tox -e build-release -- --upload"
|
||||||
.format(v=version))
|
.format(v=version))
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,7 @@ def get_lib_path(executable, name, required=True):
|
||||||
return data
|
return data
|
||||||
elif prefix == 'ImportError':
|
elif prefix == 'ImportError':
|
||||||
if required:
|
if required:
|
||||||
wrapper = os.environ["QUTE_QT_WRAPPER"]
|
wrapper = os.environ.get("QUTE_QT_WRAPPER", "unset")
|
||||||
raise Error(
|
raise Error(
|
||||||
f"Could not import {name} with {executable}: {data} "
|
f"Could not import {name} with {executable}: {data} "
|
||||||
f"(QUTE_QT_WRAPPER: {wrapper})"
|
f"(QUTE_QT_WRAPPER: {wrapper})"
|
||||||
|
|
|
||||||
|
|
@ -453,7 +453,6 @@ def install_dev_requirements(venv_dir: pathlib.Path) -> None:
|
||||||
utils.print_title("Installing dev dependencies")
|
utils.print_title("Installing dev dependencies")
|
||||||
pip_install(venv_dir,
|
pip_install(venv_dir,
|
||||||
'-r', str(requirements_file('dev')),
|
'-r', str(requirements_file('dev')),
|
||||||
'-r', str(requirements_file('check-manifest')),
|
|
||||||
'-r', str(requirements_file('flake8')),
|
'-r', str(requirements_file('flake8')),
|
||||||
'-r', str(requirements_file('mypy')),
|
'-r', str(requirements_file('mypy')),
|
||||||
'-r', str(requirements_file('pyroma')),
|
'-r', str(requirements_file('pyroma')),
|
||||||
|
|
|
||||||
22
setup.py
22
setup.py
|
|
@ -9,7 +9,15 @@
|
||||||
import re
|
import re
|
||||||
import ast
|
import ast
|
||||||
import os
|
import os
|
||||||
import os.path
|
import sys
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
# Add repo root to path so we can import scripts. Prior to PEP517 support this
|
||||||
|
# was the default behavior for setuptools.
|
||||||
|
# https://github.com/pypa/setuptools/issues/3939#issuecomment-1573619382
|
||||||
|
# > If users want to import local modules they are recommended to explicitly add
|
||||||
|
# > the current directory to sys.path at the top of setup.py.
|
||||||
|
sys.path.append(".")
|
||||||
|
|
||||||
from scripts import setupcommon as common
|
from scripts import setupcommon as common
|
||||||
|
|
||||||
|
|
@ -17,7 +25,7 @@ import setuptools
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
BASEDIR = os.path.dirname(os.path.realpath(__file__))
|
BASEDIR = pathlib.Path(__file__).resolve().parent
|
||||||
except NameError:
|
except NameError:
|
||||||
BASEDIR = None
|
BASEDIR = None
|
||||||
|
|
||||||
|
|
@ -42,8 +50,8 @@ def _get_constant(name):
|
||||||
The value of the argument.
|
The value of the argument.
|
||||||
"""
|
"""
|
||||||
field_re = re.compile(r'__{}__\s+=\s+(.*)'.format(re.escape(name)))
|
field_re = re.compile(r'__{}__\s+=\s+(.*)'.format(re.escape(name)))
|
||||||
path = os.path.join(BASEDIR, 'qutebrowser', '__init__.py')
|
init_path = BASEDIR / 'qutebrowser' / '__init__.py'
|
||||||
line = field_re.search(read_file(path)).group(1)
|
line = field_re.search(read_file(init_path)).group(1)
|
||||||
value = ast.literal_eval(line)
|
value = ast.literal_eval(line)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
@ -91,6 +99,6 @@ try:
|
||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
if BASEDIR is not None:
|
if BASEDIR is not None:
|
||||||
path = os.path.join(BASEDIR, 'qutebrowser', 'git-commit-id')
|
git_commit_id_path = BASEDIR / 'qutebrowser' / 'git-commit-id'
|
||||||
if os.path.exists(path):
|
if git_commit_id_path.exists():
|
||||||
os.remove(path)
|
git_commit_id_path.unlink()
|
||||||
|
|
|
||||||
|
|
@ -132,10 +132,10 @@ def _apply_platform_markers(config, item):
|
||||||
(
|
(
|
||||||
config.webengine
|
config.webengine
|
||||||
and version.qtwebengine_versions(avoid_init=True).webengine
|
and version.qtwebengine_versions(avoid_init=True).webengine
|
||||||
== utils.VersionNumber(6, 9)
|
> utils.VersionNumber(6, 9)
|
||||||
and testutils.ON_CI
|
and testutils.ON_CI
|
||||||
),
|
),
|
||||||
"Flaky with QtWebEngine 6.9 on CI",
|
"Flaky with QtWebEngine 6.9+ on CI",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"qt69_ci_skip", # WORKAROUND: https://github.com/qutebrowser/qutebrowser/issues/8444#issuecomment-2569610110
|
"qt69_ci_skip", # WORKAROUND: https://github.com/qutebrowser/qutebrowser/issues/8444#issuecomment-2569610110
|
||||||
|
|
@ -143,10 +143,10 @@ def _apply_platform_markers(config, item):
|
||||||
(
|
(
|
||||||
config.webengine
|
config.webengine
|
||||||
and version.qtwebengine_versions(avoid_init=True).webengine
|
and version.qtwebengine_versions(avoid_init=True).webengine
|
||||||
== utils.VersionNumber(6, 9)
|
> utils.VersionNumber(6, 9)
|
||||||
and testutils.ON_CI
|
and testutils.ON_CI
|
||||||
),
|
),
|
||||||
"Skipped with QtWebEngine 6.9 on CI",
|
"Skipped with QtWebEngine 6.9+ on CI",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<!-- target: hello.txt -->
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<div id="host"></div>
|
||||||
|
<script>
|
||||||
|
const hostElement = document.getElementById('host');
|
||||||
|
const shadowRoot = hostElement.attachShadow({ mode: 'open' });
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = '/data/hello.txt';
|
||||||
|
a.textContent = 'In shadow DOM';
|
||||||
|
shadowRoot.appendChild(a);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Scrolling</title>
|
<title>Scrolling</title>
|
||||||
|
<script>requestAnimationFrame(() => console.log('position_absolute loaded'))</script>
|
||||||
</head>
|
</head>
|
||||||
<body style="position: absolute">
|
<body style="position: absolute">
|
||||||
<a href="/data/hello.txt" id="link">Just a link</a>
|
<a href="/data/hello.txt" id="link">Just a link</a>
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,11 @@
|
||||||
console.log("[PASS] Positions equal: " + old_position);
|
console.log("[PASS] Positions equal: " + old_position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(() => console.log('simple loaded'))
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body onload="console.log('simple loaded')">
|
<body>
|
||||||
<a href="/data/hello.txt" id="link">Just a link</a>
|
<a href="/data/hello.txt" id="link">Just a link</a>
|
||||||
<button>blub</button>
|
<button>blub</button>
|
||||||
<p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus</p>
|
<p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus</p>
|
||||||
|
|
|
||||||
|
|
@ -103,3 +103,11 @@ Feature: Using completion
|
||||||
And I run :completion-item-focus next
|
And I run :completion-item-focus next
|
||||||
And I run :cmd-set-text -s :set
|
And I run :cmd-set-text -s :set
|
||||||
Then the completion model should be option
|
Then the completion model should be option
|
||||||
|
|
||||||
|
Scenario: Page focus after using completion (#8750)
|
||||||
|
When I open data/insert_mode_settings/html/input.html
|
||||||
|
And I run :cmd-set-text :
|
||||||
|
And I run :mode-leave
|
||||||
|
And I run :click-element id qute-input
|
||||||
|
And I run :fake-key -g someinput
|
||||||
|
Then the javascript message "contents: someinput" should be logged
|
||||||
|
|
|
||||||
|
|
@ -417,7 +417,7 @@ def update_documentation():
|
||||||
try:
|
try:
|
||||||
subprocess.run(['asciidoc'], stdout=subprocess.DEVNULL,
|
subprocess.run(['asciidoc'], stdout=subprocess.DEVNULL,
|
||||||
stderr=subprocess.DEVNULL, check=True)
|
stderr=subprocess.DEVNULL, check=True)
|
||||||
except OSError:
|
except (OSError, subprocess.CalledProcessError):
|
||||||
pytest.skip("Docs outdated and asciidoc unavailable!")
|
pytest.skip("Docs outdated and asciidoc unavailable!")
|
||||||
|
|
||||||
update_script = os.path.join(script_path, 'asciidoc2html.py')
|
update_script = os.path.join(script_path, 'asciidoc2html.py')
|
||||||
|
|
|
||||||
|
|
@ -78,8 +78,8 @@ Feature: Downloading things from a website.
|
||||||
And I open data/downloads/issue1243.html
|
And I open data/downloads/issue1243.html
|
||||||
And I hint with args "links download" and follow a
|
And I hint with args "links download" and follow a
|
||||||
And I wait for "Asking question <qutebrowser.utils.usertypes.Question default='qutebrowser-download' mode=<PromptMode.download: 5> option=None text=* title='Save file to:'>, *" in the log
|
And I wait for "Asking question <qutebrowser.utils.usertypes.Question default='qutebrowser-download' mode=<PromptMode.download: 5> option=None text=* title='Save file to:'>, *" in the log
|
||||||
Then the error "Download error: No handler found for qute://" should be shown
|
Then the error "Download error: Invalid host (from path): ''" should be shown
|
||||||
And "NotFoundError while handling qute://* URL" should be logged
|
And "UrlInvalidError while handling qute://* URL" should be logged
|
||||||
|
|
||||||
Scenario: Downloading a data: link (issue 1214)
|
Scenario: Downloading a data: link (issue 1214)
|
||||||
When I set downloads.location.suggestion to filename
|
When I set downloads.location.suggestion to filename
|
||||||
|
|
@ -129,6 +129,14 @@ Feature: Downloading things from a website.
|
||||||
And I wait for "Download drip finished" in the log
|
And I wait for "Download drip finished" in the log
|
||||||
Then the downloaded file drip should be 128 bytes big
|
Then the downloaded file drip should be 128 bytes big
|
||||||
|
|
||||||
|
Scenario: Shutting down with a download question
|
||||||
|
When I set downloads.location.prompt to true
|
||||||
|
And I open data/downloads/download.bin without waiting
|
||||||
|
And I wait for "Asking question <qutebrowser.utils.usertypes.Question default='*' mode=<PromptMode.download: 5> option=None text='Please enter a location for <b>http://localhost:*/data/downloads/download.bin</b>' title='Save file to:'>, *" in the log
|
||||||
|
And I run :close
|
||||||
|
Then qutebrowser should quit
|
||||||
|
# (and no crash should happen)
|
||||||
|
|
||||||
Scenario: Downloading a file with spaces
|
Scenario: Downloading a file with spaces
|
||||||
When I open data/downloads/download with spaces.bin without waiting
|
When I open data/downloads/download with spaces.bin without waiting
|
||||||
And I wait until the download is finished
|
And I wait until the download is finished
|
||||||
|
|
@ -669,6 +677,21 @@ Feature: Downloading things from a website.
|
||||||
Then the downloaded file download.bin should exist
|
Then the downloaded file download.bin should exist
|
||||||
And the downloaded file download2.bin should not exist
|
And the downloaded file download2.bin should not exist
|
||||||
|
|
||||||
|
@qt>=6.9
|
||||||
|
Scenario: Nested download prompts (#8674)
|
||||||
|
When I set downloads.location.prompt to true
|
||||||
|
And I open data/downloads/download.bin without waiting
|
||||||
|
And I wait for "Asking question <qutebrowser.utils.usertypes.Question default='*' mode=<PromptMode.download: 5> option=None text=* title='Save file to:'>, *" in the log
|
||||||
|
And I open data/downloads/download.bin without waiting
|
||||||
|
And I wait for "Asking question <qutebrowser.utils.usertypes.Question default='*' mode=<PromptMode.download: 5> option=None text=* title='Save file to:'>, *" in the log
|
||||||
|
And I open data/downloads/download.bin without waiting
|
||||||
|
And I wait for "Asking question <qutebrowser.utils.usertypes.Question default='*' mode=<PromptMode.download: 5> option=None text=* title='Save file to:'>, *" in the log
|
||||||
|
And I run :prompt-accept
|
||||||
|
And I run :mode-leave
|
||||||
|
And I run :mode-leave
|
||||||
|
And I wait until the download is finished
|
||||||
|
Then the downloaded file download.bin should exist
|
||||||
|
|
||||||
@qtwebengine_skip # We can't get the UA from the page there
|
@qtwebengine_skip # We can't get the UA from the page there
|
||||||
Scenario: user-agent when using :download
|
Scenario: user-agent when using :download
|
||||||
When I open user-agent
|
When I open user-agent
|
||||||
|
|
|
||||||
|
|
@ -386,6 +386,13 @@ Feature: Various utility commands.
|
||||||
And I run :jseval console.log(window.navigator.userAgent)
|
And I run :jseval console.log(window.navigator.userAgent)
|
||||||
Then the header User-Agent should be set to toaster
|
Then the header User-Agent should be set to toaster
|
||||||
|
|
||||||
|
Scenario: User-agent header with redirect
|
||||||
|
When I run :set -u localhost content.headers.user_agent toaster
|
||||||
|
And I open redirect-to?url=headers without waiting
|
||||||
|
And I wait until headers is loaded
|
||||||
|
And I run :jseval console.log(window.navigator.userAgent)
|
||||||
|
Then the header User-Agent should be set to toaster
|
||||||
|
|
||||||
Scenario: User-agent header (JS)
|
Scenario: User-agent header (JS)
|
||||||
When I set content.headers.user_agent to toaster
|
When I set content.headers.user_agent to toaster
|
||||||
And I open about:blank
|
And I open about:blank
|
||||||
|
|
|
||||||
|
|
@ -327,6 +327,7 @@ Feature: Scrolling
|
||||||
|
|
||||||
Scenario: Relative scroll position with a position:absolute page
|
Scenario: Relative scroll position with a position:absolute page
|
||||||
When I open data/scroll/position_absolute.html
|
When I open data/scroll/position_absolute.html
|
||||||
|
And I wait for "* position_absolute loaded" in the log
|
||||||
And I run :scroll-to-perc 100
|
And I run :scroll-to-perc 100
|
||||||
And I wait until the scroll position changed
|
And I wait until the scroll position changed
|
||||||
And I run :scroll-page --bottom-navigate next 0 1
|
And I run :scroll-page --bottom-navigate next 0 1
|
||||||
|
|
@ -339,3 +340,11 @@ Feature: Scrolling
|
||||||
And I run :tab-next
|
And I run :tab-next
|
||||||
And I run :jseval --world main checkAnchor()
|
And I run :jseval --world main checkAnchor()
|
||||||
Then "[*] [PASS] Positions equal: *" should be logged
|
Then "[*] [PASS] Positions equal: *" should be logged
|
||||||
|
|
||||||
|
Scenario: Showing/hiding statusbar (#2236, #8223)
|
||||||
|
When I set statusbar.show to never
|
||||||
|
And I run :scroll-to-perc 100
|
||||||
|
And I wait until the scroll position changed
|
||||||
|
And I run :cmd-set-text /
|
||||||
|
And I run :fake-key -g <Escape>
|
||||||
|
Then "Scroll position changed to Py*.QtCore.QPoint()" should not be logged
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@ Feature: Saving and loading sessions
|
||||||
@qtwebkit_skip
|
@qtwebkit_skip
|
||||||
Scenario: Scrolling (qtwebengine)
|
Scenario: Scrolling (qtwebengine)
|
||||||
When I open data/scroll/simple.html
|
When I open data/scroll/simple.html
|
||||||
|
And I wait for "* simple loaded" in the log
|
||||||
And I run :scroll-px 10 20
|
And I run :scroll-px 10 20
|
||||||
And I wait until the scroll position changed to 10/20
|
And I wait until the scroll position changed to 10/20
|
||||||
Then the session should look like:
|
Then the session should look like:
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue