Compare commits
5 Commits
Author | SHA1 | Date |
---|---|---|
Alexey Min | d642a42fe9 | |
Alexey Min | 0ef4dccb8f | |
Alexey Min | 245bccbfcb | |
Alexey Min | a263b0d458 | |
Alexey Min | d3080b8ea6 |
|
@ -31,6 +31,9 @@ def main():
|
|||
# Initialize or require config
|
||||
if args.action == "init":
|
||||
return config_init.frontend(args)
|
||||
elif args.action == "gui":
|
||||
# "pmbootstrap gui" also does not require initialized config
|
||||
return frontend.gui(args)
|
||||
elif not os.path.exists(args.config):
|
||||
raise RuntimeError("Please specify a config file, or run"
|
||||
" 'pmbootstrap init' to generate one.")
|
||||
|
|
|
@ -71,8 +71,15 @@ def root(args, cmd, suffix="native", working_dir="/", output="log",
|
|||
executables = executables_absolute_path()
|
||||
cmd_chroot = [executables["chroot"], chroot, "/bin/sh", "-c",
|
||||
pmb.helpers.run.flat_cmd(cmd, working_dir)]
|
||||
cmd_sudo = ["sudo", "env", "-i", executables["sh"], "-c",
|
||||
pmb.helpers.run.flat_cmd(cmd_chroot, env=env_all)]
|
||||
|
||||
if "sudo_askpass_program" in args:
|
||||
cmd_sudo = ["env", f"SUDO_ASKPASS={args.sudo_askpass_program}",
|
||||
"sudo", "--askpass", "env", "-i", executables["sh"], "-c",
|
||||
pmb.helpers.run.flat_cmd(cmd_chroot, env=env_all)]
|
||||
else:
|
||||
cmd_sudo = ["sudo", "env", "-i", executables["sh"], "-c",
|
||||
pmb.helpers.run.flat_cmd(cmd_chroot, env=env_all)]
|
||||
|
||||
return pmb.helpers.run_core.core(args, msg, cmd_sudo, None, output,
|
||||
output_return, check, True,
|
||||
disable_timeout)
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
# Copyright 2021 Alexey Minnekhanov
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
|
||||
_have_pyqt5 = False
|
||||
_have_pygtk = False
|
||||
|
||||
|
||||
def test_installed_gui_tooklits():
|
||||
global _have_pyqt5, _have_pygtk
|
||||
try:
|
||||
import PyQt5
|
||||
_have_pyqt5 = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
import gi
|
||||
_have_pygtk = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def raise_no_gui_toolkits():
|
||||
raise RuntimeError(
|
||||
"Can't run GUI: you need to install either PyQt5 or pygobject!\n"
|
||||
"You can do it using your distribution's package manager.")
|
||||
|
||||
|
||||
def run_gui_qt5(args):
|
||||
import pmb.gui.qt5
|
||||
pmb.gui.qt5.start(args)
|
||||
|
||||
|
||||
def run_gui_gtk(args):
|
||||
# TODO: implement
|
||||
pass
|
||||
|
||||
|
||||
def run_gui_autoselect(args):
|
||||
global _have_pyqt5, _have_pygtk
|
||||
|
||||
prefer_qt = False
|
||||
if os.environ["KDE_FULL_SESSION"] == "true" \
|
||||
or os.environ["XDG_CURRENT_DESKTOP"] == "KDE":
|
||||
prefer_qt = True
|
||||
logging.debug("KDE session detected")
|
||||
|
||||
# TODO: add test for magic env vars for Gnome(-based) DEs
|
||||
|
||||
# Be fair, true random
|
||||
if not prefer_qt and time.time() % 2 == 0:
|
||||
prefer_qt = True
|
||||
|
||||
if prefer_qt and _have_pyqt5:
|
||||
run_gui_qt5(args)
|
||||
return
|
||||
|
||||
# if no Qt preference, try gtk first
|
||||
if _have_pygtk:
|
||||
run_gui_gtk(args)
|
||||
return
|
||||
if _have_pyqt5:
|
||||
run_gui_qt5(args)
|
||||
return
|
||||
# give up
|
||||
raise_no_gui_toolkits()
|
||||
|
||||
|
||||
def run_gui(args):
|
||||
test_installed_gui_tooklits()
|
||||
|
||||
if not _have_pygtk and not _have_pyqt5:
|
||||
raise_no_gui_toolkits()
|
||||
return
|
||||
|
||||
force_qt5 = False
|
||||
force_gtk = False
|
||||
if args.qt5:
|
||||
force_qt5 = args.qt5
|
||||
if args.gtk:
|
||||
force_gtk = args.gtk
|
||||
|
||||
if force_gtk and force_qt5:
|
||||
raise RuntimeError("You must select only one of qt5, gtk options!")
|
||||
|
||||
if force_gtk:
|
||||
if not _have_pygtk:
|
||||
raise RuntimeError("Cannot use gtk: pygobject is not installed!")
|
||||
run_gui_gtk(args)
|
||||
elif force_qt5:
|
||||
if not _have_pyqt5:
|
||||
raise RuntimeError("Cannot use Qt5: PyQt5 is not installed!")
|
||||
run_gui_qt5(args)
|
||||
else:
|
||||
run_gui_autoselect(args)
|
|
@ -0,0 +1,112 @@
|
|||
#!/usr/bin/env python3
|
||||
# Copyright 2021 Alexey Minnekhanov
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# man sudo.conf: askpass:
|
||||
# The fully qualified path to a helper program used to read the user's
|
||||
# password when no terminal is available. This may be the case when sudo
|
||||
# is executed from a graphical (as opposed to text-based) application.
|
||||
# The program specified by askpass should display the argument passed to
|
||||
# it as the prompt and write the user's password to the standard output.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PyQt5.QtCore import PYQT_VERSION, QObject, pyqtSignal, pyqtSlot,\
|
||||
pyqtProperty, QStringListModel
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtQml import QQmlApplicationEngine, QQmlListProperty,\
|
||||
qmlRegisterType
|
||||
from PyQt5.QtQuick import QQuickWindow
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
# since 5.11 PyQt uses internal sip module
|
||||
if PYQT_VERSION >= 0x050B00: # 5.11.0 == 0x5, 0xB, 0x00
|
||||
from PyQt5 import sip
|
||||
else:
|
||||
import sip
|
||||
|
||||
|
||||
PROMPT = ""
|
||||
PASSWORD = ""
|
||||
|
||||
|
||||
class Askpass(QObject):
|
||||
"""
|
||||
This class is instantiated in QML like:
|
||||
|
||||
import Pmb 1.0 as Pmb
|
||||
|
||||
Pmb.Askpass {
|
||||
id: askpass
|
||||
}
|
||||
|
||||
Then prompt property is used inother QML components like
|
||||
|
||||
text: asskpass.prompt
|
||||
askpass.set_pass('...')
|
||||
|
||||
"""
|
||||
prompt_changed = pyqtSignal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
@pyqtProperty(str, notify=prompt_changed)
|
||||
def prompt(self) -> str:
|
||||
global PROMPT
|
||||
return PROMPT
|
||||
|
||||
@pyqtSlot(str)
|
||||
def set_pass(self, p: str) -> None:
|
||||
global PASSWORD
|
||||
PASSWORD = p
|
||||
|
||||
|
||||
def sudo_askpass(prompt: str):
|
||||
global PROMPT, PASSWORD
|
||||
PROMPT = prompt
|
||||
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
sip.setdestroyonexit(False)
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
engine = QQmlApplicationEngine()
|
||||
|
||||
qmlRegisterType(Askpass, 'Pmb', 1, 0, 'Askpass')
|
||||
|
||||
engine.load(f"{script_dir}/qml/askpass.qml")
|
||||
if len(engine.rootObjects()) < 1:
|
||||
print("Failed to load QML!", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# set window icon
|
||||
root_objects = engine.rootObjects() # type: list[QObject]
|
||||
# QML's ApplicationWindow instantiates QQuickWindow
|
||||
app_window = root_objects[0] # type: QQuickWindow
|
||||
app_window.setIcon(QIcon(f"{script_dir}/img/pmos-logo.svg"))
|
||||
|
||||
engine.quit.connect(app.quit)
|
||||
app.exec_()
|
||||
|
||||
# print('<put-your-password-here>') # for debugging purposes only
|
||||
if len(PASSWORD) < 1:
|
||||
sys.exit(1)
|
||||
|
||||
print(PASSWORD)
|
||||
sys.stdout.flush()
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def usage(pname: str):
|
||||
print(f"Usage: {pname} <prompt>")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1:
|
||||
sudo_askpass(sys.argv[1])
|
||||
else:
|
||||
usage(sys.argv[0])
|
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><g transform="translate(100,100) rotate(180) translate(0, 6.698729810778069)"><polygon points="29.7,17.5 25.9,27.8 15.0,26.0 0.0,0.0 65.0,0.0 58.0,8.5 65.0,17.0 29.8,17.0" fill="#090"/><g transform="translate(100,0) rotate(120) "><polygon points="29.7,17.5 25.9,27.8 15.0,26.0 0.0,0.0 65.0,0.0 58.0,8.5 65.0,17.0 29.8,17.0" fill="#090"/></g><g transform="translate(50,86.60254037844386) rotate(240) "><polygon points="29.7,17.5 25.9,27.8 15.0,26.0 0.0,0.0 65.0,0.0 58.0,8.5 65.0,17.0 29.8,17.0" fill="#090"/></g></g></svg>
|
After Width: | Height: | Size: 609 B |
|
@ -0,0 +1,186 @@
|
|||
# Copyright 2019 Martijn Braam
|
||||
# Copyright 2021 Alexey Minnekhanov
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# This file serves as some layer of abstraction on top of raw pmbootstrap API.
|
||||
# Every GUI layer interaction with pmbootstrap should go through this file.
|
||||
|
||||
import logging
|
||||
|
||||
# typing is available since python 3.5
|
||||
from typing import List, Optional, Set
|
||||
|
||||
import pmb.chroot
|
||||
import pmb.config
|
||||
import pmb.config.pmaports
|
||||
import pmb.helpers.devices
|
||||
import pmb.helpers.git
|
||||
import pmb.helpers.ui
|
||||
import pmb.parse._apkbuild
|
||||
|
||||
|
||||
class DeviceInfo:
|
||||
def __init__(self):
|
||||
# copy deviceinfo_attributes from pmb
|
||||
for attr_name in pmb.config.deviceinfo_attributes:
|
||||
setattr(self, attr_name, None)
|
||||
|
||||
# properties taken from pmb/config/__init__.py
|
||||
# general
|
||||
self.name = None
|
||||
self.manufacturer = None
|
||||
self.codename = None
|
||||
self.year = None
|
||||
self.dtb = None
|
||||
self.modules_initfs = []
|
||||
self.arch = None
|
||||
|
||||
# device
|
||||
self.chassis = None
|
||||
self.keyboard = False
|
||||
self.external_storage = False
|
||||
self.screen_width = None
|
||||
self.screen_height = None
|
||||
self.dev_touchscreen = None
|
||||
self.dev_touchscreen_calibration = None
|
||||
self.append_dtb = None
|
||||
|
||||
# bootloader
|
||||
self.flash_method = "none"
|
||||
self.boot_filesystem = None
|
||||
|
||||
# flash
|
||||
self.flash_heimdall_partition_kernel = None
|
||||
self.flash_heimdall_partition_initfs = None
|
||||
self.flash_heimdall_partition_system = None
|
||||
self.flash_heimdall_partition_vbmeta = None
|
||||
self.flash_fastboot_partition_kernel = None
|
||||
self.flash_fastboot_partition_system = None
|
||||
self.flash_fastboot_partition_vbmeta = None
|
||||
self.generate_legacy_uboot_initfs = None
|
||||
self.kernel_cmdline = None
|
||||
self.generate_bootimg = None
|
||||
self.bootimg_qcdt = False
|
||||
self.bootimg_mtk_mkimage = False
|
||||
self.bootimg_dtb_second = False
|
||||
self.flash_offset_base = None
|
||||
self.flash_offset_kernel = None
|
||||
self.flash_offset_ramdisk = None
|
||||
self.flash_offset_second = None
|
||||
self.flash_offset_tags = None
|
||||
self.flash_pagesize = None
|
||||
self.flash_fastboot_max_size = None
|
||||
self.flash_sparse = False
|
||||
self.rootfs_image_sector_size = None
|
||||
self.sd_embed_firmware = None
|
||||
self.sd_embed_firmware_step_size = None
|
||||
self.partition_blacklist = []
|
||||
self.boot_part_start = None
|
||||
self.root_filesystem = None
|
||||
self.flash_kernel_on_update = None
|
||||
|
||||
# weston (some legacy?)
|
||||
self.weston_pixman_type = None
|
||||
|
||||
# Keymaps
|
||||
self.keymaps = []
|
||||
|
||||
# extra properties that are used by some devices
|
||||
self.getty = None
|
||||
self.no_framebuffer = False
|
||||
self.framebuffer_landscape = False
|
||||
self.usb_rndis_function = None
|
||||
self.usb_idVendor = None
|
||||
self.usb_idProduct = None
|
||||
self.mesa_driver = None
|
||||
self.dev_internal_storage = None
|
||||
self.dev_internal_storage_repartition = None
|
||||
self.bootimg_blobpack = None
|
||||
self.bootimg_pxa = None
|
||||
self.bootimg_append_seandroidenforce = None
|
||||
self.disable_dhcpd = False
|
||||
self.swap_size_recommended = None
|
||||
self.initfs_compression = None
|
||||
|
||||
def __repr__(self):
|
||||
return f'<DeviceInfo {self.codename}>'
|
||||
|
||||
def fill_from_pmb_deviceinfo(self, dev: dict) -> None:
|
||||
for key in dev:
|
||||
if not hasattr(self, key):
|
||||
logging.debug(f"{dev['codename']}: unknown deviceinfo key: "
|
||||
f"{key}")
|
||||
setattr(self, key, dev[key])
|
||||
|
||||
# split some strings into lists
|
||||
self.modules_initfs = self.modules_initfs.split() \
|
||||
if type(self.modules_initfs) == str else []
|
||||
|
||||
|
||||
def list_vendors(args) -> Set[str]:
|
||||
return pmb.helpers.devices.list_vendors(args)
|
||||
|
||||
|
||||
def list_vendor_codenames(args, vendor: str,
|
||||
unmaintained: Optional[bool] = None) -> List[str]:
|
||||
return pmb.helpers.devices.list_codenames(args, vendor, unmaintained)
|
||||
|
||||
|
||||
def list_device_kernels(args, codename: str) -> dict:
|
||||
"""
|
||||
Get device kernel subpackages
|
||||
:param args: global program state
|
||||
:param codename: device codename (for example qemu-amd64)
|
||||
:return: dict('kernel_subpkgname' => 'description', ...)
|
||||
"""
|
||||
return pmb.parse._apkbuild.kernels(args, codename)
|
||||
|
||||
|
||||
def list_deviceinfos(args) -> List[DeviceInfo]:
|
||||
""" Get a list of all devices with the information contained in the
|
||||
deviceinfo
|
||||
|
||||
:returns: list of DeviceInfo objects for all known devices
|
||||
:rtype: List[DeviceInfo]
|
||||
"""
|
||||
raw = pmb.helpers.devices.list_deviceinfos(args)
|
||||
result = []
|
||||
for device in raw:
|
||||
row = DeviceInfo()
|
||||
row.fill_from_pmb_deviceinfo(raw[device])
|
||||
result.append(row)
|
||||
return list(sorted(result, key=lambda k: k.codename))
|
||||
|
||||
|
||||
def get_channels_config(args) -> dict:
|
||||
channels_cfg = pmb.helpers.git.parse_channels_cfg(args)
|
||||
return channels_cfg["channels"]
|
||||
|
||||
|
||||
def switch_to_channel(args, channel: str) -> None:
|
||||
# always switch to safe device before switching branch
|
||||
cfg = pmb.config.load(args)
|
||||
cfg['pmbootstrap']['device'] = 'qemu-amd64'
|
||||
pmb.config.save(args, cfg)
|
||||
# zap!
|
||||
pmb.chroot.zap(args, confirm=False)
|
||||
# do the switch
|
||||
pmb.config.pmaports.switch_to_channel_branch(args, channel)
|
||||
|
||||
|
||||
def get_current_channel(args) -> str:
|
||||
repo_path = pmb.helpers.git.get_path(args, "pmaports")
|
||||
# Get branch name (if on branch) or current commit
|
||||
ref = pmb.helpers.git.rev_parse(args, repo_path,
|
||||
extra_args=["--abbrev-ref"])
|
||||
if ref == "HEAD":
|
||||
ref = pmb.helpers.git.rev_parse(args, repo_path)[0:8]
|
||||
if ref == "master":
|
||||
return "edge"
|
||||
return ref
|
||||
|
||||
|
||||
def list_uis(args, codename: str) -> list[tuple[str, str]]:
|
||||
info = pmb.parse.deviceinfo(args, codename)
|
||||
ui_list = pmb.helpers.ui.list(args, info["arch"])
|
||||
return ui_list
|
|
@ -0,0 +1,68 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
// cutom package, registered in python code
|
||||
import Pmb 1.0 as Pmb
|
||||
|
||||
ApplicationWindow {
|
||||
id: appWindow
|
||||
visible: true
|
||||
width: 400
|
||||
height: contentCol.height + 20
|
||||
title: qsTr("pmbootstrap sudo askpass")
|
||||
|
||||
Pmb.Askpass {
|
||||
id: askpass
|
||||
}
|
||||
|
||||
Item {
|
||||
id: content
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
|
||||
Column {
|
||||
id: contentCol
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
|
||||
width: parent.width
|
||||
spacing: 5
|
||||
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
Label {
|
||||
height: input.height
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: askpass.prompt !== "" ? askpass.prompt : qsTr("Enter password: ")
|
||||
}
|
||||
|
||||
Item { height: 10; width: 10; } // spacer
|
||||
|
||||
TextField {
|
||||
id: input
|
||||
echoMode: TextInput.Password
|
||||
placeholderText: "*****"
|
||||
onAccepted: returnOk()
|
||||
}
|
||||
}
|
||||
|
||||
DialogButtonBox {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
|
||||
|
||||
onAccepted: returnOk()
|
||||
onRejected: {
|
||||
askpass.set_pass('')
|
||||
appWindow.close()
|
||||
}
|
||||
}
|
||||
} // Column
|
||||
}
|
||||
|
||||
function returnOk() {
|
||||
askpass.set_pass(input.text)
|
||||
appWindow.close()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
// cutom package, registered in python code
|
||||
import Pmb 1.0 as Pmb
|
||||
|
||||
ApplicationWindow {
|
||||
id: appWindow
|
||||
visible: true
|
||||
width: 800
|
||||
height: 534
|
||||
title: qsTr("pmbootstrap GUI")
|
||||
|
||||
header: Item {
|
||||
height: bgImg.height
|
||||
|
||||
Rectangle {
|
||||
// white background
|
||||
color: "white"
|
||||
height: bgImg.height
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
Image {
|
||||
id: bgImg
|
||||
cache: true
|
||||
source: "../img/header.jpg"
|
||||
height: 111
|
||||
width: 1076
|
||||
}
|
||||
}
|
||||
|
||||
Pmb.Devices {
|
||||
id: pmbDevices
|
||||
}
|
||||
|
||||
Item {
|
||||
id: content
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
|
||||
Column {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
width: parent.width
|
||||
|
||||
Row {
|
||||
height: cbChannels.height
|
||||
Label {
|
||||
height: cbChannels.height
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: qsTr("Select postmarketOS release: ")
|
||||
}
|
||||
ComboBox {
|
||||
id: cbChannels
|
||||
model: pmbDevices.channels
|
||||
currentIndex: pmbDevices.current_channel
|
||||
onActivated: {
|
||||
pmbDevices.set_channel(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
height: cbVendors.height
|
||||
Label {
|
||||
height: cbVendors.height
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: qsTr("Select manufacturer: ")
|
||||
}
|
||||
ComboBox {
|
||||
id: cbVendors
|
||||
model: pmbDevices.vendors
|
||||
onActivated: {
|
||||
pmbDevices.select_vendor(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
height: cbDevices.height
|
||||
Label {
|
||||
height: cbDevices.height
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: qsTr("Select device: ")
|
||||
}
|
||||
ComboBox {
|
||||
id: cbDevices
|
||||
model: pmbDevices.vendor_devices
|
||||
onActivated: {
|
||||
pmbDevices.select_device(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
height: cbDeviceKernels.height
|
||||
Label {
|
||||
height: cbDeviceKernels.height
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: qsTr("Select kernel: ")
|
||||
}
|
||||
ComboBox {
|
||||
id: cbDeviceKernels
|
||||
model: pmbDevices.device_kernels
|
||||
onActivated: {
|
||||
pmbDevices.select_kernel(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
height: cbUis.height
|
||||
Label {
|
||||
height: cbUis.height
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: qsTr("Select UI: ")
|
||||
}
|
||||
ComboBox {
|
||||
id: cbUis
|
||||
model: pmbDevices.uis_list
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Component.onCompleted: {
|
||||
//console.log("vendors: ", pmbdevices.vendors)
|
||||
//}
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
# Copyright 2021 Alexey Minnekhanov
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PyQt5.QtCore import PYQT_VERSION, QObject, pyqtSignal, pyqtSlot,\
|
||||
pyqtProperty, QStringListModel
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtQml import QQmlApplicationEngine, QQmlListProperty,\
|
||||
qmlRegisterType
|
||||
from PyQt5.QtQuick import QQuickWindow
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
# since 5.11 PyQt uses internal sip module
|
||||
if PYQT_VERSION >= 0x050B00: # 5.11.0 == 0x5, 0xB, 0x00
|
||||
from PyQt5 import sip
|
||||
else:
|
||||
import sip
|
||||
|
||||
import pmb.gui.pmb_api
|
||||
|
||||
_args = None
|
||||
|
||||
|
||||
class PmbDevices(QObject):
|
||||
vendors_changed = pyqtSignal()
|
||||
channels_changed = pyqtSignal()
|
||||
current_channel_changed = pyqtSignal()
|
||||
devices_changed = pyqtSignal()
|
||||
device_kernels_changed = pyqtSignal()
|
||||
uis_changed = pyqtSignal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._vendors = sorted(list(pmb.gui.pmb_api.list_vendors(_args)))
|
||||
self._channels = pmb.gui.pmb_api.get_channels_config(_args)
|
||||
self._channel_names = []
|
||||
for ch in self._channels:
|
||||
self._channel_names.append(ch)
|
||||
self._selected_vendor = None
|
||||
if len(self._vendors) > 0:
|
||||
self._selected_vendor = 0
|
||||
self._avail_codenames = []
|
||||
self._selected_codename = None
|
||||
self._avail_kernels = None
|
||||
self._sel_kernel = None
|
||||
|
||||
@pyqtProperty(list, notify=vendors_changed)
|
||||
def vendors(self) -> list[str]:
|
||||
return self._vendors
|
||||
|
||||
@pyqtProperty(list, notify=channels_changed)
|
||||
def channels(self) -> list[str]:
|
||||
ret = [ch + " " + self._channels[ch]['description'] for ch in self._channels.keys()]
|
||||
return ret
|
||||
|
||||
@pyqtProperty(int, notify=current_channel_changed)
|
||||
def current_channel(self) -> int:
|
||||
cname = pmb.gui.pmb_api.get_current_channel(_args)
|
||||
if cname in self._channel_names:
|
||||
return self._channel_names.index(cname)
|
||||
return -1
|
||||
|
||||
@pyqtSlot(int)
|
||||
def set_channel(self, idx: int) -> None:
|
||||
pmb.gui.pmb_api.switch_to_channel(_args, self._channel_names[idx])
|
||||
# reload vendors list
|
||||
self._vendors = sorted(list(pmb.gui.pmb_api.list_vendors(_args)))
|
||||
self.vendors_changed.emit()
|
||||
self._selected_vendor = 0
|
||||
self.devices_changed.emit()
|
||||
self._selected_codename = 0
|
||||
self.uis_changed.emit()
|
||||
self._avail_kernels = None
|
||||
self.device_kernels_changed.emit()
|
||||
|
||||
@pyqtProperty(list, notify=devices_changed)
|
||||
def vendor_devices(self) -> list[str]:
|
||||
if self._selected_vendor is None:
|
||||
return list()
|
||||
self._avail_codenames = pmb.gui.pmb_api.list_vendor_codenames(
|
||||
_args, self._vendors[self._selected_vendor])
|
||||
return self._avail_codenames
|
||||
|
||||
@pyqtSlot(int)
|
||||
def select_vendor(self, vidx: int) -> None:
|
||||
self._selected_vendor = vidx
|
||||
self.devices_changed.emit()
|
||||
self._selected_codename = None
|
||||
self.uis_changed.emit()
|
||||
self._avail_kernels = None
|
||||
self.device_kernels_changed.emit()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def select_device(self, idx: int) -> None:
|
||||
self._selected_codename = self._avail_codenames[idx]
|
||||
self.uis_changed.emit()
|
||||
self._avail_kernels = None
|
||||
self.device_kernels_changed.emit()
|
||||
|
||||
@pyqtProperty(list, notify=device_kernels_changed)
|
||||
def device_kernels(self) -> list[str]:
|
||||
ret = []
|
||||
if self._selected_codename:
|
||||
self._avail_kernels = pmb.gui.pmb_api.list_device_kernels(
|
||||
_args, self._selected_codename)
|
||||
self._sel_kernel = 0
|
||||
if self._avail_kernels:
|
||||
for kernel in self._avail_kernels.keys():
|
||||
ret.append(f"{kernel} ({self._avail_kernels[kernel]})")
|
||||
return ret
|
||||
|
||||
@pyqtSlot(int)
|
||||
def select_kernel(self, idx: int) -> None:
|
||||
self._sel_kernel = idx
|
||||
|
||||
@pyqtProperty(list, notify=uis_changed)
|
||||
def uis_list(self) -> list[str]:
|
||||
ret = []
|
||||
if self._selected_codename:
|
||||
uis = pmb.gui.pmb_api.list_uis(_args, self._selected_codename)
|
||||
else:
|
||||
uis = pmb.gui.pmb_api.list_uis(_args, "qemu-amd64")
|
||||
for tup in uis:
|
||||
ret.append(f"{tup[0]} ({tup[1]})")
|
||||
return ret
|
||||
|
||||
|
||||
def configure_sudo_askpass_program(args):
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
# Launch our own GUI askpass handler by default
|
||||
askpass_program = f"{script_dir}/askpass.py"
|
||||
if "SSH_ASKPASS" in os.environ:
|
||||
askpass_program = os.environ["SSH_ASKPASS"]
|
||||
logging.debug(f"autodetected SSH_ASKPASS program: {askpass_program}")
|
||||
_args.sudo_askpass_program = askpass_program
|
||||
|
||||
|
||||
def start(args):
|
||||
global _args
|
||||
_args = args
|
||||
|
||||
configure_sudo_askpass_program(args)
|
||||
|
||||
# We will need this, directory which contains this script
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
sip.setdestroyonexit(False)
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
engine = QQmlApplicationEngine()
|
||||
|
||||
qmlRegisterType(PmbDevices, 'Pmb', 1, 0, 'Devices')
|
||||
|
||||
engine.load(f"{script_dir}/qml/main.qml")
|
||||
if len(engine.rootObjects()) < 1:
|
||||
logging.error("Failed to load QML!")
|
||||
return 1
|
||||
|
||||
root_objects = engine.rootObjects() # type: list[QObject]
|
||||
# QML's ApplicationWindow instantiates QQuickWindow
|
||||
app_window = root_objects[0] # type: QQuickWindow
|
||||
app_window.setIcon(QIcon(f"{script_dir}/img/pmos-logo.svg"))
|
||||
|
||||
engine.quit.connect(app.quit)
|
||||
retval = app.exec_()
|
||||
logging.debug(f"Qt5: exiting with code {retval}")
|
||||
return retval
|
|
@ -16,6 +16,7 @@ import pmb.chroot.other
|
|||
import pmb.config
|
||||
import pmb.export
|
||||
import pmb.flasher
|
||||
import pmb.gui
|
||||
import pmb.helpers.devices
|
||||
import pmb.helpers.git
|
||||
import pmb.helpers.lint
|
||||
|
@ -598,3 +599,7 @@ def lint(args):
|
|||
def status(args):
|
||||
if not pmb.helpers.status.print_status(args, args.details):
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def gui(args):
|
||||
pmb.gui.run_gui(args)
|
||||
|
|
|
@ -72,7 +72,18 @@ def root(args, cmd, working_dir=None, output="log", output_return=False,
|
|||
"""
|
||||
if env:
|
||||
cmd = ["sh", "-c", flat_cmd(cmd, env=env)]
|
||||
cmd = ["sudo"] + cmd
|
||||
|
||||
# fom `man sudo`:
|
||||
# If the -A (askpass) option
|
||||
# is specified, a (possibly graphical) helper program is executed to
|
||||
# read the user's password and output the password to the standard output.
|
||||
# If the SUDO_ASKPASS environment variable is set, it specifies the path
|
||||
# to the helper program.
|
||||
if "sudo_askpass_program" in args:
|
||||
cmd = ["env", f"SUDO_ASKPASS={args.sudo_askpass_program}",
|
||||
"sudo", "--askpass"] + cmd
|
||||
else:
|
||||
cmd = ["sudo"] + cmd
|
||||
|
||||
return user(args, cmd, working_dir, output, output_return, check, env,
|
||||
True)
|
||||
|
|
|
@ -490,6 +490,15 @@ def arguments_status(subparser):
|
|||
return ret
|
||||
|
||||
|
||||
def arguments_gui(subparser):
|
||||
ret = subparser.add_parser("gui", help="Run pmbootstrap with GUI")
|
||||
ret.add_argument("--qt5", action="store_true",
|
||||
help="Force Qt-based GUI")
|
||||
ret.add_argument("--gtk", action="store_true",
|
||||
help="Force Gtk-based GUI")
|
||||
return ret
|
||||
|
||||
|
||||
def package_completer(prefix, action, parser, parsed_args):
|
||||
args = parsed_args
|
||||
pmb.config.merge_with_args(args)
|
||||
|
@ -618,6 +627,7 @@ def arguments():
|
|||
arguments_newapkbuild(sub)
|
||||
arguments_lint(sub)
|
||||
arguments_status(sub)
|
||||
arguments_gui(sub)
|
||||
|
||||
# Action: log
|
||||
log = sub.add_parser("log", help="follow the pmbootstrap logfile")
|
||||
|
|
Loading…
Reference in New Issue