summaryrefslogtreecommitdiff
path: root/crates/flasher-qt/src
diff options
context:
space:
mode:
authorAndrew Guschin <guschin@altlinux.org>2026-08-03 17:52:58 +0400
committerAndrew Guschin <guschin@altlinux.org>2026-08-03 17:52:58 +0400
commit1da6636ef354b77857a57059e47c3801304361cf (patch)
treea8f07c59cd01ff275283d35dacc452c3730fe4a7 /crates/flasher-qt/src
parent04c1d3d8751ae55b0dc963fa32359875f58a3fe1 (diff)
remake ui with kirigamimain
Diffstat (limited to 'crates/flasher-qt/src')
-rw-r--r--crates/flasher-qt/src/main.rs59
-rw-r--r--crates/flasher-qt/src/ui.cpp404
2 files changed, 22 insertions, 441 deletions
diff --git a/crates/flasher-qt/src/main.rs b/crates/flasher-qt/src/main.rs
index e304418..243406b 100644
--- a/crates/flasher-qt/src/main.rs
+++ b/crates/flasher-qt/src/main.rs
@@ -1,43 +1,30 @@
-use std::{
- env,
- ffi::CString,
- fs,
- os::raw::{c_char, c_int},
-};
+use cxx_qt_lib::{QByteArray, QGuiApplication, QQmlApplicationEngine, QString, QUrl};
+use std::fs;
const BOARDS: &str = include_str!("../../../alt-rootfs-installer/SUPPORTED-BOARDS");
-
-unsafe extern "C" {
- fn run_qt_app(
- argc: c_int,
- argv: *mut *mut c_char,
- boards: *const c_char,
- drives: *const c_char,
- ) -> c_int;
-}
+const QML: &str = include_str!("../qml/Main.qml");
fn main() {
- let arguments: Vec<CString> = env::args()
- .map(|arg| CString::new(arg).expect("application argument contains a NUL byte"))
- .collect();
- let mut argument_pointers: Vec<*mut c_char> = arguments
- .iter()
- .map(|arg| arg.as_ptr().cast_mut())
- .collect();
- let boards = CString::new(BOARDS).expect("board list contains a NUL byte");
- let drives = CString::new(available_drives().join("\n"))
- .expect("drive description contains a NUL byte");
+ let mut app = QGuiApplication::new();
+ app.pin_mut()
+ .set_application_name(&QString::from("ALT Image Writer"));
+ app.pin_mut()
+ .set_organization_name(&QString::from("ALT Linux"));
+ app.pin_mut()
+ .set_application_version(&QString::from(env!("CARGO_PKG_VERSION")));
- // QApplication only borrows argv and the two strings for the duration of
- // this call; all backing CStrings remain alive until the event loop exits.
- unsafe {
- run_qt_app(
- argument_pointers.len() as c_int,
- argument_pointers.as_mut_ptr(),
- boards.as_ptr(),
- drives.as_ptr(),
- );
- }
+ let boards = serde_json::to_string(BOARDS).expect("serialize supported boards");
+ let drives = serde_json::to_string(&available_drives().join("\n"))
+ .expect("serialize available drives");
+ let qml = QML
+ .replace("@SUPPORTED_BOARDS@", &boards)
+ .replace("@AVAILABLE_DRIVES@", &drives);
+
+ let mut engine = QQmlApplicationEngine::new();
+ engine
+ .pin_mut()
+ .load_data(&QByteArray::from(qml.as_str()), &QUrl::default());
+ app.pin_mut().exec();
}
fn available_drives() -> Vec<String> {
@@ -45,7 +32,6 @@ fn available_drives() -> Vec<String> {
let Ok(entries) = fs::read_dir("/sys/block") else {
return drives;
};
-
for entry in entries.flatten() {
let name = entry.file_name().to_string_lossy().into_owned();
if name.starts_with("loop") || name.starts_with("ram") || name.starts_with("zram") {
@@ -57,7 +43,6 @@ fn available_drives() -> Vec<String> {
if !removable && !name.starts_with("mmcblk") {
continue;
}
-
let sectors = fs::read_to_string(entry.path().join("size"))
.ok()
.and_then(|value| value.trim().parse::<u64>().ok())
diff --git a/crates/flasher-qt/src/ui.cpp b/crates/flasher-qt/src/ui.cpp
deleted file mode 100644
index 0af1987..0000000
--- a/crates/flasher-qt/src/ui.cpp
+++ /dev/null
@@ -1,404 +0,0 @@
-#include <QApplication>
-#include <QButtonGroup>
-#include <QCheckBox>
-#include <QComboBox>
-#include <QDialog>
-#include <QDialogButtonBox>
-#include <QFileDialog>
-#include <QFormLayout>
-#include <QFrame>
-#include <QGroupBox>
-#include <QHBoxLayout>
-#include <QLabel>
-#include <QLineEdit>
-#include <QMainWindow>
-#include <QMenuBar>
-#include <QMessageBox>
-#include <QPushButton>
-#include <QRadioButton>
-#include <QScrollArea>
-#include <QStatusBar>
-#include <QStyle>
-#include <QTreeWidget>
-#include <QVBoxLayout>
-
-namespace {
-
-struct Board {
- QString name;
- QString platform;
-};
-
-QVector<Board> parseBoards(const QString &text) {
- QVector<Board> boards;
- QString platform = QStringLiteral("Other");
- for (const QString &rawLine : text.split('\n')) {
- const QString line = rawLine.trimmed();
- if (line.isEmpty())
- continue;
- if (line.endsWith(':')) {
- platform = line.left(line.size() - 1);
- continue;
- }
- for (const QString &name : line.split(QRegExp(QStringLiteral("\\s+")), Qt::SkipEmptyParts))
- boards.push_back({name, platform});
- }
- return boards;
-}
-
-QLabel *description(const QString &text) {
- auto *label = new QLabel(text);
- label->setWordWrap(true);
- label->setForegroundRole(QPalette::Mid);
- return label;
-}
-
-QGroupBox *group(const QString &title, QLayout *layout) {
- auto *box = new QGroupBox(title);
- box->setLayout(layout);
- return box;
-}
-
-class BoardDialog final : public QDialog {
-public:
- explicit BoardDialog(const QVector<Board> &boards, QWidget *parent = nullptr)
- : QDialog(parent), boards_(boards) {
- setWindowTitle(tr("Select Target Board"));
- resize(620, 620);
- setModal(true);
-
- auto *layout = new QVBoxLayout(this);
- search_ = new QLineEdit;
- search_->setPlaceholderText(tr("Search boards, platforms, or architectures…"));
- search_->setClearButtonEnabled(true);
- tree_ = new QTreeWidget;
- tree_->setHeaderHidden(true);
- tree_->setRootIsDecorated(true);
- tree_->setAlternatingRowColors(true);
- tree_->setUniformRowHeights(true);
- layout->addWidget(search_);
- layout->addWidget(tree_, 1);
-
- auto *buttons = new QDialogButtonBox(QDialogButtonBox::Cancel);
- layout->addWidget(buttons);
- connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
- connect(search_, &QLineEdit::textChanged, this, [this](const QString &text) { populate(text); });
- connect(tree_, &QTreeWidget::itemActivated, this, [this](QTreeWidgetItem *item) {
- if (item && item->parent()) {
- selectedName_ = item->data(0, Qt::UserRole).toString();
- selectedPlatform_ = item->parent()->text(0);
- accept();
- }
- });
- populate({});
- }
-
- QString selectedName() const { return selectedName_; }
- QString selectedPlatform() const { return selectedPlatform_; }
-
-private:
- void populate(const QString &query) {
- tree_->clear();
- QMap<QString, QTreeWidgetItem *> headings;
- const QString needle = query.trimmed();
- for (const Board &board : boards_) {
- if (!needle.isEmpty() && !board.name.contains(needle, Qt::CaseInsensitive)
- && !board.platform.contains(needle, Qt::CaseInsensitive))
- continue;
- auto *&heading = headings[board.platform];
- if (!heading) {
- heading = new QTreeWidgetItem(tree_, {board.platform});
- QFont font = heading->font(0);
- font.setBold(true);
- heading->setFont(0, font);
- heading->setFlags(heading->flags() & ~Qt::ItemIsSelectable);
- }
- auto *item = new QTreeWidgetItem(heading, {board.name});
- item->setData(0, Qt::UserRole, board.name);
- }
- tree_->expandAll();
- if (!query.isEmpty() && tree_->topLevelItemCount() > 0) {
- auto *heading = tree_->topLevelItem(0);
- if (heading->childCount() > 0)
- tree_->setCurrentItem(heading->child(0));
- }
- }
-
- QVector<Board> boards_;
- QLineEdit *search_ = nullptr;
- QTreeWidget *tree_ = nullptr;
- QString selectedName_;
- QString selectedPlatform_;
-};
-
-class MainWindow final : public QMainWindow {
-public:
- MainWindow(const QString &boardsText, const QString &drivesText)
- : boards_(parseBoards(boardsText)) {
- setWindowTitle(tr("ALT Image Writer"));
- resize(780, 820);
- setMinimumSize(620, 600);
-
- auto *about = menuBar()->addMenu(tr("&Help"))->addAction(tr("&About"));
- connect(about, &QAction::triggered, this, [this] {
- QMessageBox::about(this, tr("About ALT Image Writer"),
- tr("<h3>ALT Image Writer</h3><p>Version 0.1.0</p>"
- "<p>Prepare ALT Linux installation media for supported "
- "single-board computers.</p>"));
- });
-
- auto *scroll = new QScrollArea;
- scroll->setWidgetResizable(true);
- scroll->setFrameShape(QFrame::NoFrame);
- setCentralWidget(scroll);
-
- auto *viewport = new QWidget;
- auto *outer = new QHBoxLayout(viewport);
- outer->setContentsMargins(20, 20, 20, 20);
- auto *content = new QWidget;
- content->setMaximumWidth(700);
- auto *layout = new QVBoxLayout(content);
- layout->setSpacing(16);
- outer->addStretch();
- outer->addWidget(content, 1);
- outer->addStretch();
- scroll->setWidget(viewport);
-
- auto *title = new QLabel(tr("Create Installation Media"));
- QFont titleFont = title->font();
- titleFont.setPointSize(titleFont.pointSize() + 7);
- titleFont.setBold(true);
- title->setFont(titleFont);
- title->setAlignment(Qt::AlignCenter);
- layout->addWidget(title);
- auto *intro = description(tr("Prepare an ALT Linux root filesystem or disk image for your board."));
- intro->setAlignment(Qt::AlignCenter);
- layout->addWidget(intro);
-
- createSource(layout);
- createHardware(layout);
- createDestination(layout, drivesText);
- createOptions(layout);
- layout->addStretch();
-
- auto *bar = new QWidget;
- auto *barLayout = new QHBoxLayout(bar);
- barLayout->setContentsMargins(12, 8, 12, 8);
- barLayout->addWidget(description(tr("Nothing will be written until you confirm.")), 1);
- writeButton_ = new QPushButton(tr("Review and Write"));
- writeButton_->setDefault(true);
- writeButton_->setEnabled(false);
- barLayout->addWidget(writeButton_);
- statusBar()->addPermanentWidget(bar, 1);
- connect(writeButton_, &QPushButton::clicked, this, [this] { review(); });
- }
-
-private:
- void createSource(QVBoxLayout *page) {
- auto *layout = new QVBoxLayout;
- layout->addWidget(description(tr("Choose a root filesystem archive or a ready-made disk image.")));
- auto *types = new QHBoxLayout;
- types->addWidget(new QLabel(tr("Source type")));
- types->addStretch();
- rootfsButton_ = new QRadioButton(tr("Root filesystem"));
- imageButton_ = new QRadioButton(tr("Disk image"));
- rootfsButton_->setChecked(true);
- types->addWidget(rootfsButton_);
- types->addWidget(imageButton_);
- layout->addLayout(types);
-
- auto *picker = new QHBoxLayout;
- sourceLabel_ = new QLabel(tr("No source selected"));
- sourceLabel_->setTextInteractionFlags(Qt::TextSelectableByMouse);
- auto *choose = new QPushButton(tr("Choose…"));
- choose->setIcon(style()->standardIcon(QStyle::SP_DialogOpenButton));
- picker->addWidget(sourceLabel_, 1);
- picker->addWidget(choose);
- layout->addLayout(picker);
- sourceHint_ = description(tr("Supported: .tar, .tar.gz, .tar.xz"));
- layout->addWidget(sourceHint_);
- page->addWidget(group(tr("Installation Source"), layout));
-
- connect(rootfsButton_, &QRadioButton::toggled, this, [this](bool checked) {
- if (checked) {
- sourceHint_->setText(tr("Supported: .tar, .tar.gz, .tar.xz"));
- fileOutputButton_->setEnabled(true);
- }
- });
- connect(imageButton_, &QRadioButton::toggled, this, [this](bool checked) {
- if (checked) {
- sourceHint_->setText(tr("Supported: .img, .img.xz"));
- driveOutputButton_->setChecked(true);
- fileOutputButton_->setEnabled(false);
- }
- });
- connect(choose, &QPushButton::clicked, this, [this] {
- const QString filter = imageButton_->isChecked()
- ? tr("Disk images (*.img *.img.xz);;All files (*)")
- : tr("Root filesystem archives (*.tar *.tar.gz *.tar.xz);;All files (*)");
- const QString path = QFileDialog::getOpenFileName(this, tr("Choose Installation Source"), {}, filter);
- if (!path.isEmpty()) {
- sourcePath_ = path;
- sourceLabel_->setText(QFileInfo(path).fileName());
- sourceLabel_->setToolTip(path);
- sourceHint_->setText(QFileInfo(path).absolutePath());
- updateReady();
- }
- });
- }
-
- void createHardware(QVBoxLayout *page) {
- auto *layout = new QVBoxLayout;
- layout->addWidget(description(tr("Boards are grouped by platform, SoC family, and architecture.")));
- auto *row = new QHBoxLayout;
- auto *labels = new QVBoxLayout;
- boardLabel_ = new QLabel(tr("No board selected"));
- boardHint_ = description(tr("Choose from %1 supported boards").arg(boards_.size()));
- labels->addWidget(boardLabel_);
- labels->addWidget(boardHint_);
- auto *select = new QPushButton(tr("Select Board…"));
- row->addLayout(labels, 1);
- row->addWidget(select);
- layout->addLayout(row);
- page->addWidget(group(tr("Target Hardware"), layout));
-
- connect(select, &QPushButton::clicked, this, [this] {
- BoardDialog dialog(boards_, this);
- if (dialog.exec() == QDialog::Accepted) {
- selectedBoard_ = dialog.selectedName();
- selectedPlatform_ = dialog.selectedPlatform();
- boardLabel_->setText(selectedBoard_);
- QFont font = boardLabel_->font();
- font.setBold(true);
- boardLabel_->setFont(font);
- boardHint_->setText(selectedPlatform_);
- updateReady();
- }
- });
- }
-
- void createDestination(QVBoxLayout *page, const QString &drivesText) {
- auto *layout = new QVBoxLayout;
- layout->addWidget(description(tr("Write to removable media or create an image file.")));
- auto *types = new QHBoxLayout;
- types->addWidget(new QLabel(tr("Output type")));
- types->addStretch();
- driveOutputButton_ = new QRadioButton(tr("Drive"));
- fileOutputButton_ = new QRadioButton(tr("Image file"));
- driveOutputButton_->setChecked(true);
- types->addWidget(driveOutputButton_);
- types->addWidget(fileOutputButton_);
- layout->addLayout(types);
-
- driveRow_ = new QWidget;
- auto *driveLayout = new QFormLayout(driveRow_);
- driveBox_ = new QComboBox;
- const QStringList drives = drivesText.split('\n', Qt::SkipEmptyParts);
- driveBox_->addItems(drives);
- if (drives.isEmpty()) {
- driveBox_->addItem(tr("No removable drives detected"));
- driveBox_->setEnabled(false);
- }
- driveLayout->addRow(tr("Storage device"), driveBox_);
- layout->addWidget(driveRow_);
-
- fileRow_ = new QWidget;
- auto *fileLayout = new QHBoxLayout(fileRow_);
- fileLayout->setContentsMargins(0, 0, 0, 0);
- outputLabel_ = new QLabel(tr("Choose where to save the image"));
- auto *choose = new QPushButton(tr("Choose…"));
- choose->setIcon(style()->standardIcon(QStyle::SP_DialogSaveButton));
- fileLayout->addWidget(outputLabel_, 1);
- fileLayout->addWidget(choose);
- fileRow_->hide();
- layout->addWidget(fileRow_);
- page->addWidget(group(tr("Destination"), layout));
-
- connect(driveOutputButton_, &QRadioButton::toggled, this, [this](bool checked) {
- driveRow_->setVisible(checked);
- fileRow_->setVisible(!checked);
- updateReady();
- });
- connect(choose, &QPushButton::clicked, this, [this] {
- const QString path = QFileDialog::getSaveFileName(this, tr("Create Disk Image"),
- QStringLiteral("alt-linux.img"), tr("Raw disk images (*.img);;All files (*)"));
- if (!path.isEmpty()) {
- outputPath_ = path;
- outputLabel_->setText(QFileInfo(path).fileName());
- outputLabel_->setToolTip(path);
- updateReady();
- }
- });
- }
-
- void createOptions(QVBoxLayout *page) {
- auto *layout = new QFormLayout;
- auto *filesystem = new QComboBox;
- filesystem->addItems({QStringLiteral("ext4"), QStringLiteral("f2fs")});
- layout->addRow(tr("Root filesystem"), filesystem);
- auto *resize = new QCheckBox(tr("Grow to fill the destination"));
- resize->setChecked(true);
- layout->addRow(tr("Resize root partition"), resize);
- layout->addRow(tr("Boot partition"), new QCheckBox(tr("Create a separate 512 MiB partition")));
- layout->addRow(tr("Encryption"), new QCheckBox(tr("Encrypt root partition with LUKS")));
- layout->addRow(tr("Serial console"), new QCheckBox(tr("Add to boot arguments")));
- auto *efi = new QComboBox;
- efi->addItems({tr("None"), tr("EFI (GPT)"), tr("EFI (MBR)")});
- layout->addRow(tr("EFI system partition"), efi);
- auto *vnc = new QComboBox;
- vnc->addItems({tr("Use image default"), tr("Enabled"), tr("Disabled")});
- layout->addRow(tr("First-boot VNC"), vnc);
- page->addWidget(group(tr("Options"), layout));
- }
-
- void updateReady() {
- const bool destination = driveOutputButton_->isChecked()
- ? driveBox_->isEnabled() && driveBox_->currentIndex() >= 0
- : !outputPath_.isEmpty();
- writeButton_->setEnabled(!sourcePath_.isEmpty() && !selectedBoard_.isEmpty() && destination);
- }
-
- void review() {
- const QString destination = driveOutputButton_->isChecked() ? driveBox_->currentText() : outputPath_;
- QMessageBox dialog(QMessageBox::Warning, tr("Ready to write?"),
- tr("Board: %1\nPlatform: %2\nDestination: %3\n\n"
- "This is an interface preview. No data will be written.")
- .arg(selectedBoard_, selectedPlatform_, destination),
- QMessageBox::Cancel | QMessageBox::Ok, this);
- dialog.setDefaultButton(QMessageBox::Cancel);
- dialog.button(QMessageBox::Ok)->setText(tr("Write"));
- if (dialog.exec() == QMessageBox::Ok)
- statusBar()->showMessage(tr("Flashing is not implemented in this preview"), 4000);
- }
-
- QVector<Board> boards_;
- QString sourcePath_;
- QString outputPath_;
- QString selectedBoard_;
- QString selectedPlatform_;
- QRadioButton *rootfsButton_ = nullptr;
- QRadioButton *imageButton_ = nullptr;
- QRadioButton *driveOutputButton_ = nullptr;
- QRadioButton *fileOutputButton_ = nullptr;
- QLabel *sourceLabel_ = nullptr;
- QLabel *sourceHint_ = nullptr;
- QLabel *boardLabel_ = nullptr;
- QLabel *boardHint_ = nullptr;
- QLabel *outputLabel_ = nullptr;
- QWidget *driveRow_ = nullptr;
- QWidget *fileRow_ = nullptr;
- QComboBox *driveBox_ = nullptr;
- QPushButton *writeButton_ = nullptr;
-};
-
-} // namespace
-
-extern "C" int run_qt_app(int argc, char **argv, const char *boards, const char *drives) {
- QApplication app(argc, argv);
- QCoreApplication::setApplicationName(QStringLiteral("ALT Image Writer"));
- QCoreApplication::setOrganizationName(QStringLiteral("ALT Linux"));
- QApplication::setWindowIcon(QApplication::style()->standardIcon(QStyle::SP_DriveHDIcon));
- MainWindow window(QString::fromUtf8(boards), QString::fromUtf8(drives));
- window.show();
- return app.exec();
-}