first commit

This commit is contained in:
Stefan Hacker
2026-03-17 14:49:47 +01:00
commit 3b81c10646
178 changed files with 12912 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.16)
project(dolphin-smb-share-tab)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
set(KF_MIN_VERSION "6.0.0")
set(QT_MIN_VERSION "6.6.0")
find_package(ECM ${KF_MIN_VERSION} CONFIG REQUIRED)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDECompilerSettings NO_POLICY_SCOPE)
find_package(Qt6 ${QT_MIN_VERSION} REQUIRED COMPONENTS Core Widgets)
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS CoreAddons I18n KIO)
add_subdirectory(src)
Executable
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
set -e
cd "$(dirname "$0")"
# Create build directory
mkdir -p build
cd build
# Configure
cmake .. -DCMAKE_INSTALL_PREFIX=/usr
# Build
make -j$(nproc)
echo ""
echo "Build successful!"
echo "To install, run: cd build && sudo make install"
echo "Then restart Dolphin: dolphin --replace &"
+13
View File
@@ -0,0 +1,13 @@
add_library(smbshareplugin MODULE
smbshareplugin.cpp
)
target_link_libraries(smbshareplugin
KF6::CoreAddons
KF6::I18n
KF6::KIOWidgets
Qt::Widgets
)
install(TARGETS smbshareplugin
DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf6/propertiesdialog)
+308
View File
@@ -0,0 +1,308 @@
#include "smbshareplugin.h"
#include <KPluginFactory>
#include <KLocalizedString>
#include <QFormLayout>
#include <QGroupBox>
#include <QLabel>
#include <QProcess>
#include <QScrollArea>
#include <QVBoxLayout>
K_PLUGIN_CLASS_WITH_JSON(SmbSharePlugin, "smbshareplugin.json")
// Permission levels for the combo boxes
static const QStringList permissionLabels = {
QStringLiteral("---"),
QStringLiteral("Read Only"),
QStringLiteral("Full Access"),
QStringLiteral("Deny")
};
// Map combo index to net usershare ACL character
static QString aclForIndex(int index)
{
switch (index) {
case 1: return QStringLiteral("R");
case 2: return QStringLiteral("F");
case 3: return QStringLiteral("D");
default: return QString();
}
}
// Map ACL character back to combo index
static int indexForAcl(const QString &acl)
{
if (acl == QLatin1String("R")) return 1;
if (acl == QLatin1String("F")) return 2;
if (acl == QLatin1String("D")) return 3;
return 0;
}
// Get list of local users (UID >= 1000, excluding nobody)
static QStringList getLocalUsers()
{
QStringList users;
QFile passwd(QStringLiteral("/etc/passwd"));
if (passwd.open(QIODevice::ReadOnly | QIODevice::Text)) {
while (!passwd.atEnd()) {
const QString line = QString::fromLocal8Bit(passwd.readLine()).trimmed();
if (line.isEmpty() || line.startsWith(QLatin1Char('#')))
continue;
const QStringList fields = line.split(QLatin1Char(':'));
if (fields.size() < 7)
continue;
const int uid = fields[2].toInt();
const QString shell = fields[6];
if (uid >= 1000 && uid < 65534
&& !shell.endsWith(QLatin1String("/nologin"))
&& !shell.endsWith(QLatin1String("/false"))) {
users << fields[0];
}
}
}
return users;
}
SmbSharePlugin::SmbSharePlugin(QObject *parent, const QVariantList &args)
: KPropertiesDialogPlugin(qobject_cast<KPropertiesDialog *>(parent))
{
Q_UNUSED(args)
m_path = properties->item().localPath();
if (m_path.isEmpty())
return;
// Default share name = folder name
m_defaultShareName = m_path.section(QLatin1Char('/'), -1);
// --- Build the UI ---
m_page = new QWidget();
auto *mainLayout = new QVBoxLayout(m_page);
// Share checkbox
m_shareCheckBox = new QCheckBox(
i18n("Share this folder with other computers on the local network"));
mainLayout->addWidget(m_shareCheckBox);
// Share name
auto *nameLayout = new QFormLayout();
m_nameEdit = new QLineEdit(m_defaultShareName);
nameLayout->addRow(i18n("Name:"), m_nameEdit);
mainLayout->addLayout(nameLayout);
// Guest access
m_guestCheckBox = new QCheckBox(i18n("Allow Guests"));
mainLayout->addWidget(m_guestCheckBox);
// User permissions section
auto *usersGroup = new QGroupBox();
usersGroup->setFlat(true);
auto *usersLayout = new QVBoxLayout(usersGroup);
// Scroll area for user list
auto *scrollArea = new QScrollArea();
scrollArea->setWidgetResizable(true);
scrollArea->setFrameShape(QFrame::NoFrame);
m_usersWidget = new QWidget();
auto *usersFormLayout = new QFormLayout(m_usersWidget);
usersFormLayout->setContentsMargins(0, 0, 0, 0);
// Add "Everyone" entry first
{
auto *combo = new QComboBox();
combo->addItems(permissionLabels);
combo->setCurrentIndex(1); // Default: Read Only
usersFormLayout->addRow(QStringLiteral("Everyone"), combo);
m_userPerms.append({QStringLiteral("Everyone"), combo});
connect(combo, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, [this]() { setDirty(true); });
}
// Add local users
const QStringList users = getLocalUsers();
for (const QString &user : users) {
auto *combo = new QComboBox();
combo->addItems(permissionLabels);
combo->setCurrentIndex(0); // Default: ---
usersFormLayout->addRow(user, combo);
m_userPerms.append({user, combo});
connect(combo, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, [this]() { setDirty(true); });
}
scrollArea->setWidget(m_usersWidget);
usersLayout->addWidget(scrollArea);
mainLayout->addWidget(usersGroup);
mainLayout->addStretch();
// Connect signals
connect(m_shareCheckBox, &QCheckBox::toggled,
this, &SmbSharePlugin::onShareToggled);
connect(m_nameEdit, &QLineEdit::textChanged,
this, [this]() { setDirty(true); });
connect(m_guestCheckBox, &QCheckBox::toggled,
this, [this]() { setDirty(true); });
// Load existing share info
loadCurrentShare();
// Initial UI state
onShareToggled(m_shareCheckBox->isChecked());
// Add the page as a tab
properties->addPage(m_page, i18n("Share"));
}
void SmbSharePlugin::onShareToggled(bool checked)
{
m_nameEdit->setEnabled(checked);
m_guestCheckBox->setEnabled(checked);
m_usersWidget->setEnabled(checked);
setDirty(true);
}
void SmbSharePlugin::loadCurrentShare()
{
// Run: net usershare info --long
QProcess proc;
proc.start(QStringLiteral("net"),
{QStringLiteral("usershare"), QStringLiteral("info"),
QStringLiteral("--long")});
proc.waitForFinished(5000);
const QString output = QString::fromLocal8Bit(proc.readAllStandardOutput());
if (output.isEmpty())
return;
// Parse the output to find a share matching our path
// Format:
// [sharename]
// path=/some/path
// comment=
// usershare_acl=Everyone:R,user:F
// guest_ok=n
QString currentSection;
QString sharePath;
QString shareAcl;
QString guestOk;
const QStringList lines = output.split(QLatin1Char('\n'));
for (const QString &line : lines) {
if (line.startsWith(QLatin1Char('['))) {
// If we had a previous section that matched, break
if (!currentSection.isEmpty() && sharePath == m_path)
break;
// New section
currentSection = line.mid(1, line.indexOf(QLatin1Char(']')) - 1);
sharePath.clear();
shareAcl.clear();
guestOk.clear();
} else if (line.startsWith(QLatin1String("path="))) {
sharePath = line.mid(5);
} else if (line.startsWith(QLatin1String("usershare_acl="))) {
shareAcl = line.mid(14);
} else if (line.startsWith(QLatin1String("guest_ok="))) {
guestOk = line.mid(9);
}
}
// Check if last section matched
if (sharePath != m_path)
return;
// Found an existing share!
m_isShared = true;
m_originalShareName = currentSection;
m_shareCheckBox->setChecked(true);
m_nameEdit->setText(currentSection);
m_guestCheckBox->setChecked(guestOk == QLatin1String("y"));
// Parse ACL: "Everyone:R,username:F"
const QStringList aclEntries = shareAcl.split(QLatin1Char(','));
for (const QString &entry : aclEntries) {
const int colonPos = entry.lastIndexOf(QLatin1Char(':'));
if (colonPos < 0)
continue;
const QString name = entry.left(colonPos);
const QString perm = entry.mid(colonPos + 1);
for (auto &up : m_userPerms) {
if (up.username.compare(name, Qt::CaseInsensitive) == 0) {
up.combo->setCurrentIndex(indexForAcl(perm));
break;
}
}
}
// Reset dirty state after loading
setDirty(false);
}
void SmbSharePlugin::applyChanges()
{
const bool wantShared = m_shareCheckBox->isChecked();
const QString shareName = m_nameEdit->text().trimmed();
// If unchecked and was previously shared, delete the share
if (!wantShared && m_isShared) {
QProcess proc;
proc.start(QStringLiteral("net"),
{QStringLiteral("usershare"), QStringLiteral("delete"),
m_originalShareName});
proc.waitForFinished(5000);
m_isShared = false;
m_originalShareName.clear();
return;
}
if (!wantShared)
return;
// If name changed, delete old share first
if (m_isShared && !m_originalShareName.isEmpty()
&& m_originalShareName != shareName) {
QProcess proc;
proc.start(QStringLiteral("net"),
{QStringLiteral("usershare"), QStringLiteral("delete"),
m_originalShareName});
proc.waitForFinished(5000);
}
// Build ACL string
QStringList aclParts;
for (const auto &up : m_userPerms) {
const QString acl = aclForIndex(up.combo->currentIndex());
if (!acl.isEmpty()) {
aclParts << up.username + QLatin1Char(':') + acl;
}
}
// Default ACL if nothing selected
if (aclParts.isEmpty()) {
aclParts << QStringLiteral("Everyone:R");
}
const QString aclString = aclParts.join(QLatin1Char(','));
const QString guestOk = m_guestCheckBox->isChecked()
? QStringLiteral("guest_ok=y")
: QStringLiteral("guest_ok=n");
// net usershare add <name> <path> <comment> <acl> <guest_ok>
QProcess proc;
proc.start(QStringLiteral("net"),
{QStringLiteral("usershare"), QStringLiteral("add"),
shareName, m_path, QString(), aclString, guestOk});
proc.waitForFinished(5000);
if (proc.exitCode() == 0) {
m_isShared = true;
m_originalShareName = shareName;
}
}
#include "smbshareplugin.moc"
+42
View File
@@ -0,0 +1,42 @@
#ifndef SMBSHAREPLUGIN_H
#define SMBSHAREPLUGIN_H
#include <KPropertiesDialog>
#include <QCheckBox>
#include <QComboBox>
#include <QLineEdit>
#include <QWidget>
struct UserPermission {
QString username;
QComboBox *combo;
};
class SmbSharePlugin : public KPropertiesDialogPlugin
{
Q_OBJECT
public:
SmbSharePlugin(QObject *parent, const QVariantList &args);
void applyChanges() override;
private:
void loadCurrentShare();
void populateUsers();
void onShareToggled(bool checked);
QString m_path;
QString m_defaultShareName;
QWidget *m_page = nullptr;
QCheckBox *m_shareCheckBox = nullptr;
QLineEdit *m_nameEdit = nullptr;
QCheckBox *m_guestCheckBox = nullptr;
QWidget *m_usersWidget = nullptr;
QVector<UserPermission> m_userPerms;
bool m_isShared = false;
QString m_originalShareName;
};
#endif
+15
View File
@@ -0,0 +1,15 @@
{
"KPlugin": {
"Name": "SMB Share",
"Name[de]": "SMB-Freigabe",
"Description": "Configure SMB/Samba folder sharing",
"Description[de]": "SMB/Samba-Ordnerfreigabe konfigurieren",
"Icon": "network-server",
"MimeTypes": [
"inode/directory"
]
},
"X-KDE-Protocols": [
"file"
]
}