Compare commits

..

No commits in common. "main" and "v3.3.0" have entirely different histories.
main ... v3.3.0

311 changed files with 1835 additions and 5292 deletions

19
.bumpversion.cfg Normal file
View File

@ -0,0 +1,19 @@
[bumpversion]
current_version = 3.3.0
commit = True
message = Release v{new_version}
tag = True
sign_tags = True
tag_name = v{new_version}
[bumpversion:file:qutebrowser/__init__.py]
parse = __version__ = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)
[bumpversion:file:misc/org.qutebrowser.qutebrowser.appdata.xml]
search = <!-- Add new releases here -->
replace = <!-- Add new releases here -->
<release version="{new_version}" date="{now:%Y-%m-%d}"/>
[bumpversion:file:doc/changelog.asciidoc]
search = (unreleased)
replace = ({now:%Y-%m-%d})

View File

@ -1,26 +0,0 @@
[tool.bumpversion]
current_version = "3.6.3"
commit = true
message = "Release v{new_version}"
tag = true
sign_tags = true
tag_name = "v{new_version}"
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
serialize = ["{major}.{minor}.{patch}"]
allow_dirty = false
[[tool.bumpversion.files]]
filename = "qutebrowser/__init__.py"
search = "__version__ = \"{current_version}\""
replace = "__version__ = \"{new_version}\""
[[tool.bumpversion.files]]
filename = "misc/org.qutebrowser.qutebrowser.appdata.xml"
search = "<!-- Add new releases here -->"
replace = """<!-- Add new releases here -->
<release version='{new_version}' date='{now:%Y-%m-%d}'/>"""
[[tool.bumpversion.files]]
filename = "doc/changelog.asciidoc"
search = "(unreleased)"
replace = "({now:%Y-%m-%d})"

View File

@ -4,7 +4,6 @@ include =
tests/*
scripts/*
branch = true
patch = subprocess
omit =
qutebrowser/__main__.py
*/__init__.py

View File

@ -42,7 +42,6 @@ exclude = .*,__pycache__,resources.py
# W503: like break before binary operator
# W504: line break after binary operator
# FI18: __future__ import "annotations" missing
# FI58: __future__ import "annotations" present
# PT004: fixture '{name}' does not return anything, add leading underscore
# PT011: pytest.raises(ValueError) is too broad, set the match parameter or use a more specific exception
# PT012: pytest.raises() block should contain a single simple statement
@ -55,11 +54,11 @@ ignore =
D102,D103,D106,D107,D104,D105,D209,D211,D401,D402,D403,D412,D413,
A003,
W503, W504,
FI18,FI58,
FI18,
PT004,
PT011,
PT012
min-version = 3.9.0
min-version = 3.8.0
max-complexity = 12
per-file-ignores =
qutebrowser/api/hook.py : N801

View File

@ -10,13 +10,15 @@ on:
jobs:
tests:
if: "github.repository == 'qutebrowser/qutebrowser'"
runs-on: ubuntu-24.04
runs-on: ubuntu-20.04
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
include:
- testenv: bleeding
image: "archlinux-webengine-unstable-qt6"
- testenv: bleeding-qt5
image: "archlinux-webengine-unstable"
container:
image: "qutebrowser/ci:${{ matrix.image }}"
@ -31,13 +33,14 @@ jobs:
- /home/runner/work/_temp/:/home/runner/work/_temp/
options: --privileged --tty
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up problem matchers
run: "python scripts/dev/ci/problemmatchers.py py3 ${{ runner.temp }}"
- name: Upgrade 3rd party assets
run: "tox exec -e ${{ matrix.testenv }} -- python scripts/dev/update_3rdparty.py --gh-token ${{ secrets.GITHUB_TOKEN }} --modern-pdfjs"
run: "tox exec -e ${{ matrix.testenv }} -- python scripts/dev/update_3rdparty.py --gh-token ${{ secrets.GITHUB_TOKEN }}"
if: "endsWith(matrix.image, '-qt6')"
- name: Run tox
run: dbus-run-session tox -e ${{ matrix.testenv }}
- name: Gather info
@ -48,7 +51,7 @@ jobs:
shell: bash
if: failure()
- name: Upload screenshots
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v4
with:
name: "end2end-screenshots-${{ steps.info.outputs.date }}-${{ steps.info.outputs.sha_short }}-${{ matrix.image }}"
path: |
@ -58,7 +61,7 @@ jobs:
irc:
timeout-minutes: 2
continue-on-error: true
runs-on: ubuntu-24.04
runs-on: ubuntu-20.04
needs: [tests]
if: "always() && github.repository == 'qutebrowser/qutebrowser'"
steps:

View File

@ -14,7 +14,7 @@ jobs:
linters:
if: "!contains(github.event.head_commit.message, '[ci skip]')"
timeout-minutes: 10
runs-on: ubuntu-24.04
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
@ -27,6 +27,7 @@ jobs:
- testenv: vulture
- testenv: misc
- testenv: pyroma
- testenv: check-manifest
- testenv: eslint
- testenv: shellcheck
args: "-f gcc" # For problem matchers
@ -34,30 +35,30 @@ jobs:
- testenv: actionlint
- testenv: package
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/cache@v5
- uses: actions/cache@v4
with:
path: |
.mypy_cache
.tox
~/.cache/pip
key: "${{ matrix.testenv }}-${{ hashFiles('misc/requirements/requirements-*.txt') }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('scripts/dev/pylint_checkers/qute_pylint/*.py') }}"
- uses: actions/setup-python@v6
- uses: actions/setup-python@v5
with:
python-version: '3.10'
- uses: actions/setup-node@v6
- uses: actions/setup-node@v4
with:
node-version: '22.x'
node-version: '16.x'
if: "matrix.testenv == 'eslint'"
- name: Set up problem matchers
run: "python scripts/dev/ci/problemmatchers.py ${{ matrix.testenv }} ${{ runner.temp }}"
- name: Install dependencies
run: |
[[ ${{ matrix.testenv }} == eslint ]] && npm install -g 'eslint@<9.0.0'
[[ ${{ matrix.testenv }} == docs ]] && sudo apt-get update && sudo apt-get install --no-install-recommends asciidoc libegl1
[[ ${{ matrix.testenv }} == vulture || ${{ matrix.testenv }} == pylint ]] && sudo apt-get update && sudo apt-get install --no-install-recommends libegl1
[[ ${{ matrix.testenv }} == docs ]] && sudo apt-get update && sudo apt-get install --no-install-recommends asciidoc libegl1-mesa
[[ ${{ matrix.testenv }} == vulture || ${{ matrix.testenv }} == pylint ]] && sudo apt-get update && sudo apt-get install --no-install-recommends libegl1-mesa
if [[ ${{ matrix.testenv }} == shellcheck ]]; then
scversion="stable"
bindir="$HOME/.local/bin"
@ -85,15 +86,21 @@ jobs:
tests-docker:
if: "!contains(github.event.head_commit.message, '[ci skip]')"
timeout-minutes: 45
runs-on: ubuntu-22.04 # not 24.04 because sandboxing fails by default (#8424)
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
include:
- testenv: py
- testenv: py-qt5
image: archlinux-webkit
- testenv: py-qt5
image: archlinux-webengine
- testenv: py
- testenv: py-qt5
image: archlinux-webengine-unstable
- testenv: py
image: archlinux-webengine-qt6
- testenv: py
image: archlinux-webengine-unstable-qt6
container:
image: "qutebrowser/ci:${{ matrix.image }}"
env:
@ -106,7 +113,7 @@ jobs:
- /home/runner/work/_temp/:/home/runner/work/_temp/
options: --privileged --tty
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up problem matchers
@ -121,7 +128,7 @@ jobs:
shell: bash
if: failure()
- name: Upload screenshots
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v4
with:
name: "end2end-screenshots-${{ steps.info.outputs.date }}-${{ steps.info.outputs.sha_short }}-${{ matrix.image }}"
path: |
@ -137,10 +144,10 @@ jobs:
fail-fast: false
matrix:
include:
### PyQt 5.15.2 (Python 3.9)
- testenv: py39-pyqt5152
os: ubuntu-22.04
python: "3.9"
### PyQt 5.15.2 (Python 3.8)
- testenv: py38-pyqt5152
os: ubuntu-20.04
python: "3.8"
### PyQt 5.15 (Python 3.10, with coverage)
# FIXME:qt6
# - testenv: py310-pyqt515-cov
@ -148,19 +155,19 @@ jobs:
# python: "3.10"
### PyQt 5.15 (Python 3.11)
- testenv: py311-pyqt515
os: ubuntu-22.04
os: ubuntu-20.04
python: "3.11"
### PyQt 6.2 (Python 3.9)
- testenv: py39-pyqt62
os: ubuntu-22.04
python: "3.9"
### PyQt 6.3 (Python 3.9)
- testenv: py39-pyqt63
os: ubuntu-22.04
python: "3.9"
### PyQt 6.2 (Python 3.8)
- testenv: py38-pyqt62
os: ubuntu-20.04
python: "3.8"
### PyQt 6.3 (Python 3.8)
- testenv: py38-pyqt63
os: ubuntu-20.04
python: "3.8"
## PyQt 6.4 (Python 3.9)
- testenv: py39-pyqt64
os: ubuntu-22.04
os: ubuntu-20.04
python: "3.9"
### PyQt 6.5 (Python 3.10)
- testenv: py310-pyqt65
@ -182,45 +189,31 @@ jobs:
- testenv: py312-pyqt67
os: ubuntu-22.04
python: "3.12"
### PyQt 6.8 (Python 3.13)
- testenv: py313-pyqt68
os: ubuntu-24.04
python: "3.13"
### PyQt 6.8 (Python 3.13)
- testenv: py313-pyqt68
os: ubuntu-24.04
python: "3.13"
### PyQt 6.9 (Python 3.14)
- testenv: py314-pyqt69
os: ubuntu-24.04
python: "3.14"
### PyQt 6.10 (Python 3.14)
- testenv: py314-pyqt610
os: ubuntu-24.04
python: "3.14"
### macOS Sonoma (M1 runner)
- testenv: py314-pyqt610
os: macos-14
python: "3.14"
### macOS Monterey
- testenv: py312-pyqt67
os: macos-12
python: "3.12"
args: "tests/unit" # Only run unit tests on macOS
### macOS Sequoia (Intel runner)
- testenv: py314-pyqt610
os: macos-15-intel
python: "3.14"
### macOS Ventura
- testenv: py312-pyqt67
os: macos-13
python: "3.12"
args: "tests/unit" # Only run unit tests on macOS
### macOS Sonoma (M1 runner)
- testenv: py312-pyqt67
os: macos-14
python: "3.12"
args: "tests/unit" # Only run unit tests on macOS
### Windows
- testenv: py314-pyqt610
os: windows-2022
python: "3.14"
- testenv: py314-pyqt610
os: windows-2025
python: "3.14"
- testenv: py312-pyqt67
os: windows-2019
python: "3.12"
runs-on: "${{ matrix.os }}"
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/cache@v5
- uses: actions/cache@v4
with:
path: |
.mypy_cache
@ -228,7 +221,7 @@ jobs:
~/.cache/pip
key: "${{ matrix.testenv }}-${{ matrix.os }}-${{ matrix.python }}-${{ hashFiles('misc/requirements/requirements-*.txt') }}-${{ hashFiles('requirements.txt') }}"
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@v5
with:
python-version: "${{ matrix.python }}"
- name: Set up problem matchers
@ -236,7 +229,7 @@ jobs:
- name: Install apt dependencies
run: |
sudo apt-get update
sudo apt-get install --no-install-recommends libyaml-dev libegl1 libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-shape0 libxcb-cursor0 libjpeg-dev
sudo apt-get install --no-install-recommends libyaml-dev libegl1-mesa libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-shape0 libxcb-cursor0
if: "startsWith(matrix.os, 'ubuntu-')"
- name: Install dependencies
run: |
@ -258,7 +251,7 @@ jobs:
if: "failure()"
- name: Upload coverage
if: "endsWith(matrix.testenv, '-cov')"
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@v3
with:
name: "${{ matrix.testenv }}"
- name: Gather info
@ -269,7 +262,7 @@ jobs:
shell: bash
if: failure()
- name: Upload screenshots
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v4
with:
name: "end2end-screenshots-${{ steps.info.outputs.date }}-${{ steps.info.outputs.sha_short }}-${{ matrix.testenv }}-${{ matrix.os }}"
path: |
@ -282,24 +275,24 @@ jobs:
permissions:
security-events: write
timeout-minutes: 15
runs-on: ubuntu-24.04
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
uses: github/codeql-action/init@v3
with:
languages: javascript, python
queries: +security-extended
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
uses: github/codeql-action/analyze@v3
irc:
timeout-minutes: 2
continue-on-error: true
runs-on: ubuntu-24.04
runs-on: ubuntu-22.04
needs: [linters, tests, tests-docker, codeql]
if: "always() && github.repository_owner == 'qutebrowser'"
steps:

View File

@ -8,16 +8,19 @@ on:
jobs:
docker:
if: "github.repository == 'qutebrowser/qutebrowser'"
runs-on: ubuntu-24.04
runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
image:
- archlinux-webkit
- archlinux-webengine
- archlinux-webengine-unstable
- archlinux-webengine-unstable-qt6
- archlinux-webengine-qt6
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.x'
- run: pip install jinja2
@ -39,7 +42,7 @@ jobs:
irc:
timeout-minutes: 2
continue-on-error: true
runs-on: ubuntu-24.04
runs-on: ubuntu-20.04
needs: [docker]
if: "always() && github.repository == 'qutebrowser/qutebrowser'"
steps:

View File

@ -14,45 +14,50 @@ jobs:
fail-fast: false
matrix:
include:
- os: macos-15-intel
- os: macos-12
toxenv: build-release-qt5
name: qt5-macos
- os: windows-2019
toxenv: build-release-qt5
name: qt5-windows
- os: macos-12
args: --debug
toxenv: build-release-qt5
name: qt5-macos-debug
- os: windows-2019
args: --debug
toxenv: build-release-qt5
name: qt5-windows-debug
- os: macos-12
toxenv: build-release
name: macos-intel
- os: macos-14
toxenv: build-release
name: macos-apple-silicon
- os: windows-latest
- os: windows-2019
toxenv: build-release
name: windows
- os: macos-15-intel
- os: macos-12
args: --debug
toxenv: build-release
name: macos-debug-intel
- os: macos-14
toxenv: build-release
name: macos-debug-apple-silicon
- os: windows-latest
- os: windows-2019
args: --debug
toxenv: build-release
name: windows-debug
runs-on: "${{ matrix.os }}"
timeout-minutes: 45
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Install nsis
if: "matrix.os == 'windows-latest'"
run: |
irm get.scoop.sh | iex
scoop update
scoop bucket add extras
scoop install nsis
Add-Content $env:GITHUB_PATH "C:\Users\runneradmin\scoop\shims"
shell: pwsh
python-version: "3.10"
- name: Install dependencies
run: |
python -m pip install -U pip
@ -72,7 +77,7 @@ jobs:
echo "sha_short=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"
shell: bash
- name: Upload artifacts
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v4
with:
name: "qutebrowser-nightly-${{ steps.info.outputs.date }}-${{ steps.info.outputs.sha_short }}-${{ matrix.name }}"
path: |
@ -84,7 +89,7 @@ jobs:
irc:
timeout-minutes: 2
continue-on-error: true
runs-on: ubuntu-24.04
runs-on: ubuntu-20.04
needs: [pyinstaller]
if: "always() && github.repository == 'qutebrowser/qutebrowser'"
steps:

View File

@ -18,20 +18,20 @@ jobs:
timeout-minutes: 20
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python 3.9
uses: actions/setup-python@v6
- name: Set up Python 3.8
uses: actions/setup-python@v5
with:
python-version: '3.9'
python-version: '3.8'
- name: Recompile requirements
run: "python3 scripts/dev/recompile_requirements.py ${{ github.event.input.environments }}"
id: requirements
- name: Install apt dependencies
run: |
sudo apt-get update
sudo apt-get install --no-install-recommends libyaml-dev libegl1 libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-shape0 libxcb-cursor0 asciidoc python3-venv xvfb
sudo apt-get install --no-install-recommends libyaml-dev libegl1-mesa libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-shape0 libxcb-cursor0 asciidoc python3-venv xvfb
- name: Install dependencies
run: |
python -m pip install -U pip
@ -41,7 +41,7 @@ jobs:
- name: Run qutebrowser smoke test
run: "xvfb-run .venv/bin/python3 -m qutebrowser --no-err-windows --nowindow --temp-basedir about:blank ':later 500 quit'"
- name: Create pull request
uses: peter-evans/create-pull-request@v8
uses: peter-evans/create-pull-request@v6
with:
committer: qutebrowser bot <bot@qutebrowser.org>
author: qutebrowser bot <bot@qutebrowser.org>

View File

@ -12,33 +12,30 @@ on:
- 'patch'
- 'minor'
- 'major'
- 'reupload' # reupload last release
# FIXME do we want a possibility to do prereleases here?
python_version:
description: 'Python version'
required: true
default: '3.14'
default: '3.12'
type: choice
options:
- '3.8'
- '3.9'
- '3.10'
- '3.11'
- '3.12'
- '3.13'
- '3.14'
jobs:
prepare:
runs-on: ubuntu-24.04
runs-on: ubuntu-20.04
timeout-minutes: 5
outputs:
version: ${{ steps.bump.outputs.version }}
version_x: ${{ steps.bump.outputs.version_x }}
release_id: ${{ inputs.release_type == 'reupload' && steps.find-release.outputs.result || steps.create-release.outputs.id }}
release_id: ${{ steps.create-release.outputs.id }}
permissions:
contents: write # To push release commit/tag
steps:
- name: Find release branch
uses: actions/github-script@v8
uses: actions/github-script@v7
id: find-branch
with:
script: |
@ -62,9 +59,9 @@ jobs:
console.log(`sorted: ${sorted}`);
return sorted.at(-1);
result-encoding: string
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@v5
with:
# Doesn't really matter what we prepare the release with, but let's
# use the same version for consistency.
@ -78,7 +75,7 @@ jobs:
git config --global user.name "qutebrowser bot"
git config --global user.email "bot@qutebrowser.org"
- name: Switch to release branch
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
ref: ${{ steps.find-branch.outputs.result }}
- name: Import GPG Key
@ -86,9 +83,9 @@ jobs:
gpg --import <<< "${{ secrets.QUTEBROWSER_BOT_GPGKEY }}"
- name: Bump version
id: bump
run: "tox -e update-version -- ${{ inputs.release_type }}"
run: "tox -e update-version -- ${{ github.event.inputs.release_type }}"
- name: Check milestone
uses: actions/github-script@v8
uses: actions/github-script@v7
with:
script: |
const milestones = await github.paginate(github.rest.issues.listMilestones, {
@ -103,74 +100,49 @@ jobs:
core.setFailed(`Found open milestone ${milestone.title} with ${milestone.open_issues} open and ${milestone.closed_issues} closed issues!`);
}
- name: Push release commit/tag
if: ${{ inputs.release_type != 'reupload' }}
run: |
git push origin ${{ steps.find-branch.outputs.result }}
git push origin v${{ steps.bump.outputs.version }}
- name: Cherry-pick release commit
if: ${{ inputs.release_type == 'patch' }}
if: ${{ github.event.inputs.release_type == 'patch' }}
run: |
git fetch origin main
git checkout main
git cherry-pick -x v${{ steps.bump.outputs.version }}
git push origin main
git checkout v${{ steps.bump.outputs.version_x }}
- name: Create release branch
if: ${{ inputs.release_type == 'minor' || inputs.release_type == 'major' }}
if: ${{ github.event.inputs.release_type != 'patch' }}
run: |
git checkout -b v${{ steps.bump.outputs.version_x }}
git push --set-upstream origin v${{ steps.bump.outputs.version_x }}
- name: Create GitHub draft release
if: ${{ inputs.release_type != 'reupload' }}
id: create-release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ steps.bump.outputs.version }}
draft: true
body: "*Release artifacts for this release are currently being uploaded...*"
- name: Find GitHub draft release
if: ${{ inputs.release_type == 'reupload' }}
id: find-release
uses: actions/github-script@v8
with:
script: |
const releases = await github.paginate(github.rest.repos.listReleases, {
owner: context.repo.owner,
repo: context.repo.repo,
});
const names = releases.map(release => release.name);
console.log(`releases: ${names}`);
const release = releases.find(release => release.tag_name === "v${{ steps.bump.outputs.version }}");
if (release === undefined) {
core.setFailed(`No release found with tag v${{ steps.bump.outputs.version }}!`);
}
if (!release.draft) {
core.setFailed(`Release ${release.tag_name} is not a draft release!`);
}
return release.id;
result-encoding: string
release:
strategy:
matrix:
include:
- os: macos-14-large # Intel
- os: macos-14 # Apple Silicon
- os: windows-2022
- os: ubuntu-24.04
- os: macos-12
- os: macos-14
- os: windows-2019
- os: ubuntu-20.04
runs-on: "${{ matrix.os }}"
timeout-minutes: 45
needs: [prepare]
permissions:
contents: write # To upload release artifacts
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
ref: v${{ inputs.release_type == 'reupload' && needs.prepare.outputs.version_x || needs.prepare.outputs.version }}
ref: v${{ needs.prepare.outputs.version }}
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@v5
with:
python-version: ${{ inputs.python_version }}
python-version: ${{ github.event.inputs.python_version }}
- name: Import GPG Key
if: ${{ startsWith(matrix.os, 'ubuntu-') }}
run: |
@ -187,7 +159,7 @@ jobs:
if: ${{ startsWith(matrix.os, 'ubuntu-') }}
run: |
sudo apt-get update
sudo apt-get install --no-install-recommends libegl1 libxml2-utils docbook-xml xsltproc docbook-xsl
sudo apt-get install --no-install-recommends libegl1-mesa libxml2-utils docbook-xml xsltproc docbook-xsl
- name: Install dependencies
run: |
python -m pip install -U pip
@ -195,20 +167,20 @@ jobs:
# FIXME consider switching to trusted publishers:
# https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/
- name: Build and upload release
run: "tox -e build-release -- --upload --no-confirm ${{ inputs.release_type == 'reupload' && '--reupload' || '' }}"
run: "tox -e build-release -- --upload --no-confirm"
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.QUTEBROWSER_BOT_PYPI_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
finalize:
runs-on: ubuntu-24.04
runs-on: ubuntu-20.04
timeout-minutes: 5
needs: [prepare, release]
permissions:
contents: write # To change release
steps:
- name: Publish final release
uses: actions/github-script@v8
uses: actions/github-script@v7
with:
script: |
await github.rest.repos.updateRelease({
@ -221,7 +193,7 @@ jobs:
irc:
timeout-minutes: 2
continue-on-error: true
runs-on: ubuntu-24.04
runs-on: ubuntu-20.04
needs: [prepare, release, finalize]
if: "${{ always() }}"
steps:

View File

@ -1,5 +1,5 @@
[mypy]
python_version = 3.9
python_version = 3.8
### --strict
warn_unused_configs = True
@ -20,7 +20,6 @@ strict_equality = True
warn_unreachable = True
disallow_any_unimported = True
enable_error_code = ignore-without-code
strict_bytes = True
### Output
show_error_context = True

View File

@ -16,7 +16,7 @@ load-plugins=qute_pylint.config,
pylint.extensions.dunder
persistent=n
py-version=3.9
py-version=3.8
[MESSAGES CONTROL]
enable=all
@ -71,8 +71,7 @@ argument-rgx=[a-z_][a-z0-9_]{0,30}$
variable-rgx=[a-z_][a-z0-9_]{0,30}$
docstring-min-length=3
no-docstring-rgx=(^_|^main$)
class-const-naming-style=snake_case
max-positional-arguments=7
class-const-naming-style = snake_case
[FORMAT]
# FIXME:v4 (lint) down to 88 again once we use black

View File

@ -44,7 +44,7 @@ image:doc/img/hints.png["screenshot 4",width=300,link="doc/img/hints.png"]
Downloads
---------
See the https://github.com/qutebrowser/qutebrowser/releases[GitHub releases
See the https://github.com/qutebrowser/qutebrowser/releases[github releases
page] for available downloads and the link:doc/install.asciidoc[INSTALL] file for
detailed instructions on how to get qutebrowser running on various platforms.
@ -84,7 +84,7 @@ Requirements
The following software and libraries are required to run qutebrowser:
* https://www.python.org/[Python] 3.9 or newer
* https://www.python.org/[Python] 3.8 or newer
* https://www.qt.io/[Qt], either 6.2.0 or newer, or 5.15.0 or newer, with the following modules:
- QtCore / qtbase
- QtQuick (part of qtbase or qtdeclarative in some distributions)
@ -105,6 +105,10 @@ websites and using it for transmission of sensitive data._
* https://palletsprojects.com/p/jinja/[jinja2]
* https://github.com/yaml/pyyaml[PyYAML]
On Python 3.8, the following backport is also required:
* https://importlib-resources.readthedocs.io/[importlib_resources]
On macOS, the following libraries are also required:
* https://pyobjc.readthedocs.io/en/latest/[pyobjc-core and pyobjc-framework-Cocoa]

View File

@ -15,279 +15,6 @@ breaking changes (such as renamed commands) can happen in minor releases.
// `Fixed` for any bug fixes.
// `Security` to invite users to upgrade in case of vulnerabilities.
[[v3.6.4]]
v3.6.4 (unreleased)
-------------------
Fixed
~~~~~
- datalist dropdowns not opening correctly on Wayland/Sway (#8831).
This was caused by an old workaround for a different QtWebEngine issue,
which is now disabled for QtWebEngine 6.6.3 and newer.
[[v3.6.3]]
v3.6.3 (2025-11-30)
-------------------
Fixed
~~~~~
- New `qt.workarounds.disable_accessibility` setting, which disables Chromium
accessibility support. By default, is it set to `auto`, which only disables
accessibility on Qt versions with known issues. This works around a bug in Qt
6.10.1 causing frequent segfaults (#8797).
[[v3.6.2]]
v3.6.2 (2025-11-27)
-------------------
Changed
~~~~~~~
* Windows and macOS releases now ship with Qt 6.10.1, which include
security patches up to Chromium 142.0.7444.162.
Fixed
~~~~~
- The version info now includes the Wayland compositor name if wayland-client is
available under a different name than `libwayland-client.so` (#8771).
- The list of Chromium extensions in `--version` / `:version` now uses the
correct Chromium data profile, also fixing a crash with Qt 6.10.1 (#8785).
- With Qt 6.10.1, `qt.workarounds.disable_hangouts_extension` now doesn't apply
on private profiles, avoiding a Qt bug leading to a crash (#8785).
[[v3.6.1]]
v3.6.1 (2025-11-03)
-------------------
Fixed
~~~~~
- A regression in v3.6.0 where the page didn't have keyboard focus after closing
the completion, so e.g. typing in an input field after hinting didn't work.
(#8750)
[[v3.6.0]]
v3.6.0 (2025-10-24)
-------------------
Added
~~~~~
- The `:version` info now shows additional information:
* The X11 window manager / Wayland compositor name (mostly useful for
bug/crash reports).
* Loaded WebExtensions (partial support landed in QtWebEngine 6.10, no
official qutebrowser support yet).
- Support for hinting elements which are part of an (open) shadow DOM.
Changed
~~~~~~~
- The `qutedmenu` userscript now sorts history by the last access time.
- Hardware accelerated 2D canvas is now enabled by default on Qt 6.8.2+,
as graphic glitches with e.g. PDF.js and Google Sheets should be fixed
nowadays. If you still run into issues, please report them and set
`qt.workarounds.disable_accelerated_2d_canvas` to `always` to disable it
again.
- Changes to binary releases:
* Windows and macOS releases are now built with Qt 6.10.0, which is based
on Chromium 134.0.6998.208 with security patches up to 140.0.7339.207.
* Windows and macOS releases are now built with Python 3.14.
* Windows releases are now built on Windows Server 2022 (previously 2019),
which might break compatibility with older Windows releases (untested).
* If using `mkvenv.py` on Linux, note that Qt now requires glibc v2.34 (v2.28
previously). This is available down to Ubuntu 22.04 LTS and Debian Bookworm
(oldstable), so this should not affect most users of desktop distributions.
Fixed
~~~~~
- Fixed crash if two new downloads start while a download prompt is already open
(#8674).
- Fixed exception when closing a qutebrowser window while a download prompt is
still open.
- Hopefully proper fix for some web pages jumping to the top when the statusbar
is hidden (#8223).
- Fix for the page header being shown on YouTube after the fullscreen
notification was hidden (#8625).
- Fix for videos losing keyboard focus when the fullscreen notification shows
(#8174).
- The workaround for microphone/camera permissions not being requested with
QtWebEngine 6.9 on Google Meet, Zoom, or other pages using the new
`<permission>` element now got extended to Qt 6.9.1+ as it's still not fixed
upstream. (#8612)
- The package version for Jinja 3.3+ is now correctly displayed in `:version`.
- Fixed crash with Qt 6.10 (and possibly older Qt versions) when navigating
from a `qute://` page to a web page, e.g. when searching on `qute://start`.
- On Wayland with Qt <= 6.9, `EGL_PLATFORM=wayland` is now set by qutebrowser to
get hardware rendering. Qt 6.10 includes an equivalent fix (#8637).
- Added workaround for per-domain User-Agent header not being used on redirects
(#8679).
- Added site-specific quirk for gitlab.gnome.org agressively blocking old
Chromium versions (and thus QtWebEngine) (#8509).
- Using `:config-list-remove` with an invalid value for the respective option
type now corrently displays an error instead of crashing.
[[v3.5.1]]
v3.5.1 (2025-06-05)
-------------------
Deprecated
~~~~~~~~~~
- QtWebKit (legacy) support got removed from CI and is now untested. If it
breaks, it's not going to be fixed, and support will be removed over the next
releases.
- Qt 5 support is currently still tested, but is also planned to get removed
over the next releases. Same goes for support for older Qt 6 versions (likely
6.2/6.3/6.4 and perhaps 6.5, see https://github.com/qutebrowser/qutebrowser/issues/8464[#8464]).
Changed
~~~~~~~
- Windows/macOS releases now bundle Qt 6.9.1, including many graphics-related
bugfixes, as well as security patches up to Chromium 136.0.7103.114.
Fixed
~~~~~
- A bogus "wildcard call disconnects from destroyed signal" warning from Qt is
now suppressed.
- PDF.js now loads correctly on Windows installations with broken mimetype
configurations.
- A "Ignoring new child ..." debug log message which got spammy with Qt 6.9 is
now removed.
- A unknown crash (possibly related to using devtools) due to weird (Py)Qt
behavior now has a workaround.
- No "QtWebEngine version mismatch" warning is now logged anymore with newer Qt
5.15 releases (but you should still stop using Qt 5).
- The PDF.js version can now correctly be extracted/displayed with newer PDF.js
versions.
- The `qute-bitwarden`, `-lastpass` and `-pass` userscripts now properly avoid
a `DeprecationWarning` from the upcoming 6.0 release of `tldextract`. The
previous fix in v3.5.1 was insufficient.
[[v3.5.0]]
v3.5.0 (2025-04-12)
-------------------
Changed
~~~~~~~
- Windows/macOS releases are now built with Qt 6.9.0
* Based on Chromium 130.0.6723.192
* Security fixes up to Chromium 133.0.6943.141
* Also fixes issues with opening links on macOS
- The `content.headers.user_agent` setting now has a new
`{upstream_browser_version_short}` template field, which is the
upstream/Chromium version but shortened to only major version.
- The default user agent now uses the shortened Chromium version and doesn't
expose the `QtWebEngine/...` part anymore, thus making it equal to the
corresponding Chromium user agent. This increases compatibilty due to various
overzealous "security" products used by a variety of websites that block
QtWebEngine, presumably as a bot (known issues existed with Whatsapp Web, UPS,
Digitec Galaxus).
- Changed features in userscripts:
* `qute-bitwarden` now passes your password to the subprocess in an
environment variable when unlocking your vault, instead of as a command
line argument. (#7781)
- New `-D no-system-pdfjs` debug flag to ignore system-wide PDF.js installations
for testing.
- Polyfill for missing `URL.parse` with PDF.js v5 and QtWebEngine < 6.9. Note
this is a "best effort" fix and you should be using the "older browsers"
("legacy") build of PDF.js instead.
Removed
~~~~~~~
- The `ua-slack` site-specific quirk, as things seem to work better nowadays
without a quirk needed.
- The `ua-whatsapp` site-specific quirk, as it's unneeded with the default UA
change described above.
Fixed
~~~~~
- Crash when trying to use the `DocumentPictureInPicture` JS API, such as done
by the new Google Workspaces Huddle feature. The API is unsupported by
QtWebEngine and now correctly disabled on the JS side. (#8449)
- Crash when a buggy notification presenter returns a duplicate ID (now an
error is shown instead).
- Crashes when running `:tab-move` or `:yank title` at startup, before a tab is
available.
- Crash with `input.insert_mode.auto_load`, when closing a new tab quickly after
opening it, but before it was fully loaded. (#3895, #8400)
- Workaround for microphone/camera permissions not being requested with
QtWebEngine 6.9.0 on Google Meet, Zoom, or other pages using the new
`<permission>` element. (#8539)
- Resolved issues in userscripts:
* `qute-bitwarden` will now prompt a re-login if its cached session has
been invalidated since last used. (#8456)
* `qute-bitwarden`, `-lastpass` and `-pass` now avoid a
`DeprecationWarning` from the upcoming 6.0 release of `tldextract`
[[v3.4.0]]
v3.4.0 (2024-12-14)
-------------------
Removed
~~~~~~~
- Support for Python 3.8 is dropped, and Python 3.9 is now required. (#8325)
- Support for macOS 12 Monterey is now dropped, and binaries will be built on
macOS 13 Ventura. (#8327)
- When using the installer on Windows 10, build 1809 or newer is now required
(previous versions required 1607 or newer, but that's not officialy supported by
Qt upstream). (#8336)
Changed
~~~~~~~
- Windows/macOS binaries are now built with Qt 6.8.1. (#8242)
- Based on Chromium 122.0.6261.171
- With security patches up to 131.0.6778.70
- Windows/macOS binaries are now using Python 3.13. (#8205)
- The `.desktop` file now also declares qutebrowser as a valid viewer for
`image/webp`. (#8340)
- Updated mimetype information for getting a suitable extension when downloading
a `data:` URL.
- The `content.javascript.clipboard` setting now defaults to "ask", which on
Qt 6.8+ will prompt the user to grant clipboard access. On older Qt versions,
this is still equivalent to `"none"` and needs to be set manually. (#8348)
- If a XHR request made via JS sets a custom `Accept-Language` header, it now
correctly has precedence over the global `content.headers.accept_language`
setting (but not per-domain overrides). This fixes subtle JS issues on
websites that rely on the custom header being sent for those requests, and
e.g. block the requests server-side otherwise. (#8370)
- Our packaging scripts now prefer the "legacy"/"for older browsers" PDF.js
build as their normal release only supports the latest Chromium version and
might break in qutebrowser on updates. **Note to packagers:** If there's a
PDF.js package in your distribution as an (optional) qutebrowser dependency,
consider also switching to this variant (same code, built differently).
Fixed
~~~~~
- Crash with recent Jinja/Markupsafe versions when viewing a finished userscript
(or potentially editor) process via `:process`.
- `scripts/open_url_in_instance.sh` now avoids `echo -n`, thus running
correctly on POSIX sh. (#8409)
- Added a workaround for a bogus QtWebEngine warning about missing spell
checking dictionaries. (#8330)
[[v3.3.1]]
v3.3.1 (2024-10-12)
-------------------
Fixed
~~~~~
- Updated the workaround for Google sign-in issues.
[[v3.3.0]]
v3.3.0 (2024-10-12)
-------------------
@ -297,16 +24,16 @@ Added
- Added the `qt.workarounds.disable_hangouts_extension` setting,
for disabling the Google Hangouts extension built into Chromium/QtWebEngine.
- Failed end2end tests will now save screenshots of the browser window when
run under xvfb (the default on linux). Screenshots will be under
`$TEMP/pytest-current/pytest-screenshots/` or attached to the GitHub actions
run as an artifact. (#7625)
Removed
~~~~~~~
- Support for macOS 11 Big Sur is dropped. Binaries are now built on macOS 12
Monterey and are unlikely to still run on older macOS versions.
- Failed end2end tests will now save screenshots of the browser window when
run under xvfb (the default on linux). Screenshots will be under
`$TEMP/pytest-current/pytest-screenshots/` or attached to the GitHub actions
run as an artifact. (#7625)
Changed
~~~~~~~
@ -318,8 +45,6 @@ Changed
respected when yanking any URL (for example, through hints with `hint links
yank`). The `{url:yank}` substitution has also been added as a version of
`{url}` that respects ignored URL query parameters. (#7879)
- Windows and macOS releases now bundle Qt 6.7.3, which includes security fixes
up to Chromium 129.0.6668.58.
Fixed
~~~~~
@ -330,7 +55,6 @@ Fixed
documentation in our settings docs now loads a live page again. (#8268)
- A rare crash when on Qt 6, a renderer process terminates with an unknown
termination reason.
- Updated the workaround for Google sign-in issues.
[[v3.2.1]]
v3.2.1 (2024-06-25)

View File

@ -41,7 +41,7 @@ If you want to find something useful to do, check the
https://github.com/qutebrowser/qutebrowser/issues[issue tracker]. Some
pointers:
* https://github.com/qutebrowser/qutebrowser/contribute[Issues which should
* https://github.com/qutebrowser/qutebrowser/labels/easy[Issues which should
be easy to solve]
* https://github.com/qutebrowser/qutebrowser/labels/component%3A%20docs[Documentation issues which require little/no coding]
@ -111,9 +111,9 @@ unittests and several linters/checkers.
Currently, the following tox environments are available:
* Tests using https://www.pytest.org[pytest]:
- `py39`, `py310`, ...: Run pytest for python 3.9/3.10/... with the system-wide PyQt.
- `py39-pyqt515`, ..., `py39-pyqt65`: Run pytest with the given PyQt version (`py310-*` etc. also works).
- `py39-pyqt515-cov`: Run with coverage support (other Python/PyQt versions work too).
- `py38`, `py39`, ...: Run pytest for python 3.8/3.9/... with the system-wide PyQt.
- `py38-pyqt515`, ..., `py38-pyqt65`: Run pytest with the given PyQt version (`py39-*` etc. also works).
- `py38-pyqt515-cov`: Run with coverage support (other Python/PyQt versions work too).
* `flake8`: Run various linting checks via https://pypi.python.org/pypi/flake8[flake8].
* `vulture`: Run https://pypi.python.org/pypi/vulture[vulture] to find
unused code portions.
@ -121,6 +121,8 @@ Currently, the following tox environments are available:
* `pyroma`: Check packaging practices with
https://pypi.python.org/pypi/pyroma/[pyroma].
* `eslint`: Run https://eslint.org/[ESLint] javascript checker.
* `check-manifest`: Check MANIFEST.in completeness with
https://github.com/mgedmin/check-manifest[check-manifest].
* `mkvenv`: Bootstrap a virtualenv for testing.
* `misc`: Run `scripts/misc_checks.py` to check for:
- untracked git files
@ -169,16 +171,16 @@ Examples:
----
# run only pytest tests which failed in last run:
tox -e py39 -- --lf
tox -e py38 -- --lf
# run only the end2end feature tests:
tox -e py39 -- tests/end2end/features
tox -e py38 -- tests/end2end/features
# run everything with undo in the generated name, based on the scenario text
tox -e py39 -- tests/end2end/features/test_tabs_bdd.py -k undo
tox -e py38 -- tests/end2end/features/test_tabs_bdd.py -k undo
# run coverage test for specific file (updates htmlcov/index.html)
tox -e py39-cov -- tests/unit/browser/test_webelem.py
tox -e py38-cov -- tests/unit/browser/test_webelem.py
----
Specifying the backend for tests
@ -602,7 +604,6 @@ Info pages:
- chrome://device-log/ (QtWebEngine >= 6.3)
- chrome://gpu/
- chrome://sandbox/ (Linux only)
- chrome://qt/ (QtWebEngine >= 6.7)
Misc. / Debugging pages:
@ -613,7 +614,6 @@ Misc. / Debugging pages:
- chrome://ukm/ (QtWebEngine >= 5.15.3)
- chrome://user-actions/ (QtWebEngine >= 5.15.3)
- chrome://webrtc-logs/ (QtWebEngine >= 5.15.3)
- chrome://extensions/ (QtWebEngine >= 6.10)
Internals pages:
@ -789,12 +789,10 @@ New PyQt release
qutebrowser release
~~~~~~~~~~~~~~~~~~~
* Make sure there are no unstaged or unpushed changes.
* Make sure CI is reasonably green.
* Make sure there are no unstaged changes and the tests are green.
* Make sure all issues with the related milestone are closed.
* Mark the https://github.com/qutebrowser/qutebrowser/milestones[milestone] as closed.
* Consider updating the completions for `content.headers.user_agent` in `configdata.yml`
and the Firefox UA in `qutebrowser/browser/webengine/webenginesettings.py`.
* Consider updating the completions for `content.headers.user_agent` in `configdata.yml`.
* Minor release: Consider updating some files from main:
- `misc/requirements/` and `requirements.txt`
- `scripts/`
@ -804,8 +802,7 @@ qutebrowser release
**Automatic release via GitHub Actions (starting with v3.0.0):**
* Double check Python version in `.github/workflows/release.yml`
* Run the `release` workflow on the `main` branch, e.g. via `gh workflow run release -f release_type=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
* 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`)
**Manual release:**

View File

@ -61,7 +61,7 @@ Why Python?::
point, I wasn't comfortable with C++ so that wasn't an alternative.
But isn't Python too slow for a browser?::
https://www.infoworld.com/article/2303031/van-rossum-python-is-not-too-slow-2.html[It's generally less of a problem than one would expect.]
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.]
Most of the heavy lifting of qutebrowser is done by Qt and
QtWebKit/QtWebEngine in C++, with the
https://wiki.python.org/moin/GlobalInterpreterLock[GIL] released.
@ -141,7 +141,7 @@ The comma prefix is used to make sure user-defined bindings don't conflict with
the built-in ones.
+
Note that you might need an additional package (e.g.
https://archlinux.org/packages/extra/any/yt-dlp/[yt-dlp] on
https://www.archlinux.org/packages/community/any/youtube-dl/[youtube-dl] on
Archlinux) to play web videos with mpv.
+
There is a very useful script for mpv, which emulates "unique application"

View File

@ -179,7 +179,7 @@ customizable for a given <<patterns,URL patterns>>.
[source,python]
----
config.set('content.images', False, '*://example.com/*')
config.set('content.images', False, '*://example.com/')
----
Alternatively, you can use `with config.pattern(...) as p:` to get a shortcut
@ -187,7 +187,7 @@ similar to `c.` which is scoped to the given domain:
[source,python]
----
with config.pattern('*://example.com/*') as p:
with config.pattern('*://example.com/') as p:
p.content.images = False
----
@ -416,8 +416,6 @@ Pre-built colorschemes
- https://github.com/gicrisf/qute-city-lights[City Lights (matte dark)]
- https://github.com/catppuccin/qutebrowser[Catppuccin]
- https://github.com/iruzo/matrix-qutebrowser[Matrix]
- https://github.com/harmtemolder/qutebrowser-solarized[Solarized]
- https://github.com/Rehpotsirhc-z/qutebrowser-doom-one[Doom One]
Avoiding flake8 errors
^^^^^^^^^^^^^^^^^^^^^^
@ -454,7 +452,7 @@ Various emacs/conkeror-like keybinding configs exist:
- https://gitlab.com/Kaligule/qutebrowser-emacs-config/blob/master/config.py[Kaligule]
- https://web.archive.org/web/20210512185023/https://me0w.net/pit/1540882719[nm0i]
- https://www.reddit.com/r/qutebrowser/comments/eh10i7/config_share_qute_with_emacs_keybindings/[jasonsun0310]
- https://git.sr.ht/~willvaughn/dots/tree/main/item/.config/qutebrowser/qutemacs.py[willvaughn]
- https://git.sr.ht/~willvaughn/dots/tree/mjolnir/item/.config/qutebrowser/qutemacs.py[willvaughn]
It's also mostly possible to get rid of modal keybindings by setting
`input.insert_mode.auto_enter` to `false`, and `input.forward_unbound_keys` to

View File

@ -26,7 +26,7 @@ Getting help
You can get help in the IRC channel
link:ircs://irc.libera.chat:6697/#qutebrowser[`#qutebrowser`] on
https://libera.chat/[Libera Chat]
(https://web.libera.chat/#qutebrowser[webchat]),
(https://web.libera.chat/#qutebrowser[webchat], https://matrix.to/#qutebrowser:libera.chat[via Matrix]),
or by writing a message to the
https://listi.jpberlin.de/mailman/listinfo/qutebrowser[mailinglist] at
mailto:qutebrowser@lists.qutebrowser.org[].

View File

@ -302,7 +302,6 @@
|<<qt.force_software_rendering,qt.force_software_rendering>>|Force software rendering for QtWebEngine.
|<<qt.highdpi,qt.highdpi>>|Turn on Qt HighDPI scaling.
|<<qt.workarounds.disable_accelerated_2d_canvas,qt.workarounds.disable_accelerated_2d_canvas>>|Disable accelerated 2d canvas to avoid graphical glitches.
|<<qt.workarounds.disable_accessibility,qt.workarounds.disable_accessibility>>|Disable accessibility to avoid crashes on Qt 6.10.1.
|<<qt.workarounds.disable_hangouts_extension,qt.workarounds.disable_hangouts_extension>>|Disable the Hangouts extension.
|<<qt.workarounds.locale,qt.workarounds.locale>>|Work around locale parsing issues in QtWebEngine 5.15.3.
|<<qt.workarounds.remove_service_workers,qt.workarounds.remove_service_workers>>|Delete the QtWebEngine Service Worker directory on every start.
@ -2276,22 +2275,21 @@ The following placeholders are defined:
* `{upstream_browser_key}`: "Version" for QtWebKit, "Chrome" for
QtWebEngine.
* `{upstream_browser_version}`: The corresponding Safari/Chrome version.
* `{upstream_browser_version_short}`: The corresponding Safari/Chrome
version, but only with its major version.
* `{qutebrowser_version}`: The currently running qutebrowser version.
The default value is equal to the default user agent of
QtWebKit/QtWebEngine, but with the `QtWebEngine/...` part removed for
increased compatibility.
The default value is equal to the unchanged user agent of
QtWebKit/QtWebEngine.
Note that the value read from JavaScript is always the global value.
Note that the value read from JavaScript is always the global value. With
QtWebEngine between 5.12 and 5.14 (inclusive), changing the value exposed
to JavaScript requires a restart.
This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,FormatString>>
Default: +pass:[Mozilla/5.0 ({os_info}) AppleWebKit/{webkit_version} (KHTML, like Gecko) {upstream_browser_key}/{upstream_browser_version_short} Safari/{webkit_version}]+
Default: +pass:[Mozilla/5.0 ({os_info}) AppleWebKit/{webkit_version} (KHTML, like Gecko) {qt_key}/{qt_version} {upstream_browser_key}/{upstream_browser_version} Safari/{webkit_version}]+
[[content.hyperlink_auditing]]
=== content.hyperlink_auditing
@ -2347,20 +2345,18 @@ Default: +pass:[false]+
=== content.javascript.clipboard
Allow JavaScript to read from or write to the clipboard.
With QtWebEngine, writing the clipboard as response to a user interaction is always allowed.
On Qt < 6.8, the `ask` setting is equivalent to `none` and permission needs to be granted manually via this setting.
This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,JSClipboardPermission>>
Type: <<types,String>>
Valid values:
* +none+: Disable access to clipboard.
* +access+: Allow reading from and writing to the clipboard.
* +access-paste+: Allow accessing the clipboard and pasting clipboard content.
* +ask+: Prompt when requested (grants 'access-paste' permission).
Default: +pass:[ask]+
Default: +pass:[none]+
[[content.javascript.enabled]]
=== content.javascript.enabled
@ -2769,9 +2765,10 @@ Type: <<types,FlagList>>
Valid values:
* +ua-whatsapp+
* +ua-google+
* +ua-slack+
* +ua-googledocs+
* +ua-gnome-gitlab+
* +js-whatsapp-web+
* +js-discord+
* +js-string-replaceall+
@ -3894,7 +3891,7 @@ Chromium has various sandboxing layers, which should be enabled for normal brows
Open `chrome://sandbox` to see the current sandbox status.
Changing this setting is only recommended if you know what you're doing, as it **disables one of Chromium's security layers**. To avoid sandboxing being accidentally disabled persistently, this setting can only be set via `config.py`, not via `:set`.
See the Chromium documentation for more details:
- https://chromium.googlesource.com/chromium/src/\+/HEAD/sandbox/linux/README.md[Linux] - https://chromium.googlesource.com/chromium/src/\+/HEAD/docs/design/sandbox.md[Windows] - https://chromium.googlesource.com/chromium/src/\+/HEAD/sandbox/mac/README.md[Mac] - https://chromium.googlesource.com/chromium/src/\+/HEAD/docs/design/sandbox_faq.md[FAQ (Windows-centric)]
- https://chromium.googlesource.com/chromium/src/\+/HEAD/docs/linux/sandboxing.md[Linux] - https://chromium.googlesource.com/chromium/src/\+/HEAD/docs/design/sandbox.md[Windows] - https://chromium.googlesource.com/chromium/src/\+/HEAD/docs/design/sandbox_faq.md[FAQ (Windows-centric)]
This setting requires a restart.
@ -3992,29 +3989,11 @@ Type: <<types,String>>
Valid values:
* +always+: Disable accelerated 2d canvas
* +auto+: Disable on Qt versions with known issues, enable otherwise
* +auto+: Disable on Qt6 < 6.6.0, enable otherwise
* +never+: Enable accelerated 2d canvas
Default: +pass:[auto]+
[[qt.workarounds.disable_accessibility]]
=== qt.workarounds.disable_accessibility
Disable accessibility to avoid crashes on Qt 6.10.1.
This setting requires a restart.
This setting is only available with the QtWebEngine backend.
Type: <<types,String>>
Valid values:
* +always+: Disable renderer accessibility
* +auto+: Disable on Qt versions with known issues, enable otherwise
* +never+: Enable renderer accessibility
Default: +pass:[auto]+
[[qt.workarounds.disable_hangouts_extension]]
=== qt.workarounds.disable_hangouts_extension
Disable the Hangouts extension.
@ -4865,7 +4844,6 @@ Lists with duplicate flags are invalid. Each item is checked against the valid v
|FuzzyUrl|A URL which gets interpreted as search if needed.
|IgnoreCase|Whether to search case insensitively.
|Int|Base class for an integer setting.
|JSClipboardPermission|Permission for page JS to access the system clipboard.
|Key|A name of a key.
|List|A list of values.

View File

@ -54,7 +54,7 @@ newer qutebrowser running with:
Note you'll need some basic libraries to use the virtualenv-installed PyQt:
----
# apt install --no-install-recommends git ca-certificates python3 python3-venv libgl1 libxkbcommon-x11-0 libegl1 libfontconfig1 libglib2.0-0 libdbus-1-3 libxcb-cursor0 libxcb-icccm4 libxcb-keysyms1 libxcb-shape0 libnss3 libxcomposite1 libxdamage1 libxrender1 libxrandr2 libxtst6 libxi6 libasound2
# apt install --no-install-recommends git ca-certificates python3 python3-venv libgl1 libxkbcommon-x11-0 libegl1-mesa libfontconfig1 libglib2.0-0 libdbus-1-3 libxcb-cursor0 libxcb-icccm4 libxcb-keysyms1 libxcb-shape0 libnss3 libxcomposite1 libxdamage1 libxrender1 libxrandr2 libxtst6 libxi6 libasound2
----
Additional hints
@ -103,18 +103,12 @@ To be able to play videos with proprietary codecs with QtWebEngine, you will
need to install an additional package from the RPM Fusion Free repository.
For more information see https://rpmfusion.org/Configuration.
With Qt 6 (recommended):
-----
# dnf install libavcodec-freeworld
-----
With Qt 5:
-----
# dnf install qt5-qtwebengine-freeworld
-----
It's currently unknown what the Qt 6 equivalent of this is.
On Archlinux
------------
@ -444,7 +438,7 @@ This installs all needed Python dependencies in a `.venv` subfolder
This comes with an up-to-date Qt/PyQt including a pre-compiled QtWebEngine
binary, but has a few caveats:
- Make sure your `python3` is Python 3.9 or newer, otherwise you'll get a "No
- Make sure your `python3` is Python 3.8 or newer, otherwise you'll get a "No
matching distribution found" error and/or qutebrowser will not run.
- It only works on 64-bit x86 systems, with other architectures you'll get the
same error.

View File

@ -432,18 +432,32 @@ Function .onInit
StrCpy $KeepReg 1
; OS version check
; https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoa#remarks
; https://learn.microsoft.com/en-us/windows/release-health/release-information
; https://learn.microsoft.com/en-us/windows/release-health/windows11-release-information
${If} ${AtLeastWin11}
Goto _os_check_pass
${ElseIf} ${IsNativeAMD64} ; Windows 10 has no x86_64 emulation on arm64
${AndIf} ${AtLeastWin10}
${AndIf} ${AtLeastBuild} 17763 ; Windows 10 1809 (also in error message below)
Goto _os_check_pass
${If} ${RunningX64}
; https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoa#remarks
GetWinVer $R0 Major
!if "${QT5}" == "True"
IntCmpU $R0 6 0 _os_check_fail _os_check_pass
GetWinVer $R1 Minor
IntCmpU $R1 2 _os_check_pass _os_check_fail _os_check_pass
!else
IntCmpU $R0 10 0 _os_check_fail _os_check_pass
GetWinVer $R1 Build
${If} $R1 >= 22000 ; Windows 11 21H2
Goto _os_check_pass
${ElseIf} $R1 >= 14393 ; Windows 10 1607
${AndIf} ${IsNativeAMD64} ; Windows 10 has no x86_64 emulation on arm64
Goto _os_check_pass
${EndIf}
!endif
${EndIf}
MessageBox MB_OK|MB_ICONSTOP "This version of ${PRODUCT_NAME} requires a 64-bit$\r$\n\
version of Windows 10 1809 or later."
_os_check_fail:
!if "${QT5}" == "True"
MessageBox MB_OK|MB_ICONSTOP "This version of ${PRODUCT_NAME} requires a 64-bit$\r$\n\
version of Windows 8 or later."
!else
MessageBox MB_OK|MB_ICONSTOP "This version of ${PRODUCT_NAME} requires a 64-bit$\r$\n\
version of Windows 10 1607 or later."
!endif
Abort
_os_check_pass:

View File

@ -131,6 +131,9 @@ ShowUninstDetails hide
!define /ifndef DIST_DIR ".\..\..\dist\${PRODUCT_NAME}-${VERSION}"
!endif
; If not defined, assume Qt6 (requires a more recent windows version)
!define /ifndef QT5 "False"
; Pack the exe header with upx if UPX is defined.
!ifdef UPX
!packhdr "$%TEMP%\exehead.tmp" '"upx" "--ultra-brute" "$%TEMP%\exehead.tmp"'

View File

@ -44,14 +44,6 @@
</content_rating>
<releases>
<!-- Add new releases here -->
<release version='3.6.3' date='2025-11-30'/>
<release version='3.6.2' date='2025-11-27'/>
<release version='3.6.1' date='2025-11-03'/>
<release version='3.6.0' date='2025-10-24'/>
<release version='3.5.1' date='2025-06-05'/>
<release version='3.5.0' date='2025-04-12'/>
<release version="3.4.0" date="2024-12-14"/>
<release version="3.3.1" date="2024-10-12"/>
<release version="3.3.0" date="2024-10-12"/>
<release version="3.2.1" date="2024-06-25"/>
<release version="3.2.0" date="2024-06-03"/>

View File

@ -48,7 +48,7 @@ Categories=Network;WebBrowser;
Exec=qutebrowser --untrusted-args %u
Terminal=false
StartupNotify=true
MimeType=text/html;text/xml;application/xhtml+xml;application/xml;application/rdf+xml;image/gif;image/webp;image/jpeg;image/png;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/qute;
MimeType=text/html;text/xml;application/xhtml+xml;application/xml;application/rdf+xml;image/gif;image/jpeg;image/png;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/qute;
Keywords=Browser
Actions=new-window;preferences;

View File

@ -25,66 +25,24 @@ INFO_PLIST_UPDATES = {
"CFBundleURLName": "local file URL",
"CFBundleURLSchemes": ["file"]
}],
'CFBundleDocumentTypes': [
{
"CFBundleTypeIconFile": "document.icns",
"CFBundleTypeName": name,
"CFBundleTypeRole": "Viewer",
"LSItemContentTypes": [content_type],
}
for name, content_type in [
("GIF image", "com.compuserve.gif"),
("HTML document", "public.html"),
("XHTML document", "public.xhtml"),
("JavaScript script", "com.netscape.javascript-source"),
("JPEG image", "public.jpeg"),
("MHTML document", "org.ietf.mhtml"),
("HTML5 Audio (Ogg)", "org.xiph.ogg-audio"),
("HTML5 Video (Ogg)", "org.xiph.oggv"),
("PNG image", "public.png"),
("SVG document", "public.svg-image"),
("Plain text document", "public.text"),
("HTML5 Video (WebM)", "org.webmproject.webm"),
("WebP image", "org.webmproject.webp"),
("PDF Document", "com.adobe.pdf"),
]
],
'UTImportedTypeDeclarations': [
{
"UTTypeConformsTo": ["public.data", "public.content"],
"UTTypeDescription": "MIME HTML document",
"UTTypeIconFile": "document.icns",
"UTTypeIdentifier": "org.ietf.mhtml",
"UTTypeReferenceURL": "https://www.ietf.org/rfc/rfc2557",
"UTTypeTagSpecification": {
"com.apple.ostype": "MHTM",
"public.filename-extension": ["mht", "mhtml"],
"public.mime-type": ["multipart/related", "application/x-mimearchive"],
},
},
{
"UTTypeConformsTo": ["public.audio"],
"UTTypeDescription": "Ogg Audio",
"UTTypeIconFile": "document.icns",
"UTTypeIdentifier": "org.xiph.ogg-audio",
"UTTypeReferenceURL": "https://xiph.org/ogg/",
"UTTypeTagSpecification": {
"public.filename-extension": ["ogg", "oga"],
"public.mime-type": ["audio/ogg"],
},
},
{
"UTTypeConformsTo": ["public.movie"],
"UTTypeDescription": "Ogg Video",
"UTTypeIconFile": "document.icns",
"UTTypeIdentifier": "org.xiph.ogv",
"UTTypeReferenceURL": "https://xiph.org/ogg/",
"UTTypeTagSpecification": {
"public.filename-extension": ["ogm", "ogv"],
"public.mime-type": ["video/ogg"],
},
},
],
'CFBundleDocumentTypes': [{
"CFBundleTypeExtensions": ["html", "htm"],
"CFBundleTypeMIMETypes": ["text/html"],
"CFBundleTypeName": "HTML document",
"CFBundleTypeOSTypes": ["HTML"],
"CFBundleTypeRole": "Viewer",
}, {
"CFBundleTypeExtensions": ["xhtml"],
"CFBundleTypeMIMETypes": ["text/xhtml"],
"CFBundleTypeName": "XHTML document",
"CFBundleTypeRole": "Viewer",
}, {
"CFBundleTypeExtensions": ["mhtml"],
"CFBundleTypeMIMETypes": ["multipart/related", "application/x-mimearchive", "message/rfc822"],
"CFBundleTypeName": "MHTML document",
"CFBundleTypeRole": "Viewer",
}],
# https://developer.apple.com/documentation/avfoundation/cameras_and_media_capture/requesting_authorization_for_media_capture_on_macos
#
# Keys based on Google Chrome's .app, except Bluetooth keys which seem to

View File

@ -0,0 +1,9 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
build==1.2.2
check-manifest==0.49
importlib_metadata==8.5.0
packaging==24.1
pyproject_hooks==1.2.0
tomli==2.0.1
zipp==3.20.2

View File

@ -0,0 +1 @@
check-manifest

View File

@ -1,73 +1,54 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
annotated-types==0.7.0
anyio==4.12.0
autocommand==2.2.2
backports.tarfile==1.2.0
bracex==2.6
build==1.3.0
bump-my-version==1.2.5
certifi==2025.11.12
cffi==2.0.0
charset-normalizer==3.4.4
click==8.1.8
cryptography==46.0.3
docutils==0.22.3
exceptiongroup==1.3.1
build==1.2.2
bump2version==1.0.1
certifi==2024.8.30
cffi==1.17.1
charset-normalizer==3.3.2
cryptography==43.0.1
docutils==0.20.1
github3.py==4.0.1
h11==0.16.0
httpcore==1.0.9
httpx==0.28.1
hunter==3.9.0
id==1.5.0
idna==3.11
importlib_metadata==8.7.0
importlib_resources==6.5.2
hunter==3.7.0
idna==3.10
importlib_metadata==8.5.0
importlib_resources==6.4.5
inflect==7.3.1
jaraco.classes==3.4.0
jaraco.collections==5.1.0
jaraco.context==6.0.1
jaraco.functools==4.0.1
jaraco.functools==4.1.0
jaraco.text==3.12.1
jeepney==0.9.0
keyring==25.7.0
jeepney==0.8.0
keyring==25.4.1
manhole==1.8.1
markdown-it-py==3.0.0
mdurl==0.1.2
more-itertools==10.8.0
nh3==0.3.2
packaging==25.0
platformdirs==4.4.0
prompt_toolkit==3.0.52
pycparser==2.23
pydantic==2.12.5
pydantic-settings==2.11.0
pydantic_core==2.41.5
Pygments==2.19.2
PyJWT==2.10.1
more-itertools==10.5.0
nh3==0.2.18
packaging==24.1
pkginfo==1.10.0
platformdirs==4.3.6
pycparser==2.22
Pygments==2.18.0
PyJWT==2.9.0
Pympler==1.1
pyproject_hooks==1.2.0
PyQt-builder==1.19.1
PyQt-builder==1.16.4
python-dateutil==2.9.0.post0
python-dotenv==1.2.1
questionary==2.1.1
readme_renderer==44.0
requests==2.32.5
readme_renderer==43.0
requests==2.32.3
requests-toolbelt==1.0.0
rfc3986==2.0.0
rich==14.2.0
rich-click==1.9.4
rich==13.8.1
SecretStorage==3.3.3
sip==6.14.0
six==1.17.0
tomli==2.3.0
tomlkit==0.13.3
twine==6.2.0
sip==6.8.6
six==1.16.0
tomli==2.0.1
twine==5.1.1
typeguard==4.3.0
typing-inspection==0.4.2
typing_extensions==4.15.0
uritemplate==4.2.0
# urllib3==2.6.2
wcmatch==10.1
wcwidth==0.2.14
zipp==3.23.0
typing_extensions==4.12.2
uritemplate==4.1.1
# urllib3==2.2.3
zipp==3.20.2

View File

@ -1,7 +1,7 @@
hunter
pympler
github3.py
bump-my-version
bump2version
requests
pyqt-builder
build
@ -9,7 +9,6 @@ twine
# Included to override setuptools' vendored version that is being included in
# the lock file by pip freeze.
importlib_resources
platformdirs
# Already included via test requirements

View File

@ -1,23 +1,23 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
attrs==25.4.0
flake8==7.3.0
flake8-bugbear==24.12.12
flake8-builtins==3.0.0
flake8-comprehensions==3.17.0
attrs==24.2.0
flake8==7.1.1
flake8-bugbear==24.8.19
flake8-builtins==2.5.0
flake8-comprehensions==3.15.0
flake8-debugger==4.1.2
flake8-deprecated==2.2.1
flake8-docstrings==1.7.0
flake8-future-import==0.4.7
flake8-plugin-utils==1.3.3
flake8-pytest-style==2.1.0
flake8-pytest-style==2.0.0
flake8-string-format==0.3.0
flake8-tidy-imports==4.12.0
flake8-tidy-imports==4.10.0
flake8-tuple==0.4.1
mccabe==0.7.0
pep8-naming==0.15.1
pycodestyle==2.14.0
pep8-naming==0.14.1
pycodestyle==2.12.1
pydocstyle==6.3.0
pyflakes==3.4.0
six==1.17.0
snowballstemmer==3.0.1
pyflakes==3.2.0
six==1.16.0
snowballstemmer==2.2.0

View File

@ -1,20 +1,21 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
chardet==5.2.0
diff_cover==10.0.0
Jinja2==3.1.6
librt==0.7.3
lxml==6.0.2
MarkupSafe==3.0.3
mypy==1.19.0
mypy_extensions==1.1.0
pathspec==0.12.1
pluggy==1.6.0
Pygments==2.19.2
diff_cover==9.2.0
importlib_resources==6.4.5
Jinja2==3.1.4
lxml==5.3.0
MarkupSafe==2.1.5
mypy==1.11.2
mypy-extensions==1.0.0
pluggy==1.5.0
Pygments==2.18.0
PyQt5-stubs==5.15.6.0
tomli==2.3.0
types-colorama==0.4.15.20250801
types-docutils==0.22.3.20251115
types-Pygments==2.19.0.20251121
types-PyYAML==6.0.12.20250915
typing_extensions==4.15.0
tomli==2.0.1
types-colorama==0.4.15.20240311
types-docutils==0.21.0.20240907
types-Pygments==2.18.0.20240506
types-PyYAML==6.0.12.20240917
types-setuptools==75.1.0.20240917
typing_extensions==4.12.2
zipp==3.20.2

View File

@ -6,3 +6,6 @@ PyQt5-stubs
types-PyYAML
types-colorama
types-Pygments
# So stubs are available even on newer Python versions
importlib_resources

View File

@ -1,8 +1,8 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
altgraph==0.17.5
importlib_metadata==8.7.0
packaging==25.0
pyinstaller==6.17.0
pyinstaller-hooks-contrib==2025.10
zipp==3.23.0
altgraph==0.17.4
importlib_metadata==8.5.0
packaging==24.1
pyinstaller==6.10.0
pyinstaller-hooks-contrib==2024.8
zipp==3.20.2

View File

@ -1,28 +1,26 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
astroid==3.3.11
certifi==2025.11.12
cffi==2.0.0
charset-normalizer==3.4.4
cryptography==46.0.3
dill==0.4.0
astroid==3.2.4
certifi==2024.8.30
cffi==1.17.1
charset-normalizer==3.3.2
cryptography==43.0.1
dill==0.3.9
github3.py==4.0.1
idna==3.11
importlib_metadata==8.7.0
isort==6.1.0
idna==3.10
isort==5.13.2
mccabe==0.7.0
pefile==2024.8.26
platformdirs==4.4.0
pycparser==2.23
PyJWT==2.10.1
pylint==3.3.9
platformdirs==4.3.6
pycparser==2.22
PyJWT==2.9.0
pylint==3.2.7
python-dateutil==2.9.0.post0
./scripts/dev/pylint_checkers
requests==2.32.5
six==1.17.0
tomli==2.3.0
tomlkit==0.13.3
typing_extensions==4.15.0
uritemplate==4.2.0
# urllib3==2.6.2
zipp==3.23.0
requests==2.32.3
six==1.16.0
tomli==2.0.1
tomlkit==0.13.2
typing_extensions==4.12.2
uritemplate==4.1.1
# urllib3==2.2.3

View File

@ -7,6 +7,7 @@ pefile
# fix qute-pylint location
#@ replace: qute[_-]pylint.* ./scripts/dev/pylint_checkers
#@ markers: typed-ast python_version<"3.8"
# Already included via test requirements
#@ ignore: urllib3

View File

@ -1,5 +1,5 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
PyQt5==5.15.2 # rq.filter: == 5.15.2
PyQt5_sip==12.17.1
PyQt5_sip==12.15.0
PyQtWebEngine==5.15.2 # rq.filter: == 5.15.2

View File

@ -1,7 +1,7 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
PyQt5==5.15.11 # rq.filter: < 5.16
PyQt5-Qt5==5.15.18
PyQt5_sip==12.17.1
PyQt5-Qt5==5.15.15
PyQt5_sip==12.15.0
PyQtWebEngine==5.15.7 # rq.filter: < 5.16
PyQtWebEngine-Qt5==5.15.18
PyQtWebEngine-Qt5==5.15.15

View File

@ -1,7 +1,7 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
PyQt5==5.15.11
PyQt5-Qt5==5.15.18
PyQt5_sip==12.17.1
PyQt5-Qt5==5.15.15
PyQt5_sip==12.15.0
PyQtWebEngine==5.15.7
PyQtWebEngine-Qt5==5.15.18
PyQtWebEngine-Qt5==5.15.15

View File

@ -1,8 +0,0 @@
# 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/

View File

@ -1,8 +0,0 @@
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/

View File

@ -4,4 +4,4 @@ PyQt6==6.2.3
PyQt6-Qt6==6.2.4
PyQt6-WebEngine==6.2.1
PyQt6-WebEngine-Qt6==6.2.4
PyQt6_sip==13.10.2
PyQt6_sip==13.8.0

View File

@ -4,4 +4,4 @@ PyQt6==6.3.1
PyQt6-Qt6==6.3.2
PyQt6-WebEngine==6.3.1
PyQt6-WebEngine-Qt6==6.3.2
PyQt6_sip==13.10.2
PyQt6_sip==13.8.0

View File

@ -4,4 +4,4 @@ PyQt6==6.4.2
PyQt6-Qt6==6.4.3
PyQt6-WebEngine==6.4.0
PyQt6-WebEngine-Qt6==6.4.3
PyQt6_sip==13.10.2
PyQt6_sip==13.8.0

View File

@ -4,4 +4,4 @@ PyQt6==6.5.3
PyQt6-Qt6==6.5.3
PyQt6-WebEngine==6.5.0
PyQt6-WebEngine-Qt6==6.5.3
PyQt6_sip==13.10.2
PyQt6_sip==13.8.0

View File

@ -4,4 +4,4 @@ PyQt6==6.6.1
PyQt6-Qt6==6.6.3
PyQt6-WebEngine==6.6.0
PyQt6-WebEngine-Qt6==6.6.3
PyQt6_sip==13.10.2
PyQt6_sip==13.8.0

View File

@ -5,4 +5,4 @@ PyQt6-Qt6==6.7.3
PyQt6-WebEngine==6.7.0
PyQt6-WebEngine-Qt6==6.7.3
PyQt6-WebEngineSubwheel-Qt6==6.7.3
PyQt6_sip==13.10.2
PyQt6_sip==13.8.0

View File

@ -1,7 +0,0 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
PyQt6==6.8.1
PyQt6-Qt6==6.8.2
PyQt6-WebEngine==6.8.0
PyQt6-WebEngine-Qt6==6.8.2
PyQt6_sip==13.10.2

View File

@ -1,4 +0,0 @@
PyQt6 >= 6.8, < 6.9
PyQt6-Qt6 >= 6.8, < 6.9
PyQt6-WebEngine >= 6.8, < 6.9
PyQt6-WebEngine-Qt6 >= 6.8, < 6.9

View File

@ -1,7 +0,0 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
PyQt6==6.9.1
PyQt6-Qt6==6.9.2
PyQt6-WebEngine==6.9.0
PyQt6-WebEngine-Qt6==6.9.2
PyQt6_sip==13.10.2

View File

@ -1,4 +0,0 @@
PyQt6 >= 6.9, < 6.10
PyQt6-Qt6 >= 6.9, < 6.10
PyQt6-WebEngine >= 6.9, < 6.10
PyQt6-WebEngine-Qt6 >= 6.9, < 6.10

View File

@ -1,8 +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/
PyQt6==6.7.1
PyQt6-Qt6==6.7.3
PyQt6-WebEngine==6.7.0
PyQt6-WebEngine-Qt6==6.7.3
PyQt6-WebEngineSubwheel-Qt6==6.7.3
PyQt6_sip==13.8.0

View File

@ -2,7 +2,3 @@ PyQt6
PyQt6-Qt6
PyQt6-WebEngine
PyQt6-WebEngine-Qt6
# WORKAROUND for https://www.riverbankcomputing.com/pipermail/pyqt/2025-October/046347.html
#@ add: --extra-index-url https://www.riverbankcomputing.com/pypi/simple/
--extra-index-url https://www.riverbankcomputing.com/pypi/simple/

View File

@ -1,8 +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/
PyQt6==6.7.1
PyQt6-Qt6==6.7.3
PyQt6-WebEngine==6.7.0
PyQt6-WebEngine-Qt6==6.7.3
PyQt6-WebEngineSubwheel-Qt6==6.7.3
PyQt6_sip==13.8.0

View File

@ -2,7 +2,3 @@ PyQt6
PyQt6-Qt6
PyQt6-WebEngine
PyQt6-WebEngine-Qt6
# WORKAROUND for https://www.riverbankcomputing.com/pipermail/pyqt/2025-October/046347.html
#@ add: --extra-index-url https://www.riverbankcomputing.com/pypi/simple/
--extra-index-url https://www.riverbankcomputing.com/pypi/simple/

View File

@ -1,18 +1,17 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
build==1.3.0
certifi==2025.11.12
charset-normalizer==3.4.4
check-manifest==0.51
docutils==0.22.3
idna==3.11
importlib_metadata==8.7.0
packaging==25.0
Pygments==2.19.2
build==1.2.2
certifi==2024.8.30
charset-normalizer==3.3.2
docutils==0.20.1
idna==3.10
importlib_metadata==8.5.0
packaging==24.1
Pygments==2.18.0
pyproject_hooks==1.2.0
pyroma==5.0.1
requests==2.32.5
tomli==2.3.0
trove-classifiers==2025.12.1.14
urllib3==2.6.2
zipp==3.23.0
pyroma==4.2
requests==2.32.3
tomli==2.0.1
trove-classifiers==2024.9.12
urllib3==2.2.3
zipp==3.20.2

View File

@ -1,2 +1 @@
pyroma
check-manifest

View File

@ -12,7 +12,12 @@ PyYAML
#@ add: pyobjc-core ; sys_platform=="darwin"
#@ add: pyobjc-framework-Cocoa ; sys_platform=="darwin"
## stdlib backports
importlib_resources
## Optional dependencies
Pygments # For :view-source --pygments or on QtWebKit
colorama # Colored log output on Windows
adblock # Improved adblocking
#@ markers: importlib_resources python_version=="3.8.*"

View File

@ -1,26 +1,26 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
alabaster==0.7.16
babel==2.17.0
certifi==2025.11.12
charset-normalizer==3.4.4
docutils==0.21.2
idna==3.11
alabaster==0.7.13
babel==2.16.0
certifi==2024.8.30
charset-normalizer==3.3.2
docutils==0.20.1
idna==3.10
imagesize==1.4.1
importlib_metadata==8.7.0
Jinja2==3.1.6
MarkupSafe==3.0.3
packaging==25.0
Pygments==2.19.2
requests==2.32.5
snowballstemmer==3.0.1
Sphinx==7.4.7
sphinxcontrib-applehelp==2.0.0
sphinxcontrib-devhelp==2.0.0
sphinxcontrib-htmlhelp==2.1.0
importlib_metadata==8.5.0
Jinja2==3.1.4
MarkupSafe==2.1.5
packaging==24.1
Pygments==2.18.0
pytz==2024.2
requests==2.32.3
snowballstemmer==2.2.0
Sphinx==7.1.2
sphinxcontrib-applehelp==1.0.4
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==2.0.1
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==2.0.0
sphinxcontrib-serializinghtml==2.0.0
tomli==2.3.0
urllib3==2.6.2
zipp==3.23.0
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-serializinghtml==1.1.5
urllib3==2.2.3
zipp==3.20.2

View File

@ -2,13 +2,12 @@
# bzr+lp:beautifulsoup
beautifulsoup4
git+https://github.com/cherrypy/cheroot.git
coverage[toml] @ git+https://github.com/nedbat/coveragepy.git
git+https://github.com/nedbat/coveragepy.git#egg=coverage[toml]
git+https://github.com/pallets/flask.git
git+https://github.com/pallets/werkzeug.git # transitive dep, but needed to work
git+https://github.com/HypothesisWorks/hypothesis.git#subdirectory=hypothesis-python
git+https://github.com/pytest-dev/pytest.git
git+https://github.com/pytest-dev/pytest-bdd.git
gherkin-official<31.0.0 # https://github.com/cucumber/gherkin/issues/373
git+https://github.com/ionelmc/pytest-benchmark.git
git+https://github.com/pytest-dev/pytest-instafail.git
git+https://github.com/pytest-dev/pytest-mock.git

View File

@ -1,67 +1,66 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
attrs==25.4.0
attrs==24.2.0
autocommand==2.2.2
backports.tarfile==1.2.0
beautifulsoup4==4.14.3
blinker==1.9.0
certifi==2025.11.12
charset-normalizer==3.4.4
cheroot==11.1.2
click==8.1.8
coverage==7.10.7
exceptiongroup==1.3.1
execnet==2.1.2
filelock==3.19.1
Flask==3.1.2
gherkin-official==29.0.0
hunter==3.9.0
hypothesis==6.141.1
idna==3.11
importlib_metadata==8.7.0
importlib_resources==6.5.2
beautifulsoup4==4.12.3
blinker==1.8.2
certifi==2024.8.30
charset-normalizer==3.3.2
cheroot==10.0.1
click==8.1.7
coverage==7.6.1
exceptiongroup==1.2.2
execnet==2.1.1
filelock==3.16.1
Flask==3.0.3
hunter==3.7.0
hypothesis==6.112.2
idna==3.10
importlib_metadata==8.5.0
importlib_resources==6.4.5 ; python_version=="3.8.*"
inflect==7.3.1
iniconfig==2.1.0
iniconfig==2.0.0
itsdangerous==2.2.0
jaraco.collections==5.1.0
jaraco.context==6.0.1
jaraco.functools==4.0.1
jaraco.functools==4.1.0
jaraco.text==3.12.1
# Jinja2==3.1.6
Mako==1.3.10
# Jinja2==3.1.4
Mako==1.3.5
manhole==1.8.1
# MarkupSafe==3.0.3
more-itertools==10.8.0
packaging==25.0
# MarkupSafe==2.1.5
more-itertools==10.5.0
packaging==24.1
parse==1.20.2
parse_type==0.6.6
pillow==11.3.0
platformdirs==4.4.0
pluggy==1.6.0
parse_type==0.6.3
platformdirs==4.3.6
pillow==10.4.0
pluggy==1.5.0
py-cpuinfo==9.0.0
Pygments==2.19.2
pytest==8.4.2
pytest-bdd==8.1.0
pytest-benchmark==5.2.3
pytest-cov==7.0.0
Pygments==2.18.0
pytest==8.3.3
pytest-bdd==7.3.0
pytest-benchmark==4.0.0
pytest-cov==5.0.0
pytest-instafail==0.5.0
pytest-mock==3.15.1
pytest-qt==4.5.0
pytest-repeat==0.9.4
pytest-rerunfailures==16.0.1
pytest-xdist==3.8.0
pytest-xvfb==3.1.1
pytest-mock==3.14.0
pytest-qt==4.4.0
pytest-repeat==0.9.3
pytest-rerunfailures==14.0
pytest-xdist==3.6.1
pytest-xvfb==3.0.0
PyVirtualDisplay==3.0
requests==2.32.5
requests-file==3.0.1
six==1.17.0
requests==2.32.3
requests-file==2.1.0
six==1.16.0
sortedcontainers==2.4.0
soupsieve==2.8
tldextract==5.3.0
tomli==2.3.0
soupsieve==2.6
tldextract==5.1.2
tomli==2.0.1
typeguard==4.3.0
typing_extensions==4.15.0
urllib3==2.6.2
vulture==2.14
Werkzeug==3.1.4
zipp==3.23.0
typing_extensions==4.12.2
urllib3==2.2.3
vulture==2.12
Werkzeug==3.0.4
zipp==3.20.2

View File

@ -39,6 +39,7 @@ tldextract
# Include them here even though we don't need them to make sure we at least
# get an up to date version.
importlib_resources
#@ markers: importlib_resources python_version=="3.8.*"
jaraco.context
platformdirs

View File

@ -1,19 +1,17 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
cachetools==6.2.3
cachetools==5.5.0
chardet==5.2.0
colorama==0.4.6
distlib==0.4.0
filelock==3.19.1
packaging==25.0
pip==25.3
platformdirs==4.4.0
pluggy==1.6.0
pyproject-api==1.9.1
setuptools==80.9.0
tomli==2.3.0
tox==4.30.3 ; python_full_version!="3.14.0b1"
typing_extensions==4.15.0
virtualenv==20.35.4
wheel==0.45.1
tox @ git+https://github.com/tox-dev/tox ; python_full_version=="3.14.0b1"
distlib==0.3.8
filelock==3.16.1
packaging==24.1
pip==24.2
platformdirs==4.3.6
pluggy==1.5.0
pyproject-api==1.8.0
setuptools==75.1.0
tomli==2.0.1
tox==4.20.0
virtualenv==20.26.6
wheel==0.44.0

View File

@ -1,5 +1,2 @@
tox
wheel
#@ markers: tox python_full_version!="3.14.0b1"
#@ add: tox @ git+https://github.com/tox-dev/tox ; python_full_version=="3.14.0b1"

View File

@ -1,4 +1,4 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
tomli==2.3.0
vulture==2.14
tomli==2.0.1
vulture==2.12

View File

@ -1,5 +1,5 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
pathspec==0.12.1
PyYAML==6.0.3
yamllint==1.37.1
PyYAML==6.0.2
yamllint==1.35.1

View File

@ -106,8 +106,6 @@ The following userscripts can be found on their own repositories.
More powerfully manage single window sessions
- [qutebrowser-url-mutator](https://codeberg.org/mister_monster/qutebrowser-url-mutator):
automatically mutates input URLs based on configurable rules
- [qute-translate-popup](https://github.com/JohnBardoe/qute-translate-popup):
selected text translation, with a qute popup!
[Zotero]: https://www.zotero.org/
[Pocket]: https://getpocket.com/

View File

@ -143,7 +143,7 @@ no_entries_found() {
# expected to write the username of that entry to the $username variable and
# the corresponding password to $password
# shellcheck disable=SC2329
# shellcheck disable=SC2317
reset_backend() {
init() { true ; }
query_entries() { true ; }
@ -199,8 +199,7 @@ choose_entry_zenity() {
}
choose_entry_zenity_radio() {
# shellcheck disable=SC2329
zenity_helper() {
zenity_helper() { # shellcheck disable=SC2317
awk '{ print $0 ; print $0 }' \
| zenity --list --radiolist \
--title "qutebrowser password fill" \
@ -280,7 +279,7 @@ pass_backend() {
# =======================================================
# backend: secret
# shellcheck disable=SC2329
# shellcheck disable=SC2317
secret_backend() {
init() {
return

View File

@ -96,8 +96,7 @@ def ask_password(password_prompt_invocation):
raise Exception('Could not unlock vault')
master_pass = process.stdout.strip()
return subprocess.check_output(
['bw', 'unlock', '--raw', '--passwordenv', 'BW_MASTERPASS'],
env={**os.environ, 'BW_MASTERPASS': master_pass},
['bw', 'unlock', '--raw', master_pass],
text=True,
).strip()
@ -133,7 +132,7 @@ def get_session_key(auto_lock, password_prompt_invocation):
def pass_(domain, encoding, auto_lock, password_prompt_invocation):
session_key = get_session_key(auto_lock, password_prompt_invocation)
process = subprocess.run(
['bw', 'list', 'items', '--nointeraction', '--session', session_key, '--url', domain],
['bw', 'list', 'items', '--session', session_key, '--url', domain],
capture_output=True,
)
@ -142,10 +141,6 @@ def pass_(domain, encoding, auto_lock, password_prompt_invocation):
msg = 'Bitwarden CLI returned for {:s} - {:s}'.format(domain, err)
stderr(msg)
if "Vault is locked" in err:
stderr("Bitwarden Vault got locked, trying again with clean session")
return pass_(domain, encoding, 0, password_prompt_invocation)
if process.returncode:
return '[]'
@ -157,7 +152,7 @@ def pass_(domain, encoding, auto_lock, password_prompt_invocation):
def get_totp_code(selection_id, domain_name, encoding, auto_lock, password_prompt_invocation):
session_key = get_session_key(auto_lock, password_prompt_invocation)
process = subprocess.run(
['bw', 'get', 'totp', '--nointeraction', '--session', session_key, selection_id],
['bw', 'get', 'totp', '--session', session_key, selection_id],
capture_output=True,
)
@ -167,10 +162,6 @@ def get_totp_code(selection_id, domain_name, encoding, auto_lock, password_promp
msg = 'Bitwarden CLI returned for {:s} - {:s}'.format(domain_name, err)
stderr(msg)
if "Vault is locked" in err:
stderr("Bitwarden Vault got locked, trying again with clean session")
return get_totp_code(selection_id, domain_name, encoding, 0, password_prompt_invocation)
if process.returncode:
return '[]'
@ -204,20 +195,12 @@ def main(arguments):
# the registered domain name and finally: the IPv4 address if that's what
# the URL represents
candidates = []
for target in filter(
None,
[
extract_result.fqdn,
(
extract_result.top_domain_under_public_suffix
if hasattr(extract_result, "top_domain_under_public_suffix")
else extract_result.registered_domain
),
extract_result.subdomain + "." + extract_result.domain,
extract_result.domain,
extract_result.ipv4,
],
):
for target in filter(None, [
extract_result.fqdn,
extract_result.registered_domain,
extract_result.subdomain + '.' + extract_result.domain,
extract_result.domain,
extract_result.ipv4]):
target_candidates = json.loads(
pass_(
target,

View File

@ -117,20 +117,7 @@ def main(arguments):
# the URL represents
candidates = []
seen_id = set()
for target in filter(
None,
[
extract_result.fqdn,
(
extract_result.top_domain_under_public_suffix
if hasattr(extract_result, "top_domain_under_public_suffix")
else extract_result.registered_domain
),
extract_result.subdomain + extract_result.domain,
extract_result.domain,
extract_result.ipv4,
],
):
for target in filter(None, [extract_result.fqdn, extract_result.registered_domain, extract_result.subdomain + extract_result.domain, extract_result.domain, extract_result.ipv4]):
target_candidates, err = pass_(target, arguments.io_encoding)
if err:
stderr("LastPass CLI returned for {:s} - {:s}".format(target, err))

View File

@ -243,20 +243,7 @@ def main(arguments):
netloc = urlparse(arguments.url).netloc
for target in filter(
None,
[
extract_result.fqdn,
(
extract_result.top_domain_under_public_suffix
if hasattr(extract_result, "top_domain_under_public_suffix")
else extract_result.registered_domain
),
extract_result.ipv4,
private_domain,
netloc,
],
):
for target in filter(None, [extract_result.fqdn, extract_result.registered_domain, extract_result.ipv4, private_domain, netloc]):
attempted_targets.append(target)
target_candidates = find_pass_candidates(target, unfiltered=arguments.unfiltered)
if not target_candidates:

View File

@ -23,7 +23,7 @@ create_menu() {
done < "$QUTE_CONFIG_DIR"/bookmarks/urls
# Finally history
printf -- '%s\n' "$(sqlite3 -separator ' ' "$QUTE_DATA_DIR/history.sqlite" 'select title, url from CompletionHistory ORDER BY last_atime DESC')"
printf -- '%s\n' "$(sqlite3 -separator ' ' "$QUTE_DATA_DIR/history.sqlite" 'select title, url from CompletionHistory')"
}
get_selection() {

View File

@ -9,16 +9,18 @@
# :spawn --userscript ripbang amazon maps
#
import os, requests, sys
import os, re, requests, sys
from urllib.parse import urlparse, parse_qs
for argument in sys.argv[1:]:
bang = '!' + argument
r = requests.get('https://html.duckduckgo.com/html/',
allow_redirects=False,
r = requests.get('https://duckduckgo.com/',
params={'q': bang + ' SEARCHTEXT'},
headers={'user-agent': 'qutebrowser ripbang'})
searchengine = r.headers['location']
searchengine = re.search("url=([^']+)", r.text).group(1)
searchengine = urlparse(searchengine).query
searchengine = parse_qs(searchengine)['uddg'][0]
searchengine = searchengine.replace('SEARCHTEXT', '{}')
if os.getenv('QUTE_FIFO'):

View File

@ -1,12 +0,0 @@
[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__",
]

View File

@ -1,5 +1,4 @@
[pytest]
pythonpath = .
log_level = NOTSET
addopts = --strict-markers --strict-config --instafail --benchmark-columns=Min,Max,Median
testpaths = tests
@ -20,7 +19,6 @@ markers =
not_frozen: Tests which can't be run if sys.frozen is True.
not_flatpak: Tests which can't be run if running with Flatpak.
no_xvfb: Tests which can't be run with Xvfb.
no_offscreen: Tests which can't be run with the offscreen platform plugin.
frozen: Tests which can only be run if sys.frozen is True.
integration: Tests which test a bigger portion of code
end2end: End to end tests which run qutebrowser as subprocess
@ -43,8 +41,7 @@ markers =
qt6_only: Tests which should only run with Qt 6
qt5_xfail: Tests which fail with Qt 5
qt6_xfail: Tests which fail with Qt 6
qt69_ci_flaky: Tests which are flaky with Qt 6.9+ on CI
qt69_ci_skip: Tests which should be skipped with Qt 6.9+ on CI
qt68_beta4_skip: Fails on Qt 6.8 beta 4
qt_log_level_fail = WARNING
qt_log_ignore =
# GitHub Actions
@ -80,29 +77,13 @@ qt_log_ignore =
# model, for example, when no completion function is available for the
# current text pattern.
QItemSelectionModel: Selecting when no model has been set will result in a no-op.
^QSaveFile::commit: File \(.*[/\\]test_failing_flush0[/\\]foo\) is not open$
^QSaveFile::commit: File \(.*/test_failing_flush0/foo\) is not open$
^The following paths were searched for Qt WebEngine dictionaries:.*
# Qt 6.9 with Xvfb
^Backend texture is not a Vulkan texture\.$
^Compositor returned null texture$
# With offscreen platform plugin
^This plugin does not support (raise\(\)|propagateSizeHints\(\)|createPlatformVulkanInstance|grabbing the keyboard)$
^QRhiGles2: Failed to create (temporary )?context$
^QVulkanInstance: Failed to initialize Vulkan$
^Unable to detect GPU vendor\.$
# Qt 5 on CI with WebKit
^qglx_findConfig: Failed to finding matching FBConfig for QSurfaceFormat\(version 2\.0, options QFlags<QSurfaceFormat::FormatOption>\(\), depthBufferSize -1, redBufferSize 1, greenBufferSize 1, blueBufferSize 1, alphaBufferSize -1, stencilBufferSize -1, samples -1, swapBehavior QSurfaceFormat::SingleBuffer, swapInterval 1, colorSpace QSurfaceFormat::DefaultColorSpace, profile QSurfaceFormat::NoProfile\)$
# Qt 6.8+ debug build
# https://github.com/qutebrowser/qutebrowser/issues/8069#issuecomment-2017644465
^QObject::connect: Connecting from COMPAT signal \(QWebEnginePage::featurePermissionRequest(ed|Canceled)\(QUrl,QWebEnginePage::Feature\)\)
xfail_strict = true
filterwarnings =
error
default:Test process .* failed to terminate!:UserWarning
# https://github.com/cucumber/gherkin/commit/2f4830093149eae7ff7bd82f683b3d3bb7320d39
# https://github.com/pytest-dev/pytest-bdd/issues/752
ignore:'maxsplit' is passed as positional argument:DeprecationWarning:gherkin.gherkin_line
# https://github.com/ionelmc/pytest-benchmark/issues/283
ignore:FileType is deprecated\. Simply open files after parsing arguments\.:PendingDeprecationWarning:pytest_benchmark.plugin
# Python 3.12: https://github.com/ionelmc/pytest-benchmark/issues/240 (fixed but not released)
ignore:(datetime\.)?datetime\.utcnow\(\) is deprecated and scheduled for removal in a future version\. Use timezone-aware objects to represent datetimes in UTC. (datetime\.)?datetime\.now\(datetime\.UTC\)\.:DeprecationWarning:pytest_benchmark\.utils
faulthandler_timeout = 90
xvfb_colordepth = 24

View File

@ -11,10 +11,10 @@ _year = datetime.date.today().year
__author__ = "Florian Bruhin"
__copyright__ = "Copyright 2013-{} Florian Bruhin (The Compiler)".format(_year)
__license__ = "GPL-3.0-or-later"
__license__ = "GPL"
__maintainer__ = __author__
__email__ = "mail@qutebrowser.org"
__version__ = "3.6.3"
__version__ = "3.3.0"
__version_info__ = tuple(int(part) for part in __version__.split('.'))
__description__ = "A keyboard-driven, vim-like browser based on Python and Qt."

View File

@ -35,8 +35,7 @@ Possible values:
import inspect
from typing import Any, Protocol, Optional, cast
from collections.abc import Iterable, Callable
from typing import Any, Callable, Iterable, Protocol, Optional, Dict, cast
from qutebrowser.utils import qtutils
from qutebrowser.commands import command, cmdexc
@ -102,7 +101,7 @@ class _CmdHandlerType(Protocol):
Below, we cast the decorated function to _CmdHandlerType to make mypy aware of this.
"""
qute_args: Optional[dict[str, 'command.ArgInfo']]
qute_args: Optional[Dict[str, 'command.ArgInfo']]
def __call__(self, *args: Any, **kwargs: Any) -> Any:
...

View File

@ -7,8 +7,7 @@
"""Hooks for extensions."""
import importlib
from typing import Any
from collections.abc import Callable
from typing import Callable, Any
from qutebrowser.extensions import loader

View File

@ -29,8 +29,7 @@ import tempfile
import pathlib
import datetime
import argparse
from typing import Optional
from collections.abc import Iterable
from typing import Iterable, Optional, List, Tuple
from qutebrowser.qt import machinery
from qutebrowser.qt.widgets import QApplication, QWidget
@ -331,7 +330,7 @@ def _open_special_pages(args):
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window='last-focused')
pages: list[tuple[str, bool, str]] = [
pages: List[Tuple[str, bool, str]] = [
# state, condition, URL
('quickstart-done',
True,

View File

@ -9,8 +9,8 @@ import pathlib
import itertools
import functools
import dataclasses
from typing import (cast, TYPE_CHECKING, Any, Optional, Union)
from collections.abc import Iterable, Sequence, Callable
from typing import (cast, TYPE_CHECKING, Any, Callable, Iterable, List, Optional,
Sequence, Set, Type, Union, Tuple)
from qutebrowser.qt import machinery
from qutebrowser.qt.core import (pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt,
@ -29,7 +29,7 @@ if TYPE_CHECKING:
from qutebrowser.keyinput import modeman
from qutebrowser.config import config, websettings
from qutebrowser.utils import (utils, objreg, usertypes, log, qtutils,
urlutils, message, jinja)
urlutils, message, jinja, version)
from qutebrowser.misc import miscwidgets, objects, sessions
from qutebrowser.browser import eventfilter, inspector
from qutebrowser.qt import sip
@ -60,7 +60,7 @@ def create(win_id: int,
mode_manager = modeman.instance(win_id)
if objects.backend == usertypes.Backend.QtWebEngine:
from qutebrowser.browser.webengine import webenginetab
tab_class: type[AbstractTab] = webenginetab.WebEngineTab
tab_class: Type[AbstractTab] = webenginetab.WebEngineTab
elif objects.backend == usertypes.Backend.QtWebKit:
from qutebrowser.browser.webkit import webkittab
tab_class = webkittab.WebKitTab
@ -142,7 +142,7 @@ class AbstractAction:
"""Attribute ``action`` of AbstractTab for Qt WebActions."""
action_base: type[Union['QWebPage.WebAction', 'QWebEnginePage.WebAction']]
action_base: Type[Union['QWebPage.WebAction', 'QWebEnginePage.WebAction']]
def __init__(self, tab: 'AbstractTab') -> None:
self._widget = cast(_WidgetType, None)
@ -639,7 +639,7 @@ class AbstractScroller(QObject):
def pos_px(self) -> QPoint:
raise NotImplementedError
def pos_perc(self) -> tuple[int, int]:
def pos_perc(self) -> Tuple[int, int]:
raise NotImplementedError
def to_perc(self, x: float = None, y: float = None) -> None:
@ -765,10 +765,10 @@ class AbstractHistory:
def _go_to_item(self, item: Any) -> None:
raise NotImplementedError
def back_items(self) -> list[Any]:
def back_items(self) -> List[Any]:
raise NotImplementedError
def forward_items(self) -> list[Any]:
def forward_items(self) -> List[Any]:
raise NotImplementedError
@ -1018,7 +1018,7 @@ class AbstractTab(QWidget):
# Note that we remember hosts here, without scheme/port:
# QtWebEngine/Chromium also only remembers hostnames, and certificates are
# for a given hostname anyways.
_insecure_hosts: set[str] = set()
_insecure_hosts: Set[str] = set()
# Sub-APIs initialized by subclasses
history: AbstractHistory
@ -1177,6 +1177,37 @@ class AbstractTab(QWidget):
navigation.url.errorString()))
navigation.accepted = False
# WORKAROUND for QtWebEngine >= 6.2 not allowing form requests from
# qute:// to outside domains.
needs_load_workarounds = (
objects.backend == usertypes.Backend.QtWebEngine and
version.qtwebengine_versions().webengine >= utils.VersionNumber(6, 2)
)
if (
needs_load_workarounds and
self.url() == QUrl("qute://start/") and
navigation.navigation_type == navigation.Type.form_submitted and
navigation.url.matches(
QUrl(config.val.url.searchengines['DEFAULT']),
urlutils.FormatOption.REMOVE_QUERY)
):
log.webview.debug(
"Working around qute://start loading issue for "
f"{navigation.url.toDisplayString()}")
navigation.accepted = False
self.load_url(navigation.url)
if (
needs_load_workarounds and
self.url() == QUrl("qute://bookmarks/") and
navigation.navigation_type == navigation.Type.back_forward
):
log.webview.debug(
"Working around qute://bookmarks loading issue for "
f"{navigation.url.toDisplayString()}")
navigation.accepted = False
self.load_url(navigation.url)
@pyqtSlot(bool)
def _on_load_finished(self, ok: bool) -> None:
assert self._widget is not None

View File

@ -2,15 +2,12 @@
#
# SPDX-License-Identifier: GPL-3.0-or-later
# pylint: disable=too-many-positional-arguments
"""Command dispatcher for TabbedBrowser."""
import os.path
import shlex
import functools
from typing import cast, Union, Optional
from collections.abc import Callable
from typing import cast, Callable, Dict, Union, Optional
from qutebrowser.qt.widgets import QApplication, QTabBar
from qutebrowser.qt.core import Qt, QUrl, QEvent, QUrlQuery
@ -71,10 +68,7 @@ class CommandDispatcher:
def _current_index(self):
"""Convenience method to get the current widget index."""
current_index = self._tabbed_browser.widget.currentIndex()
if current_index == -1:
raise cmdutils.CommandError("No WebView available yet!")
return current_index
return self._tabbed_browser.widget.currentIndex()
def _current_url(self):
"""Convenience method to get the current url."""
@ -642,7 +636,7 @@ class CommandDispatcher:
widget = self._current_widget()
url = self._current_url()
handlers: dict[str, Callable[..., QUrl]] = {
handlers: Dict[str, Callable[..., QUrl]] = {
'prev': functools.partial(navigate.prevnext, prev=True),
'next': functools.partial(navigate.prevnext, prev=False),
'up': navigate.path_up,
@ -868,6 +862,10 @@ class CommandDispatcher:
Args:
count: How many tabs to switch back.
"""
if self._count() == 0:
# Running :tab-prev after last tab was closed
# See https://github.com/qutebrowser/qutebrowser/issues/1448
return
newidx = self._current_index() - count
if newidx >= 0:
self._set_current_index(newidx)
@ -884,6 +882,10 @@ class CommandDispatcher:
Args:
count: How many tabs to switch forward.
"""
if self._count() == 0:
# Running :tab-next after last tab was closed
# See https://github.com/qutebrowser/qutebrowser/issues/1448
return
newidx = self._current_index() + count
if newidx < self._count():
self._set_current_index(newidx)
@ -1134,7 +1136,8 @@ class CommandDispatcher:
else:
cmd = os.path.expanduser(cmd)
proc = guiprocess.GUIProcess(what='command', verbose=verbose,
output_messages=output_messages)
output_messages=output_messages,
parent=self._tabbed_browser)
if detach:
ok = proc.start_detached(cmd, args)
if not ok:
@ -1163,7 +1166,7 @@ class CommandDispatcher:
if count is not None:
env['QUTE_COUNT'] = str(count)
idx = self._tabbed_browser.widget.currentIndex()
idx = self._current_index()
if idx != -1:
env['QUTE_TAB_INDEX'] = str(idx + 1)
env['QUTE_TITLE'] = self._tabbed_browser.widget.page_title(idx)

View File

@ -13,8 +13,7 @@ import functools
import pathlib
import tempfile
import enum
from typing import Any, IO, Optional, Union
from collections.abc import MutableSequence
from typing import Any, Dict, IO, List, MutableSequence, Optional, Union
from qutebrowser.qt.core import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex,
QTimer, QAbstractListModel, QUrl)
@ -188,22 +187,15 @@ def transform_path(path):
"""
if not utils.is_windows:
return path
path = utils.expand_windows_drive(path)
# Drive dependent working directories are not supported, e.g.
# E:filename is invalid
if re.search(r'^[A-Z]:[^\\]', path, re.IGNORECASE):
return None
# Paths like COM1, ...
# See https://github.com/qutebrowser/qutebrowser/issues/82
if sys.version_info[:2] >= (3, 13):
if os.path.isreserved(path): # pylint: disable=no-member
return None
else:
if pathlib.Path(path).is_reserved(): # pylint: disable=else-if-used
return None
if pathlib.Path(path).is_reserved():
return None
return path
@ -455,7 +447,7 @@ class AbstractDownloadItem(QObject):
UnsupportedAttribute, IO[bytes], None
] = UnsupportedAttribute()
self.raw_headers: Union[
UnsupportedAttribute, dict[bytes, bytes]
UnsupportedAttribute, Dict[bytes, bytes]
] = UnsupportedAttribute()
self._filename: Optional[str] = None
@ -907,7 +899,7 @@ class AbstractDownloadManager(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self.downloads: list[AbstractDownloadItem] = []
self.downloads: List[AbstractDownloadItem] = []
self._update_timer = usertypes.Timer(self, 'download-update')
self._update_timer.timeout.connect(self._update_gui)
self._update_timer.setInterval(_REFRESH_INTERVAL)

View File

@ -5,8 +5,7 @@
"""The ListView to display downloads in."""
import functools
from typing import Union
from collections.abc import MutableSequence, Callable
from typing import Callable, MutableSequence, Tuple, Union
from qutebrowser.qt.core import pyqtSlot, QSize, Qt
from qutebrowser.qt.widgets import QListView, QSizePolicy, QMenu, QStyleFactory
@ -18,8 +17,8 @@ from qutebrowser.utils import qtutils, utils
_ActionListType = MutableSequence[
Union[
tuple[None, None], # separator
tuple[str, Callable[[], None]],
Tuple[None, None], # separator
Tuple[str, Callable[[], None]],
]
]

View File

@ -6,12 +6,11 @@
from qutebrowser.qt import machinery
from qutebrowser.qt.core import QObject, QEvent, Qt, QTimer
from qutebrowser.qt.gui import QKeyEvent
from qutebrowser.qt.widgets import QWidget
from qutebrowser.config import config
from qutebrowser.utils import log, message, usertypes, qtutils, version, utils
from qutebrowser.keyinput import modeman, keyutils
from qutebrowser.utils import log, message, usertypes, qtutils
from qutebrowser.keyinput import modeman
class ChildEventFilter(QObject):
@ -38,8 +37,8 @@ class ChildEventFilter(QObject):
if event.type() == QEvent.Type.ChildAdded:
child = event.child()
if not isinstance(child, QWidget):
# Can e.g. happen when dragging text, or accessibility tree
# nodes since Qt 6.9
# Can e.g. happen when dragging text
log.misc.debug(f"Ignoring new child {qtutils.qobj_repr(child)}")
return False
log.misc.debug(
@ -55,30 +54,21 @@ class ChildEventFilter(QObject):
# - This is a child event filter on a tab (self._widget is not None)
# - We find an old existing child which is a QQuickWidget and is
# currently focused.
# - We're using an affected QtWebEngine version
# - We're using QtWebEngine >= 6.4 (older versions are not affected)
children = [
c for c in self._widget.findChildren(
QWidget, "", Qt.FindChildOption.FindDirectChildrenOnly)
if c is not child and
c.hasFocus() and
c.metaObject() is not None and
c.metaObject().className() == "QQuickWidget" # Qt 6.4+
c.metaObject().className() == "QQuickWidget"
]
if children and version.qtwebengine_versions().webengine < utils.VersionNumber(6, 6, 3):
if children:
log.misc.debug("Focusing new child")
child.setFocus()
child.installEventFilter(self._filter)
elif event.type() == QEvent.Type.ChildRemoved:
if isinstance(event, QKeyEvent):
# WORKAROUND for unknown (Py)Qt bug
info = keyutils.KeyInfo.from_event(event)
log.misc.warning(
f"ChildEventFilter: ignoring key event {info} "
f"on {qtutils.qobj_repr(obj)}"
)
return False
child = event.child()
log.misc.debug(
f"{qtutils.qobj_repr(obj)}: removed child {qtutils.qobj_repr(child)}")

View File

@ -12,8 +12,7 @@ import functools
import glob
import textwrap
import dataclasses
from typing import cast, Optional
from collections.abc import Sequence
from typing import cast, List, Sequence, Tuple, Optional
from qutebrowser.qt.core import pyqtSignal, QObject, QUrl
@ -208,9 +207,9 @@ class MatchingScripts:
"""All userscripts registered to run on a particular url."""
url: QUrl
start: list[GreasemonkeyScript] = dataclasses.field(default_factory=list)
end: list[GreasemonkeyScript] = dataclasses.field(default_factory=list)
idle: list[GreasemonkeyScript] = dataclasses.field(default_factory=list)
start: List[GreasemonkeyScript] = dataclasses.field(default_factory=list)
end: List[GreasemonkeyScript] = dataclasses.field(default_factory=list)
idle: List[GreasemonkeyScript] = dataclasses.field(default_factory=list)
@dataclasses.dataclass
@ -218,8 +217,8 @@ class LoadResults:
"""The results of loading all Greasemonkey scripts."""
successful: list[GreasemonkeyScript] = dataclasses.field(default_factory=list)
errors: list[tuple[str, str]] = dataclasses.field(default_factory=list)
successful: List[GreasemonkeyScript] = dataclasses.field(default_factory=list)
errors: List[Tuple[str, str]] = dataclasses.field(default_factory=list)
def successful_str(self) -> str:
"""Get a string with all successfully loaded scripts.
@ -295,10 +294,10 @@ class GreasemonkeyManager(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._run_start: list[GreasemonkeyScript] = []
self._run_end: list[GreasemonkeyScript] = []
self._run_idle: list[GreasemonkeyScript] = []
self._in_progress_dls: list[downloads.AbstractDownloadItem] = []
self._run_start: List[GreasemonkeyScript] = []
self._run_end: List[GreasemonkeyScript] = []
self._run_idle: List[GreasemonkeyScript] = []
self._in_progress_dls: List[downloads.AbstractDownloadItem] = []
def load_scripts(self, *, force: bool = False) -> LoadResults:
"""Re-read Greasemonkey scripts from disk.

View File

@ -12,15 +12,8 @@ import html
import enum
import dataclasses
from string import ascii_lowercase
from typing import (TYPE_CHECKING, Optional)
from collections.abc import (
Iterable,
Iterator,
Mapping,
MutableSequence,
Sequence,
Callable,
)
from typing import (TYPE_CHECKING, Callable, Dict, Iterable, Iterator, List, Mapping,
MutableSequence, Optional, Sequence, Set)
from qutebrowser.qt.core import pyqtSignal, pyqtSlot, QObject, Qt, QUrl
from qutebrowser.qt.widgets import QLabel
@ -182,11 +175,11 @@ class HintContext:
add_history: bool
first: bool
baseurl: QUrl
args: list[str]
args: List[str]
group: str
all_labels: list[HintLabel] = dataclasses.field(default_factory=list)
labels: dict[str, HintLabel] = dataclasses.field(default_factory=dict)
all_labels: List[HintLabel] = dataclasses.field(default_factory=list)
labels: Dict[str, HintLabel] = dataclasses.field(default_factory=dict)
to_follow: Optional[str] = None
first_run: bool = True
filterstr: Optional[str] = None
@ -1040,7 +1033,7 @@ class WordHinter:
def __init__(self) -> None:
# will be initialized on first use.
self.words: set[str] = set()
self.words: Set[str] = set()
self.dictionary = None
def ensure_initialized(self) -> None:
@ -1150,7 +1143,7 @@ class WordHinter:
"""
self.ensure_initialized()
hints = []
used_hints: set[str] = set()
used_hints: Set[str] = set()
words = iter(self.words)
for elem in elems:
hint = self.new_hint_for(elem, used_hints, words)

View File

@ -8,8 +8,7 @@ import os
import time
import contextlib
import pathlib
from typing import cast, Optional
from collections.abc import Mapping, MutableSequence
from typing import cast, Mapping, MutableSequence, Optional
from qutebrowser.qt import machinery
from qutebrowser.qt.core import pyqtSlot, QUrl, QObject, pyqtSignal

View File

@ -6,7 +6,7 @@
import re
import posixpath
from typing import Optional
from typing import Optional, Set
from qutebrowser.qt.core import QUrl
@ -79,7 +79,7 @@ def incdec(url, count, inc_or_dec):
inc_or_dec: Either 'increment' or 'decrement'.
"""
urlutils.ensure_valid(url)
segments: Optional[set[str]] = (
segments: Optional[Set[str]] = (
set(config.val.url.incdec_segments)
)

View File

@ -242,7 +242,7 @@ class PACFetcher(QObject):
pac_prefix = "pac+"
assert url.scheme().startswith(pac_prefix)
url.setScheme(url.scheme().removeprefix(pac_prefix))
url.setScheme(url.scheme()[len(pac_prefix):])
self._pac_url = url
with qtlog.disable_qt_msghandler():

View File

@ -4,7 +4,7 @@
"""Handling of proxies."""
from typing import Optional
from typing import Optional, List
from qutebrowser.qt.core import QUrl, pyqtSlot
from qutebrowser.qt.network import QNetworkProxy, QNetworkProxyFactory, QNetworkProxyQuery
@ -71,7 +71,7 @@ class ProxyFactory(QNetworkProxyFactory):
capabilities &= ~lookup_cap
proxy.setCapabilities(capabilities)
def queryProxy(self, query: QNetworkProxyQuery = QNetworkProxyQuery()) -> list[QNetworkProxy]:
def queryProxy(self, query: QNetworkProxyQuery = QNetworkProxyQuery()) -> List[QNetworkProxy]:
"""Get the QNetworkProxies for a query.
Args:

View File

@ -11,7 +11,6 @@ from qutebrowser.qt.core import QUrl, QUrlQuery
from qutebrowser.utils import resources, javascript, jinja, standarddir, log, urlutils
from qutebrowser.config import config
from qutebrowser.misc import objects
_SYSTEM_PATHS = [
@ -70,8 +69,19 @@ def generate_pdfjs_page(filename, url):
return html
def _get_polyfills() -> str:
return resources.read_file("javascript/pdfjs_polyfills.js")
def _generate_polyfills():
return """
if (typeof Promise.withResolvers === 'undefined') {
Promise.withResolvers = function () {
let resolve, reject
const promise = new Promise((res, rej) => {
resolve = res
reject = rej
})
return { promise, resolve, reject }
}
}
"""
def _generate_pdfjs_script(filename):
@ -111,7 +121,7 @@ def _generate_pdfjs_script(filename):
});
}
});
""").render(url=js_url, polyfills=_get_polyfills())
""").render(url=js_url, polyfills=_generate_polyfills())
def get_pdfjs_res_and_path(path):
@ -128,12 +138,7 @@ def get_pdfjs_res_and_path(path):
content = None
file_path = None
if 'no-system-pdfjs' in objects.debug_flags:
system_paths = []
else:
system_paths = _SYSTEM_PATHS[:]
system_paths += [
system_paths = _SYSTEM_PATHS + [
# fallback
os.path.join(standarddir.data(), 'pdfjs'),
# hardcoded fallback for --temp-basedir
@ -163,7 +168,7 @@ def get_pdfjs_res_and_path(path):
if path == "build/pdf.worker.mjs":
content = b"\n".join(
[
_get_polyfills().encode("ascii"),
_generate_polyfills().encode("ascii"),
content,
]
)

View File

@ -9,7 +9,7 @@ import os.path
import shutil
import functools
import dataclasses
from typing import IO, Optional
from typing import Dict, IO, Optional
from qutebrowser.qt.core import pyqtSlot, pyqtSignal, QTimer, QUrl
from qutebrowser.qt.widgets import QApplication
@ -73,7 +73,7 @@ class DownloadItem(downloads.AbstractDownloadItem):
"""
super().__init__(manager=manager, parent=manager)
self.fileobj: Optional[IO[bytes]] = None
self.raw_headers: dict[bytes, bytes] = {}
self.raw_headers: Dict[bytes, bytes] = {}
self._autoclose = True
self._retry_info = None

View File

@ -18,8 +18,7 @@ import textwrap
import urllib
import collections
import secrets
from typing import TypeVar, Optional, Union
from collections.abc import Sequence, Callable
from typing import TypeVar, Callable, Dict, List, Optional, Union, Sequence, Tuple
from qutebrowser.qt.core import QUrlQuery, QUrl
@ -36,7 +35,7 @@ pyeval_output = ":pyeval was never called"
csrf_token: Optional[str] = None
_HANDLERS: dict[str, "_HandlerCallable"] = {}
_HANDLERS: Dict[str, "_HandlerCallable"] = {}
class Error(Exception):
@ -78,7 +77,7 @@ class Redirect(Exception):
# Return value: (mimetype, data) (encoded as utf-8 if a str is returned)
_HandlerRet = tuple[str, Union[str, bytes]]
_HandlerRet = Tuple[str, Union[str, bytes]]
_HandlerCallable = Callable[[QUrl], _HandlerRet]
_Handler = TypeVar('_Handler', bound=_HandlerCallable)
@ -106,7 +105,7 @@ class add_handler: # noqa: N801,N806 pylint: disable=invalid-name
return self._function(url)
def data_for_url(url: QUrl) -> tuple[str, bytes]:
def data_for_url(url: QUrl) -> Tuple[str, bytes]:
"""Get the data to show for the given URL.
Args:
@ -123,24 +122,25 @@ def data_for_url(url: QUrl) -> tuple[str, bytes]:
path = url.path()
host = url.host()
query = url.query()
# A url like "qute:foo" is split as "scheme:path", not "scheme:host".
log.misc.debug("url: {}, path: {}, host {}".format(
url.toDisplayString(), path, host))
if not path or not host:
new_url = QUrl()
new_url.setScheme('qute')
# When path is absent, e.g. qute://help (with no trailing slash)
if host:
new_url.setHost(host)
# When host is absent, e.g. qute:help
else:
new_url.setHost(path)
if not host:
# Redirect qute:help -> qute://help/
new_url = QUrl(url)
new_url.setHost(path)
new_url.setPath('/')
if not new_url.host(): # Valid path but not valid host
raise UrlInvalidError(f"Invalid host (from path): {path!r}")
raise Redirect(new_url)
if not path:
# Redirect qute://help -> qute://help/
new_url = QUrl(url)
new_url.setPath('/')
raise Redirect(new_url)
if query:
new_url.setQuery(query)
if new_url.host(): # path was a valid host
raise Redirect(new_url)
try:
handler = _HANDLERS[host]
@ -180,7 +180,7 @@ def qute_bookmarks(_url: QUrl) -> _HandlerRet:
@add_handler('tabs')
def qute_tabs(_url: QUrl) -> _HandlerRet:
"""Handler for qute://tabs. Display information about all open tabs."""
tabs: dict[str, list[tuple[str, str]]] = collections.defaultdict(list)
tabs: Dict[str, List[Tuple[str, str]]] = collections.defaultdict(list)
for win_id, window in objreg.window_registry.items():
if sip.isdeleted(window):
continue
@ -201,7 +201,7 @@ def qute_tabs(_url: QUrl) -> _HandlerRet:
def history_data(
start_time: float,
offset: int = None
) -> Sequence[dict[str, Union[str, int]]]:
) -> Sequence[Dict[str, Union[str, int]]]:
"""Return history data.
Arguments:

View File

@ -10,12 +10,11 @@ import html
import enum
import netrc
import tempfile
from typing import Optional
from collections.abc import Mapping, Iterable, Iterator, Callable
from typing import Callable, Mapping, List, Optional, Iterable, Iterator
from qutebrowser.qt.core import QUrl, pyqtBoundSignal
from qutebrowser.config import config, configtypes
from qutebrowser.config import config
from qutebrowser.utils import (usertypes, message, log, objreg, jinja, utils,
qtutils, version, urlutils)
from qutebrowser.mainwindow import mainwindow
@ -26,15 +25,8 @@ class CallSuper(Exception):
"""Raised when the caller should call the superclass instead."""
def custom_headers(
url: QUrl, *, fallback_accept_language: bool = True
) -> list[tuple[bytes, bytes]]:
"""Get the combined custom headers.
Arguments:
fallback_accept_language: Whether to include the global (rather than
per-domain override) accept language header as well.
"""
def custom_headers(url):
"""Get the combined custom headers."""
headers = {}
dnt_config = config.instance.get('content.headers.do_not_track', url=url)
@ -48,17 +40,9 @@ def custom_headers(
encoded_value = b"" if value is None else value.encode('ascii')
headers[encoded_header] = encoded_value
# On QtWebEngine, we have fallback_accept_language set to False here for XHR
# requests, so that we don't end up overriding headers that are set via the XHR API.
#
# The global Accept-Language header is set via
# QWebEngineProfile::setHttpAcceptLanguage already anyways, so we only need
# to take care of URL pattern overrides here.
#
# note: Once we drop QtWebKit, we could hardcode fallback_accept_language to False.
accept_language = config.instance.get('content.headers.accept_language',
url=url, fallback=fallback_accept_language)
if accept_language is not None and not isinstance(accept_language, usertypes.Unset):
url=url)
if accept_language is not None:
headers[b'Accept-Language'] = accept_language.encode('ascii')
return sorted(headers.items())
@ -319,7 +303,6 @@ def feature_permission(url, option, msg, yes_action, no_action, abort_on,
None otherwise.
"""
config_val = config.instance.get(option, url=url)
opt = config.instance.get_opt(option)
if config_val == 'ask':
if url.isValid():
urlstr = url.toString(QUrl.UrlFormattingOption.RemovePassword | QUrl.ComponentFormattingOption.FullyEncoded)
@ -345,21 +328,12 @@ def feature_permission(url, option, msg, yes_action, no_action, abort_on,
cancel_action=no_action, abort_on=abort_on,
title='Permission request', text=text, url=urlstr,
option=option)
if isinstance(opt.typ, configtypes.AsBool):
config_val = opt.typ.to_bool(config_val)
if config_val is True:
elif config_val:
yes_action()
return None
elif config_val is False:
else:
no_action()
return None
else:
raise AssertionError(
f"Unsupported value for permission prompt setting ({option}), expected boolean or "
f"'ask', got: {config_val} ({type(config_val)})"
)
def get_tab(win_id, target):
@ -471,7 +445,7 @@ class FileSelectionMode(enum.Enum):
folder = enum.auto()
def choose_file(qb_mode: FileSelectionMode) -> list[str]:
def choose_file(qb_mode: FileSelectionMode) -> List[str]:
"""Select file(s)/folder for up-/downloading, using an external command.
Args:
@ -511,10 +485,10 @@ def choose_file(qb_mode: FileSelectionMode) -> list[str]:
def _execute_fileselect_command(
command: list[str],
command: List[str],
qb_mode: FileSelectionMode,
tmpfilename: Optional[str] = None
) -> list[str]:
) -> List[str]:
"""Execute external command to choose file.
Args:
@ -548,7 +522,7 @@ def _execute_fileselect_command(
def _validated_selected_files(
qb_mode: FileSelectionMode,
selected_files: list[str],
selected_files: List[str],
) -> Iterator[str]:
"""Validates selected files if they are.

View File

@ -15,7 +15,7 @@ import os.path
import html
import functools
import collections
from collections.abc import MutableMapping
from typing import MutableMapping
from qutebrowser.qt.core import pyqtSignal, QUrl, QObject

View File

@ -4,8 +4,7 @@
"""Generic web element related code."""
from typing import Optional, TYPE_CHECKING, Union
from collections.abc import Iterator
from typing import Iterator, Optional, Set, TYPE_CHECKING, Union, Dict
import collections.abc
from qutebrowser.qt import machinery
@ -94,7 +93,7 @@ class AbstractWebElement(collections.abc.MutableMapping): # type: ignore[type-a
"""Get the geometry for this element."""
raise NotImplementedError
def classes(self) -> set[str]:
def classes(self) -> Set[str]:
"""Get a set of classes assigned to this element."""
raise NotImplementedError
@ -337,7 +336,7 @@ class AbstractWebElement(collections.abc.MutableMapping): # type: ignore[type-a
log.webelem.debug("Sending fake click to {!r} at position {} with "
"target {}".format(self, pos, click_target))
target_modifiers: dict[usertypes.ClickTarget, KeyboardModifierType] = {
target_modifiers: Dict[usertypes.ClickTarget, KeyboardModifierType] = {
usertypes.ClickTarget.normal: Qt.KeyboardModifier.NoModifier,
usertypes.ClickTarget.window: Qt.KeyboardModifier.AltModifier | Qt.KeyboardModifier.ShiftModifier,
usertypes.ClickTarget.tab: Qt.KeyboardModifier.ControlModifier,

View File

@ -125,8 +125,8 @@ import copy
import enum
import dataclasses
import collections
from typing import (Any, Optional, Union)
from collections.abc import Iterator, Mapping, MutableMapping, Sequence
from typing import (Any, Iterator, Mapping, MutableMapping, Optional, Set, Tuple, Union,
Sequence, List)
from qutebrowser.config import config
from qutebrowser.utils import usertypes, utils, log, version
@ -212,7 +212,7 @@ class _Setting:
return str(value)
return str(self.mapping[value])
def chromium_tuple(self, value: Any) -> Optional[tuple[str, str]]:
def chromium_tuple(self, value: Any) -> Optional[Tuple[str, str]]:
"""Get the Chromium key and value, or None if no value should be set."""
if self.mapping is not None and self.mapping[value] is None:
return None
@ -242,7 +242,7 @@ class _Definition:
def __init__(
self,
*args: _Setting,
mandatory: set[str],
mandatory: Set[str],
prefix: str,
switch_names: Mapping[Optional[str], str] = None,
) -> None:
@ -255,7 +255,7 @@ class _Definition:
else:
self._switch_names = {None: _BLINK_SETTINGS}
def prefixed_settings(self) -> Iterator[tuple[str, _Setting]]:
def prefixed_settings(self) -> Iterator[Tuple[str, _Setting]]:
"""Get all "prepared" settings.
Yields tuples which contain the Chromium setting key (e.g. 'blink-settings' or
@ -399,7 +399,7 @@ def settings(
*,
versions: version.WebEngineVersions,
special_flags: Sequence[str],
) -> Mapping[str, Sequence[tuple[str, str]]]:
) -> Mapping[str, Sequence[Tuple[str, str]]]:
"""Get necessary blink settings to configure dark mode for QtWebEngine.
Args:
@ -413,12 +413,12 @@ def settings(
variant = _variant(versions)
log.init.debug(f"Darkmode variant: {variant.name}")
result: Mapping[str, list[tuple[str, str]]] = collections.defaultdict(list)
result: Mapping[str, List[Tuple[str, str]]] = collections.defaultdict(list)
blink_settings_flag = f'--{_BLINK_SETTINGS}='
for flag in special_flags:
if flag.startswith(blink_settings_flag):
for pair in flag.removeprefix(blink_settings_flag).split(','):
for pair in flag[len(blink_settings_flag):].split(','):
key, val = pair.split('=', maxsplit=1)
result[_BLINK_SETTINGS].append((key, val))

View File

@ -109,7 +109,6 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
}
new_types = {
"WebSocket": interceptors.ResourceType.websocket, # added in Qt 6.4
"Json": interceptors.ResourceType.json, # added in Qt 6.8
}
for qt_name, qb_value in new_types.items():
qt_value = getattr(
@ -188,9 +187,7 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
if request.is_blocked:
info.block(True)
for header, value in shared.custom_headers(
url=url, fallback_accept_language=not is_xhr
):
for header, value in shared.custom_headers(url=url):
if header.lower() == b'accept' and is_xhr:
# https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/setRequestHeader
# says: "If no Accept header has been set using this, an Accept header

View File

@ -33,8 +33,7 @@ import dataclasses
import itertools
import functools
import subprocess
from typing import Any, Optional, TYPE_CHECKING
from collections.abc import Iterator
from typing import Any, List, Dict, Optional, Iterator, Type, TYPE_CHECKING
from qutebrowser.qt import machinery
from qutebrowser.qt.core import (Qt, QObject, QVariant, QMetaType, QByteArray, pyqtSlot,
@ -196,7 +195,7 @@ class NotificationBridgePresenter(QObject):
def __init__(self, parent: QObject = None) -> None:
super().__init__(parent)
self._active_notifications: dict[int, 'QWebEngineNotification'] = {}
self._active_notifications: Dict[int, 'QWebEngineNotification'] = {}
self._adapter: Optional[AbstractNotificationAdapter] = None
config.instance.changed.connect(self._init_adapter)
@ -233,8 +232,8 @@ class NotificationBridgePresenter(QObject):
def _get_adapter_candidates(
self,
setting: str,
) -> list[type[AbstractNotificationAdapter]]:
candidates: dict[str, list[type[AbstractNotificationAdapter]]] = {
) -> List[Type[AbstractNotificationAdapter]]:
candidates: Dict[str, List[Type[AbstractNotificationAdapter]]] = {
"libnotify": [
DBusNotificationAdapter,
SystrayNotificationAdapter,
@ -286,10 +285,7 @@ class NotificationBridgePresenter(QObject):
if replaces_id is None:
if notification_id in self._active_notifications:
message.error(f"Got duplicate notification id {notification_id} "
f"from {self._adapter.NAME}")
self._drop_adapter()
return
raise Error(f"Got duplicate id {notification_id}")
qt_notification.show()
self._active_notifications[notification_id] = qt_notification
@ -669,7 +665,7 @@ class _ServerCapabilities:
kde_origin_name: bool
@classmethod
def from_list(cls, capabilities: list[str]) -> "_ServerCapabilities":
def from_list(cls, capabilities: List[str]) -> "_ServerCapabilities":
return cls(
actions='actions' in capabilities,
body_markup='body-markup' in capabilities,
@ -955,10 +951,10 @@ class DBusNotificationAdapter(AbstractNotificationAdapter):
qtutils.extract_enum_val(QMetaType.Type.QStringList),
)
def _get_hints_arg(self, *, origin_url: QUrl, icon: QImage) -> dict[str, Any]:
def _get_hints_arg(self, *, origin_url: QUrl, icon: QImage) -> Dict[str, Any]:
"""Get the hints argument for present()."""
origin_url_str = origin_url.toDisplayString()
hints: dict[str, Any] = {
hints: Dict[str, Any] = {
# Include the origin in case the user wants to do different things
# with different origin's notifications.
"x-qutebrowser-origin": origin_url_str,
@ -988,7 +984,7 @@ class DBusNotificationAdapter(AbstractNotificationAdapter):
title: str,
body: str,
actions: QDBusArgument,
hints: dict[str, Any],
hints: Dict[str, Any],
timeout: int,
) -> Any:
"""Wrapper around DBus call to use keyword args."""

View File

@ -5,8 +5,7 @@
"""QtWebEngine specific part of the web element API."""
from typing import (
TYPE_CHECKING, Any, Optional, Union)
from collections.abc import Iterator, Callable
TYPE_CHECKING, Any, Callable, Dict, Iterator, Optional, Set, Tuple, Union)
from qutebrowser.qt.core import QRect, QEventLoop
from qutebrowser.qt.widgets import QApplication
@ -25,11 +24,11 @@ class WebEngineElement(webelem.AbstractWebElement):
_tab: "webenginetab.WebEngineTab"
def __init__(self, js_dict: dict[str, Any],
def __init__(self, js_dict: Dict[str, Any],
tab: 'webenginetab.WebEngineTab') -> None:
super().__init__(tab)
# Do some sanity checks on the data we get from JS
js_dict_types: dict[str, Union[type, tuple[type, ...]]] = {
js_dict_types: Dict[str, Union[type, Tuple[type, ...]]] = {
'id': int,
'text': str,
'value': (str, int, float),
@ -106,7 +105,7 @@ class WebEngineElement(webelem.AbstractWebElement):
log.stub()
return QRect()
def classes(self) -> set[str]:
def classes(self) -> Set[str]:
"""Get a list of classes assigned to this element."""
return set(self._js_dict['class_name'].split())

View File

@ -12,7 +12,7 @@ Module attributes:
import os
import operator
import pathlib
from typing import cast, Any, Optional, Union, TYPE_CHECKING
from typing import cast, Any, List, Optional, Tuple, Union, TYPE_CHECKING
from qutebrowser.qt import machinery
from qutebrowser.qt.gui import QFont
@ -26,7 +26,7 @@ from qutebrowser.config import config, websettings
from qutebrowser.config.websettings import AttributeInfo as Attr
from qutebrowser.misc import pakjoy
from qutebrowser.utils import (standarddir, qtutils, message, log,
urlmatch, usertypes, objreg, version, utils)
urlmatch, usertypes, objreg, version)
if TYPE_CHECKING:
from qutebrowser.browser.webengine import interceptor
@ -216,10 +216,6 @@ class WebEngineSettings(websettings.AbstractSettings):
QWebEngineSettings.WebAttribute.JavascriptCanAccessClipboard: True,
QWebEngineSettings.WebAttribute.JavascriptCanPaste: True,
},
'ask': {
QWebEngineSettings.WebAttribute.JavascriptCanAccessClipboard: False,
QWebEngineSettings.WebAttribute.JavascriptCanPaste: False,
},
}
def set_unknown_url_scheme_policy(
@ -285,7 +281,6 @@ class ProfileSetter:
self._set_hardcoded_settings()
self.set_persistent_cookie_policy()
self.set_dictionary_language()
self.disable_persistent_permissions_policy()
def _set_hardcoded_settings(self):
"""Set up settings with a fixed value."""
@ -348,23 +343,7 @@ class ProfileSetter:
log.config.debug("Found dicts: {}".format(filenames))
self._profile.setSpellCheckLanguages(filenames)
should_enable = bool(filenames)
if self._profile.isSpellCheckEnabled() != should_enable:
# Only setting conditionally as a WORKAROUND for a bogus Qt error message:
# https://bugreports.qt.io/browse/QTBUG-131969
self._profile.setSpellCheckEnabled(should_enable)
def disable_persistent_permissions_policy(self):
"""Disable webengine's permission persistence."""
if machinery.IS_QT6: # for mypy
try:
# New in WebEngine 6.8.0
self._profile.setPersistentPermissionsPolicy(
QWebEngineProfile.PersistentPermissionsPolicy.AskEveryTime
)
except AttributeError:
pass
self._profile.setSpellCheckEnabled(bool(filenames))
def _update_settings(option):
@ -378,12 +357,6 @@ def _update_settings(option):
def _init_user_agent_str(ua):
global parsed_user_agent
parsed_user_agent = websettings.UserAgent.parse(ua)
if parsed_user_agent.upstream_browser_version.endswith(".0.0.0"):
# https://codereview.qt-project.org/c/qt/qtwebengine/+/616314
# but we still want the full version available to users if they want it.
qtwe_versions = version.qtwebengine_versions()
assert qtwe_versions.chromium is not None
parsed_user_agent.upstream_browser_version = qtwe_versions.chromium
def init_user_agent():
@ -417,70 +390,16 @@ def _init_profile(profile: QWebEngineProfile) -> None:
lambda url: profile.clearVisitedLinks([url]))
_global_settings.init_settings()
_maybe_disable_hangouts_extension(profile)
def _maybe_disable_hangouts_extension(profile: QWebEngineProfile) -> None:
"""Disable the Hangouts extension for Qt 6.10+."""
if not config.val.qt.workarounds.disable_hangouts_extension:
return
if machinery.IS_QT6: # mypy
try:
ext_manager = profile.extensionManager()
except AttributeError:
return # added in QtWebEngine 6.10
qtwe_versions = version.qtwebengine_versions(avoid_init=True)
if (
qtwe_versions.webengine == utils.VersionNumber(6, 10, 1)
and profile.isOffTheRecord()
):
# WORKAROUND for https://github.com/qutebrowser/qutebrowser/issues/8785
log.misc.warning(
"Not disabling Hangouts extension on private profile to avoid "
"QtWebEngine crash with Qt 6.10.1")
return
assert ext_manager is not None # mypy
for info in ext_manager.extensions():
if info.id() == pakjoy.HANGOUTS_EXT_ID:
log.misc.debug(f"Disabling extension: {info.name()}")
# setExtensionEnabled(info, False) seems to segfault
ext_manager.unloadExtension(info)
def _clear_webengine_permissions_json():
"""Remove QtWebEngine's persistent permissions file, if present.
We have our own permissions feature and don't integrate with their one.
This only needs to be called when you are on Qt6.8 but PyQt<6.8, since if
we have access to the `setPersistentPermissionsPolicy()` we will use that
to disable the Qt feature.
This needs to be called before we call `setPersistentStoragePath()`
because Qt will load the file during that.
"""
permissions_file = pathlib.Path(standarddir.data()) / "webengine" / "permissions.json"
try:
permissions_file.unlink(missing_ok=True)
except OSError as err:
log.init.warning(
f"Error while cleaning up webengine permissions file: {err}"
)
def default_qt_profile() -> QWebEngineProfile:
"""Get the default profile from Qt."""
if machinery.IS_QT6:
return QWebEngineProfile("Default")
else:
return QWebEngineProfile.defaultProfile()
def _init_default_profile():
"""Init the default QWebEngineProfile."""
global default_profile
default_profile = default_qt_profile()
if machinery.IS_QT6:
default_profile = QWebEngineProfile("Default")
else:
default_profile = QWebEngineProfile.defaultProfile()
assert not default_profile.isOffTheRecord()
assert parsed_user_agent is None # avoid earlier profile initialization
@ -488,25 +407,13 @@ def _init_default_profile():
init_user_agent()
ua_version = version.qtwebengine_versions()
logger = log.init.warning
if machinery.IS_QT5:
# With Qt 5.15, we can't quite be sure about which QtWebEngine patch version
# we're getting, as ELF parsing might be broken and there's no other way.
# For most of the code, we don't really care about the patch version though.
assert (
non_ua_version.webengine.strip_patch() == ua_version.webengine.strip_patch()
), (non_ua_version, ua_version)
logger = log.init.debug
if ua_version.webengine != non_ua_version.webengine:
logger(
log.init.warning(
"QtWebEngine version mismatch - unexpected behavior might occur, "
"please open a bug about this.\n"
f" Early version: {non_ua_version}\n"
f" Real version: {ua_version}")
_clear_webengine_permissions_json()
default_profile.setCachePath(
os.path.join(standarddir.cache(), 'webengine'))
default_profile.setPersistentStoragePath(
@ -539,23 +446,13 @@ def _init_site_specific_quirks():
# default_ua = ("Mozilla/5.0 ({os_info}) "
# "AppleWebKit/{webkit_version} (KHTML, like Gecko) "
# "{qt_key}/{qt_version} "
# "{upstream_browser_key}/{upstream_browser_version_short} "
# "{upstream_browser_key}/{upstream_browser_version} "
# "Safari/{webkit_version}")
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}"
)
no_qtwe_ua = ("Mozilla/5.0 ({os_info}) "
"AppleWebKit/{webkit_version} (KHTML, like Gecko) "
"{upstream_browser_key}/{upstream_browser_version} "
"Safari/{webkit_version}")
firefox_ua = "Mozilla/5.0 ({os_info}; rv:90.0) Gecko/20100101 Firefox/90.0"
def maybe_newer_chrome_ua(at_least_version):
"""Return a new UA if our current chrome version isn't at least at_least_version."""
@ -570,14 +467,23 @@ def _init_site_specific_quirks():
"Safari/537.36"
)
utils.unused(maybe_newer_chrome_ua)
user_agents = [
# Needed to avoid a ""WhatsApp works with Google Chrome 36+" error
# page which doesn't allow to use WhatsApp Web at all. Also see the
# additional JS quirk: qutebrowser/javascript/quirks/whatsapp_web.user.js
# https://github.com/qutebrowser/qutebrowser/issues/4445
("ua-whatsapp", 'https://web.whatsapp.com/', no_qtwe_ua),
# Needed to avoid a "you're using a browser [...] that doesn't allow us
# to keep your account secure" error.
# https://github.com/qutebrowser/qutebrowser/issues/5182
("ua-google", "https://accounts.google.com/*", firefox_ua),
("ua-gnome-gitlab", "https://gitlab.gnome.org/*", not_mozilla_ua),
("ua-google", 'https://accounts.google.com/*', firefox_ua),
# Needed because Slack adds an error which prevents using it relatively
# aggressively, despite things actually working fine.
# October 2023: Slack claims they only support 112+. On #7951 at least
# one user claims it still works fine on 108 based Qt versions.
("ua-slack", 'https://*.slack.com/*', maybe_newer_chrome_ua(112)),
]
for name, pattern, ua in user_agents:
@ -603,7 +509,7 @@ def _init_default_settings():
- Make sure the devtools always get images/JS permissions.
- On Qt 6, make sure files in the data path can load external resources.
"""
devtools_settings: list[tuple[str, Any]] = [
devtools_settings: List[Tuple[str, Any]] = [
('content.javascript.enabled', True),
('content.images', True),
('content.cookies.accept', 'all'),
@ -616,7 +522,7 @@ def _init_default_settings():
hide_userconfig=True)
if machinery.IS_QT6:
userscripts_settings: list[tuple[str, Any]] = [
userscripts_settings: List[Tuple[str, Any]] = [
("content.local_content_can_access_remote_urls", True),
("content.local_content_can_access_file_urls", False),
]

Some files were not shown because too many files have changed in this diff Show More