Last active
March 14, 2025 03:38
-
-
Save roflsunriz/095dd7369862fce5becd5f93a39957a8 to your computer and use it in GitHub Desktop.
WSAInstallAPK.py : GUI Program that performs installing APK for WSA (Windows Android Subsystem)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys | |
import os | |
import json | |
import subprocess | |
import threading | |
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, | |
QHBoxLayout, QLabel, QLineEdit, QPushButton, | |
QTextEdit, QRadioButton, QFrame, QMessageBox, | |
QFileDialog) | |
from PyQt6.QtCore import Qt, QPoint, QRectF | |
from PyQt6.QtGui import QPalette, QColor, QPainter, QPainterPath | |
import pkg_resources | |
import subprocess | |
import sys | |
def install_required_packages(): | |
required = {'PyQt6'} | |
installed = {pkg.key for pkg in pkg_resources.working_set} | |
missing = required - installed | |
if missing: | |
python = sys.executable | |
subprocess.check_call([python, '-m', 'pip', 'install', *missing], stdout=subprocess.DEVNULL) | |
# 必要なパッケージをインストール | |
install_required_packages() | |
# 言語用の辞書の定義 | |
LANGUAGES = { | |
"ja": { | |
"title": "Windows Android Subsystem 用 GUI実行ツール のじゃ", | |
"adb_folder": "adbフォルダの場所:", | |
"apk_path": "インストールするAPKパス:", | |
"log_label": "ログ:", | |
"run": "実行", | |
"exit": "終了", | |
"browse": "参照", | |
"language": "言語選択:", | |
}, | |
"en": { | |
"title": "Windows Android Subsystem GUI Tool", | |
"adb_folder": "adb Folder Path:", | |
"apk_path": "APK Path:", | |
"log_label": "Log:", | |
"run": "Run", | |
"exit": "Exit", | |
"browse": "Browse", | |
"language": "Language:", | |
} | |
} | |
LOG_MESSAGES = { | |
"ja": { | |
"going_to_dir": "指定ディレクトリ: {adb_path} に移動中のじゃ...", | |
"error_adb": "エラー: adbパスが存在しないのじゃ!", | |
"command_execute": "コマンド実行: {cmd}", | |
"error": "エラー: {error}", | |
"install_apk": "インストールするAPK: {apk_path}", | |
"error_apk": "エラー: APKファイルが存在しないのじゃ!", | |
"exception": "例外発生: {e}", | |
}, | |
"en": { | |
"going_to_dir": "Changing current directory to: {adb_path} ...", | |
"error_adb": "Error: adb folder does not exist!", | |
"command_execute": "Executing command: {cmd}", | |
"error": "Error: {error}", | |
"install_apk": "Installing APK: {apk_path}", | |
"error_apk": "Error: APK file does not exist!", | |
"exception": "Exception occurred: {e}", | |
} | |
} | |
# 設定ファイルのパスを変更 | |
CONFIG_DIR = os.path.join(os.getcwd(), "misc", "config") | |
SETTINGS_FILE = os.path.join(CONFIG_DIR, "wsainstallapk_config.json") | |
def load_settings(): | |
"""設定ファイルから設定値を読み込むのじゃ""" | |
defaults = {"adb_folder": r"C:\platform-tools", "apk_path": ""} | |
# 設定ディレクトリが存在しない場合は作成 | |
if not os.path.exists(CONFIG_DIR): | |
os.makedirs(CONFIG_DIR) | |
if os.path.isfile(SETTINGS_FILE): | |
try: | |
with open(SETTINGS_FILE, "r", encoding="utf-8") as f: | |
data = json.load(f) | |
defaults.update(data) | |
except Exception as e: | |
print("設定読み込み失敗:", e) | |
return defaults | |
def save_settings(adb_folder, apk_path): | |
"""現在の設定値を設定ファイルに保存するのじゃ""" | |
data = {"adb_folder": adb_folder, "apk_path": apk_path} | |
try: | |
with open(SETTINGS_FILE, "w", encoding="utf-8") as f: | |
json.dump(data, f, ensure_ascii=False, indent=4) | |
except Exception as e: | |
print("設定保存失敗:", e) | |
def append_log(log_widget, message): | |
log_widget.append(message + "\n") | |
def run_commands(adb_path, apk_path, log_widget, run_button, language): | |
"""コマンドを実行するのじゃ""" | |
trans = LOG_MESSAGES[language] | |
try: | |
run_button.setEnabled(False) | |
append_log(log_widget, trans["going_to_dir"].format(adb_path=adb_path)) | |
if not os.path.isdir(adb_path): | |
append_log(log_widget, trans["error_adb"]) | |
run_button.setEnabled(True) | |
return | |
connect_cmd = ["adb", "connect", "127.0.0.1:58526"] | |
append_log(log_widget, trans["command_execute"].format(cmd=" ".join(connect_cmd))) | |
result = subprocess.run(connect_cmd, cwd=adb_path, capture_output=True, text=True) | |
append_log(log_widget, result.stdout) | |
if result.stderr: | |
append_log(log_widget, trans["error"].format(error=result.stderr)) | |
append_log(log_widget, trans["install_apk"].format(apk_path=apk_path)) | |
if not os.path.isfile(apk_path): | |
append_log(log_widget, trans["error_apk"]) | |
run_button.setEnabled(True) | |
return | |
install_cmd = ["adb", "install", apk_path] | |
append_log(log_widget, trans["command_execute"].format(cmd=" ".join(install_cmd))) | |
result = subprocess.run(install_cmd, cwd=adb_path, capture_output=True, text=True) | |
append_log(log_widget, result.stdout) | |
if result.stderr: | |
append_log(log_widget, trans["error"].format(error=result.stderr)) | |
except Exception as e: | |
append_log(log_widget, trans["exception"].format(e=str(e))) | |
finally: | |
run_button.setEnabled(True) | |
def select_apk(entry_apk): | |
"""APKファイル選択用のダイアログを開くのじゃ""" | |
filepath, _ = QFileDialog.getOpenFileName( | |
None, | |
"APKファイルを選択するのじゃ", | |
"", | |
"APKファイル (*.apk)" | |
) | |
if filepath: | |
entry_apk.setText(filepath) | |
def select_adb_folder(entry_adb): | |
"""adbフォルダー選択用のダイアログを開くのじゃ""" | |
folder = QFileDialog.getExistingDirectory( | |
None, | |
"adbフォルダを選択するのじゃ", | |
"" | |
) | |
if folder: | |
entry_adb.setText(folder) | |
def update_language(root, lbl_adb, lbl_apk, lbl_log, btn_run, btn_exit, btn_browse_apk, btn_browse_adb, lbl_lang, language_var): | |
lang = language_var.get() | |
root.setWindowTitle(LANGUAGES[lang]["title"]) | |
lbl_adb.setText(LANGUAGES[lang]["adb_folder"]) | |
lbl_apk.setText(LANGUAGES[lang]["apk_path"]) | |
lbl_log.setText(LANGUAGES[lang]["log_label"]) | |
btn_run.setText(LANGUAGES[lang]["run"]) | |
btn_exit.setText(LANGUAGES[lang]["exit"]) | |
btn_browse_apk.setText(LANGUAGES[lang]["browse"]) | |
btn_browse_adb.setText(LANGUAGES[lang]["browse"]) | |
lbl_lang.setText(LANGUAGES[lang]["language"]) | |
class CustomTitleBar(QWidget): | |
def __init__(self, parent=None): | |
super().__init__(parent) | |
self.parent = parent | |
layout = QHBoxLayout() | |
layout.setContentsMargins(0, 0, 0, 0) | |
self.title = QLabel(LANGUAGES["ja"]["title"]) | |
self.title.setStyleSheet("color: #333; font-weight: bold;") | |
layout.addWidget(self.title) | |
layout.addStretch() | |
self.minimize_button = QPushButton("-") | |
self.close_button = QPushButton("×") | |
for button in (self.minimize_button, self.close_button): | |
button.setFixedSize(45, 30) | |
button.setStyleSheet(""" | |
QPushButton { | |
background-color: transparent; | |
border: none; | |
color: #555; | |
} | |
QPushButton:hover { | |
background-color: rgba(0, 0, 0, 0.1); | |
} | |
""") | |
self.minimize_button.clicked.connect(self.parent.showMinimized) | |
self.close_button.clicked.connect(self.parent.close) | |
layout.addWidget(self.minimize_button) | |
layout.addWidget(self.close_button) | |
self.setLayout(layout) | |
def mousePressEvent(self, event): | |
if event.button() == Qt.MouseButton.LeftButton: | |
self.parent.drag_position = event.globalPosition().toPoint() - self.parent.frameGeometry().topLeft() | |
event.accept() | |
def mouseMoveEvent(self, event): | |
if event.buttons() == Qt.MouseButton.LeftButton: | |
self.parent.move(event.globalPosition().toPoint() - self.parent.drag_position) | |
event.accept() | |
class MainWindow(QMainWindow): | |
def __init__(self): | |
super().__init__() | |
self.drag_position = QPoint() | |
self.setWindowFlags(Qt.WindowType.FramelessWindowHint) | |
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) | |
self.current_language = "ja" # 現在の言語を保持 | |
# メインウィジェットの設定 | |
main_widget = QWidget() | |
self.setCentralWidget(main_widget) | |
# メインレイアウト | |
layout = QVBoxLayout(main_widget) | |
layout.setContentsMargins(0, 0, 0, 0) | |
# カスタムタイトルバーの追加 | |
self.title_bar = CustomTitleBar(self) | |
layout.addWidget(self.title_bar) | |
# コンテンツエリア | |
content = QWidget() | |
content.setObjectName("contentArea") | |
content_layout = QVBoxLayout(content) | |
# 言語選択 | |
lang_frame = QFrame() | |
lang_layout = QHBoxLayout(lang_frame) | |
lang_label = QLabel(LANGUAGES["ja"]["language"]) | |
self.ja_radio = QRadioButton("日本語") | |
self.en_radio = QRadioButton("English") | |
self.ja_radio.setChecked(True) | |
# 言語切り替えイベントの設定 | |
self.ja_radio.toggled.connect(lambda: self.change_language("ja")) | |
self.en_radio.toggled.connect(lambda: self.change_language("en")) | |
lang_layout.addWidget(lang_label) | |
lang_layout.addWidget(self.ja_radio) | |
lang_layout.addWidget(self.en_radio) | |
content_layout.addWidget(lang_frame) | |
# 入力フィールド | |
self.adb_folder_edit = QLineEdit() | |
self.apk_path_edit = QLineEdit() | |
for label_text, line_edit, button_text in [ | |
(LANGUAGES["ja"]["adb_folder"], self.adb_folder_edit, LANGUAGES["ja"]["browse"]), | |
(LANGUAGES["ja"]["apk_path"], self.apk_path_edit, LANGUAGES["ja"]["browse"]) | |
]: | |
frame = QFrame() | |
frame_layout = QHBoxLayout(frame) | |
label = QLabel(label_text) | |
browse_button = QPushButton(button_text) | |
frame_layout.addWidget(label) | |
frame_layout.addWidget(line_edit) | |
frame_layout.addWidget(browse_button) | |
content_layout.addWidget(frame) | |
# ブラウズボタンのクリックイベントを設定 | |
if line_edit == self.adb_folder_edit: | |
browse_button.clicked.connect(lambda: select_adb_folder(self.adb_folder_edit)) | |
else: | |
browse_button.clicked.connect(lambda: select_apk(self.apk_path_edit)) | |
# 設定の読み込み | |
settings = load_settings() | |
self.adb_folder_edit.setText(settings["adb_folder"]) | |
self.apk_path_edit.setText(settings["apk_path"]) | |
# ログエリア | |
log_label = QLabel(LANGUAGES["ja"]["log_label"]) | |
self.log_text = QTextEdit() | |
self.log_text.setReadOnly(True) | |
content_layout.addWidget(log_label) | |
content_layout.addWidget(self.log_text) | |
# ボタンエリア | |
button_frame = QFrame() | |
button_layout = QHBoxLayout(button_frame) | |
self.run_button = QPushButton(LANGUAGES["ja"]["run"]) | |
self.exit_button = QPushButton(LANGUAGES["ja"]["exit"]) | |
# 実行ボタンのクリックイベントを設定 | |
self.run_button.clicked.connect(self.execute_commands) | |
self.exit_button.clicked.connect(self.close_application) | |
button_layout.addWidget(self.run_button) | |
button_layout.addWidget(self.exit_button) | |
content_layout.addWidget(button_frame) | |
layout.addWidget(content) | |
# スタイル設定 | |
self.setStyleSheet(""" | |
QMainWindow { | |
background: transparent; | |
} | |
#contentArea { | |
background: rgba(240, 249, 255, 0.95); | |
border-radius: 10px; | |
border: 1px solid rgba(0, 120, 212, 0.3); | |
padding: 20px; | |
} | |
QFrame { | |
background: transparent; | |
} | |
QLabel { | |
color: #333333; | |
font-size: 12px; | |
} | |
QRadioButton { | |
color: #333333; | |
font-size: 12px; | |
spacing: 5px; | |
} | |
QRadioButton::indicator { | |
width: 13px; | |
height: 13px; | |
} | |
QRadioButton::indicator:checked { | |
background-color: #0078D4; | |
border: 2px solid #FFFFFF; | |
border-radius: 7px; | |
} | |
QRadioButton::indicator:unchecked { | |
background-color: #FFFFFF; | |
border: 2px solid #999999; | |
border-radius: 7px; | |
} | |
QPushButton { | |
background: qlineargradient(x1:0, y1:0, x2:0, y2:1, | |
stop:0 #f0f0f0, stop:1 #e5e5e5); | |
border: 1px solid #ccc; | |
border-radius: 4px; | |
padding: 5px 15px; | |
color: #333; | |
font-size: 12px; | |
} | |
QPushButton:hover { | |
background: qlineargradient(x1:0, y1:0, x2:0, y2:1, | |
stop:0 #e5e5e5, stop:1 #d5d5d5); | |
} | |
QLineEdit { | |
border: 1px solid #aaa; | |
border-radius: 4px; | |
padding: 5px; | |
background: rgba(255, 255, 255, 0.8); | |
color: #333333; | |
font-size: 12px; | |
} | |
QTextEdit { | |
border: 1px solid #aaa; | |
border-radius: 4px; | |
background: rgba(255, 255, 255, 0.8); | |
color: #333333; | |
font-size: 12px; | |
padding: 5px; | |
} | |
""") | |
self.setMinimumSize(800, 600) | |
self.setWindowTitle(LANGUAGES["ja"]["title"]) | |
def paintEvent(self, event): | |
painter = QPainter(self) | |
painter.setRenderHint(QPainter.RenderHint.Antialiasing) | |
path = QPainterPath() | |
rect = QRectF(self.rect()) # QRectをQRectFに変換 | |
path.addRoundedRect(rect, 10, 10) | |
painter.fillPath(path, QColor(255, 255, 255, 245)) | |
def execute_commands(self): | |
"""実行ボタンが押されたときの処理なのじゃ""" | |
adb_path = self.adb_folder_edit.text() | |
apk_path = self.apk_path_edit.text() | |
# 別スレッドで実行 | |
threading.Thread(target=run_commands, args=( | |
adb_path, | |
apk_path, | |
self.log_text, | |
self.run_button, | |
self.current_language | |
)).start() | |
def close_application(self): | |
"""終了ボタンが押されたときの処理なのじゃ""" | |
# 設定を保存 | |
save_settings(self.adb_folder_edit.text(), self.apk_path_edit.text()) | |
self.close() | |
def change_language(self, lang): | |
"""言語を切り替えるのじゃ""" | |
if not self.ja_radio.isChecked() and not self.en_radio.isChecked(): | |
return | |
self.current_language = lang | |
# ウィンドウタイトルの更新 | |
self.setWindowTitle(LANGUAGES[lang]["title"]) | |
self.title_bar.title.setText(LANGUAGES[lang]["title"]) | |
# 各ラベルとボタンのテキストを更新 | |
for frame in self.findChildren(QFrame): | |
for child in frame.children(): | |
if isinstance(child, QLabel): | |
if "adb_folder" in LANGUAGES[lang].values() and child.text() in LANGUAGES["ja"].values(): | |
child.setText(LANGUAGES[lang]["adb_folder"]) | |
elif "apk_path" in LANGUAGES[lang].values() and child.text() in LANGUAGES["ja"].values(): | |
child.setText(LANGUAGES[lang]["apk_path"]) | |
elif "log_label" in LANGUAGES[lang].values() and child.text() in LANGUAGES["ja"].values(): | |
child.setText(LANGUAGES[lang]["log_label"]) | |
elif "language" in LANGUAGES[lang].values() and child.text() in LANGUAGES["ja"].values(): | |
child.setText(LANGUAGES[lang]["language"]) | |
elif isinstance(child, QPushButton): | |
if child.text() == LANGUAGES["ja"]["browse"]: | |
child.setText(LANGUAGES[lang]["browse"]) | |
elif child.text() == LANGUAGES["ja"]["run"]: | |
child.setText(LANGUAGES[lang]["run"]) | |
elif child.text() == LANGUAGES["ja"]["exit"]: | |
child.setText(LANGUAGES[lang]["exit"]) | |
def main(): | |
app = QApplication(sys.argv) | |
window = MainWindow() | |
window.show() | |
sys.exit(app.exec()) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
How to use: