164 lines
5.9 KiB
Python
164 lines
5.9 KiB
Python
"""El Torito boot image configuration dialog."""
|
|
|
|
# This file is part of ISO Scalpel.
|
|
# Copyright (C) 2025 Jeremy Anderson <info@dcos.net>
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, see <https://www.gnu.org/licenses/>
|
|
# or write to the Free Software Foundation, Inc., 51 Franklin Street,
|
|
# Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
from __future__ import annotations
|
|
|
|
from PySide6.QtWidgets import (
|
|
QCheckBox,
|
|
QComboBox,
|
|
QDialog,
|
|
QDialogButtonBox,
|
|
QFileDialog,
|
|
QFormLayout,
|
|
QGroupBox,
|
|
QHBoxLayout,
|
|
QLabel,
|
|
QLineEdit,
|
|
QMessageBox,
|
|
QPushButton,
|
|
QSpinBox,
|
|
QVBoxLayout,
|
|
QWidget,
|
|
)
|
|
|
|
from ..iso_handler import BootInfo, IsoHandler
|
|
|
|
|
|
class BootDialog(QDialog):
|
|
"""Configure or clear an El Torito boot record."""
|
|
|
|
def __init__(self, handler: IsoHandler, parent=None):
|
|
super().__init__(parent)
|
|
self.setWindowTitle("Boot Image (El Torito)")
|
|
self.setMinimumWidth(480)
|
|
self._handler = handler
|
|
info = handler.get_boot_info()
|
|
|
|
grp = QGroupBox("Boot Configuration")
|
|
f = QFormLayout(grp)
|
|
|
|
self.bootable = QCheckBox("Bootable")
|
|
self.bootable.setChecked(info.bootable)
|
|
f.addRow(self.bootable)
|
|
|
|
self.boot_file = QLineEdit(info.boot_image_path)
|
|
self.boot_file.setPlaceholderText("Path inside ISO, e.g. /BOOT.IMG;1")
|
|
browse = QPushButton("Browse host file…")
|
|
browse.clicked.connect(self._browse)
|
|
row = QHBoxLayout()
|
|
row.addWidget(self.boot_file, 1)
|
|
row.addWidget(browse)
|
|
w = QWidget()
|
|
w.setLayout(row)
|
|
f.addRow("Boot image:", w)
|
|
self._local_file: str | None = None
|
|
|
|
self.platform = QComboBox()
|
|
self.platform.addItems(["0 — x86 (BIOS)", "1 — PowerPC", "2 — Mac", "0xEF — EFI"])
|
|
f.addRow("Platform ID:", self.platform)
|
|
if info.efi:
|
|
self.platform.setCurrentIndex(3)
|
|
|
|
self.media = QComboBox()
|
|
# pycdlib accepts: 'noemul', 'floppy', 'hdemul'. Floppy geometry is
|
|
# conveyed via boot_load_size, so we offer the common sizes as a hint.
|
|
self._media_items = [
|
|
("noemul (no emulation)", "noemul"),
|
|
("floppy 1.2 MiB", "floppy"),
|
|
("floppy 1.44 MiB", "floppy"),
|
|
("floppy 2.88 MiB", "floppy"),
|
|
("hdemul (hard disk)", "hdemul"),
|
|
]
|
|
for label, _val in self._media_items:
|
|
self.media.addItem(label)
|
|
cur = {"noemul": 0, "floppy": 1, "hdemul": 4}.get(info.media_name, 0)
|
|
self.media.setCurrentIndex(cur)
|
|
f.addRow("Media:", self.media)
|
|
|
|
self.load_seg = QSpinBox()
|
|
self.load_seg.setRange(0, 0xFFFF)
|
|
self.load_seg.setValue(info.load_segment or 0x07C0)
|
|
self.load_seg.setDisplayIntegerBase(16)
|
|
self.load_seg.setPrefix("0x")
|
|
f.addRow("Load segment:", self.load_seg)
|
|
|
|
self.load_size = QSpinBox()
|
|
self.load_size.setRange(0, 100000)
|
|
self.load_size.setSpecialValueText("auto (whole file)")
|
|
self.load_size.setValue(info.load_size or 0)
|
|
f.addRow("Load size (sectors):", self.load_size)
|
|
|
|
self.info_table = QCheckBox("Patch boot-info-table (common for ISOLINUX)")
|
|
self.info_table.setChecked(info.boot_info_table)
|
|
f.addRow(self.info_table)
|
|
|
|
# clear button
|
|
self.clear_btn = QPushButton("Remove boot record")
|
|
self.clear_btn.clicked.connect(self._clear)
|
|
|
|
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
|
buttons.accepted.connect(self._apply)
|
|
buttons.rejected.connect(self.reject)
|
|
|
|
layout = QVBoxLayout(self)
|
|
layout.addWidget(grp)
|
|
layout.addWidget(QLabel("Tip: the boot image file is added to the ISO automatically "
|
|
"when you select a host file."))
|
|
layout.addStretch(1)
|
|
bl = QHBoxLayout()
|
|
bl.addWidget(self.clear_btn)
|
|
bl.addStretch(1)
|
|
bl.addWidget(buttons)
|
|
layout.addLayout(bl)
|
|
|
|
# ------------------------------------------------------------------ handlers
|
|
def _browse(self) -> None:
|
|
path, _ = QFileDialog.getOpenFileName(self, "Select boot image")
|
|
if path:
|
|
self._local_file = path
|
|
self.boot_file.setText(path)
|
|
|
|
def _clear(self) -> None:
|
|
self._handler.clear_boot()
|
|
QMessageBox.information(self, "Boot", "Boot record removed. Save the image to persist.")
|
|
self.accept()
|
|
|
|
def _apply(self) -> None:
|
|
info = self._collect()
|
|
try:
|
|
self._handler.set_boot(info, boot_file_local=self._local_file)
|
|
except Exception as exc: # noqa: BLE001 -- user-facing error boundary
|
|
QMessageBox.critical(self, "Boot", f"Failed to set boot record:\n{exc}")
|
|
return
|
|
self.accept()
|
|
|
|
def _collect(self) -> BootInfo:
|
|
info = BootInfo()
|
|
info.bootable = self.bootable.isChecked()
|
|
info.boot_image_path = self.boot_file.text().strip()
|
|
info.platform_id = int(self.platform.currentText().split(" ", 1)[0], 0)
|
|
info.media_name = self._media_items[self.media.currentIndex()][1]
|
|
info.load_segment = self.load_seg.value()
|
|
info.load_size = self.load_size.value() or None
|
|
info.boot_info_table = self.info_table.isChecked()
|
|
info.efi = info.platform_id == 0xEF
|
|
info.boot_catalog_path = "BOOT.CAT;1"
|
|
return info
|