From 1da6636ef354b77857a57059e47c3801304361cf Mon Sep 17 00:00:00 2001 From: Andrew Guschin Date: Mon, 3 Aug 2026 17:52:58 +0400 Subject: remake ui with kirigami --- .cargo/config.toml | 4 + .gitignore | 2 + Cargo.lock | 424 +++++++++++++++++++- README.md | 7 +- crates/flasher-qt/Cargo.toml | 10 +- crates/flasher-qt/build.rs | 27 +- crates/flasher-qt/qml/Main.qml | 437 +++++++++++++++++++++ crates/flasher-qt/src/main.rs | 59 ++- crates/flasher-qt/src/ui.cpp | 404 ------------------- ...ssion-019f9999-ccdd-7234-912a-62e5b68a7e9d.html | 2 +- tools/qmake6 | 12 + 11 files changed, 914 insertions(+), 474 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 crates/flasher-qt/qml/Main.qml delete mode 100644 crates/flasher-qt/src/ui.cpp create mode 100755 tools/qmake6 diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..2c823cb --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,4 @@ +[env] +# CXX-Qt otherwise prefers Qt 5 when both major versions are installed. +QT_VERSION_MAJOR = "6" +QMAKE = { value = "tools/qmake6", relative = true } diff --git a/.gitignore b/.gitignore index a6067a9..7f3fa74 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .* !.gitignore !.gitattributes +!.cargo/ +!.cargo/config.toml /target diff --git a/Cargo.lock b/Cargo.lock index e4847cc..2b68b74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,18 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + [[package]] name = "autocfg" version = "1.5.1" @@ -44,6 +56,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -57,6 +71,222 @@ dependencies = [ "target-lexicon", ] +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clang-format" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "696283b40e1a39d208ee614b92e5f6521d16962edeb47c48372585ec92419943" +dependencies = [ + "thiserror", +] + +[[package]] +name = "clap" +version = "4.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301b56658598e48f3648647ac6fc887be7e7108eddfa4e9b63fcf3ec58c0cadf" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94a65403d1a1bd28f7dc68eb8506e8874808ee5eecb59298de588e2e1407a078" +dependencies = [ + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width 0.1.14", +] + +[[package]] +name = "codespan-reporting" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" +dependencies = [ + "serde", + "termcolor", + "unicode-width 0.2.2", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cxx" +version = "1.0.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe442a792c7c736eea18b32a7f8a3b63cf8aafabda6760042dc2fdeda456291" +dependencies = [ + "cc", + "cxx-build", + "cxxbridge-cmd", + "cxxbridge-flags", + "cxxbridge-macro", + "foldhash", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3184a94384c663718698311a78a51ac00c484c10b4eeac06fb0a068c5f64fa2" +dependencies = [ + "cc", + "codespan-reporting 0.13.1", + "indexmap", + "proc-macro2", + "quote", + "scratch", + "syn 3.0.3", +] + +[[package]] +name = "cxx-gen" +version = "0.7.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb0f24fd8cafa20f043e24372ca5d8273ab1fdce055ee977b989f82c1bcd89b5" +dependencies = [ + "codespan-reporting 0.13.1", + "indexmap", + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "cxx-qt" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d46ee3abd1539d109fdc009e3e3fc5a041c57aef5e3a80cba887338fc0748f" +dependencies = [ + "cxx", + "cxx-qt-build", + "cxx-qt-macro", + "qt-build-utils", + "static_assertions", + "thiserror", +] + +[[package]] +name = "cxx-qt-build" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57aded134d0a8d39ab6d6af639f5cd0f46affd2a6b3d9ae87848fd85113f143c" +dependencies = [ + "cc", + "codespan-reporting 0.11.1", + "cxx-gen", + "cxx-qt-gen", + "proc-macro2", + "qt-build-utils", + "quote", + "serde", + "serde_json", +] + +[[package]] +name = "cxx-qt-gen" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "735d282c0b9cb39db7e55846e7d8f57be6ec3c4edb6037bb04f50caf88a6ee6a" +dependencies = [ + "clang-format", + "convert_case", + "cxx-gen", + "indoc", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "cxx-qt-lib" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3483e43878378f0e4789033e9e93dfc04242b879389a1084766fc5d17f292b6b" +dependencies = [ + "cxx", + "cxx-qt", + "cxx-qt-build", + "qt-build-utils", +] + +[[package]] +name = "cxx-qt-macro" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb44925fc99e07859ee49bdc89ea519bb2052016154ffa4f4898c0cb4d86c81e" +dependencies = [ + "cxx-qt-gen", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "cxxbridge-cmd" +version = "1.0.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0148d8fd1199329ddf1d157a5e134e51ceff37c6a7ddd38615c399d81cb05d8d" +dependencies = [ + "clap", + "codespan-reporting 0.13.1", + "indexmap", + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52850339faed2eaadd24e286dc1d8268cc6f8a7bd9524d713adc9099566b4c89" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c77c856545d886c9bd5215409ebb63b925e262135248b50c79e5a5f194ee47c" +dependencies = [ + "indexmap", + "proc-macro2", + "quote", + "syn 3.0.3", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -91,10 +321,17 @@ dependencies = [ name = "flasher-qt" version = "0.1.0" dependencies = [ - "cc", - "pkg-config", + "cxx-qt-build", + "cxx-qt-lib", + "serde_json", ] +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "futures-channel" version = "0.3.33" @@ -214,6 +451,17 @@ dependencies = [ "system-deps", ] +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", +] + [[package]] name = "gio" version = "0.21.5" @@ -427,6 +675,31 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom", + "libc", +] + [[package]] name = "libadwaita" version = "0.8.1" @@ -464,6 +737,15 @@ version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +[[package]] +name = "link-cplusplus" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f78c730aaa7d0b9336a299029ea49f9ee53b0ed06e9202e8cb7db9bae7b8c82" +dependencies = [ + "cc", +] + [[package]] name = "memchr" version = "2.8.3" @@ -533,6 +815,20 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "qt-build-utils" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e575368c069f599b9918e592ce6eb6b4bc04d52b6825f7fcc89500f9e1de96b" +dependencies = [ + "anyhow", + "cc", + "semver", + "serde", + "thiserror", + "which", +] + [[package]] name = "quote" version = "1.0.47" @@ -542,6 +838,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rustc_version" version = "0.4.1" @@ -551,12 +853,34 @@ dependencies = [ "semver", ] +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "scratch" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d68f2ec51b097e4c1a75b681a8bec621909b5e91f15bb7b840c4f2f7b01148b2" + [[package]] name = "semver" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + [[package]] name = "serde_core" version = "1.0.229" @@ -577,6 +901,19 @@ dependencies = [ "syn 3.0.3", ] +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + [[package]] name = "serde_spanned" version = "1.1.1" @@ -604,6 +941,18 @@ version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.119" @@ -645,6 +994,35 @@ version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "toml" version = "1.1.3+spec-1.1.0" @@ -702,12 +1080,48 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "version-compare" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" +[[package]] +name = "which" +version = "8.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3ef584124b911bcc3875c2f1472e80f24361ceb789bd1c62b3e9a3df9ff43c" +dependencies = [ + "libc", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + [[package]] name = "windows-link" version = "0.2.1" @@ -731,3 +1145,9 @@ checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" dependencies = [ "memchr", ] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/README.md b/README.md index dc9468d..de65c49 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ This workspace contains two independent interfaces for `alt-rootfs-installer`: - `crates/flasher-gtk` — GTK 4/libadwaita -- `crates/flasher-qt` — native Qt 5 Widgets +- `crates/flasher-qt` — Qt 6/KDE Kirigami Both are interface previews. They inspect supported boards and removable drives, but do not write to any device. @@ -12,10 +12,11 @@ Both are interface previews. They inspect supported boards and removable drives, On ALT Linux: ```sh -sudo apt-get install gcc-c++ libgtk4-devel libadwaita-devel qt5-base-devel +sudo apt-get install gcc-c++ libgtk4-devel libadwaita-devel \ + qt6-base-devel qt6-declarative-devel kf6-kirigami ``` -The Qt crate discovers Qt 5 through `pkg-config` and uses the platform's native Qt widget style. It does not require QML or Qt Quick. +The Qt crate uses the maintained CXX-Qt bindings and contains no handwritten C++. Its interface is QML using KDE's Kirigami framework. `.cargo/config.toml` selects Qt 6 explicitly on systems that also have Qt 5 installed. ## Build diff --git a/crates/flasher-qt/Cargo.toml b/crates/flasher-qt/Cargo.toml index d964edc..a4f0642 100644 --- a/crates/flasher-qt/Cargo.toml +++ b/crates/flasher-qt/Cargo.toml @@ -2,10 +2,12 @@ name = "flasher-qt" version = "0.1.0" edition = "2024" -description = "A native Qt Widgets interface for preparing ALT Linux SBC images" +description = "A Qt 6/KDE Kirigami interface for preparing ALT Linux SBC images" license = "GPL-2.0-or-later" -build = "build.rs" + +[dependencies] +cxx-qt-lib = { version = "0.9.1", features = ["qt_gui", "qt_qml", "link_qt_object_files"] } +serde_json = "1" [build-dependencies] -cc = "1.4" -pkg-config = "0.3" +cxx-qt-build = { version = "0.9.1", features = ["link_qt_object_files"] } diff --git a/crates/flasher-qt/build.rs b/crates/flasher-qt/build.rs index e4bdfc2..d57bff5 100644 --- a/crates/flasher-qt/build.rs +++ b/crates/flasher-qt/build.rs @@ -1,25 +1,6 @@ fn main() { - println!("cargo:rerun-if-changed=src/ui.cpp"); - - let qt = pkg_config::Config::new() - .atleast_version("5.12") - .cargo_metadata(false) - .probe("Qt5Widgets") - .expect("Qt 5 Widgets development files were not found (install qt5-base-devel)"); - - let mut build = cc::Build::new(); - build.cpp(true).file("src/ui.cpp").flag_if_supported("-std=c++17"); - for path in &qt.include_paths { - build.include(path); - } - build.compile("flasher_qt_ui"); - - // Keep shared Qt libraries after our static C++ archive on the linker - // command line; GNU ld resolves static archives from left to right. - for path in &qt.link_paths { - println!("cargo:rustc-link-search=native={}", path.display()); - } - println!("cargo:rustc-link-lib=Qt5Widgets"); - println!("cargo:rustc-link-lib=Qt5Gui"); - println!("cargo:rustc-link-lib=Qt5Core"); + cxx_qt_build::CxxQtBuilder::new() + .qt_module("Qml") + .qt_module("Quick") + .build(); } diff --git a/crates/flasher-qt/qml/Main.qml b/crates/flasher-qt/qml/Main.qml new file mode 100644 index 0000000..805c6ed --- /dev/null +++ b/crates/flasher-qt/qml/Main.qml @@ -0,0 +1,437 @@ +import QtQuick +import QtQuick.Controls as Controls +import QtQuick.Layouts +import QtQuick.Dialogs +import org.kde.kirigami as Kirigami + +Kirigami.ApplicationWindow { + id: root + visible: true + width: 760 + height: 820 + minimumWidth: 600 + minimumHeight: 580 + title: qsTr("ALT Image Writer") + + readonly property string supportedBoardsText: @SUPPORTED_BOARDS@ + readonly property string availableDrivesText: @AVAILABLE_DRIVES@ + property bool sourceIsImage: false + property bool outputIsFile: false + property string sourcePath: "" + property string outputPath: "" + property string selectedBoard: "" + property string selectedPlatform: "" + property var driveNames: availableDrivesText.length ? availableDrivesText.split("\n") : [] + + function localPath(url) { + var value = url.toString() + return value.startsWith("file://") ? decodeURIComponent(value.substring(7)) : value + } + + function fileName(path) { + var parts = path.split("/") + return parts[parts.length - 1] + } + + function ready() { + return sourcePath.length > 0 && selectedBoard.length > 0 + && (outputIsFile ? outputPath.length > 0 : driveBox.currentIndex >= 0 && driveNames.length > 0) + } + + globalDrawer: Kirigami.GlobalDrawer { + isMenu: true + actions: [ + Kirigami.Action { + text: qsTr("About ALT Image Writer") + icon.name: "help-about" + onTriggered: aboutDialog.open() + } + ] + } + + pageStack.initialPage: Kirigami.ScrollablePage { + title: qsTr("Create Installation Media") + actions: [ + Kirigami.Action { + text: qsTr("About") + icon.name: "help-about" + displayHint: Kirigami.DisplayHint.IconOnly + onTriggered: aboutDialog.open() + } + ] + + ColumnLayout { + width: Math.min(parent.width, Kirigami.Units.gridUnit * 42) + anchors.horizontalCenter: parent.horizontalCenter + spacing: Kirigami.Units.largeSpacing + + Controls.Label { + text: qsTr("Prepare an ALT Linux root filesystem or disk image for your board.") + color: Kirigami.Theme.disabledTextColor + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + + Kirigami.AbstractCard { + Layout.fillWidth: true + contentItem: ColumnLayout { + spacing: Kirigami.Units.smallSpacing + Kirigami.Heading { text: qsTr("Installation Source"); level: 3 } + Controls.Label { + text: qsTr("Choose a root filesystem archive or a ready-made disk image.") + color: Kirigami.Theme.disabledTextColor + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + Kirigami.FormLayout { + Layout.fillWidth: true + RowLayout { + Kirigami.FormData.label: qsTr("Source type:") + Controls.RadioButton { + text: qsTr("Root filesystem") + checked: true + onCheckedChanged: if (checked) sourceIsImage = false + } + Controls.RadioButton { + text: qsTr("Disk image") + onCheckedChanged: if (checked) { + sourceIsImage = true + driveOutput.checked = true + } + } + } + RowLayout { + Kirigami.FormData.label: qsTr("Source:") + Controls.Label { + text: sourcePath.length ? fileName(sourcePath) : qsTr("No source selected") + elide: Text.ElideMiddle + Layout.fillWidth: true + } + Controls.Button { + text: qsTr("Choose…") + icon.name: "document-open" + onClicked: sourceDialog.open() + } + } + } + Controls.Label { + text: sourcePath.length ? sourcePath + : sourceIsImage ? qsTr("Supported: .img, .img.xz") + : qsTr("Supported: .tar, .tar.gz, .tar.xz") + color: Kirigami.Theme.disabledTextColor + elide: Text.ElideMiddle + Layout.fillWidth: true + } + } + } + + Kirigami.AbstractCard { + Layout.fillWidth: true + contentItem: ColumnLayout { + Kirigami.Heading { text: qsTr("Target Hardware"); level: 3 } + Controls.Label { + text: qsTr("Boards are grouped by platform, SoC family, and architecture.") + color: Kirigami.Theme.disabledTextColor + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + RowLayout { + Layout.fillWidth: true + Kirigami.Icon { + source: "computer-symbolic" + implicitWidth: Kirigami.Units.iconSizes.medium + implicitHeight: implicitWidth + } + ColumnLayout { + spacing: 0 + Layout.fillWidth: true + Controls.Label { + text: selectedBoard.length ? selectedBoard : qsTr("No board selected") + font.bold: selectedBoard.length > 0 + } + Controls.Label { + text: selectedBoard.length ? selectedPlatform : qsTr("Choose a supported target board") + color: Kirigami.Theme.disabledTextColor + } + } + Controls.Button { + text: qsTr("Select Board…") + onClicked: boardDialog.open() + } + } + } + } + + Kirigami.AbstractCard { + Layout.fillWidth: true + contentItem: ColumnLayout { + Kirigami.Heading { text: qsTr("Destination"); level: 3 } + Controls.Label { + text: qsTr("Write to removable media or create an image file.") + color: Kirigami.Theme.disabledTextColor + } + Kirigami.FormLayout { + Layout.fillWidth: true + RowLayout { + Kirigami.FormData.label: qsTr("Output type:") + Controls.RadioButton { + id: driveOutput + text: qsTr("Drive") + checked: true + onCheckedChanged: if (checked) outputIsFile = false + } + Controls.RadioButton { + text: qsTr("Image file") + enabled: !sourceIsImage + onCheckedChanged: if (checked) outputIsFile = true + } + } + Controls.ComboBox { + id: driveBox + visible: !outputIsFile + Kirigami.FormData.label: qsTr("Storage device:") + Layout.fillWidth: true + model: driveNames + displayText: currentIndex < 0 || driveNames.length === 0 + ? qsTr("No removable drives detected") : currentText + enabled: driveNames.length > 0 + } + RowLayout { + visible: outputIsFile + Kirigami.FormData.label: qsTr("Image file:") + Controls.Label { + text: outputPath.length ? outputPath : qsTr("Choose where to save the image") + elide: Text.ElideMiddle + Layout.fillWidth: true + } + Controls.Button { + text: qsTr("Choose…") + icon.name: "document-save" + onClicked: outputDialog.open() + } + } + } + } + } + + Kirigami.AbstractCard { + Layout.fillWidth: true + contentItem: ColumnLayout { + Kirigami.Heading { text: qsTr("Options"); level: 3 } + Kirigami.FormLayout { + Layout.fillWidth: true + Controls.ComboBox { + Kirigami.FormData.label: qsTr("Root filesystem:") + model: ["ext4", "f2fs"] + } + Controls.CheckBox { + Kirigami.FormData.label: qsTr("Resize root partition:") + text: qsTr("Grow to fill the destination") + checked: true + } + Controls.CheckBox { + Kirigami.FormData.label: qsTr("Boot partition:") + text: qsTr("Create a separate 512 MiB partition") + } + Controls.CheckBox { + Kirigami.FormData.label: qsTr("Encryption:") + text: qsTr("Encrypt root partition with LUKS") + } + Controls.CheckBox { + Kirigami.FormData.label: qsTr("Serial console:") + text: qsTr("Add to boot arguments") + } + Controls.ComboBox { + Kirigami.FormData.label: qsTr("EFI system partition:") + model: [qsTr("None"), qsTr("EFI (GPT)"), qsTr("EFI (MBR)")] + } + Controls.ComboBox { + Kirigami.FormData.label: qsTr("First-boot VNC:") + model: [qsTr("Use image default"), qsTr("Enabled"), qsTr("Disabled")] + } + } + } + } + + Kirigami.InlineMessage { + id: previewMessage + Layout.fillWidth: true + type: Kirigami.MessageType.Information + text: qsTr("Flashing is not implemented in this interface preview.") + showCloseButton: true + } + } + } + + footer: Controls.ToolBar { + contentItem: RowLayout { + Controls.Label { + text: qsTr("Nothing will be written until you confirm.") + color: Kirigami.Theme.disabledTextColor + Layout.leftMargin: Kirigami.Units.largeSpacing + Layout.fillWidth: true + } + Controls.Button { + text: qsTr("Review and Write") + icon.name: "media-flash-sd-mmc" + highlighted: true + enabled: root.ready() + Layout.rightMargin: Kirigami.Units.largeSpacing + onClicked: reviewDialog.open() + } + } + } + + Controls.Dialog { + id: boardDialog + parent: Controls.Overlay.overlay + anchors.centerIn: parent + width: Math.min(root.width - Kirigami.Units.gridUnit * 2, Kirigami.Units.gridUnit * 34) + height: Math.min(root.height - Kirigami.Units.gridUnit * 4, Kirigami.Units.gridUnit * 34) + modal: true + title: qsTr("Select Target Board") + standardButtons: Controls.Dialog.Cancel + + ListModel { id: allBoards } + ListModel { id: filteredBoards } + + function populate() { + if (allBoards.count > 0) return + var platform = "Other" + var lines = supportedBoardsText.split("\n") + for (var i = 0; i < lines.length; ++i) { + var line = lines[i].trim() + if (!line.length) continue + if (line.endsWith(":")) { + platform = line.substring(0, line.length - 1) + } else { + var names = line.split(/\s+/) + for (var j = 0; j < names.length; ++j) + allBoards.append({ boardName: names[j], platformName: platform }) + } + } + filter("") + } + + function filter(query) { + filteredBoards.clear() + var needle = query.toLowerCase() + for (var i = 0; i < allBoards.count; ++i) { + var item = allBoards.get(i) + if (!needle.length || item.boardName.toLowerCase().includes(needle) + || item.platformName.toLowerCase().includes(needle)) + filteredBoards.append({ boardName: item.boardName, platformName: item.platformName }) + } + } + + onOpened: { + populate() + searchField.forceActiveFocus() + } + + contentItem: ColumnLayout { + Controls.TextField { + id: searchField + Layout.fillWidth: true + placeholderText: qsTr("Search boards or platforms…") + onTextChanged: boardDialog.filter(text) + } + Controls.Label { + text: qsTr("%1 matching boards").arg(filteredBoards.count) + color: Kirigami.Theme.disabledTextColor + } + ListView { + id: boardList + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + model: filteredBoards + section.property: "platformName" + section.criteria: ViewSection.FullString + section.delegate: Kirigami.ListSectionHeader { text: section } + delegate: Controls.ItemDelegate { + required property string boardName + required property string platformName + width: boardList.width + text: boardName + icon.name: "computer-symbolic" + onClicked: { + selectedBoard = boardName + selectedPlatform = platformName + boardDialog.close() + } + } + Controls.ScrollBar.vertical: Controls.ScrollBar {} + } + } + } + + Controls.Dialog { + id: reviewDialog + parent: Controls.Overlay.overlay + anchors.centerIn: parent + modal: true + title: qsTr("Ready to write?") + standardButtons: Controls.Dialog.Cancel | Controls.Dialog.Ok + onAccepted: previewMessage.visible = true + contentItem: Kirigami.FormLayout { + Controls.Label { Kirigami.FormData.label: qsTr("Board:"); text: selectedBoard } + Controls.Label { Kirigami.FormData.label: qsTr("Platform:"); text: selectedPlatform } + Controls.Label { + Kirigami.FormData.label: qsTr("Destination:") + text: outputIsFile ? outputPath : driveBox.currentText + elide: Text.ElideMiddle + } + Controls.Label { + text: qsTr("This is an interface preview. No data will be written.") + color: Kirigami.Theme.disabledTextColor + wrapMode: Text.WordWrap + } + } + } + + Controls.Dialog { + id: aboutDialog + parent: Controls.Overlay.overlay + anchors.centerIn: parent + modal: true + title: qsTr("About ALT Image Writer") + standardButtons: Controls.Dialog.Ok + contentItem: ColumnLayout { + Kirigami.Icon { + source: "media-flash-sd-mmc" + implicitWidth: Kirigami.Units.iconSizes.huge + implicitHeight: implicitWidth + Layout.alignment: Qt.AlignHCenter + } + Kirigami.Heading { text: qsTr("ALT Image Writer"); level: 2; Layout.alignment: Qt.AlignHCenter } + Controls.Label { text: qsTr("Version 0.1.0"); Layout.alignment: Qt.AlignHCenter } + Controls.Label { + text: qsTr("Prepare ALT Linux installation media for supported single-board computers.") + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignHCenter + Layout.preferredWidth: Kirigami.Units.gridUnit * 22 + } + } + } + + FileDialog { + id: sourceDialog + title: qsTr("Choose Installation Source") + fileMode: FileDialog.OpenFile + nameFilters: sourceIsImage + ? [qsTr("Disk images (*.img *.img.xz)"), qsTr("All files (*)")] + : [qsTr("Root filesystem archives (*.tar *.tar.gz *.tar.xz)"), qsTr("All files (*)")] + onAccepted: sourcePath = localPath(selectedFile) + } + + FileDialog { + id: outputDialog + title: qsTr("Create Disk Image") + fileMode: FileDialog.SaveFile + defaultSuffix: "img" + nameFilters: [qsTr("Raw disk images (*.img)"), qsTr("All files (*)")] + onAccepted: outputPath = localPath(selectedFile) + } +} 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 = 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 { @@ -45,7 +32,6 @@ fn available_drives() -> Vec { 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 { if !removable && !name.starts_with("mmcblk") { continue; } - let sectors = fs::read_to_string(entry.path().join("size")) .ok() .and_then(|value| value.trim().parse::().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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace { - -struct Board { - QString name; - QString platform; -}; - -QVector parseBoards(const QString &text) { - QVector 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 &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 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 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("

ALT Image Writer

Version 0.1.0

" - "

Prepare ALT Linux installation media for supported " - "single-board computers.

")); - }); - - 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 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(); -} diff --git a/pi-session-019f9999-ccdd-7234-912a-62e5b68a7e9d.html b/pi-session-019f9999-ccdd-7234-912a-62e5b68a7e9d.html index 0c2e0c7..32cb770 100644 --- a/pi-session-019f9999-ccdd-7234-912a-62e5b68a7e9d.html +++ b/pi-session-019f9999-ccdd-7234-912a-62e5b68a7e9d.html @@ -1159,7 +1159,7 @@ - +