PyQt5でウェブ 【前編】 QWebEngine / QQmlApplicationEngineのキャッシュとプロファイル
開発::application
序
またしても無知を晒すPython*Qt*WebEngineのお話。
Unsurfが「Incognitoブラウジングにいいよ」とか言ったけれど、実際プロファイルもキャッシュも残ってしまうので、なんとかならないかな、ということを考えた。
この記事、及びこのソフトウェアのコードは、PyQtに関する情報を調べる中で「いくら検索しても情報や実例が出てこない」という問題に悩まされたことを踏まえてコードを書き残すことを主眼としたものであり、内容はレベルの高いものではない。むしろ稚拙といっていい。私自身があまり理解していないのだから。
QWebEngineの場合
PyQt5を使うQWebEngineの場合の情報はこちらのドキュメントにあるQWebEngineProfile
で、cachePath
,
persistentStoragePath
,
storageName
といった値でコントロールされることがわかる。
QWebEngineの場合はデフォルト名が<appname>/QWebEngine
で固定であり、従来の方式だと~/.local/QWebEngine
なんかになってしまってよろしくない。
そこでこのコード
#!/usr/bin/python
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWebEngineWidgets import *
from PyQt5.QtGui import QIcon
import re
import os
= sys.argv
argv = len(argv)
argc
if (argc != 2):
print("Usage:\n unsurf.py addr")
quit()
# For opening local file.
= argv[1]
url if re.match('^[a-z]+://', url):
pass # do nothing.
elif re.match('^/', url):
= 'file://' + url
url else:
= "file://" + os.getcwd() + "/" + url
url
= QApplication([])
app
# Choice application icon. I don't know smart way for choice generic web browser icon.
for iconpath in ["Papirus/64x64/apps/redhat-web-browser.svg", "Vibrancy-Colors/apps/96/browser.png", "Papirus/64x64/apps/internet-web-browser.svg", "breeze/apps/48/plasma-browser-integration.svg", "breeze/apps/48/internet-web-browser.svg", "Adwaita/scalable/apps/web-browser-symbolic.svg", "gnome/256x256/apps/web-browser.png", "ePapirus/22x22/actions/web-browser.svg", "AwOken/clear/128x128/apps/browser.png", "andromeda/apps/48/internet-web-browser.svg"]:
if os.path.exists("/usr/share/icons/" + iconpath):
"web-browser", QIcon("/usr/share/icons/" + iconpath)))
app.setWindowIcon(QIcon.fromTheme(break
= QWebEngineView()
web "Unsruf Quick WebBrowser")
web.setWindowTitle(
web.load(QUrl(url))
web.show()
sys.exit(app.exec_())
にちょっと書き足しておく。まず単純にアプリ名を与えてあげれば固有にはなる。
- app = QApplication([])
+ app = QApplication(sys.argv)
プロファイル名のセットの仕方はQWebEngineProfile
とQWebEnginePage
を使ってこんな感じ
= QWebEngineView()
web = QWebEngineProfile("unsurf", web)
pf = QWebEnginePage(pf, web)
page web.setPage(page)
これでプロファイル名が”unsurf”になる。 ただ、今回の場合はどちらかというと、「Unsurfは常にincognito」ということが重要になるので、キャッシュパスを知るには
= QWebEngineProfile.defaultProfile()
dpf print(dpf.cachePath())
プロファイルパスは
= QWebEngineProfile.defaultProfile()
dpf print(dpf.persistentStoragePath())
だから、
- sys.exit(app.exec_())
+ app.exec_()
+
+ shutil.rmtree(dpf.persistentStoragePath())
+ shutil.rmtree(dpf.cachePath())
+
+ sys.exit()
って感じ。おっと、import
も忘れずに。
ところがそもそもの話としてQWebEngineにはoffTheRecordモードが入っていて、使い方がわからず調べ回った結果、QWebEngineProfile
にからっぽのQObject
を与えるか、そもそも何も与えなければ良い、ということがわかった。
なので最終的には
--- a/unsurf.py
+++ b/unsurf.py
@@ -23,18 +23,23 @@ elif re.match('^/', url):
else:
url = "file://" + os.getcwd() + "/" + url
-app = QApplication([])
-
+app = QApplication(["unsurf"])
# Choice application icon. I don't know smart way for choice generic web browser icon.
for iconpath in ["Papirus/64x64/apps/redhat-web-browser.svg", "Vibrancy-Colors/apps/96/browser.png", "Papirus/64x64/apps/internet-web-browser.svg", "breeze/apps/48/plasma-browser-integration.svg", "breeze/apps/48/internet-web-browser.svg", "Adwaita/scalable/apps/web-browser-symbolic.svg", "gnome/256x256/apps/web-browser.png", "ePapirus/22x22/actions/web-browser.svg", "AwOken/clear/128x128/apps/browser.png", "andromeda/apps/48/internet-web-browser.svg"]:
if os.path.exists("/usr/share/icons/" + iconpath):
app.setWindowIcon(QIcon.fromTheme("web-browser", QIcon("/usr/share/icons/" + iconpath)))
break-
web = QWebEngineView()+
+# Set profile name if you need.
+pf = QWebEngineProfile()
+page = QWebEnginePage(pf, web)
+web.setPage(page)
+
web.setWindowTitle("Unsruf Quick WebBrowser")
web.load(QUrl(url))
web.show()
-sys.exit(app.exec_())
\ No newline at end of file+sys.exit(app.exec_())
って感じ。
QQmlApplicationEngineの場合
QMLを使うQQmlApplicationEngineの場合は
= QtGui.QGuiApplication(["Qt Web Viewer"]) app
だとプロファイルは~/.local/Qt Web Viewer
に、キャッシュは~/.cache/Qt Web Viewer
に置かれる形(Linux上において)。
公式ドキュメントはこちらで、QQuickWebEngineProfile
に含まれているcachePath
とpersistentStoragePath
というプロパティでコントロールされる。
QQuickWebEngineProfile
はPyQt5/QtWebEngine
で提供されている。
で、QWebEngineProfile
の情報は調べればちらほら出てくるのだけど、QQuickWebEngineProfile
のほうは全くといっていいほど出てこない。
ワードを変えると出てくるものもなくはないので、Googleがだめなだけ、という気はするけど、DuckDuckGoにすればヒットするとかそんなことはない。Bingに至ってはクォートしたところでワードを無視するので、QtWebEngine
の話ばかり出てきて役に立たない。
プロファイルのパスの取得方法は
= QtWebEngine.QQuickWebEngineProfile.defaultProfile() profile
でいけるから、QWebEngineProfileと似た感じではある。
でも、QQmlApplicationEngine
にsetPage
がないので同じようにはいかない。
検索しても類似コードも見つからずどうしようもない展開に。 なので質問してみたんだけど、回答がこなかったのでがんばった。本当にがんばった。
結果分かったのは、「Qtの場合はコード上で書くけど、QtQuick(QML)の場合はあくまでQML上で書くんだよ」ということ。
QMLオブジェクトとつなげる方法があるのかはよくわからないけれど、QQuickWebEngineProfile
はQML上で書けってことのようだ。
ということで直したんだけど、オリジナルのリポジトリは消してしまっていたので元がどうだったか説明するのが難しい。 diffはあるので、diffを見ると別物である。
diff --git a/qwview.py b/qwview.py
index 2f81beb..a4352a4 100755--- a/qwview.py
+++ b/qwview.py
@@ -1,18 +1,37 @@
#!/usr/bin/python
import sys
import os-from PyQt5 import QtGui, QtQml
+from PyQt5 import QtGui, QtQml, QtWebEngine
from OpenGL import GL #Linux workaround. See: http://goo.gl/s0SkFl+from docopt import docopt
-argv = sys.argv
-argc = len(argv)
+__doc__ = """{f}
-if (argc != 2):
- print("Usage:\n qwview.py addr")
- quit()
+Usage:
+ {f} [-i] [-p <profile_name>] <URL>
+ {f} -h | --help
-app = QtGui.QGuiApplication(["Qt Web Viewer"])
+Options:
+ -i --incognito Enable off the record mode.
+ -p --profile <profile_name> Use profile (storage) name.
+ -h --help Show this help and exit.
+""".format(f=__file__)
+
+otr_mode = False
+pfname = "Default"
+
+args = docopt(__doc__)
+if args['--incognito']:
+ otr_mode = True
+if args['--profile']:
+ pfname = args['--profile']
+
+app = QtGui.QGuiApplication(["QtWebViewer"])
+app.setWindowIcon(QtGui.QIcon(":/browser"))
engine = QtQml.QQmlApplicationEngine()-engine.rootContext().setContextProperty("myUrl", argv[1])
-engine.load("{home}/lib/qappview.qml".format(home=os.environ["HOME"]))
+cx = engine.rootContext()
+cx.setContextProperty("myUrl", args["<URL>"])
+cx.setContextProperty("isOffTheRecord", otr_mode)
+cx.setContextProperty("viewStorageName", pfname)
+engine.load("{home}/lib/qwview.qml".format(home=os.environ["HOME"]))
app.exec_()diff --git a/qwview.qml b/qwview.qml
index aae4ab7..4f7fac1 100644--- a/qwview.qml
+++ b/qwview.qml
@@ -1,13 +1,41 @@
-import QtQuick 2.0
+import QtQuick 2.9
import QtQuick.Window 2.0-import QtWebEngine 1.0
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import QtWebEngine 1.9
Window {+ id: root
width: 1024
height: 750
visible: true
WebEngineView {+ id: webview
anchors.fill: parent
url: myUrl+ profile {
+ offTheRecord: isOffTheRecord
+ storageName: viewStorageName
+ }
+ onLoadingChanged: {
+ switch (loadRequest.status) {
+ case WebEngineLoadRequest.LoadStartedStatus:
+ loadProgressBar.visible = true
+ break
+
+ default:
+ loadProgressBar.visible = false
+ break
+ }
+ }
+ onLoadProgressChanged: loadProgressBar.value = loadProgress
+ }
+ ProgressBar {
+ id: loadProgressBar
+ width: root.width
+ from: 0
+ to: 100
+ value: 0
+ visible: false
} }
新しいリポジトリはこちら。コードも掲載するとこんな感じ。
Python.
#!/usr/bin/python
import sys
import os
from PyQt5 import QtGui, QtQml, QtWebEngine
from OpenGL import GL #Linux workaround. See: http://goo.gl/s0SkFl
from docopt import docopt
= """{f}
__doc__
Usage:
{f} [-i] [-p <profile_name>] <URL>
{f} -h | --help
Options:
-i --incognito Enable off the record mode.
-p --profile <profile_name> Use profile (storage) name.
-h --help Show this help and exit.
""".format(f=__file__)
= False
otr_mode = "Default"
pfname
= docopt(__doc__)
args if args['--incognito']:
= True
otr_mode if args['--profile']:
= args['--profile']
pfname
= QtGui.QGuiApplication(["QtWebViewer"])
app ":/browser"))
app.setWindowIcon(QtGui.QIcon(= QtQml.QQmlApplicationEngine()
engine = engine.rootContext()
cx "myUrl", args["<URL>"])
cx.setContextProperty("isOffTheRecord", otr_mode)
cx.setContextProperty("viewStorageName", pfname)
cx.setContextProperty("{home}/lib/qwview.qml".format(home=os.environ["HOME"]))
engine.load( app.exec_()
QML
import QtQuick 2.9
import QtQuick.Window 2.0
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtWebEngine 1.9
Window {id: root
width: 1024
height: 750
visible: true
WebEngineView {id: webview
.fill: parent
anchorsurl: myUrl
profile {offTheRecord: isOffTheRecord
storageName: viewStorageName
}onLoadingChanged: {
switch (loadRequest.status) {
case WebEngineLoadRequest.LoadStartedStatus:
.visible = true
loadProgressBarbreak
default:
.visible = false
loadProgressBarbreak
}
}onLoadProgressChanged: loadProgressBar.value = loadProgress
}
ProgressBar {id: loadProgressBar
width: root.width
from: 0
to: 100
value: 0
visible: false
} }
QMLで書かれているWebEngineView.profile.storageName
にセットする形でできた。
もともとはローカルファイルを見る用に考えていたのだけど、機能的にはUnsurfよりも多く乗る形になった。
で、作りました
そんなこんなでQtのWebEngine周りをいじってたので、もうちょっと実用的なのが欲しいなと思って作っちゃいました。
今までのものと比べるとだいぶコードも長くて、機能的には
- タブ機能
- アドレスバー (検索はしない。スキーマの補完はする)
- ページタイトルのサポート
- タブ切り替え (クリック, Ctrl+Pg{Up,Down})
- タブ閉じ (クリック, Ctrl+W)
- キーボード・ショートカット(リロード, 戻る・進む)
- プロファイル切り替え
といったところ。ローカルファイルよりもウェブ優先になっていたりと、Unsurfとはちょっと違う。
すごい簡単に作れた感じだけれども、QtもGUIアプリケーションもPythonも全然慣れてないので、かなり時間もかかったし、一個ずつ調べながら形にしていくみたいな感じだった。
昔、IEコントロールを使ったブラウザ(架装ブラウザとか呼ばれてた)がすごく流行ってたくさんあった。私はmoon browserとkikiをすごく使っていたし、今でも残っているSlaipnirとかLunascapeとかも元々はそういうブラウザだった。Mozillaと「マルチエンジン実装!」なんてのも流行ったね。
今はブラウザが高度化していたり、拡張機能への依存度が上がったりして、そうしたブラウザはほとんどなくなってしまって、大手ですらも見なくなったのだけど、QtでWebEngineを使うとすごく簡単に実装できるから(経験に乏しい私でも手探りでできたくらいだから)、やってみても面白いかも知れない。
ちなみにこのブラウザ、WebEngine(Blink。Chromiumと同じもの)を使っている関係上、パフォーマンスや表現能力はGtkWebKitを使うSurfやMidoriよりもよかったりする。 なんだか今回、エンジン自作を期待されてしまったような気がするのだけれど、実用的にはエンジン自作はもはや一般レベルでできるものではないので、ポイントを抑えて自分で作るのが良いと思う。 拡張機能を作るのがベストっていうことも多いだろうけれども。
個人的にはよりシンプルで軽く、プロファイル機能があり、余計なことをしないブラウザが欲しかったのでかなり満足。 (off the recordなブラウザが欲しい場合はUnsurf.pyがよく機能する)