v1.2.0 — Linux Flasher, Kali Creator, fixed flashing & counterfeit detection
New features:
- Linux Flasher tab: download+decompress+flash pipeline for RPi OS, Ubuntu,
Debian, Fedora, Kali, DietPi, Alpine, Arch ARM with built-in image catalog
- Kali Creator tab: 4 sub-tabs for USB/SD, VM creation, Docker/Podman
container pulls, and cloud image downloads
- DownloadManager: async downloads with resume support and speed tracking
- Decompressor: streaming .xz (xz-embedded), .gz (zlib), .zip decompression
- ImageCatalog: built-in catalog + remote fetch from rpi-imager JSON endpoint
- SevenZipExtractor: QProcess wrapper for 7z.exe with progress parsing
- Bundled xz-embedded third-party library for native XZ decompression
Bug fixes:
- Fixed VirtualDisk::flashToDisk() — added FSCTL_ALLOW_EXTENDED_DASD_IO,
FSCTL_LOCK_VOLUME, FSCTL_DISMOUNT_VOLUME, 32MB aligned buffers,
WriteFile retry logic (3 attempts), FlushFileBuffers before close
- Fixed VirtualDisk::captureFromDisk() with same improvements
- Fixed ImagingTab::onFlashIso() — now populates targetVolumeLetters from
disk snapshot so IsoFlasher can properly lock/dismount volumes
- Fixed SD card counterfeit detection false positives:
- Changed from write-one-read-one to write-all-then-read-all algorithm
to properly detect NAND address wrapping on fake cards
- Added volume lock/dismount before probing to prevent filesystem
interference (journal writes, metadata updates)
- Added FSCTL_ALLOW_EXTENDED_DASD_IO for probes near end of disk
- Fixed overly aggressive vendor string check — USB card readers
legitimately report "USB"/"Mass Storage", no longer flagged
- Added handle re-open between write and verify phases to defeat
USB reader hardware cache
- README: documented how to unlock the secret menu, added new feature docs
This commit is contained in:
560
src/ui/tabs/NonWindowsFsTab.cpp
Normal file
560
src/ui/tabs/NonWindowsFsTab.cpp
Normal file
@@ -0,0 +1,560 @@
|
||||
#include "NonWindowsFsTab.h"
|
||||
|
||||
#include "core/disk/DiskEnumerator.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QProcess>
|
||||
#include <QProgressBar>
|
||||
#include <QPushButton>
|
||||
#include <QSpinBox>
|
||||
#include <QTabWidget>
|
||||
#include <QTableWidget>
|
||||
#include <QTableWidgetItem>
|
||||
#include <QTextEdit>
|
||||
#include <QThread>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace spw
|
||||
{
|
||||
|
||||
NonWindowsFsTab::NonWindowsFsTab(QWidget* parent) : QWidget(parent)
|
||||
{
|
||||
setupUi();
|
||||
checkWslAvailability();
|
||||
checkDriverAvailability();
|
||||
}
|
||||
|
||||
NonWindowsFsTab::~NonWindowsFsTab() = default;
|
||||
|
||||
QString NonWindowsFsTab::formatSize(uint64_t bytes)
|
||||
{
|
||||
if (bytes >= 1099511627776ULL)
|
||||
return QString("%1 TB").arg(bytes / 1099511627776.0, 0, 'f', 2);
|
||||
if (bytes >= 1073741824ULL)
|
||||
return QString("%1 GB").arg(bytes / 1073741824.0, 0, 'f', 1);
|
||||
if (bytes >= 1048576ULL)
|
||||
return QString("%1 MB").arg(bytes / 1048576.0, 0, 'f', 0);
|
||||
return QString("%1 KB").arg(bytes / 1024.0, 0, 'f', 0);
|
||||
}
|
||||
|
||||
void NonWindowsFsTab::setupUi()
|
||||
{
|
||||
auto* mainLayout = new QVBoxLayout(this);
|
||||
|
||||
auto* innerTabs = new QTabWidget();
|
||||
setupWslTab();
|
||||
setupDriverTab();
|
||||
setupInfoTab();
|
||||
|
||||
// ---- WSL2 Tab ----
|
||||
auto* wslWidget = new QWidget();
|
||||
auto* wslLayout = new QVBoxLayout(wslWidget);
|
||||
|
||||
m_wslAvailLabel = new QLabel();
|
||||
m_wslAvailLabel->setWordWrap(true);
|
||||
m_wslAvailLabel->setStyleSheet("font-weight: bold; padding: 4px;");
|
||||
wslLayout->addWidget(m_wslAvailLabel);
|
||||
|
||||
auto* wslInfo = new QLabel(
|
||||
tr("WSL2's wsl --mount command (Windows 10 21H2 / Build 21364+) can attach a physical disk "
|
||||
"to WSL2, making ext4, Btrfs, XFS, F2FS, ZFS, JFFS2, and other Linux filesystems "
|
||||
"readable and writable directly from Windows via the \\\\wsl$ share."));
|
||||
wslInfo->setWordWrap(true);
|
||||
wslInfo->setStyleSheet("color: #aaaaaa; font-style: italic;");
|
||||
wslLayout->addWidget(wslInfo);
|
||||
|
||||
auto* wslMountGroup = new QGroupBox(tr("Mount Disk via WSL2"));
|
||||
auto* wslMountLayout = new QVBoxLayout(wslMountGroup);
|
||||
|
||||
auto* diskRow = new QHBoxLayout();
|
||||
diskRow->addWidget(new QLabel(tr("Disk:")));
|
||||
m_wslDiskCombo = new QComboBox();
|
||||
diskRow->addWidget(m_wslDiskCombo, 1);
|
||||
diskRow->addWidget(new QLabel(tr("Partition:")));
|
||||
m_wslPartSpin = new QSpinBox();
|
||||
m_wslPartSpin->setRange(0, 128);
|
||||
m_wslPartSpin->setValue(1);
|
||||
m_wslPartSpin->setSpecialValueText(tr("Whole disk (0)"));
|
||||
diskRow->addWidget(m_wslPartSpin);
|
||||
wslMountLayout->addLayout(diskRow);
|
||||
|
||||
auto* fsRow = new QHBoxLayout();
|
||||
fsRow->addWidget(new QLabel(tr("Filesystem type:")));
|
||||
m_wslFsTypeCombo = new QComboBox();
|
||||
m_wslFsTypeCombo->addItems({
|
||||
tr("auto (let WSL2 detect)"),
|
||||
tr("ext4"), tr("ext3"), tr("ext2"),
|
||||
tr("btrfs"), tr("xfs"), tr("f2fs"),
|
||||
tr("jffs2"), tr("nilfs2"),
|
||||
tr("hfsplus"), tr("ufs"),
|
||||
tr("vfat"), tr("exfat"), tr("ntfs"),
|
||||
});
|
||||
fsRow->addWidget(m_wslFsTypeCombo, 1);
|
||||
wslMountLayout->addLayout(fsRow);
|
||||
|
||||
auto* mountBtnRow = new QHBoxLayout();
|
||||
m_wslMountBtn = new QPushButton(tr("Mount via WSL2"));
|
||||
m_wslMountBtn->setStyleSheet(
|
||||
"QPushButton { background-color: #d4a574; color: #1e1e2e; font-weight: bold; "
|
||||
"border-radius: 4px; } QPushButton:hover { background-color: #e0b584; }");
|
||||
connect(m_wslMountBtn, &QPushButton::clicked, this, &NonWindowsFsTab::onWslMount);
|
||||
mountBtnRow->addWidget(m_wslMountBtn);
|
||||
|
||||
m_wslUnmountBtn = new QPushButton(tr("Unmount All WSL Disks"));
|
||||
connect(m_wslUnmountBtn, &QPushButton::clicked, this, &NonWindowsFsTab::onWslUnmountAll);
|
||||
mountBtnRow->addWidget(m_wslUnmountBtn);
|
||||
|
||||
m_wslRefreshBtn = new QPushButton(tr("Refresh Mounts"));
|
||||
connect(m_wslRefreshBtn, &QPushButton::clicked, this, &NonWindowsFsTab::onWslRefreshMounts);
|
||||
mountBtnRow->addWidget(m_wslRefreshBtn);
|
||||
wslMountLayout->addLayout(mountBtnRow);
|
||||
|
||||
wslLayout->addWidget(wslMountGroup);
|
||||
|
||||
// Mounted disks table
|
||||
auto* wslMountedGroup = new QGroupBox(tr("Currently Mounted WSL2 Disks"));
|
||||
auto* wslMountedLayout = new QVBoxLayout(wslMountedGroup);
|
||||
|
||||
m_wslMountsTable = new QTableWidget(0, 3);
|
||||
m_wslMountsTable->setHorizontalHeaderLabels({tr("Device"), tr("Mount Point"), tr("Filesystem")});
|
||||
m_wslMountsTable->horizontalHeader()->setStretchLastSection(true);
|
||||
m_wslMountsTable->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
m_wslMountsTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
m_wslMountsTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
wslMountedLayout->addWidget(m_wslMountsTable);
|
||||
|
||||
auto* wslTableBtnRow = new QHBoxLayout();
|
||||
m_wslUnmountBtn = new QPushButton(tr("Unmount Selected"));
|
||||
m_wslUnmountBtn->setStyleSheet(
|
||||
"QPushButton { background-color: #cc3333; color: white; border-radius: 4px; padding: 4px 12px; }"
|
||||
"QPushButton:hover { background-color: #ee4444; }");
|
||||
connect(m_wslUnmountBtn, &QPushButton::clicked, this, &NonWindowsFsTab::onWslUnmount);
|
||||
wslTableBtnRow->addWidget(m_wslUnmountBtn);
|
||||
|
||||
m_wslOpenBtn = new QPushButton(tr("Open in Explorer"));
|
||||
connect(m_wslOpenBtn, &QPushButton::clicked, this, &NonWindowsFsTab::onOpenMountPoint);
|
||||
wslTableBtnRow->addWidget(m_wslOpenBtn);
|
||||
wslTableBtnRow->addStretch();
|
||||
wslMountedLayout->addLayout(wslTableBtnRow);
|
||||
|
||||
wslLayout->addWidget(wslMountedGroup);
|
||||
|
||||
m_wslStatusLabel = new QLabel();
|
||||
m_wslStatusLabel->setWordWrap(true);
|
||||
wslLayout->addWidget(m_wslStatusLabel);
|
||||
|
||||
innerTabs->addTab(wslWidget, tr("WSL2 Mount"));
|
||||
|
||||
// ---- Driver Tab ----
|
||||
auto* drvWidget = new QWidget();
|
||||
auto* drvLayout = new QVBoxLayout(drvWidget);
|
||||
|
||||
auto* drvInfo = new QLabel(
|
||||
tr("Third-party kernel drivers give Windows native access to Linux/Mac filesystems "
|
||||
"with a real drive letter — no WSL2 required.\n\n"
|
||||
"Open-source drivers detected and installed automatically if present:"));
|
||||
drvInfo->setWordWrap(true);
|
||||
drvInfo->setStyleSheet("color: #aaaaaa; font-style: italic;");
|
||||
drvLayout->addWidget(drvInfo);
|
||||
|
||||
m_drvDriverStatus = new QTextEdit();
|
||||
m_drvDriverStatus->setReadOnly(true);
|
||||
m_drvDriverStatus->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
m_drvDriverStatus->setFont(QFont("Courier New", 9));
|
||||
drvLayout->addWidget(m_drvDriverStatus);
|
||||
|
||||
auto* drvMountGroup = new QGroupBox(tr("Mount with Driver"));
|
||||
auto* drvMountLayout = new QVBoxLayout(drvMountGroup);
|
||||
|
||||
auto* drvDiskRow = new QHBoxLayout();
|
||||
drvDiskRow->addWidget(new QLabel(tr("Disk:")));
|
||||
m_drvDiskCombo = new QComboBox();
|
||||
drvDiskRow->addWidget(m_drvDiskCombo, 1);
|
||||
drvDiskRow->addWidget(new QLabel(tr("Part:")));
|
||||
m_drvPartSpin = new QSpinBox();
|
||||
m_drvPartSpin->setRange(1, 128);
|
||||
m_drvPartSpin->setValue(1);
|
||||
drvDiskRow->addWidget(m_drvPartSpin);
|
||||
drvMountLayout->addLayout(drvDiskRow);
|
||||
|
||||
auto* drvSelectRow = new QHBoxLayout();
|
||||
drvSelectRow->addWidget(new QLabel(tr("Driver:")));
|
||||
m_drvDriverCombo = new QComboBox();
|
||||
m_drvDriverCombo->addItems({
|
||||
tr("Ext2Fsd (ext2/3/4 — open source, requires install)"),
|
||||
tr("WinBtrfs (Btrfs — open source, requires install)"),
|
||||
tr("WinHFSPlus (HFS+ — open source, read-only)"),
|
||||
tr("ZFSin (ZFS — OpenZFS port for Windows)"),
|
||||
});
|
||||
drvSelectRow->addWidget(m_drvDriverCombo, 1);
|
||||
drvMountLayout->addLayout(drvSelectRow);
|
||||
|
||||
auto* drvBtnRow = new QHBoxLayout();
|
||||
m_drvMountBtn = new QPushButton(tr("Mount with Driver"));
|
||||
m_drvMountBtn->setStyleSheet(
|
||||
"QPushButton { background-color: #d4a574; color: #1e1e2e; font-weight: bold; "
|
||||
"border-radius: 4px; } QPushButton:hover { background-color: #e0b584; }");
|
||||
connect(m_drvMountBtn, &QPushButton::clicked, this, &NonWindowsFsTab::onDriverMount);
|
||||
drvBtnRow->addWidget(m_drvMountBtn);
|
||||
m_drvUnmountBtn = new QPushButton(tr("Unmount"));
|
||||
connect(m_drvUnmountBtn, &QPushButton::clicked, this, &NonWindowsFsTab::onDriverUnmount);
|
||||
drvBtnRow->addWidget(m_drvUnmountBtn);
|
||||
auto* drvRefreshBtn = new QPushButton(tr("Refresh Driver Status"));
|
||||
connect(drvRefreshBtn, &QPushButton::clicked, this, &NonWindowsFsTab::onRefreshDriverStatus);
|
||||
drvBtnRow->addWidget(drvRefreshBtn);
|
||||
drvMountLayout->addLayout(drvBtnRow);
|
||||
|
||||
drvLayout->addWidget(drvMountGroup);
|
||||
|
||||
m_drvStatusLabel = new QLabel();
|
||||
m_drvStatusLabel->setWordWrap(true);
|
||||
drvLayout->addWidget(m_drvStatusLabel);
|
||||
|
||||
innerTabs->addTab(drvWidget, tr("Kernel Drivers"));
|
||||
|
||||
// ---- Info Tab ----
|
||||
auto* infoWidget = new QWidget();
|
||||
auto* infoLayout = new QVBoxLayout(infoWidget);
|
||||
m_infoText = new QTextEdit();
|
||||
m_infoText->setReadOnly(true);
|
||||
m_infoText->setHtml(tr(
|
||||
"<h3>Linux & Mac Filesystem Access on Windows</h3>"
|
||||
"<p>Windows cannot natively read ext4, Btrfs, XFS, HFS+, F2FS, ZFS etc. "
|
||||
"There are two ways to access them:</p>"
|
||||
|
||||
"<h4>Option 1: WSL2 Mount (recommended, no install needed)</h4>"
|
||||
"<p>Windows 10 21H2+ and Windows 11 include <code>wsl --mount</code> which attaches "
|
||||
"a physical disk to WSL2. The files are then accessible at "
|
||||
"<code>\\\\wsl$\\Ubuntu\\mnt\\wsl\\PhysicalDrive1p1\\</code></p>"
|
||||
"<pre>wsl --mount \\\\.\\PhysicalDrive1 --partition 1 --type ext4</pre>"
|
||||
"<p>Supports: ext2, ext3, ext4, btrfs, xfs, f2fs, jffs2, nilfs2</p>"
|
||||
|
||||
"<h4>Option 2: Third-party kernel drivers</h4>"
|
||||
"<ul>"
|
||||
"<li><b>Ext2Fsd</b> — ext2/3/4 read/write driver. Free & open source. "
|
||||
"<a href='https://www.ext2fsd.com/'>ext2fsd.com</a></li>"
|
||||
"<li><b>WinBtrfs</b> — Full Btrfs read/write driver. Open source. "
|
||||
"<a href='https://github.com/maharmstone/btrfs'>github.com/maharmstone/btrfs</a></li>"
|
||||
"<li><b>WinHFSPlus</b> — HFS+ read-only. Open source. "
|
||||
"<a href='https://github.com/JetBrains/WinHFSPlus'>github.com/JetBrains/WinHFSPlus</a></li>"
|
||||
"<li><b>ZFSin</b> — OpenZFS port for Windows. "
|
||||
"<a href='https://github.com/openzfsonwindows/ZFSin'>github.com/openzfsonwindows/ZFSin</a></li>"
|
||||
"</ul>"
|
||||
|
||||
"<h4>Future: Native Drivers</h4>"
|
||||
"<p>Setec Partition Wizard includes a roadmap for built-in kernel-mode filesystem "
|
||||
"drivers (IFS drivers) that will provide native Windows access to Linux/Mac filesystems "
|
||||
"without requiring any third-party software. This requires the Windows Driver Kit (WDK) "
|
||||
"and kernel signing — watch for updates.</p>"
|
||||
));
|
||||
infoLayout->addWidget(m_infoText);
|
||||
innerTabs->addTab(infoWidget, tr("How It Works"));
|
||||
|
||||
mainLayout->addWidget(innerTabs);
|
||||
}
|
||||
|
||||
void NonWindowsFsTab::setupWslTab() {}
|
||||
void NonWindowsFsTab::setupDriverTab() {}
|
||||
void NonWindowsFsTab::setupInfoTab() {}
|
||||
|
||||
void NonWindowsFsTab::checkWslAvailability()
|
||||
{
|
||||
QProcess p;
|
||||
p.setProcessChannelMode(QProcess::MergedChannels);
|
||||
p.start("wsl.exe", {"--status"});
|
||||
p.waitForFinished(5000);
|
||||
m_wslAvailable = (p.exitCode() == 0);
|
||||
|
||||
if (m_wslAvailLabel)
|
||||
{
|
||||
if (m_wslAvailable)
|
||||
{
|
||||
m_wslAvailLabel->setText(tr("✓ WSL2 is available — Linux filesystem mounting enabled"));
|
||||
m_wslAvailLabel->setStyleSheet("color: #a8e6a0; font-weight: bold; padding: 4px;");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_wslAvailLabel->setText(
|
||||
tr("✗ WSL2 not detected. Install WSL2: run 'wsl --install' in an admin PowerShell, "
|
||||
"then restart. Windows 10 21H2+ required."));
|
||||
m_wslAvailLabel->setStyleSheet("color: #ff9944; font-weight: bold; padding: 4px;");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NonWindowsFsTab::checkDriverAvailability()
|
||||
{
|
||||
if (!m_drvDriverStatus) return;
|
||||
|
||||
QString status;
|
||||
|
||||
// Check for Ext2Fsd service
|
||||
{
|
||||
QProcess p;
|
||||
p.start("sc.exe", {"query", "Ext2Fsd"});
|
||||
p.waitForFinished(3000);
|
||||
bool found = (p.exitCode() == 0);
|
||||
status += found ? "✓ Ext2Fsd (ext2/3/4): INSTALLED\n" : "✗ Ext2Fsd (ext2/3/4): not installed\n";
|
||||
}
|
||||
|
||||
// Check for WinBtrfs
|
||||
{
|
||||
QProcess p;
|
||||
p.start("sc.exe", {"query", "btrfs"});
|
||||
p.waitForFinished(3000);
|
||||
bool found = (p.exitCode() == 0);
|
||||
status += found ? "✓ WinBtrfs (Btrfs): INSTALLED\n" : "✗ WinBtrfs (Btrfs): not installed\n";
|
||||
}
|
||||
|
||||
// Check for WinHFSPlus
|
||||
{
|
||||
QProcess p;
|
||||
p.start("sc.exe", {"query", "WinHFSPlus"});
|
||||
p.waitForFinished(3000);
|
||||
bool found = (p.exitCode() == 0);
|
||||
status += found ? "✓ WinHFSPlus (HFS+): INSTALLED\n" : "✗ WinHFSPlus (HFS+): not installed\n";
|
||||
}
|
||||
|
||||
// Check for ZFSin
|
||||
{
|
||||
QProcess p;
|
||||
p.start("sc.exe", {"query", "zfs"});
|
||||
p.waitForFinished(3000);
|
||||
bool found = (p.exitCode() == 0);
|
||||
status += found ? "✓ ZFSin (ZFS): INSTALLED\n" : "✗ ZFSin (ZFS): not installed\n";
|
||||
}
|
||||
|
||||
m_drvDriverStatus->setPlainText(status);
|
||||
}
|
||||
|
||||
void NonWindowsFsTab::refreshDisks(const SystemDiskSnapshot& snapshot)
|
||||
{
|
||||
m_snapshot = snapshot;
|
||||
populateDiskCombo();
|
||||
}
|
||||
|
||||
void NonWindowsFsTab::populateDiskCombo()
|
||||
{
|
||||
if (m_wslDiskCombo) m_wslDiskCombo->clear();
|
||||
if (m_drvDiskCombo) m_drvDiskCombo->clear();
|
||||
|
||||
for (const auto& disk : m_snapshot.disks)
|
||||
{
|
||||
QString label = QString("Disk %1: %2 (%3)")
|
||||
.arg(disk.id)
|
||||
.arg(QString::fromStdWString(disk.model))
|
||||
.arg(formatSize(disk.sizeBytes));
|
||||
if (m_wslDiskCombo) m_wslDiskCombo->addItem(label, disk.id);
|
||||
if (m_drvDiskCombo) m_drvDiskCombo->addItem(label, disk.id);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// WSL2 Slots
|
||||
// ============================================================================
|
||||
|
||||
void NonWindowsFsTab::onWslMount()
|
||||
{
|
||||
if (!m_wslAvailable)
|
||||
{
|
||||
QMessageBox::warning(this, tr("WSL2 Not Available"),
|
||||
tr("WSL2 is not installed or not running.\n\n"
|
||||
"Install WSL2: open an admin PowerShell and run:\n"
|
||||
" wsl --install\n\nThen restart Windows."));
|
||||
return;
|
||||
}
|
||||
|
||||
int diskId = m_wslDiskCombo->currentData().toInt();
|
||||
int partition = m_wslPartSpin->value();
|
||||
QString fsType = m_wslFsTypeCombo->currentText().split(' ').first();
|
||||
if (fsType == "auto") fsType.clear();
|
||||
|
||||
QString devPath = QString("\\\\.\\PhysicalDrive%1").arg(diskId);
|
||||
|
||||
QStringList args = {"--mount", devPath};
|
||||
if (partition > 0)
|
||||
args << "--partition" << QString::number(partition);
|
||||
if (!fsType.isEmpty())
|
||||
args << "--type" << fsType;
|
||||
|
||||
m_wslStatusLabel->setText(tr("Mounting disk %1 via WSL2...").arg(diskId));
|
||||
|
||||
auto* thread = QThread::create([this, args]() {
|
||||
QProcess p;
|
||||
p.setProcessChannelMode(QProcess::MergedChannels);
|
||||
p.start("wsl.exe", args);
|
||||
p.waitForFinished(30000);
|
||||
QString out = QString::fromLocal8Bit(p.readAll());
|
||||
int code = p.exitCode();
|
||||
|
||||
QMetaObject::invokeMethod(this, [this, out, code]() {
|
||||
if (code == 0)
|
||||
{
|
||||
m_wslStatusLabel->setText(tr("✓ Mounted. Access via \\\\wsl$\\<distro>\\mnt\\wsl\\"));
|
||||
m_wslStatusLabel->setStyleSheet("color: #a8e6a0;");
|
||||
onWslRefreshMounts();
|
||||
emit statusMessage(tr("WSL2 disk mount successful"));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_wslStatusLabel->setText(tr("✗ Mount failed (exit %1):\n%2").arg(code).arg(out));
|
||||
m_wslStatusLabel->setStyleSheet("color: #ff6b6b;");
|
||||
}
|
||||
}, Qt::QueuedConnection);
|
||||
});
|
||||
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
|
||||
thread->start();
|
||||
}
|
||||
|
||||
void NonWindowsFsTab::onWslUnmount()
|
||||
{
|
||||
int row = m_wslMountsTable->currentRow();
|
||||
if (row < 0) return;
|
||||
|
||||
QString device = m_wslMountsTable->item(row, 0)->text();
|
||||
|
||||
QProcess p;
|
||||
p.setProcessChannelMode(QProcess::MergedChannels);
|
||||
p.start("wsl.exe", {"--unmount", device});
|
||||
p.waitForFinished(15000);
|
||||
|
||||
if (p.exitCode() == 0)
|
||||
{
|
||||
m_wslStatusLabel->setText(tr("✓ Unmounted: %1").arg(device));
|
||||
onWslRefreshMounts();
|
||||
emit statusMessage(tr("WSL2 disk unmounted"));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_wslStatusLabel->setText(tr("✗ Unmount failed: %1")
|
||||
.arg(QString::fromLocal8Bit(p.readAll())));
|
||||
}
|
||||
}
|
||||
|
||||
void NonWindowsFsTab::onWslUnmountAll()
|
||||
{
|
||||
QProcess p;
|
||||
p.start("wsl.exe", {"--unmount"});
|
||||
p.waitForFinished(15000);
|
||||
m_wslStatusLabel->setText(p.exitCode() == 0
|
||||
? tr("✓ All WSL2 disks unmounted.")
|
||||
: tr("Unmount all: %1").arg(QString::fromLocal8Bit(p.readAll())));
|
||||
onWslRefreshMounts();
|
||||
emit statusMessage(tr("All WSL2 disks unmounted"));
|
||||
}
|
||||
|
||||
void NonWindowsFsTab::onWslRefreshMounts()
|
||||
{
|
||||
// Parse `wsl --list --verbose` or check mounted disks via wsl
|
||||
m_wslMountsTable->setRowCount(0);
|
||||
|
||||
QProcess p;
|
||||
p.setProcessChannelMode(QProcess::MergedChannels);
|
||||
// wsl --mount --bare lists currently attached physical disks
|
||||
p.start("wsl.exe", {"--list", "--verbose"});
|
||||
p.waitForFinished(5000);
|
||||
// For now, just show a note — full parsing of wsl mount state requires
|
||||
// reading /proc/mounts from inside WSL which we do via wsl -e cat /proc/mounts
|
||||
QProcess p2;
|
||||
p2.start("wsl.exe", {"-e", "cat", "/proc/mounts"});
|
||||
p2.waitForFinished(5000);
|
||||
QString mounts = QString::fromUtf8(p2.readAll());
|
||||
|
||||
int row = 0;
|
||||
for (const auto& line : mounts.split('\n'))
|
||||
{
|
||||
// Only show entries that look like physical disks mounted via wsl --mount
|
||||
if (!line.contains("/mnt/wsl/") && !line.startsWith("/dev/sd"))
|
||||
continue;
|
||||
auto parts = line.split(' ', Qt::SkipEmptyParts);
|
||||
if (parts.size() < 3) continue;
|
||||
|
||||
m_wslMountsTable->insertRow(row);
|
||||
m_wslMountsTable->setItem(row, 0, new QTableWidgetItem(parts[0])); // device
|
||||
m_wslMountsTable->setItem(row, 1, new QTableWidgetItem(parts[1])); // mountpoint
|
||||
m_wslMountsTable->setItem(row, 2, new QTableWidgetItem(parts[2])); // fstype
|
||||
++row;
|
||||
}
|
||||
}
|
||||
|
||||
void NonWindowsFsTab::onOpenMountPoint()
|
||||
{
|
||||
int row = m_wslMountsTable->currentRow();
|
||||
if (row < 0) return;
|
||||
|
||||
// Open \\wsl$\ in Explorer
|
||||
QString mountPt = m_wslMountsTable->item(row, 1)->text();
|
||||
// Convert /mnt/wsl/... to \\wsl$\<distro>\mnt\wsl\...
|
||||
QProcess::startDetached("explorer.exe", {"\\\\wsl$"});
|
||||
emit statusMessage(tr("Opened \\\\wsl$ in Explorer — navigate to your mount point"));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Driver-based mount slots
|
||||
// ============================================================================
|
||||
|
||||
void NonWindowsFsTab::onDriverMount()
|
||||
{
|
||||
int diskId = m_drvDiskCombo->currentData().toInt();
|
||||
int part = m_drvPartSpin->value();
|
||||
int driver = m_drvDriverCombo->currentIndex();
|
||||
|
||||
// Build a DevPath like \\.\PhysicalDrive1
|
||||
QString devPath = QString("\\\\.\\PhysicalDrive%1").arg(diskId);
|
||||
|
||||
QString msg;
|
||||
switch (driver)
|
||||
{
|
||||
case 0: // Ext2Fsd
|
||||
msg = tr("Ext2Fsd assigns a drive letter automatically after mounting via its service.\n\n"
|
||||
"If Ext2Fsd is installed, use 'Ext2 Volume Manager' from the Start menu for GUI control, "
|
||||
"or the ext2mgr command line tool.\n\n"
|
||||
"Device: %1 Partition: %2").arg(devPath).arg(part);
|
||||
break;
|
||||
case 1: // WinBtrfs
|
||||
msg = tr("WinBtrfs mounts automatically when a Btrfs partition is detected.\n\n"
|
||||
"Ensure the WinBtrfs driver is installed and the service is running.\n"
|
||||
"Device: %1 Partition: %2").arg(devPath).arg(part);
|
||||
break;
|
||||
case 2: // WinHFSPlus
|
||||
msg = tr("WinHFSPlus provides read-only HFS+ access.\n\n"
|
||||
"Install the driver package, then the HFS+ partition should appear "
|
||||
"automatically as a drive letter.\n"
|
||||
"Device: %1 Partition: %2").arg(devPath).arg(part);
|
||||
break;
|
||||
case 3: // ZFSin
|
||||
msg = tr("ZFSin (OpenZFS on Windows) mounts ZFS pools automatically.\n\n"
|
||||
"Import the pool: zpool import -d %1\n"
|
||||
"Then mount: zfs mount -a").arg(devPath);
|
||||
break;
|
||||
default:
|
||||
msg = tr("Select a driver.");
|
||||
}
|
||||
|
||||
m_drvStatusLabel->setText(msg);
|
||||
emit statusMessage(tr("Driver mount instructions shown"));
|
||||
}
|
||||
|
||||
void NonWindowsFsTab::onDriverUnmount()
|
||||
{
|
||||
m_drvStatusLabel->setText(
|
||||
tr("Use the driver's own tools to unmount:\n"
|
||||
"• Ext2Fsd: Ext2 Volume Manager → right-click → Disconnect\n"
|
||||
"• WinBtrfs: Disk Management → Remove Drive Letter\n"
|
||||
"• WinHFSPlus: Disk Management → Remove Drive Letter\n"
|
||||
"• ZFSin: zfs unmount <dataset> then zpool export <pool>"));
|
||||
}
|
||||
|
||||
void NonWindowsFsTab::onRefreshDriverStatus()
|
||||
{
|
||||
checkDriverAvailability();
|
||||
emit statusMessage(tr("Driver status refreshed"));
|
||||
}
|
||||
|
||||
} // namespace spw
|
||||
Reference in New Issue
Block a user