【Ubuntu / Python】Pixiでpywebviewの煩雑なapt installは回避できるか
pywebview
のインストールは簡単になる?
そもそもtkinter
は、「
tkinter
で作る画面の例
コード
import tkinter as tk
from io import BytesIO
from tkinter import messagebox
import requests
from PIL import Image, ImageTk
def on_click():
label.config(text="ボタンがクリックされました!")
def show_entry():
label.config(text=f"入力: {entry.get()}")
def show_selected():
selected = listbox.get(listbox.curselection())
messagebox.showinfo("選択", f"リストから選択: {selected}")
def check_action():
label.config(text=f"チェック状態: {chk_var.get()}")
def radio_action():
label.config(text=f"ラジオ選択: {radio_var.get()}")
def show_text():
label.config(text=f"テキスト内容: {text.get('1.0', tk.END).strip()}")
root = tk.Tk()
root.title("Tkinter サンプル")
root.geometry("800x600")
top_frame = tk.Frame(root)
top_frame.pack(fill=tk.X, padx=10, pady=10)
label = tk.Label(top_frame, text="こんにちは、Tkinter!", font=("Meiryo", 16))
label.pack(side=tk.LEFT, padx=10)
button = tk.Button(top_frame, text="クリック", command=on_click)
button.pack(side=tk.LEFT, padx=10)
entry_frame = tk.Frame(root)
entry_frame.pack(fill=tk.X, padx=10, pady=10)
entry = tk.Entry(entry_frame, font=("Meiryo", 14))
entry.pack(side=tk.LEFT, padx=10)
show_button = tk.Button(entry_frame, text="入力を表示", command=show_entry)
show_button.pack(side=tk.LEFT, padx=10)
list_frame = tk.Frame(root)
list_frame.pack(fill=tk.X, padx=10, pady=10)
listbox = tk.Listbox(list_frame, height=5)
for item in ["りんご", "みかん", "バナナ", "ぶどう", "もも"]:
listbox.insert(tk.END, item)
listbox.pack(side=tk.LEFT, padx=10)
list_btn = tk.Button(list_frame, text="リスト選択表示", command=show_selected)
list_btn.pack(side=tk.LEFT, padx=10)
chk_var = tk.BooleanVar()
chk = tk.Checkbutton(root, text="チェックしてみてください", variable=chk_var, command=check_action)
chk.pack(pady=10)
radio_var = tk.StringVar(value="A")
radio_frame = tk.Frame(root)
radio_frame.pack(pady=10)
tk.Label(radio_frame, text="ラジオボタン:").pack(side=tk.LEFT)
for v in ["A", "B", "C"]:
tk.Radiobutton(radio_frame, text=v, variable=radio_var, value=v, command=radio_action).pack(side=tk.LEFT)
text_frame = tk.Frame(root)
text_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
text = tk.Text(text_frame, height=5)
text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
text_btn = tk.Button(text_frame, text="表示", command=show_text)
text_btn.pack(side=tk.LEFT, padx=10)
try:
response = requests.get("https://upload.wikimedia.org/wikipedia/commons/c/c6/PwrdLogo200.gif")
img = Image.open(BytesIO(response.content))
img = img.resize((120, 120))
photo = ImageTk.PhotoImage(img)
img_label = tk.Label(root, image=photo)
img_label.image = photo
img_label.pack(pady=10)
except Exception:
tk.Label(root, text="画像ファイルが見つかりません").pack(pady=10)
root.mainloop()
後ろ向きに捉えれば、tkinter
は画面の結構に融通が利かないとも言えます。その中で画面を凝るには限界があるため、しばしばpywebview
は、そうしたものの内の一つです。
pywebview
のサンプル(
このサンプルでは、次のように
import webview
webview.create_window('Hello world', 'https://pywebview.flowrl.com/')
webview.start()
Web技術を使う手法の例
Electron
WebView
pywebview
は、後者の
pywebview
の不便
当たり前ですが、pywebview
をインストールする必要があります。この手のものにしては珍しいことに、
pip install pywebview
の一つで済みます。問題は
Qt かGTK か
pywebview
を使うに当たり、どちらを基にするか選ぶことになっているようです。
PythonでQtを使う
参考までに、(
こちらの記事では
対してこちらのチュートリアルでは
それぞれの例を示しておきます。
pyqt6
pip install pyqt6
pyqt6
の実装例
import random
import sys
from PyQt6.QtCore import QSize, Qt
from PyQt6.QtWidgets import (
QApplication,
QLabel,
QMainWindow,
QPushButton,
QVBoxLayout,
QWidget,
)
MESSAGES = [
"Hello PyQt6!",
"It's a nice day!",
"Python and PyQt",
"Let's enjoy the PyQt!",
"Button clicked.",
]
def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("PyQt6 sample app")
self.setFixedSize(QSize(300, 100))
central_widget = QWidget()
layout = QVBoxLayout()
central_widget.setLayout(layout)
self.setCentralWidget(central_widget)
self.label = QLabel("-- Message Area --")
self.label.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(self.label, stretch=1)
button = QPushButton("Press Me!")
button.clicked.connect(self.show_random_message)
layout.addWidget(button, stretch=1)
def show_random_message(self):
self.label.setText(random.choice(MESSAGES))
if __name__ == "__main__":
main()
pyside6
pip install pyside6
pyside6
の実装例
import random
import sys
from PySide6.QtCore import Qt, Slot
from PySide6.QtWidgets import QApplication, QLabel, QPushButton, QVBoxLayout, QWidget
def main():
app = QApplication(sys.argv)
widget = MyWidget()
widget.show()
sys.exit(app.exec_())
class MyWidget(QWidget):
def __init__(self):
QWidget.__init__(self)
self.hello = [
"Hallo Welt",
"問天地好在",
"Hei maailma",
"Hola Mundo",
"Привет мир",
]
self.button = QPushButton("Click me!")
self.message = QLabel("Hello World")
self.message.setAlignment(Qt.AlignHCenter)
self.layout = QVBoxLayout(self)
self.layout.addWidget(self.message)
self.layout.addWidget(self.button)
# Connecting the signal
self.button.clicked.connect(self.magic)
@Slot()
def magic(self):
self.message.setText(random.choice(self.hello))
if __name__ == "__main__":
main()
[qt]
を付けてインストールします。
pip install pywebview[qt]
[gtk]
を付けてインストールします。
pip install pywebview[gtk]
apt install
幾多のpywebview
をインストールするだけでは動きません。
sudo apt install python3-pyqt5 python3-pyqt5.qtwebengine python3-pyqt5.qtwebchannel libqt5webkit5-dev
sudo apt install python3-pyqt5 python3-pyqt5.qtwebkit python-pyqt5 python-pyqt5.qtwebkit libqt5webkit5-dev
sudo apt install python3-gi python3-gi-cairo gir1.2-gtk-3.0 gir1.2-webkit2-4.1
環境への影響
大人しく
そもそもpywebview
とは全く関係ないところに影響する可能性や、誰か別のユーザーによって削除される可能性もあります。
また、何をインストールしたのか後から確認しづらいこともあり、不要になっても除去しないままにしてしまうことも多いのではないでしょうか。こうした整理のしづらさは嫌厭されることがあり、
環境全体への影響が少しでも低減できれば、つまり
本題
偖、本記事では「
インストールは次のようにします。
curl -fsSL https://pixi.sh/install.sh | sh
wget -qO- https://pixi.sh/install.sh | sh
ターミナルを再起動することでpixi
コマンドが有効になります。
conda -forge とPyPI
パッケージマネージャー | インストール元 |
---|---|
︙ |
特に何も指定しなければ、
「prefix.dev
にて確認することができます。大抵の場合
conda-forge
の他にもanaconda
などのconda-forge
の
なおpywebview
は
プロジェクト作成
インストールの事は一度忘れて、曩にプロジェクトを作っておきます。sample_app
というプロジェクト名にしましたが、今後一切顧みないので自由に決めて構いません。
pixi init sample_app
殆ど空のプロジェクトができるので、下図のような構成を作りました。なおプログラムの内容は本記事の主旨と関係ないため、特に説明していません。
ソースコード
import time
import webview
class QuitFlag:
def __init__(self):
self._flag = True
@property
def flag(self):
return self._flag
@flag.setter
def flag(self, flag):
self._flag = flag
class Api:
def __init__(self, qFlag: QuitFlag):
self._qFlag = qFlag
self.logs = []
def log(self, value):
self.logs.append(value)
print(f"log: {value}")
return "Logged: " + value
def get_logs(self):
print("get_logs: called")
return self.logs
def exit(self):
print("exit: called")
self._qFlag.flag = False
# print(f'flag: {qFlag.flag} ({qFlag})')
@property
def qFlag(self):
return self._qFlag
qFlag = QuitFlag()
api = Api(qFlag)
def quit(window: webview.Window, qFlag: QuitFlag):
while qFlag.flag:
# print(f'flag: {qFlag.flag} ({qFlag})')
time.sleep(2)
print('breaked loop')
window.destroy()
print('quit app')
window = webview.create_window(
'App Index',
'statics/index.html',
js_api = api
)
webview.start(quit, [window, qFlag])
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>PyWebView チュートリアル</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<h1>PyWebView チュートリアル</h1>
<button onclick="sendLog()" class="main-btn">ログを送信</button>
<button onclick="showLogs()" class="main-btn">履歴を表示</button>
<button onclick="exitApp()" class="exit-btn">終了</button>
<div id="logs"></div>
<script src="scripts.js"></script>
</body>
</html>
body {
font-family: sans-serif;
margin: 2em;
}
#logs {
margin-top: 1em;
background: #f4f4f4;
padding: 1em;
border-radius: 8px;
}
button {
padding: 0.7em 1.5em;
margin-right: 0.5em;
border: none;
border-radius: 6px;
font-size: 1em;
cursor: pointer;
transition: background 0.2s;
}
.main-btn {
background: #4caf50;
color: white;
}
.main-btn:hover {
background: #388e3c;
}
.exit-btn {
background: #f44336;
color: white;
}
.exit-btn:hover {
background: #c62828;
}
function sendLog() {
pywebview.api.log("Let's play PyWebView!").then(response => {
alert(response);
});
}
function showLogs() {
pywebview.api.get_logs().then(logs => {
document.getElementById('logs').innerHTML =
"<strong>ログ履歴:</strong><br>" + logs.map(l => `<div>${l}</div>`).join('');
});
}
function exitApp() {
pywebview.api.exit();
}
ついでに「タスク」を作って登録しておきました。
︙
[tasks]
main = "python src/main.py"
︙
タスクとは
今、src/main.py
を実行するためのコマンドは次のようになります。
pixi run python src/main.py
または、仮想環境を有効にするような方法もあります。
$ pixi shell
(sample_app) $ python src/main.py
仕方ないとはいえ、どちらにせよ文字数が多く、逐一キーボードによる打鍵で入力するには煩わしく感じるでしょう。「タスク」を登録すれば、これを省略できます。
# pixi task add タスク名 実行する内容
pixi task add main "python src/main.py"
ここではmain
という名でタスクを作っており、その内容はpython src/main.py
です。これを実行するには次のようにします。
# pixi run タスク名
pixi run main
これでpixi run python src/main.py
を実行したと同等になります。
インストール
愈愈インストールに取り掛かりますが、先んじてpixi.toml
を示しておきます。着目すべきは[dependencies]
と[pypi-dependencies]
の箇所です。
[workspace]
channels = ["conda-forge"]
name = "sample_app"
platforms = ["linux-64"]
version = "0.1.0"
[tasks]
main = "python src/main.py"
[dependencies]
python = ">=3.13.5,<3.14"
pyqt = ">=5.15.11,<6"
pyqtwebengine = ">=5.15.11,<6"
pyqtwebkit = ">=5.15.11,<6"
pygobject = ">=3.50.0,<4"
gtk3 = ">=3.24.43,<4"
[pypi-dependencies]
pywebview = { version = ">=6.0, <7", extras = ["qt"] }
pixi.toml
を直接編集してこれらを記載したら、次のコマンドから一挙にインストールできます。
pixi install
コマンドの場合
# pywebview
pixi add --pypi pywebview[qt]
# python
pixi add python
# pyqt
pixi add pyqt
# pyqtwebengine
pixi add pyqtwebengine
# pyqtwebkit
pixi add pyqtwebkit
# pygobject
pixi add pygobject
# gtk3
pixi add gtk3
指摘
Qt かGTK か
[pypi-dependencies]
pywebview = { version = ">=6.0, <7", extras = ["qt"] }
pywebview
をインストールするには
[dependencies]
︙
pygobject = ">=3.50.0,<4"
gtk3 = ">=3.24.43,<4"
しかしこの箇所に連なる名は、全て
これらはエラーメッセージにて
[pywebview] GTK cannot be loaded
︙
ModuleNotFoundError: No module named 'gi'
と言われてpygobject
を入れ、
[pywebview] GTK cannot be loaded
Traceback (most recent call last):
︙
gi.require_version('Gtk', '3.0')
~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^
︙
ValueError: Namespace Gtk not available
と言われてgtk3
を入れ、
[pywebview] GTK cannot be loaded
Traceback (most recent call last):
︙
gi.require_version('WebKit2', '4.1')
~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
︙
ValueError: Namespace WebKit2 not available
と言われてwebkit2gtk4.1
があったため入れようとしたものの、
pixi add webkit2gtk4.1
Error: × failed to solve requirements of environment 'default' for platform 'linux-64'
├─▶ failed to solve the environment
╰─▶ Cannot solve the request because of: webkit2gtk4.1 * cannot be installed because there are no viable
options:
└─ webkit2gtk4.1 2.48.4 | 2.48.4 | 2.48.5 would require
└─ __glibc >=2.34,<3.0.a0, for which no candidates were found.
この通り失敗したという経緯があってのものです。このエラーメッセージからは進展することができませんでした。
結局、アプリケーション自体は動いているものの、この通りエラーは解消しなかったため、完全な状態を整えることには失敗していると言えます。
QtWebChannel かQtWebKit か
しかし
なお、
実行の様子
アプリ起動直後
画面が動くだけでなく、
跋
- 未解消のエラーがあること
-
の場合を試していないことGTK -
をインストールできないことQtWebChannel
といった問題・課題が残るものの、辛くも動作するところまで漕ぎ着けました。先にも述べたように、
Discussion