├── qsshfs.ico
├── qsshfs_16.png
├── qsshfs_32.png
├── mountinfo.cpp
├── res.qrc
├── de.skycoder42.qsshfs.desktop
├── qpm.json
├── mountinfo.h
├── README.md
├── .gitignore
├── main.cpp
├── editremotedialog.h
├── mountcontroller.h
├── qsshfs.pro
├── mainwindow.h
├── mountmodel.h
├── LICENSE
├── qsshfs-installer.sh
├── editremotedialog.cpp
├── mountcontroller.cpp
├── editremotedialog.ui
├── mountmodel.cpp
├── mainwindow.cpp
└── mainwindow.ui
/qsshfs.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Skycoder42/qsshfs/HEAD/qsshfs.ico
--------------------------------------------------------------------------------
/qsshfs_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Skycoder42/qsshfs/HEAD/qsshfs_16.png
--------------------------------------------------------------------------------
/qsshfs_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Skycoder42/qsshfs/HEAD/qsshfs_32.png
--------------------------------------------------------------------------------
/mountinfo.cpp:
--------------------------------------------------------------------------------
1 | #include "mountinfo.h"
2 |
3 | bool MountInfo::isValid() const
4 | {
5 | return !name.isNull();
6 | }
7 |
--------------------------------------------------------------------------------
/res.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | qsshfs.ico
4 |
5 |
6 |
--------------------------------------------------------------------------------
/de.skycoder42.qsshfs.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Type=Application
3 | Version=1.1
4 | Name=Qt sshfs GUI
5 | Comment=A gui wrapper around sshfs, written in Qt
6 | Exec=qsshfs
7 | Icon=qsshfs
8 | Terminal=false
9 | Categories=Development;Qt;
--------------------------------------------------------------------------------
/qpm.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "",
3 | "description": "",
4 | "dependencies": [
5 | "de.skycoder42.dialog-master@1.2.5",
6 | "de.skycoder42.qpathedit@2.0.0"
7 | ],
8 | "license": "NONE",
9 | "pri_filename": "",
10 | "webpage": ""
11 | }
--------------------------------------------------------------------------------
/mountinfo.h:
--------------------------------------------------------------------------------
1 | #ifndef MOUNTINFO_H
2 | #define MOUNTINFO_H
3 |
4 | #include
5 |
6 | struct MountInfo
7 | {
8 | QString name;
9 | QString hostName;
10 | QString userOverwrite;
11 | QString remotePath;
12 | QString localPath;
13 |
14 | bool isValid() const;
15 | };
16 |
17 | #endif // MOUNTINFO_H
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # qsshfs
2 | A gui wrapper around sshfs
3 |
4 | Allows you to easily manage connected sshfs mountpoints via a simply gui, and via your systemtray. You can create predefined mounts by combining the tool with the ssh config file.
5 |
6 | ## Installation
7 | AUR-Package: https://aur.archlinux.org/packages/qsshfs/
8 |
9 | ## Icons
10 | - https://www.iconfinder.com/icons/30483/folder_remote_ssh_icon
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # C++ objects and libs
2 |
3 | *.slo
4 | *.lo
5 | *.o
6 | *.a
7 | *.la
8 | *.lai
9 | *.so
10 | *.dll
11 | *.dylib
12 |
13 | # Qt-es
14 |
15 | /.qmake.cache
16 | /.qmake.stash
17 | *.pro.user
18 | *.pro.user.*
19 | *.qbs.user
20 | *.qbs.user.*
21 | *.moc
22 | moc_*.cpp
23 | qrc_*.cpp
24 | ui_*.h
25 | Makefile*
26 | *build-*
27 |
28 | # QtCreator
29 |
30 | *.autosave
31 |
32 | # QtCtreator Qml
33 | *.qmlproject.user
34 | *.qmlproject.user.*
35 |
36 | # QtCtreator CMake
37 | CMakeLists.txt.user
38 |
39 | # qpm
40 | vendor
--------------------------------------------------------------------------------
/main.cpp:
--------------------------------------------------------------------------------
1 | #include "mainwindow.h"
2 | #include
3 |
4 | int main(int argc, char *argv[])
5 | {
6 | QApplication a(argc, argv);
7 | QApplication::setApplicationName(QStringLiteral(TARGET));
8 | QApplication::setApplicationVersion(QStringLiteral(VERSION));
9 | QApplication::setOrganizationName(QStringLiteral(COMPANY));
10 | QApplication::setOrganizationDomain(QStringLiteral(BUNDLE_PREFIX));
11 | QApplication::setApplicationDisplayName(QStringLiteral(APPNAME));
12 | QApplication::setWindowIcon(QIcon(QStringLiteral(":/icons/main.ico")));
13 | QApplication::setFallbackSessionManagementEnabled(false);
14 |
15 | MainWindow w;
16 | if(!QApplication::arguments().contains(QStringLiteral("--hidden")))
17 | w.show();
18 |
19 | return a.exec();
20 | }
21 |
--------------------------------------------------------------------------------
/editremotedialog.h:
--------------------------------------------------------------------------------
1 | #ifndef EDITREMOTEDIALOG_H
2 | #define EDITREMOTEDIALOG_H
3 |
4 | #include "mountinfo.h"
5 |
6 | #include
7 |
8 | namespace Ui {
9 | class EditRemoteDialog;
10 | }
11 |
12 | class EditRemoteDialog : public QDialog
13 | {
14 | Q_OBJECT
15 |
16 | public:
17 | static MountInfo editInfo(const MountInfo &oldInfo = {}, QWidget *parent = nullptr);
18 |
19 | private slots:
20 | void on_nameLineEdit_textChanged(const QString &text);
21 | void on_hostnameComboBox_editTextChanged(const QString &text);
22 | void on_editConfigButton_clicked();
23 |
24 |
25 | private:
26 | Ui::EditRemoteDialog *ui;
27 |
28 | explicit EditRemoteDialog(QWidget *parent = nullptr);
29 | ~EditRemoteDialog();
30 |
31 | void readSshConfig(const QString &fileName);
32 | };
33 |
34 | #endif // EDITREMOTEDIALOG_H
35 |
--------------------------------------------------------------------------------
/mountcontroller.h:
--------------------------------------------------------------------------------
1 | #ifndef MOUNTCONTROLLER_H
2 | #define MOUNTCONTROLLER_H
3 |
4 | #include "mountinfo.h"
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | class MountController : public QObject
11 | {
12 | Q_OBJECT
13 |
14 | public:
15 | explicit MountController(QObject *parent = nullptr);
16 |
17 | MountInfo mountInfo(const QString &name) const;
18 | bool isMounted(const QString &name);
19 |
20 | public slots:
21 | void addMount(const MountInfo &info);
22 | void removeMount(const QString &name);
23 | void reloadState();
24 |
25 | void mount(const QString &name);
26 | void unmount(const QString &name);
27 |
28 | signals:
29 | void mountChanged(const QString &name);
30 | void mountError(const QString &name, const QString &errorLog, int exitCode = -1);
31 |
32 | private:
33 | struct MountState
34 | {
35 | MountInfo info;
36 | bool mounted;
37 | QPointer process;
38 |
39 | MountState(const MountInfo &info = {});
40 | };
41 |
42 | QHash _mounts;
43 |
44 | QProcess *createProcess(const QString &name, bool forMount);
45 | };
46 |
47 | #endif // MOUNTCONTROLLER_H
48 |
--------------------------------------------------------------------------------
/qsshfs.pro:
--------------------------------------------------------------------------------
1 | TEMPLATE = app
2 |
3 | QT += core gui widgets
4 |
5 | TARGET = qsshfs
6 | APPNAME = "Qt sshfs GUI"
7 | VERSION = 1.1.0
8 | COMPANY = Skycoder42
9 | BUNDLE_PREFIX = de.skycoder42
10 |
11 | DEFINES += "TARGET=\\\"$$TARGET\\\""
12 | DEFINES += "\"APPNAME=\\\"$$APPNAME\\\"\""
13 | DEFINES += "VERSION=\\\"$$VERSION\\\""
14 | DEFINES += "COMPANY=\\\"$$COMPANY\\\""
15 | DEFINES += "BUNDLE_PREFIX=\\\"$$BUNDLE_PREFIX\\\""
16 |
17 | DEFINES += QT_DEPRECATED_WARNINGS
18 | DEFINES += QT_ASCII_CAST_WARNINGS
19 |
20 | include(vendor/vendor.pri)
21 |
22 | HEADERS += mainwindow.h \
23 | editremotedialog.h \
24 | mountmodel.h \
25 | mountinfo.h \
26 | mountcontroller.h
27 |
28 | SOURCES += main.cpp\
29 | mainwindow.cpp \
30 | editremotedialog.cpp \
31 | mountmodel.cpp \
32 | mountinfo.cpp \
33 | mountcontroller.cpp
34 |
35 | FORMS += mainwindow.ui \
36 | editremotedialog.ui
37 |
38 | RESOURCES += \
39 | res.qrc
40 |
41 | win32 {
42 | QMAKE_TARGET_PRODUCT = $$APPNAME
43 | QMAKE_TARGET_COMPANY = $$COMPANY
44 | QMAKE_TARGET_COPYRIGHT = "Felix Barz"
45 | } else:mac {
46 | QMAKE_TARGET_BUNDLE_PREFIX = $${BUNDLE_PREFIX}.
47 | }
48 |
49 | target.path = $$[QT_INSTALL_BINS]
50 | INSTALLS += target
51 |
--------------------------------------------------------------------------------
/mainwindow.h:
--------------------------------------------------------------------------------
1 | #ifndef MAINWINDOW_H
2 | #define MAINWINDOW_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include "mountmodel.h"
9 |
10 | namespace Ui {
11 | class MainWindow;
12 | }
13 |
14 | class MainWindow : public QMainWindow
15 | {
16 | Q_OBJECT
17 |
18 | public:
19 | explicit MainWindow(QWidget *parent = nullptr);
20 | ~MainWindow();
21 |
22 | private slots:
23 | void mountError(const QString &name, const QString &errorLog, int exitCode);
24 |
25 | void reloadCurrent(const QModelIndex &uiIndex);
26 | void updateAutostart(bool checked);
27 |
28 | void commitShutdown(QSessionManager &sm);
29 |
30 | void on_actionAdd_Host_triggered();
31 | void on_actionEdit_Host_triggered();
32 | void on_actionRemove_Host_triggered();
33 | void on_actionMount_triggered(bool checked);
34 | void on_actionOpen_Folder_triggered();
35 | void on_actionAbout_triggered();
36 |
37 | void on_treeView_activated(const QModelIndex &index);
38 |
39 | private:
40 | Ui::MainWindow *ui;
41 | MountModel *model;
42 | QSortFilterProxyModel *sortModel;
43 |
44 | QSystemTrayIcon *trayIco;
45 |
46 | bool isAutostart();
47 | };
48 |
49 | #endif // MAINWINDOW_H
50 |
--------------------------------------------------------------------------------
/mountmodel.h:
--------------------------------------------------------------------------------
1 | #ifndef MOUNTMODEL_H
2 | #define MOUNTMODEL_H
3 |
4 | #include
5 | #include
6 | #include "mountinfo.h"
7 | #include "mountcontroller.h"
8 |
9 | class MountModel : public QAbstractTableModel
10 | {
11 | Q_OBJECT
12 |
13 | public:
14 | explicit MountModel(QObject *parent = nullptr);
15 |
16 | MountController *controller();
17 |
18 | QMenu *createMountMenu(QWidget *parent);
19 |
20 | MountInfo mountInfo(const QModelIndex &index) const;
21 | void addMountInfo(const MountInfo &info);
22 | void updateMountInfo(const QModelIndex &index, const MountInfo &info);
23 | void removeMountInfo(const QModelIndex &index);
24 |
25 | bool isMounted(const QModelIndex &index) const;
26 | void mount(const QModelIndex &index);
27 | void unmount(const QModelIndex &index);
28 | void reload();
29 |
30 | QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
31 |
32 | int rowCount(const QModelIndex &parent = QModelIndex()) const override;
33 | int columnCount(const QModelIndex &parent = QModelIndex()) const override;
34 |
35 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
36 |
37 | private slots:
38 | void updateMounted(const QString &name);
39 |
40 | void triggered(bool checked);
41 |
42 | private:
43 | MountController *_controller;
44 | QStringList _names;
45 |
46 | QMenu *_mntMenu;
47 | QHash _mntActions;
48 |
49 | void addMntAction(const QString &name);
50 | void saveState();
51 | };
52 |
53 | #endif // MOUNTMODEL_H
54 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2017, Felix Barz
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | * Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | * Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | * Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/qsshfs-installer.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | TMPDIR="/var/tmp/qsshfs-installer"
4 | QSSHFS_DIR="$TMPDIR/qsshfs"
5 | QPM_DIR="$TMPDIR/qpm"
6 | QSSHFS="https://github.com/Skycoder42/qsshfs.git"
7 | QSSHFS_VER="1.1.0"
8 | QPM_VER="0.11.0"
9 | export GOPATH="$QPM_DIR/gopath"
10 | QPM="$GOPATH/bin/qpm"
11 |
12 | src_fetch() {
13 | git clone $QSSHFS $QSSHFS_DIR
14 | }
15 |
16 | src_prepare() {
17 | cd $QSSHFS_DIR
18 | git reset --hard $QSSHFS_VER
19 | }
20 |
21 | make_dirs() {
22 | mkdir -p $TMPDIR
23 | mkdir -p $QSSHFS_DIR
24 | mkdir -p $QPM_DIR
25 | mkdir -p $GOPATH
26 | }
27 |
28 | clean() {
29 | rm -Rf $TMPDIR
30 | }
31 |
32 | make_qpm() {
33 | echo "This is the Gopath: $GOPATH"
34 | go get qpm.io/qpm
35 | cd $GOPATH/src/qpm.io
36 | git submodule init
37 | git submodule update
38 | go install qpm.io/qpm
39 | unset GOPATH
40 | }
41 |
42 | make_qsshfs() {
43 | cd $QSSHFS_DIR
44 | $QPM install
45 | qmake -r "./"
46 | make -j"$(nproc --all)"
47 | }
48 |
49 | install_qsshfs() {
50 | cd $QSSHFS_DIR
51 | sudo make install
52 | sudo ln -s /usr/lib64/qt5/bin/qsshfs /usr/bin/qsshfs
53 | sudo install -D -m644 de.skycoder42.qsshfs.desktop "/usr/share/applications/de.skycoder42.qsshfs.desktop"
54 | sudo install -D -m644 qsshfs_32.png "/usr/share/icons/hicolor/32x32/apps/qsshfs.png"
55 | sudo install -D -m644 qsshfs_16.png "/usr/share/icons/hicolor/16x16/apps/qsshfs.png"
56 | sudo install -D -m755 $0 "/usr/bin/qsshfs-installer"
57 | }
58 |
59 | installer() {
60 |
61 | clean
62 | src_fetch
63 | src_prepare
64 | make_qpm
65 | make_qsshfs
66 | install_qsshfs
67 | }
68 |
69 | uninstaller() {
70 | sudo rm -f /usr/lib64/qt5/bin/qsshfs
71 | sudo rm -f /usr/share/applications/de.skycoder42.qsshfs.desktop
72 | sudo rm -f /usr/share/icons/hicolor/32x32/apps/qsshfs.png
73 | sudo rm -f /usr/share/icons/hicolor/16x16/apps/qsshfs.png
74 | }
75 |
76 | help() {
77 | echo "This is the installer script for qsshfs. This will fetch, compile and install qsshfs by itself."
78 | echo " "install": Will compile and install qsshfs."
79 | echo " "uninstall": Will remove the installed version of qsshfs."
80 | }
81 |
82 | for i in "$@"
83 | do
84 | case $i in
85 | install)
86 | installer
87 | ;;
88 | uninstall)
89 | uninstaller
90 | ;;
91 | -h | --help | help)
92 | help
93 | ;;
94 | esac
95 | done
96 |
--------------------------------------------------------------------------------
/editremotedialog.cpp:
--------------------------------------------------------------------------------
1 | #include "editremotedialog.h"
2 | #include "ui_editremotedialog.h"
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | MountInfo EditRemoteDialog::editInfo(const MountInfo &oldInfo, QWidget *parent)
10 | {
11 | EditRemoteDialog dialog(parent);
12 |
13 | if(oldInfo.isValid()) {
14 | dialog.ui->nameLineEdit->setText(oldInfo.name);
15 | dialog.ui->nameLineEdit->setEnabled(false);
16 | dialog.ui->hostnameComboBox->setCurrentText(oldInfo.hostName);
17 | dialog.ui->userLineEdit->setText(oldInfo.userOverwrite);
18 | dialog.ui->remoteMountpointLineEdit->setText(oldInfo.remotePath);
19 | dialog.ui->localMountpointPathEdit->setPath(oldInfo.localPath);
20 | }
21 |
22 | if(dialog.exec() == QDialog::Accepted) {
23 | MountInfo info;
24 | info.name = dialog.ui->nameLineEdit->text();
25 | info.hostName = dialog.ui->hostnameComboBox->currentText();
26 | info.userOverwrite = dialog.ui->userLineEdit->text();
27 | info.remotePath = dialog.ui->remoteMountpointLineEdit->text();
28 | if(info.remotePath.isEmpty())
29 | info.remotePath = QStringLiteral("/");
30 | info.localPath = dialog.ui->localMountpointPathEdit->path();
31 | if(info.localPath.isEmpty())
32 | info.localPath = dialog.ui->localMountpointPathEdit->placeholder();
33 | return info;
34 | } else
35 | return {};
36 | }
37 |
38 | EditRemoteDialog::EditRemoteDialog(QWidget *parent) :
39 | QDialog(parent),
40 | ui(new Ui::EditRemoteDialog)
41 | {
42 | DialogMaster::masterDialog(this, true);
43 | ui->setupUi(this);
44 |
45 | QDir home = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
46 | readSshConfig(QStringLiteral("/etc/ssh/ssh_config"));
47 | readSshConfig(home.absoluteFilePath(QStringLiteral(".ssh/config")));
48 |
49 | home.mkdir(QStringLiteral("mnt"));
50 | ui->localMountpointPathEdit->setDefaultDirectory(home.absoluteFilePath(QStringLiteral("mnt")));
51 |
52 | on_nameLineEdit_textChanged({});
53 | on_hostnameComboBox_editTextChanged(ui->hostnameComboBox->currentText());
54 | }
55 |
56 | EditRemoteDialog::~EditRemoteDialog()
57 | {
58 | delete ui;
59 | }
60 |
61 | void EditRemoteDialog::on_nameLineEdit_textChanged(const QString &text)
62 | {
63 | ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!text.isEmpty());
64 | }
65 |
66 | void EditRemoteDialog::on_hostnameComboBox_editTextChanged(const QString &text)
67 | {
68 | QDir home = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
69 | auto path = home.absoluteFilePath(QStringLiteral("mnt/") + text);
70 | ui->localMountpointPathEdit->setPlaceholder(path);
71 | }
72 |
73 | void EditRemoteDialog::on_editConfigButton_clicked()
74 | {
75 | QDir home = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
76 | QDesktopServices::openUrl(QUrl::fromLocalFile(home.absoluteFilePath(QStringLiteral("./.ssh/config"))));
77 | }
78 |
79 | void EditRemoteDialog::readSshConfig(const QString &fileName)
80 | {
81 | QFile sshConfig(fileName);
82 | if(sshConfig.open(QIODevice::ReadOnly | QIODevice::Text)) {
83 | QTextStream stream(&sshConfig);
84 | QString line;
85 | while(stream.readLineInto(&line)) {
86 | if(line.startsWith(QStringLiteral("Host ")))
87 | ui->hostnameComboBox->addItem(line.mid(5).trimmed());
88 | }
89 | sshConfig.close();
90 | } else {
91 | DialogMaster::warning(parentWidget(),
92 | tr("Failed to read ssh config file \"%1\" with error: \"%2\"\n"
93 | "Make sure the file exists and is readable by this user!")
94 | .arg(fileName)
95 | .arg(sshConfig.errorString()));
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/mountcontroller.cpp:
--------------------------------------------------------------------------------
1 | #include "mountcontroller.h"
2 |
3 | #include
4 | #include
5 |
6 | MountController::MountController(QObject *parent) :
7 | QObject(parent),
8 | _mounts()
9 | {}
10 |
11 | MountInfo MountController::mountInfo(const QString &name) const
12 | {
13 | return _mounts.value(name).info;
14 | }
15 |
16 | bool MountController::isMounted(const QString &name)
17 | {
18 | return _mounts.value(name).mounted;
19 | }
20 |
21 | void MountController::addMount(const MountInfo &info)
22 | {
23 | _mounts.insert(info.name, info);
24 |
25 | //check mount state
26 | QProcess state;
27 | state.start(QStringLiteral("mount -l -t fuse.sshfs"));
28 | if(state.waitForFinished(1000)) {
29 | auto data = state.readAll();
30 | if(data.contains(QDir::cleanPath(info.localPath).toUtf8()))
31 | _mounts[info.name].mounted = true;
32 | } else
33 | qWarning() << "Unable to get mount state";
34 | }
35 |
36 | void MountController::removeMount(const QString &name)
37 | {
38 | _mounts.remove(name);
39 | }
40 |
41 | void MountController::reloadState()
42 | {
43 | QProcess state;
44 | state.start(QStringLiteral("mount -l -t fuse.sshfs"));
45 | if(state.waitForFinished(1000)) {
46 | auto data = state.readAll();
47 | for(auto it = _mounts.begin(); it != _mounts.end(); it++) {
48 | it->mounted = data.contains(QDir::cleanPath(it->info.localPath).toUtf8());
49 | emit mountChanged(it->info.name);
50 | }
51 | } else
52 | qWarning() << "Unable to get mount state";
53 | }
54 |
55 | void MountController::mount(const QString &name)
56 | {
57 | if(!_mounts.contains(name))
58 | return;
59 | auto &state = _mounts[name];
60 | if(state.mounted)
61 | return;
62 |
63 | if(state.process) {
64 | emit mountChanged(name);
65 | emit mountError(name, tr("Wait for the previous mount/unmount to finish"));
66 | return;
67 | }
68 | auto env = QProcessEnvironment::systemEnvironment();
69 | if(!env.contains(QStringLiteral("SSH_ASKPASS"))) {
70 | emit mountChanged(name);
71 | emit mountError(name, tr("The SSH_ASKPASS environment variable must be set!\n"
72 | "You can use for example \"ksshaskpass\""));
73 | return;
74 | }
75 |
76 | QDir mntDir(state.info.localPath);
77 | if(!mntDir.exists()){
78 | if(!mntDir.mkpath(QStringLiteral("."))) {
79 | emit mountChanged(name);
80 | emit mountError(name,
81 | tr("Failed to create mount directory %1")
82 | .arg(mntDir.absolutePath()));
83 | return;
84 | }
85 | }
86 |
87 | state.process = createProcess(name, true);
88 | state.process->setProgram(QStringLiteral("sshfs"));
89 | QStringList args;
90 | args.append(state.info.hostName + QLatin1Char(':') + state.info.remotePath);
91 | args.append(state.info.localPath);
92 | state.process->setArguments(args);
93 |
94 | state.process->start();
95 | }
96 |
97 | void MountController::unmount(const QString &name)
98 | {
99 | if(!_mounts.contains(name))
100 | return;
101 | auto &state = _mounts[name];
102 | if(!state.mounted)
103 | return;
104 |
105 | if(state.process) {
106 | emit mountChanged(name);
107 | emit mountError(name, tr("Wait for the previous mount/unmount to finish"));
108 | return;
109 | }
110 |
111 | state.process = createProcess(name, false);
112 | state.process->setProgram(QStringLiteral("fusermount"));
113 | QStringList args;
114 | args.append(QStringLiteral("-u"));
115 | args.append(state.info.localPath);
116 | state.process->setArguments(args);
117 |
118 | state.process->start();
119 | }
120 |
121 | QProcess *MountController::createProcess(const QString &name, bool forMount)
122 | {
123 | auto process = new QProcess(this);
124 | process->setProcessChannelMode(QProcess::MergedChannels);
125 | connect(process, QOverload::of(&QProcess::finished),
126 | this, [=](int exitCode, QProcess::ExitStatus exitStatus){
127 | if(exitStatus != QProcess::NormalExit) {
128 | emit mountChanged(name);
129 | emit mountError(name, process->errorString());
130 | } else if(exitCode != EXIT_SUCCESS) {
131 | emit mountChanged(name);
132 | emit mountError(name, QString::fromUtf8(process->readAll()), exitCode);
133 | } else {
134 | _mounts[name].mounted = forMount;
135 | emit mountChanged(name);
136 | }
137 | process->deleteLater();
138 | });
139 | return process;
140 | }
141 |
142 | MountController::MountState::MountState(const MountInfo &info) :
143 | info(info),
144 | mounted(false),
145 | process(nullptr)
146 | {}
147 |
--------------------------------------------------------------------------------
/editremotedialog.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | EditRemoteDialog
4 |
5 |
6 |
7 | 0
8 | 0
9 | 416
10 | 230
11 |
12 |
13 |
14 | Edit Mount Config
15 |
16 |
17 | -
18 |
19 |
20 | &Name:
21 |
22 |
23 | nameLineEdit
24 |
25 |
26 |
27 | -
28 |
29 |
30 | -
31 |
32 |
33 | &Hostname:
34 |
35 |
36 | hostnameComboBox
37 |
38 |
39 |
40 | -
41 |
42 |
-
43 |
44 |
45 | true
46 |
47 |
48 |
49 | -
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | -
59 |
60 |
61 | &User:
62 |
63 |
64 | userLineEdit
65 |
66 |
67 |
68 | -
69 |
70 |
71 | leave empty for user from config
72 |
73 |
74 |
75 | -
76 |
77 |
78 | &Remote Mountpoint:
79 |
80 |
81 | remoteMountpointLineEdit
82 |
83 |
84 |
85 | -
86 |
87 |
88 | /
89 |
90 |
91 |
92 | -
93 |
94 |
95 | &Local Mountpoint:
96 |
97 |
98 | localMountpointPathEdit
99 |
100 |
101 |
102 | -
103 |
104 |
105 | QPathEdit::JoinedButton
106 |
107 |
108 |
109 | ..
110 |
111 |
112 | QPathEdit::ExistingFolder
113 |
114 |
115 | QFileDialog::DontResolveSymlinks|QFileDialog::ShowDirsOnly
116 |
117 |
118 | true
119 |
120 |
121 |
122 | -
123 |
124 |
125 | &Mount:
126 |
127 |
128 | mountCheckBox
129 |
130 |
131 |
132 | -
133 |
134 |
135 | -
136 |
137 |
138 | Qt::Horizontal
139 |
140 |
141 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 | QPathEdit
150 | QWidget
151 |
152 |
153 |
154 |
155 |
156 |
157 | buttonBox
158 | accepted()
159 | EditRemoteDialog
160 | accept()
161 |
162 |
163 | 248
164 | 254
165 |
166 |
167 | 157
168 | 274
169 |
170 |
171 |
172 |
173 | buttonBox
174 | rejected()
175 | EditRemoteDialog
176 | reject()
177 |
178 |
179 | 316
180 | 260
181 |
182 |
183 | 286
184 | 274
185 |
186 |
187 |
188 |
189 |
190 |
--------------------------------------------------------------------------------
/mountmodel.cpp:
--------------------------------------------------------------------------------
1 | #include "mountmodel.h"
2 |
3 | #include
4 |
5 | MountModel::MountModel(QObject *parent) :
6 | QAbstractTableModel(parent),
7 | _controller(new MountController(this)),
8 | _names(),
9 | _mntMenu(nullptr),
10 | _mntActions()
11 | {
12 | connect(_controller, &MountController::mountChanged,
13 | this, &MountModel::updateMounted);
14 |
15 | QSettings settings;
16 | auto max = settings.beginReadArray(QStringLiteral("mounts"));
17 | for(auto i = 0; i < max; i++) {
18 | MountInfo data;
19 | settings.setArrayIndex(i);
20 | data.name = settings.value(QStringLiteral("name")).toString();
21 | data.hostName = settings.value(QStringLiteral("hostName")).toString();
22 | data.userOverwrite = settings.value(QStringLiteral("userOverwrite")).toString();
23 | data.remotePath = settings.value(QStringLiteral("remotePath")).toString();
24 | data.localPath = settings.value(QStringLiteral("localPath")).toString();
25 | _controller->addMount(data);
26 | _names.append(data.name);
27 | }
28 | settings.endArray();
29 | }
30 |
31 | MountController *MountModel::controller()
32 | {
33 | return _controller;
34 | }
35 |
36 | QMenu *MountModel::createMountMenu(QWidget *parent)
37 | {
38 | if(!_mntMenu) {
39 | _mntMenu = new QMenu(tr("Mounts"), parent);
40 | _mntMenu->setIcon(QIcon::fromTheme(QStringLiteral("gtk-connect")));
41 |
42 | foreach (auto name, _names)
43 | addMntAction(name);
44 | }
45 |
46 | return _mntMenu;
47 | }
48 |
49 | MountInfo MountModel::mountInfo(const QModelIndex &index) const
50 | {
51 | if (!index.isValid() ||
52 | index.row() < 0 ||
53 | index.row() >= _names.size())
54 | return {};
55 | else
56 | return _controller->mountInfo(_names[index.row()]);
57 | }
58 |
59 | void MountModel::addMountInfo(const MountInfo &info)
60 | {
61 | if(_names.contains(info.name)) {
62 | //TODO show error
63 | return;
64 | }
65 |
66 | beginInsertRows(QModelIndex(), _names.size(), _names.size());
67 | _controller->addMount(info);
68 | _names.append(info.name);
69 | addMntAction(info.name);
70 | endInsertRows();
71 |
72 | saveState();
73 | }
74 |
75 | void MountModel::updateMountInfo(const QModelIndex &index, const MountInfo &info)
76 | {
77 | if (!index.isValid() ||
78 | index.row() < 0 ||
79 | index.row() >= _names.size())
80 | return;
81 |
82 | if(_names[index.row()] != info.name) {
83 | //TODO show error
84 | return;
85 | }
86 | if(_controller->isMounted(info.name)) {
87 | //TODO show error
88 | return;
89 | }
90 |
91 | _controller->removeMount(_names[index.row()]);
92 | _controller->addMount(info);
93 | emit dataChanged(index.sibling(index.row(), 0),
94 | index.sibling(index.row(), 2));
95 |
96 | saveState();
97 | }
98 |
99 | void MountModel::removeMountInfo(const QModelIndex &index)
100 | {
101 | if (!index.isValid() ||
102 | index.row() < 0 ||
103 | index.row() >= _names.size())
104 | return;
105 |
106 | auto name = _names[index.row()];
107 | if(_controller->isMounted(name)) {
108 | //TODO show error
109 | return;
110 | }
111 |
112 | beginRemoveRows(index.parent(), index.row(), index.row());
113 | auto act = _mntActions.take(name);
114 | if(act)
115 | act->deleteLater();
116 | _names.removeAt(index.row());
117 | _controller->removeMount(name);
118 | endRemoveRows();
119 |
120 | saveState();
121 | }
122 |
123 | bool MountModel::isMounted(const QModelIndex &index) const
124 | {
125 | if (!index.isValid() ||
126 | index.row() < 0 ||
127 | index.row() >= _names.size())
128 | return false;
129 | else
130 | return _controller->isMounted(_names[index.row()]);
131 | }
132 |
133 | void MountModel::mount(const QModelIndex &index)
134 | {
135 | if (!index.isValid() ||
136 | index.row() < 0 ||
137 | index.row() >= _names.size())
138 | return;
139 |
140 | _controller->mount(_names[index.row()]);
141 | }
142 |
143 | void MountModel::unmount(const QModelIndex &index)
144 | {
145 | if (!index.isValid() ||
146 | index.row() < 0 ||
147 | index.row() >= _names.size())
148 | return;
149 |
150 | _controller->unmount(_names[index.row()]);
151 | }
152 |
153 | void MountModel::reload()
154 | {
155 | beginResetModel();
156 | _controller->reloadState();
157 | endResetModel();
158 | }
159 |
160 | QVariant MountModel::headerData(int section, Qt::Orientation orientation, int role) const
161 | {
162 | if(orientation != Qt::Horizontal || role != Qt::DisplayRole)
163 | return {};
164 |
165 | switch (section) {
166 | case 0:
167 | return tr("Name");
168 | case 1:
169 | return tr("Host");
170 | case 2:
171 | return tr("Mounted");
172 | default:
173 | Q_UNREACHABLE();
174 | return {};
175 | }
176 | }
177 |
178 | int MountModel::rowCount(const QModelIndex &parent) const
179 | {
180 | if (parent.isValid())
181 | return 0;
182 | else
183 | return _names.size();
184 | }
185 |
186 | int MountModel::columnCount(const QModelIndex &parent) const
187 | {
188 | if (parent.isValid())
189 | return 0;
190 | else
191 | return 3;
192 | }
193 |
194 | QVariant MountModel::data(const QModelIndex &index, int role) const
195 | {
196 | if (!index.isValid() ||
197 | index.row() < 0 ||
198 | index.row() >= _names.size())
199 | return {};
200 |
201 | auto data = _controller->mountInfo(_names[index.row()]);
202 | switch (index.column()) {
203 | case 0:
204 | if(role == Qt::DisplayRole)
205 | return data.name;
206 | break;
207 | case 1:
208 | if(role == Qt::DisplayRole) {
209 | auto host = data.hostName;
210 | if(!data.userOverwrite.isEmpty())
211 | host.prepend(data.userOverwrite + QLatin1Char('@'));
212 | return host;
213 | }
214 | break;
215 | case 2:
216 | if(role == Qt::CheckStateRole)
217 | return _controller->isMounted(data.name) ? Qt::Checked : Qt::Unchecked;
218 | else if(role == Qt::DisplayRole)
219 | return _controller->isMounted(data.name) ? data.localPath : QString();
220 | break;
221 | default:
222 | break;
223 | }
224 |
225 | return {};
226 | }
227 |
228 | void MountModel::updateMounted(const QString &name)
229 | {
230 | auto mIndex = index(_names.indexOf(name), 2);
231 | if(mIndex.isValid())
232 | emit dataChanged(mIndex, mIndex, {Qt::CheckStateRole});
233 |
234 | auto act = _mntActions.value(name);
235 | if(act)
236 | act->setChecked(_controller->isMounted(name));
237 | }
238 |
239 | void MountModel::triggered(bool checked)
240 | {
241 | auto act = qobject_cast(sender());
242 | if(act) {
243 | auto name = _mntActions.key(act);
244 | if(!name.isEmpty()) {
245 | if(checked)
246 | _controller->mount(name);
247 | else
248 | _controller->unmount(name);
249 | act->setChecked(!checked);
250 | }
251 | }
252 | }
253 |
254 | void MountModel::addMntAction(const QString &name)
255 | {
256 | if(_mntMenu) {
257 | auto act = _mntMenu->addAction(name);
258 | act->setCheckable(true);
259 | act->setChecked(_controller->isMounted(name));
260 | connect(act, &QAction::triggered,
261 | this, &MountModel::triggered);
262 | _mntActions.insert(name, act);
263 | }
264 | }
265 |
266 | void MountModel::saveState()
267 | {
268 | QSettings settings;
269 | settings.beginWriteArray(QStringLiteral("mounts"), _names.size());
270 | for(auto i = 0; i < _names.size(); i++) {
271 | const auto &data = _controller->mountInfo(_names[i]);
272 | settings.setArrayIndex(i);
273 | settings.setValue(QStringLiteral("name"), data.name);
274 | settings.setValue(QStringLiteral("hostName"), data.hostName);
275 | settings.setValue(QStringLiteral("userOverwrite"), data.userOverwrite);
276 | settings.setValue(QStringLiteral("remotePath"), data.remotePath);
277 | settings.setValue(QStringLiteral("localPath"), data.localPath);
278 | }
279 | settings.endArray();
280 | }
281 |
--------------------------------------------------------------------------------
/mainwindow.cpp:
--------------------------------------------------------------------------------
1 | #include "editremotedialog.h"
2 | #include "mainwindow.h"
3 | #include "ui_mainwindow.h"
4 | #include
5 | #include
6 | #include
7 |
8 | MainWindow::MainWindow(QWidget *parent) :
9 | QMainWindow(parent),
10 | ui(new Ui::MainWindow),
11 | model(new MountModel(this)),
12 | sortModel(new QSortFilterProxyModel(this)),
13 | trayIco(new QSystemTrayIcon(windowIcon(), this))
14 | {
15 | ui->setupUi(this);
16 | ui->treeView->setParent(this);
17 | centralWidget()->deleteLater();
18 | setCentralWidget(ui->treeView);
19 |
20 | sortModel->setSourceModel(model);
21 | ui->treeView->setModel(sortModel);
22 | auto s = new QAction(this);
23 | s->setSeparator(true);
24 | ui->treeView->addActions({
25 | ui->actionMount,
26 | ui->actionOpen_Folder,
27 | s,
28 | ui->actionEdit_Host,
29 | ui->actionRemove_Host
30 | });
31 |
32 | trayIco->setToolTip(QApplication::applicationDisplayName());
33 | auto menu = new QMenu(this);
34 | menu->addAction(QIcon::fromTheme(QStringLiteral("window-new")), tr("Show main window"),
35 | this, &MainWindow::show);
36 | menu->addMenu(model->createMountMenu(menu));
37 | menu->addAction(ui->action_Reload_Mounts);
38 | menu->addSeparator();
39 | auto runAction = menu->addAction(QIcon::fromTheme(QStringLiteral("games-config-options")), tr("Keep running"),
40 | qApp, [](bool triggered){
41 | QApplication::setQuitOnLastWindowClosed(!triggered);
42 | });
43 | runAction->setCheckable(true);
44 | auto startAction = menu->addAction(QIcon::fromTheme(QStringLiteral("system-run")), tr("Autostart"),
45 | this, &MainWindow::updateAutostart);
46 | startAction->setCheckable(true);
47 | menu->addAction(QIcon::fromTheme(QStringLiteral("gtk-quit")), tr("Quit"),
48 | qApp, &QApplication::quit);
49 | trayIco->setContextMenu(menu);
50 | trayIco->setVisible(true);
51 |
52 | QSettings settings;
53 | settings.beginGroup(QStringLiteral("gui"));
54 | restoreGeometry(settings.value(QStringLiteral("geom")).toByteArray());
55 | restoreState(settings.value(QStringLiteral("state")).toByteArray());
56 | ui->treeView->header()->restoreState(settings.value(QStringLiteral("header")).toByteArray());
57 | runAction->setChecked(settings.value(QStringLiteral("background"), true).toBool());
58 | startAction->setChecked(isAutostart());
59 | QApplication::setQuitOnLastWindowClosed(!runAction->isChecked());
60 | settings.endGroup();
61 |
62 | connect(ui->actionExit, &QAction::triggered,
63 | qApp, &QApplication::quit);
64 | connect(ui->actionAbout_Qt, &QAction::triggered,
65 | qApp, &QApplication::aboutQt);
66 | connect(ui->action_Reload_Mounts, &QAction::triggered,
67 | model, &MountModel::reload);
68 |
69 | connect(model, &MountModel::modelReset,
70 | this, [this](){
71 | reloadCurrent(QModelIndex());
72 | });
73 | connect(ui->treeView->selectionModel(), &QItemSelectionModel::currentChanged,
74 | this, &MainWindow::reloadCurrent);
75 |
76 | connect(model->controller(), &MountController::mountError,
77 | this, &MainWindow::mountError);
78 |
79 | connect(qApp, &QApplication::commitDataRequest,
80 | this, &MainWindow::commitShutdown);
81 | connect(qApp, &QApplication::saveStateRequest,
82 | this, &MainWindow::commitShutdown);
83 | }
84 |
85 | MainWindow::~MainWindow()
86 | {
87 | QSettings settings;
88 | settings.beginGroup(QStringLiteral("gui"));
89 | settings.setValue(QStringLiteral("geom"), saveGeometry());
90 | settings.setValue(QStringLiteral("state"), saveState());
91 | settings.setValue(QStringLiteral("header"), ui->treeView->header()->saveState());
92 | settings.setValue(QStringLiteral("background"), !QApplication::quitOnLastWindowClosed());
93 | settings.endGroup();
94 |
95 | delete ui;
96 | }
97 |
98 | void MainWindow::mountError(const QString &name, const QString &errorLog, int exitCode)
99 | {
100 | reloadCurrent(ui->treeView->currentIndex());
101 |
102 | auto conf = DialogMaster::createCritical(tr("Failed to mount/unmount %1").arg(name));
103 | conf.parent = isVisible() ? this : nullptr;
104 | conf.title = conf.text;
105 | if(exitCode == -1){
106 | conf.text = tr("The mount or unmount operation failed! Check the details for the "
107 | "generated error log");
108 | } else {
109 | conf.text = tr("The mount or unmount operation failed with exit code %1! Check the details for the "
110 | "generated error log")
111 | .arg(exitCode);
112 | }
113 | conf.details = errorLog;
114 | DialogMaster::messageBox(conf);
115 | }
116 |
117 | void MainWindow::reloadCurrent(const QModelIndex &uiIndex)
118 | {
119 | auto index = sortModel->mapToSource(uiIndex);
120 | ui->actionMount->setEnabled(index.isValid());
121 |
122 | if(index.isValid()) {
123 | auto mounted = model->isMounted(index);
124 | ui->actionEdit_Host->setEnabled(!mounted);
125 | ui->actionRemove_Host->setEnabled(!mounted);
126 | ui->actionMount->setChecked(mounted);
127 | } else {
128 | ui->actionEdit_Host->setEnabled(false);
129 | ui->actionRemove_Host->setEnabled(false);
130 | ui->actionMount->setChecked(false);
131 | }
132 | }
133 |
134 | void MainWindow::updateAutostart(bool checked)
135 | {
136 | auto resPath = QStringLiteral("%1/autostart/%2.sh")
137 | .arg(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation))
138 | .arg(QCoreApplication::applicationName());
139 | if(checked) {
140 | QFile file(resPath);
141 | if(file.open(QIODevice::WriteOnly | QIODevice::Text)) {
142 | file.write(QStringLiteral("#!/bin/sh\n%1 --hidden")
143 | .arg(QCoreApplication::applicationName())
144 | .toUtf8());
145 | file.close();
146 | file.setPermissions(file.permissions() | QFileDevice::ExeUser);
147 | }
148 | } else
149 | QFile::remove(resPath);
150 | }
151 |
152 | void MainWindow::commitShutdown(QSessionManager &sm)
153 | {
154 | auto args = sm.restartCommand();
155 | if(!isVisible()) {
156 | if(!args.contains(QStringLiteral("--hidden")))
157 | args.append(QStringLiteral("--hidden"));
158 | } else
159 | args.removeAll(QStringLiteral("--hidden"));
160 | sm.setRestartCommand(args);
161 | sm.setRestartHint(QSessionManager::RestartIfRunning);
162 | }
163 |
164 | void MainWindow::on_actionAdd_Host_triggered()
165 | {
166 | auto info = EditRemoteDialog::editInfo({}, this);
167 | if(info.isValid())
168 | model->addMountInfo(info);
169 | }
170 |
171 | void MainWindow::on_actionEdit_Host_triggered()
172 | {
173 | auto index = sortModel->mapToSource(ui->treeView->currentIndex());
174 | if(index.isValid()) {
175 | auto info = EditRemoteDialog::editInfo(model->mountInfo(index), this);
176 | if(info.isValid())
177 | model->updateMountInfo(index, info);
178 | }
179 | }
180 |
181 | void MainWindow::on_actionRemove_Host_triggered()
182 | {
183 | auto index = sortModel->mapToSource(ui->treeView->currentIndex());
184 | if(index.isValid()) {
185 | if(DialogMaster::question(this, tr("Do you really want to remove the selected mount?")))
186 | model->removeMountInfo(index);
187 | }
188 | }
189 |
190 | void MainWindow::on_actionMount_triggered(bool checked)
191 | {
192 | auto index = sortModel->mapToSource(ui->treeView->currentIndex());
193 | if(index.isValid()) {
194 | if(checked)
195 | model->mount(index);
196 | else
197 | model->unmount(index);
198 | }
199 | }
200 |
201 | void MainWindow::on_actionOpen_Folder_triggered()
202 | {
203 | auto index = sortModel->mapToSource(ui->treeView->currentIndex());
204 | if(index.isValid()) {
205 | auto info = model->mountInfo(index);
206 | QDesktopServices::openUrl(QUrl::fromLocalFile(info.localPath));
207 | }
208 | }
209 |
210 | void MainWindow::on_actionAbout_triggered()
211 | {
212 | DialogMaster::about(this,
213 | tr("A gui wrapper around sshfs"),
214 | true,
215 | QUrl(QStringLiteral("https://github.com/Skycoder42")));
216 | }
217 |
218 | bool MainWindow::isAutostart()
219 | {
220 | auto resPath = QStringLiteral("%1/autostart/%2.sh")
221 | .arg(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation))
222 | .arg(QCoreApplication::applicationName());
223 | return QFile::exists(resPath);
224 | }
225 |
226 | void MainWindow::on_treeView_activated(const QModelIndex &index)
227 | {
228 | auto srcIndex = sortModel->mapToSource(index);
229 | if(srcIndex.isValid()) {
230 | if(!model->isMounted(srcIndex))
231 | model->mount(srcIndex);
232 | else {
233 | auto info = model->mountInfo(srcIndex);
234 | QDesktopServices::openUrl(QUrl::fromLocalFile(info.localPath));
235 | }
236 | }
237 | }
238 |
--------------------------------------------------------------------------------
/mainwindow.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | MainWindow
4 |
5 |
6 |
7 | 0
8 | 0
9 | 575
10 | 394
11 |
12 |
13 |
14 | Qt::ToolButtonFollowStyle
15 |
16 |
17 | true
18 |
19 |
20 | true
21 |
22 |
23 |
24 | -
25 |
26 |
27 | Qt::ActionsContextMenu
28 |
29 |
30 | QAbstractItemView::NoEditTriggers
31 |
32 |
33 | true
34 |
35 |
36 | true
37 |
38 |
39 | false
40 |
41 |
42 | false
43 |
44 |
45 | true
46 |
47 |
48 |
49 |
50 |
51 |
97 |
98 |
99 | TopToolBarArea
100 |
101 |
102 | false
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | ..
115 |
116 |
117 | &Exit
118 |
119 |
120 |
121 |
122 |
123 | ..
124 |
125 |
126 | &Add Host
127 |
128 |
129 | Ctrl+Ins
130 |
131 |
132 |
133 |
134 | false
135 |
136 |
137 |
138 | ..
139 |
140 |
141 | &Edit Host
142 |
143 |
144 | Ctrl+E
145 |
146 |
147 |
148 |
149 | false
150 |
151 |
152 |
153 | ..
154 |
155 |
156 | &Remove Host
157 |
158 |
159 | Del
160 |
161 |
162 |
163 |
164 | true
165 |
166 |
167 | false
168 |
169 |
170 |
171 | ..
172 |
173 |
174 | &Mount
175 |
176 |
177 | Ctrl+M
178 |
179 |
180 |
181 |
182 | false
183 |
184 |
185 |
186 | ..
187 |
188 |
189 | Open &Folder
190 |
191 |
192 | Ctrl+O
193 |
194 |
195 |
196 |
197 |
198 | ..
199 |
200 |
201 | Mount &all
202 |
203 |
204 | Ctrl+A, Ctrl+M
205 |
206 |
207 |
208 |
209 |
210 | ..
211 |
212 |
213 | &Unmount all
214 |
215 |
216 | Ctrl+A, Ctrl+U
217 |
218 |
219 |
220 |
221 |
222 | ..
223 |
224 |
225 | &About
226 |
227 |
228 |
229 |
230 |
231 | ..
232 |
233 |
234 | About &Qt
235 |
236 |
237 |
238 |
239 |
240 | ..
241 |
242 |
243 | &Reload Mounts
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 | actionMount
252 | toggled(bool)
253 | actionOpen_Folder
254 | setEnabled(bool)
255 |
256 |
257 | -1
258 | -1
259 |
260 |
261 | -1
262 | -1
263 |
264 |
265 |
266 |
267 | actionMount
268 | toggled(bool)
269 | actionEdit_Host
270 | setDisabled(bool)
271 |
272 |
273 | -1
274 | -1
275 |
276 |
277 | -1
278 | -1
279 |
280 |
281 |
282 |
283 | actionMount
284 | toggled(bool)
285 | actionRemove_Host
286 | setDisabled(bool)
287 |
288 |
289 | -1
290 | -1
291 |
292 |
293 | -1
294 | -1
295 |
296 |
297 |
298 |
299 |
300 |
--------------------------------------------------------------------------------