From 8fc9d1bbc411d66a151b5e56d3075ecefdeb2f88 Mon Sep 17 00:00:00 2001 From: zhaominyong Date: Thu, 16 Sep 2021 16:05:46 +0800 Subject: [PATCH] =?UTF-8?q?=E9=98=B6=E6=AE=B5=E4=BF=9D=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backup-daemon/main.cpp | 20 +- backup-daemon/systembackupproxy.cpp | 8 +- common/mydefine.h | 3 + common/utils.cpp | 25 +- kybackup/app.qrc | 9 + kybackup/component/clicklabel.cpp | 36 ++ kybackup/component/clicklabel.h | 41 ++ kybackup/component/hoverwidget.cpp | 67 ++++ kybackup/component/hoverwidget.h | 52 +++ kybackup/component/myiconbutton.cpp | 55 +++ kybackup/component/myiconbutton.h | 24 ++ kybackup/component/myiconlabel.cpp | 48 +++ kybackup/component/myiconlabel.h | 23 ++ kybackup/functypeconverter.cpp | 88 +++++ kybackup/functypeconverter.h | 52 +++ kybackup/gsettingswrapper.cpp | 42 +++ kybackup/gsettingswrapper.h | 21 ++ kybackup/imageutil.cpp | 62 ++++ kybackup/imageutil.h | 14 + kybackup/kybackup.pro | 60 ++- kybackup/leftsiderbarwidget.cpp | 92 +++++ kybackup/leftsiderbarwidget.h | 34 ++ kybackup/main.cpp | 187 +++++++++- kybackup/maindialog.cpp | 187 ++++++++++ kybackup/maindialog.h | 34 ++ kybackup/maindialog.ui | 26 +- kybackup/module/systembackup.cpp | 80 ++++ kybackup/module/systembackup.h | 17 + kybackup/qtsingleapplication/qtlocalpeer.cpp | 230 ++++++++++++ kybackup/qtsingleapplication/qtlocalpeer.h | 79 ++++ kybackup/qtsingleapplication/qtlockedfile.cpp | 193 ++++++++++ kybackup/qtsingleapplication/qtlockedfile.h | 97 +++++ .../qtsingleapplication/qtlockedfile_unix.cpp | 115 ++++++ .../qtsingleapplication.cpp | 351 ++++++++++++++++++ .../qtsingleapplication/qtsingleapplication.h | 106 ++++++ kybackup/resource/images/data_backup.svg | 12 + kybackup/resource/images/data_restore.svg | 12 + kybackup/resource/images/ghost_image.svg | 12 + kybackup/resource/images/sysem_restore.svg | 12 + kybackup/resource/images/system_backup.png | Bin 0 -> 72461 bytes kybackup/xatom-helper.cpp | 214 +++++++++++ kybackup/xatom-helper.h | 107 ++++++ yhkylin-backup-tool.pro.user | 4 +- 43 files changed, 2911 insertions(+), 40 deletions(-) create mode 100644 kybackup/app.qrc create mode 100644 kybackup/component/clicklabel.cpp create mode 100644 kybackup/component/clicklabel.h create mode 100644 kybackup/component/hoverwidget.cpp create mode 100644 kybackup/component/hoverwidget.h create mode 100644 kybackup/component/myiconbutton.cpp create mode 100644 kybackup/component/myiconbutton.h create mode 100644 kybackup/component/myiconlabel.cpp create mode 100644 kybackup/component/myiconlabel.h create mode 100644 kybackup/functypeconverter.cpp create mode 100644 kybackup/functypeconverter.h create mode 100644 kybackup/gsettingswrapper.cpp create mode 100644 kybackup/gsettingswrapper.h create mode 100644 kybackup/imageutil.cpp create mode 100644 kybackup/imageutil.h create mode 100644 kybackup/leftsiderbarwidget.cpp create mode 100644 kybackup/leftsiderbarwidget.h create mode 100644 kybackup/module/systembackup.cpp create mode 100644 kybackup/module/systembackup.h create mode 100644 kybackup/qtsingleapplication/qtlocalpeer.cpp create mode 100644 kybackup/qtsingleapplication/qtlocalpeer.h create mode 100644 kybackup/qtsingleapplication/qtlockedfile.cpp create mode 100644 kybackup/qtsingleapplication/qtlockedfile.h create mode 100644 kybackup/qtsingleapplication/qtlockedfile_unix.cpp create mode 100644 kybackup/qtsingleapplication/qtsingleapplication.cpp create mode 100644 kybackup/qtsingleapplication/qtsingleapplication.h create mode 100644 kybackup/resource/images/data_backup.svg create mode 100644 kybackup/resource/images/data_restore.svg create mode 100644 kybackup/resource/images/ghost_image.svg create mode 100644 kybackup/resource/images/sysem_restore.svg create mode 100644 kybackup/resource/images/system_backup.png create mode 100644 kybackup/xatom-helper.cpp create mode 100644 kybackup/xatom-helper.h diff --git a/backup-daemon/main.cpp b/backup-daemon/main.cpp index 717413c..d6ce657 100755 --- a/backup-daemon/main.cpp +++ b/backup-daemon/main.cpp @@ -8,6 +8,7 @@ #include #include "../common/reflect.h" #include "mymountproxy.h" + // test end int main(int argc, char *argv[]) @@ -25,16 +26,17 @@ int main(int argc, char *argv[]) qInstallMessageHandler(Utils::customMessageHandler); // test begin - //MyMountProxy * proxy = (MyMountProxy*)Reflect::createObject("MyMountProxy"); - //proxy->mountBackupPartition(); - qDebug() << QString("测试 begin"); - BackupWrapper backupWrapper; - backupWrapper.m_backupName = "赵民勇test"; - backupWrapper.m_backupPaths << "/"; - backupWrapper.m_backupExcludePaths = Utils::getFromExcludePathsFile(); - MyBackupManager manager; - manager.goBackup(backupWrapper); + +// MyMountProxy * proxy = (MyMountProxy*)Reflect::createObject("MyMountProxy"); +// proxy->mountBackupPartition(); + +// BackupWrapper backupWrapper; +// backupWrapper.m_backupName = "赵民勇test"; +// backupWrapper.m_backupPaths << "/"; +// backupWrapper.m_backupExcludePaths = Utils::getFromExcludePathsFile(); +// MyBackupManager manager; +// manager.goBackup(backupWrapper); qDebug() << QString("测试 end"); // test end diff --git a/backup-daemon/systembackupproxy.cpp b/backup-daemon/systembackupproxy.cpp index 606a392..841de11 100755 --- a/backup-daemon/systembackupproxy.cpp +++ b/backup-daemon/systembackupproxy.cpp @@ -180,17 +180,20 @@ QStringList SystemBackupProxy::getRsyncArgs(SystemBackupScene scene) case SystemBackupScene::SYSTEM_BACKUP : args << "-avAXW"; args << "--progress"; + args << "--no-inc-recursive"; args << "--ignore-missing-args"; break ; case SystemBackupScene::INC_SYSTEM_BACKUP : args << "-avAXr"; args << "--progress"; + args << "--no-inc-recursive"; args << "--ignore-missing-args"; args << QString("--link-dest=../../%1/data").arg(m_backupWrapper.m_baseUuid); break ; case SystemBackupScene::INC_SYSTEM_BACKUP_AT_BASE : args << "-avAXr"; args << "--progress"; + args << "--no-inc-recursive"; args << "--ignore-missing-args"; args << "--delete"; break ; @@ -356,7 +359,10 @@ bool SystemBackupProxy::backupEfi() } args << efiPath; - args << m_destPath; + QString destPath = m_destPath + "/boot"; + destPath.replace("//", "/"); + Utils::mkpath(destPath); + args << destPath; m_p = new RsyncPathToDirProcess(this); bool result = m_p->start(args); diff --git a/common/mydefine.h b/common/mydefine.h index 5b32eef..1abb676 100755 --- a/common/mydefine.h +++ b/common/mydefine.h @@ -34,8 +34,11 @@ #define BACKUP_PARSE_STATE_INC_SUCCESS_STRTING "inc backup finished" #define BACKUP_PARSE_STATE_INC_FAIL_STRTING "inc backup unfinished" +#define THEME_YHKYLIN_BACKUP_TOOLS "yhkylin-backup-tools" + #define PID_STRING_LEN 1024 + /** * @brief 备份还原操作类型 */ diff --git a/common/utils.cpp b/common/utils.cpp index f487c7c..1ee5d12 100755 --- a/common/utils.cpp +++ b/common/utils.cpp @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -257,6 +256,8 @@ void Utils::excludeFstabBindPath(QStringList &excludes) continue ; if (line.startsWith("UUID=")) continue ; + if (line.startsWith("/dev/")) + continue ; if (line.isEmpty()) continue ; if (!line.contains("bind")) @@ -273,9 +274,9 @@ void Utils::excludeFstabBindPath(QStringList &excludes) continue; fields << field; } - // 配置文件/etc/fstab每行6个域,第二个域为挂载路径 + // 配置文件/etc/fstab每行6个域,第二个域为挂载路径, 第一个域就是待排除路径 if (6 == fields.size()) - excludes << fields.at(1); + excludes << fields.at(0); } file.close(); @@ -299,7 +300,7 @@ bool Utils::generateExcludePathsFile() QTextStream in(&excludePathFile); in << "/backup" << endl; //分区 - in << "/boot/efi" << endl; + // in << "/boot/efi" << endl; in << "/cdrom" << endl; in << "/dev" << endl; // efi原始目录在/boot/efi,备份到目标目录为/efi下,再还原时已经单独处理了,批量还原时应该屏蔽此目录 @@ -353,13 +354,9 @@ bool Utils::isDirExist(const QString& fullDirName) */ QStringList Utils::getFromExcludePathsFile() { - QString excludeFile = Utils::m_sysRootPath + EXCLUDE_FILE_PATH; - excludeFile.replace("//", "/"); - QFile excludePathFile(excludeFile); - if (!excludePathFile.open(QIODevice::ReadOnly)) { QStringList list; list << "/backup"; - list << "/boot/efi"; + // list << "/boot/efi"; list << "/cdrom"; list << "/dev"; list << "/efi"; @@ -386,16 +383,6 @@ QStringList Utils::getFromExcludePathsFile() } return list; - } - - QTextStream out(&excludePathFile); - QString strAll = out.readAll(); - excludePathFile.close(); - - QStringList list = strAll.split("\n"); - list.removeAll(QString("")); - - return list; } /** diff --git a/kybackup/app.qrc b/kybackup/app.qrc new file mode 100644 index 0000000..57bad45 --- /dev/null +++ b/kybackup/app.qrc @@ -0,0 +1,9 @@ + + + resource/images/data_backup.svg + resource/images/data_restore.svg + resource/images/ghost_image.svg + resource/images/sysem_restore.svg + resource/images/system_backup.png + + diff --git a/kybackup/component/clicklabel.cpp b/kybackup/component/clicklabel.cpp new file mode 100644 index 0000000..3060ebd --- /dev/null +++ b/kybackup/component/clicklabel.cpp @@ -0,0 +1,36 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#include "clicklabel.h" + +ClickLabel::ClickLabel(const QString &text, QWidget *parent) +{ + setText(text); + adjustSize(); +} + +ClickLabel::~ClickLabel() +{ +} + +void ClickLabel::mousePressEvent(QMouseEvent *event){ + if (event->button() == Qt::LeftButton) + emit clicked(); +// QLabel::mousePressEvent(event); +} diff --git a/kybackup/component/clicklabel.h b/kybackup/component/clicklabel.h new file mode 100644 index 0000000..3625b29 --- /dev/null +++ b/kybackup/component/clicklabel.h @@ -0,0 +1,41 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#ifndef CLICKLABEL_H +#define CLICKLABEL_H + +#include +#include + +class ClickLabel : public QLabel +{ + Q_OBJECT + +public: + explicit ClickLabel(const QString &text, QWidget *parent = 0); + ~ClickLabel(); + +protected: + void mousePressEvent(QMouseEvent * event); + +signals: + void clicked(); +}; + +#endif // CLICKLABEL_H diff --git a/kybackup/component/hoverwidget.cpp b/kybackup/component/hoverwidget.cpp new file mode 100644 index 0000000..3499fd4 --- /dev/null +++ b/kybackup/component/hoverwidget.cpp @@ -0,0 +1,67 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#include "hoverwidget.h" + +#include +#include + +#include + +ResHoverWidget::ResHoverWidget(QString mname, QWidget *parent) : + QWidget(parent), + _name(mname) +{ + setAttribute(Qt::WA_DeleteOnClose); +} + +ResHoverWidget::~ResHoverWidget() +{ +} + +void ResHoverWidget::enterEvent(QEvent *event){ + emit enterWidget(_name); + + QWidget::enterEvent(event); +} + +void ResHoverWidget::leaveEvent(QEvent *event){ + emit leaveWidget(_name); + + QWidget::leaveEvent(event); +} + +void ResHoverWidget::mousePressEvent(QMouseEvent *event){ + + if (event->button() == Qt::LeftButton){ + emit widgetClicked(_name); + } + + QWidget::mousePressEvent(event); +} + +//子类化一个QWidget,为了能够使用样式表,则需要提供paintEvent事件。 +//这是因为QWidget的paintEvent()是空的,而样式表要通过paint被绘制到窗口中。 +void ResHoverWidget::paintEvent(QPaintEvent *event){ + Q_UNUSED(event) + QStyleOption opt; + opt.init(this); + QPainter p(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); +} diff --git a/kybackup/component/hoverwidget.h b/kybackup/component/hoverwidget.h new file mode 100644 index 0000000..56076d2 --- /dev/null +++ b/kybackup/component/hoverwidget.h @@ -0,0 +1,52 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#ifndef HOVERWIDGET_H +#define HOVERWIDGET_H + +#include +#include +#include + +class ResHoverWidget : public QWidget +{ + Q_OBJECT + +public: + explicit ResHoverWidget(QString mname, QWidget *parent = 0); + ~ResHoverWidget(); + +public: + QString _name; + +protected: + virtual void enterEvent(QEvent * event); + virtual void leaveEvent(QEvent * event); + virtual void paintEvent(QPaintEvent * event); + + virtual void mousePressEvent(QMouseEvent * event); + +Q_SIGNALS: + void enterWidget(QString name); + void leaveWidget(QString name); + + void widgetClicked(QString name); +}; + +#endif // HOVERWIDGET_H diff --git a/kybackup/component/myiconbutton.cpp b/kybackup/component/myiconbutton.cpp new file mode 100644 index 0000000..45980b1 --- /dev/null +++ b/kybackup/component/myiconbutton.cpp @@ -0,0 +1,55 @@ +#include "myiconbutton.h" +#include +#include +#include +#include + +MyIconButton::MyIconButton(QWidget *parent) : + QPushButton(parent) +{ + m_iconButton = new QPushButton(this); + m_iconButton->setCheckable(false); + m_iconButton->setFixedSize(QSize(24, 24)); + m_iconButton->setFocusPolicy(Qt::NoFocus); + m_iconButton->setFlat(true); +// m_iconButton->setStyleSheet("QPushButton:checked{border: none;}" +// "QPushButton:!checked{border: none;}"); + + m_textLabel = new QLabel(this); + QSizePolicy textLabelPolicy = m_textLabel->sizePolicy(); + textLabelPolicy.setHorizontalPolicy(QSizePolicy::Fixed); + textLabelPolicy.setVerticalPolicy(QSizePolicy::Fixed); + m_textLabel->setSizePolicy(textLabelPolicy); + m_textLabel->setScaledContents(true); + + QHBoxLayout *hLayout = new QHBoxLayout(); + hLayout->setContentsMargins(8, 0, 0, 0); + hLayout->addWidget(m_iconButton, Qt::AlignCenter); + hLayout->addWidget(m_textLabel); + hLayout->addStretch(); + setLayout(hLayout); + + connect(m_iconButton, &QPushButton::clicked, this, &QPushButton::click); +} + +MyIconButton::~MyIconButton() +{} + +void MyIconButton::setThemeIcon(const QString &themeIconName) +{ + m_themeIconName = themeIconName; + m_iconButton->setIcon(QIcon::fromTheme(themeIconName)); +} + +void MyIconButton::setDesplayText(const QString &text) +{ + m_textLabel->setFixedWidth(this->width() - 40); + QFontMetrics fontMetrics(m_textLabel->font()); + int fontSize = fontMetrics.width(text); + if (fontSize > m_textLabel->width()) { + m_textLabel->setText(fontMetrics.elidedText(text, Qt::ElideRight, m_textLabel->width())); + } else { + m_textLabel->setText(text); + } +} + diff --git a/kybackup/component/myiconbutton.h b/kybackup/component/myiconbutton.h new file mode 100644 index 0000000..d8cca2c --- /dev/null +++ b/kybackup/component/myiconbutton.h @@ -0,0 +1,24 @@ +#ifndef MYICONBUTTON_H +#define MYICONBUTTON_H + +#include +#include + +class MyIconButton : public QPushButton +{ + Q_OBJECT +public: + explicit MyIconButton(QWidget *parent = nullptr); + ~MyIconButton(); + + void setThemeIcon(const QString &themeIconName); + + void setDesplayText(const QString &text); + +private: + QPushButton *m_iconButton; + QLabel *m_textLabel; + QString m_themeIconName; +}; + +#endif // MYICONBUTTON_H diff --git a/kybackup/component/myiconlabel.cpp b/kybackup/component/myiconlabel.cpp new file mode 100644 index 0000000..f82754e --- /dev/null +++ b/kybackup/component/myiconlabel.cpp @@ -0,0 +1,48 @@ +#include "myiconlabel.h" +#include +#include + +MyIconLabel::MyIconLabel(QWidget *parent /*= nullptr*/) : + QLabel(parent) +{ + m_iconLabel = new QLabel(this); + // m_iconLabel->setFixedSize(QSize(24, 24)); + // border:1px solid black; + const QString greySheetStyle = "min-width: 36px; min-height: 36px;max-width:36px; max-height: 36px;border-radius: 18px; background:grey"; + m_iconLabel->setStyleSheet(greySheetStyle); + + m_textLabel = new QLabel(this); + QSizePolicy textLabelPolicy = m_textLabel->sizePolicy(); + textLabelPolicy.setHorizontalPolicy(QSizePolicy::Fixed); + textLabelPolicy.setVerticalPolicy(QSizePolicy::Fixed); + m_textLabel->setSizePolicy(textLabelPolicy); + m_textLabel->setScaledContents(true); + + QHBoxLayout *hLayout = new QHBoxLayout(); + hLayout->setContentsMargins(8, 0, 0, 0); + hLayout->addWidget(m_iconLabel, Qt::AlignCenter); + hLayout->addWidget(m_textLabel); + hLayout->addStretch(); + setLayout(hLayout); +} + +MyIconLabel::~MyIconLabel() +{} + +void MyIconLabel::setThemeIcon(const QString &themeIconName) +{ + m_themeIconName = themeIconName; + m_textLabel->setPixmap(QIcon::fromTheme(themeIconName).pixmap(QSize(24,24))); +} + +void MyIconLabel::setDesplayText(const QString &text) +{ + m_textLabel->setFixedWidth(this->width() - 40); + QFontMetrics fontMetrics(m_textLabel->font()); + int fontSize = fontMetrics.width(text); + if (fontSize > m_textLabel->width()) { + m_textLabel->setText(fontMetrics.elidedText(text, Qt::ElideRight, m_textLabel->width())); + } else { + m_textLabel->setText(text); + } +} diff --git a/kybackup/component/myiconlabel.h b/kybackup/component/myiconlabel.h new file mode 100644 index 0000000..ce81657 --- /dev/null +++ b/kybackup/component/myiconlabel.h @@ -0,0 +1,23 @@ +#ifndef MYICONLABEL_H +#define MYICONLABEL_H + +#include + +class MyIconLabel : public QLabel +{ + Q_OBJECT +public: + explicit MyIconLabel(QWidget *parent = nullptr); + virtual ~MyIconLabel(); + + void setThemeIcon(const QString &themeIconName); + + void setDesplayText(const QString &text); + +private: + QLabel *m_iconLabel; + QLabel *m_textLabel; + QString m_themeIconName; +}; + +#endif // MYICONLABEL_H diff --git a/kybackup/functypeconverter.cpp b/kybackup/functypeconverter.cpp new file mode 100644 index 0000000..f839dd5 --- /dev/null +++ b/kybackup/functypeconverter.cpp @@ -0,0 +1,88 @@ + +#include "functypeconverter.h" + +FuncTypeConverter::FuncTypeConverter() +{ + m_metaModule = QMetaEnum::fromType(); +} + +FuncTypeConverter::~FuncTypeConverter() +{ +} + +QString FuncTypeConverter::keycodeTokeystring(int code){ + //未匹配到则返回空 + return m_metaModule.valueToKey(code); +} + +int FuncTypeConverter::keystringTokeycode(QString string){ + //QString to const char * + QByteArray ba = string.toUpper().toLocal8Bit(); const char * str = ba.data(); + return m_metaModule.keyToValue(str); +} + +/** + * @brief 功能编码对应的功能名称 + * @param code 功能编码 + * @return 功能名称 + */ +QString FuncTypeConverter::keycodeTokeyi18nstring(int code) +{ + QString nameString; + switch (code) { + case BACKUP_SYSTEM: + nameString = tr("System Backup"); + break; + case RESTORE_SYSTEM: + nameString = tr("System Recovery"); + break; + case BACKUP_DATA: + nameString = tr("Data Backup"); + break; + case RESTORE_DATA: + nameString = tr("Data Recovery"); + break; + case OPERATION_LOG: + nameString = tr("Log Records"); + break; + case GHOST_IMAGE: + nameString = tr("Ghost Image"); + break; + default: + break; + } + return nameString; +} + +/** + * @brief 功能编码对应的主题图标名称 + * @param code 功能编码 + * @return 主题图标名称 + */ +QString FuncTypeConverter::keycodeToThemeIconString(int code) +{ + QString nameString; + switch (code) { + case BACKUP_SYSTEM: + nameString = tr("ukui-bf-system-backup-symbolic"); + break; + case RESTORE_SYSTEM: + nameString = tr("ukui-bf-system-restore-symbolic"); + break; + case BACKUP_DATA: + nameString = tr("ukui-bf-data-backup-symbolic"); + break; + case RESTORE_DATA: + nameString = tr("ukui-bf-data-restore-symbolic"); + break; + case OPERATION_LOG: + nameString = tr("ukui-bf-operation-log-symbolic"); + break; + case GHOST_IMAGE: + nameString = tr("ukui-bf-ghost-mirror-symbolic"); + break; + default: + break; + } + return nameString; +} diff --git a/kybackup/functypeconverter.h b/kybackup/functypeconverter.h new file mode 100644 index 0000000..d551457 --- /dev/null +++ b/kybackup/functypeconverter.h @@ -0,0 +1,52 @@ +#ifndef FUNCTYPECONVERTER_H +#define FUNCTYPECONVERTER_H + +#include +#include + +class FuncTypeConverter : public QObject +{ + Q_OBJECT + +public: + explicit FuncTypeConverter(); + ~FuncTypeConverter(); + +public: + // 枚举值对应的枚举变量名 + QString keycodeTokeystring(int code); + // 枚举变量名对应的枚举值 + int keystringTokeycode(QString string); + /** + * @brief 功能编码对应的功能名称 + * @param code 功能编码 + * @return 功能名称 + */ + QString keycodeTokeyi18nstring(int code); + /** + * @brief 功能编码对应的主题图标名称 + * @param code 功能编码 + * @return 主题图标名称 + */ + QString keycodeToThemeIconString(int code); + +public: + QMetaEnum m_metaModule; + + /** + * @brief 主界面功能类型 + */ + enum FunType{ + BACKUP_SYSTEM, + RESTORE_SYSTEM, + BACKUP_DATA, + RESTORE_DATA, + OPERATION_LOG, + GHOST_IMAGE, + TOTALMODULES, + }; + Q_ENUM(FunType) + +}; + +#endif // FUNCTYPECONVERTER_H diff --git a/kybackup/gsettingswrapper.cpp b/kybackup/gsettingswrapper.cpp new file mode 100644 index 0000000..785096f --- /dev/null +++ b/kybackup/gsettingswrapper.cpp @@ -0,0 +1,42 @@ +/** + * @brief 由于当前仅用于主题变化时,备份还原工具的一些图标跟随变化,此处暂时先简单封装。后续根据使用情况另行优化。 + */ + +#include "gsettingswrapper.h" +#include +#include +#include + +#define FITTHEMEWINDOW "org.ukui.style" + +/** + * @brief 绑定连接UKui风格的主题 + * @param QWidget *, 跟随风格变化的窗体对象指针 + */ +void GSettingsWrapper::connectUkuiStyleSchema(QWidget * widgetPtr, QSize size) +{ + if (widgetPtr != nullptr && QGSettings::isSchemaInstalled(FITTHEMEWINDOW)) { + // c++11后能确保多线程并发场景的局部静态对象的唯一性 + static QGSettings *pGsettingThemeData = new QGSettings(FITTHEMEWINDOW); + + QObject::connect(pGsettingThemeData, &QGSettings::changed, [=](const QString &key) { + if (key == "iconThemeName") { + QIcon titleIcon = QIcon::fromTheme("yhkylin-backup-tools"); + if (widgetPtr->inherits("QLabel")) { + QLabel *labelPtr = qobject_cast(widgetPtr); + const QPixmap * pixmapPtr = labelPtr->pixmap(); + if (pixmapPtr != nullptr) { + //labelPtr->setPixmap(titleIcon.pixmap(titleIcon.actualSize(pixmapPtr->size()))); + labelPtr->setPixmap(titleIcon.pixmap(titleIcon.actualSize(size))); + } else { + labelPtr->setPixmap(titleIcon.pixmap(titleIcon.actualSize(QSize(24, 24)))); + } + } else { + widgetPtr->setWindowIcon(titleIcon); + } + } + }); + } +} + + diff --git a/kybackup/gsettingswrapper.h b/kybackup/gsettingswrapper.h new file mode 100644 index 0000000..1584027 --- /dev/null +++ b/kybackup/gsettingswrapper.h @@ -0,0 +1,21 @@ +#ifndef GSETTINGSWRAPPER_H +#define GSETTINGSWRAPPER_H + +#include +#include +class QWidget; + +/** + * @brief 由于当前仅用于主题变化时,备份还原工具的一些图标跟随变化,此处暂时先简单封装。后续根据使用情况另行优化。 + */ +class GSettingsWrapper +{ +public: + /** + * @brief 绑定连接UKui风格的主题 + * @param QWidget *, 跟随风格变化的窗体对象指针 + */ + static void connectUkuiStyleSchema(QWidget * labelPtr, QSize size = QSize(24, 24)); +}; + +#endif // GSETTINGSWRAPPER_H diff --git a/kybackup/imageutil.cpp b/kybackup/imageutil.cpp new file mode 100644 index 0000000..034db6d --- /dev/null +++ b/kybackup/imageutil.cpp @@ -0,0 +1,62 @@ +#include "imageutil.h" + +#include +#include + +const QPixmap ImageUtil::loadSvg(const QString &path, const QString color, int size) +{ + int origSize = size; + const auto ratio = qApp->devicePixelRatio(); + if ( 2 == ratio) { + size += origSize; + } else if (3 == ratio) { + size += origSize; + } + QPixmap pixmap(size, size); + QSvgRenderer renderer(path); + pixmap.fill(Qt::transparent); + + QPainter painter; + painter.begin(&pixmap); + renderer.render(&painter); + painter.end(); + + pixmap.setDevicePixelRatio(ratio); + return drawSymbolicColoredPixmap(pixmap, color); +} + +QPixmap ImageUtil::drawSymbolicColoredPixmap(const QPixmap &source, QString cgColor) +{ + QImage img = source.toImage(); + for (int x = 0; x < img.width(); x++) { + for (int y = 0; y < img.height(); y++) { + auto color = img.pixelColor(x, y); + if (color.alpha() > 0) { + if ( "white" == cgColor) { + color.setRed(255); + color.setGreen(255); + color.setBlue(255); + img.setPixelColor(x, y, color); + } else if( "black" == cgColor) { + color.setRed(0); + color.setGreen(0); + color.setBlue(0); + img.setPixelColor(x, y, color); + } else if ("gray"== cgColor) { + color.setRed(152); + color.setGreen(163); + color.setBlue(164); + img.setPixelColor(x, y, color); + } else if ("blue" == cgColor){ + color.setRed(61); + color.setGreen(107); + color.setBlue(229); + img.setPixelColor(x, y, color); + } else { + return source; + } + } + } + } + return QPixmap::fromImage(img); +} diff --git a/kybackup/imageutil.h b/kybackup/imageutil.h new file mode 100644 index 0000000..7b8e154 --- /dev/null +++ b/kybackup/imageutil.h @@ -0,0 +1,14 @@ +#ifndef IMAGEUTIL_H +#define IMAGEUTIL_H + +#include +#include + +class ImageUtil +{ +public: + static const QPixmap loadSvg(const QString &path, const QString color, int size = 16); + static QPixmap drawSymbolicColoredPixmap(const QPixmap &source, QString cgColor); +}; + +#endif // IMAGEUTIL_H diff --git a/kybackup/kybackup.pro b/kybackup/kybackup.pro index 0619882..bfb7206 100644 --- a/kybackup/kybackup.pro +++ b/kybackup/kybackup.pro @@ -1,10 +1,22 @@ -QT += core gui +QT += core gui svg QT += dbus +QT += network + +# 适配窗口管理器圆角阴影 +QT += KWindowSystem x11extras greaterThan(QT_MAJOR_VERSION, 4): QT += widgets CONFIG += c++11 +# 适配窗口管理器圆角阴影 +LIBS +=-lpthread +LIBS +=-lX11 + +# 配置gsettings +CONFIG += link_pkgconfig +PKGCONFIG += gsettings-qt + # The following define makes your compiler emit warnings if you use # any Qt feature that has been marked deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the @@ -17,17 +29,59 @@ DEFINES += QT_DEPRECATED_WARNINGS #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 HEADERS += \ + ../common/mydefine.h \ + ../common/mylittleparse.h \ + ../common/utils.h \ backup_manager_interface.h \ - maindialog.h + component/clicklabel.h \ + component/hoverwidget.h \ + component/myiconbutton.h \ + component/myiconlabel.h \ + functypeconverter.h \ + gsettingswrapper.h \ + imageutil.h \ + leftsiderbarwidget.h \ + maindialog.h \ + module/systembackup.h \ + qtsingleapplication/qtlocalpeer.h \ + qtsingleapplication/qtlockedfile.h \ + qtsingleapplication/qtsingleapplication.h \ + xatom-helper.h SOURCES += \ + ../common/mydefine.cpp \ + ../common/mylittleparse.cpp \ + ../common/utils.cpp \ backup_manager_interface.cpp \ + component/clicklabel.cpp \ + component/hoverwidget.cpp \ + component/myiconbutton.cpp \ + component/myiconlabel.cpp \ + functypeconverter.cpp \ + gsettingswrapper.cpp \ + imageutil.cpp \ + leftsiderbarwidget.cpp \ main.cpp \ - maindialog.cpp + maindialog.cpp \ + module/systembackup.cpp \ + qtsingleapplication/qtlocalpeer.cpp \ + qtsingleapplication/qtlockedfile.cpp \ + qtsingleapplication/qtlockedfile_unix.cpp \ + qtsingleapplication/qtsingleapplication.cpp \ + xatom-helper.cpp FORMS += \ maindialog.ui +TRANSLATIONS += qt_zh_CN.ts + +# !system(lrelease qt_zh_CN.ts): error("Failed to generate qm") +# system(cp qt_zh_CN.qm images) + +RESOURCES += \ + app.qrc + + # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin diff --git a/kybackup/leftsiderbarwidget.cpp b/kybackup/leftsiderbarwidget.cpp new file mode 100644 index 0000000..31ea723 --- /dev/null +++ b/kybackup/leftsiderbarwidget.cpp @@ -0,0 +1,92 @@ +#include "leftsiderbarwidget.h" +#include +#include +#include +#include +#include "../common/mydefine.h" +#include "component/myiconbutton.h" + +LeftsiderbarWidget::LeftsiderbarWidget(QWidget *parent, StartMode mode) + : QWidget(parent) +{ + m_leftSideBarVLayout = new QVBoxLayout(); + m_leftSideBarVLayout->setObjectName(QString::fromUtf8("m_leftSideBarVLayout")); + m_leftSideBarVLayout->setContentsMargins(5,0,0,0); + setLayout(m_leftSideBarVLayout); + m_leftSideBarVLayout->setSpacing(5); + + QWidget *titleWidget = new QWidget(this); + m_leftSideBarVLayout->addWidget(titleWidget); + + QHBoxLayout * titleLayout = new QHBoxLayout(); + m_mTitleIcon = new QLabel(); + m_titleLabel = new QLabel(tr("Backup and Restore")); + titleWidget->setLayout(titleLayout); + titleLayout->addWidget(m_mTitleIcon); + titleLayout->addWidget(m_titleLabel); + + m_mTitleIcon->setFixedSize(24, 24); + QIcon titleIcon = QIcon::fromTheme(THEME_YHKYLIN_BACKUP_TOOLS); + m_mTitleIcon->setPixmap(titleIcon.pixmap(titleIcon.actualSize(QSize(24, 24)))); + m_titleLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + m_leftSideBarVLayout->addSpacing(10); + + // 功能列表 + int funcNum = FuncTypeConverter::FunType::TOTALMODULES; + if (StartMode::livecd == mode) + funcNum = 2; + m_funcGroup = new QButtonGroup(this); + FuncTypeConverter kvConverter; + for (int type = 0; type < funcNum; ++type) { + QString themeIconName = kvConverter.keycodeToThemeIconString(type); + QString mnamei18nString = kvConverter.keycodeTokeyi18nstring(type); //设置TEXT + + + MyIconButton *funcButton = new MyIconButton(this); + QString btnName = "btn" + QString::number(type + 1); + funcButton->setObjectName(btnName); + funcButton->setFixedSize(180, 40); + funcButton->setDesplayText(mnamei18nString); + funcButton->setThemeIcon(themeIconName); + funcButton->setToolTip(mnamei18nString); + funcButton->setCheckable(true); + // 設置無邊框,跟隨背景色 + funcButton->setFlat(true); + // 設置了setStyleSheet,不能再跟隨主題,捨棄此種方式 +// funcButton->setStyleSheet("QPushButton:hover{background-color: rgba(55,144,250,0.30);border-radius: 4px;}" +// "QPushButton:checked{background-color: palette(highlight);border-radius: 4px;}" +// "QPushButton:!checked{border: none;}"); + m_funcGroup->addButton(funcButton, type); + + + m_leftSideBarVLayout->addWidget(funcButton); + } + + + m_leftSideBarVLayout->addSpacing(8); + + m_leftSideBarVLayout->addStretch(); +} + +LeftsiderbarWidget::~LeftsiderbarWidget() +{ + +} + + +void LeftsiderbarWidget::paintEvent(QPaintEvent *event) +{ + QStyleOption opt; + opt.init(this); + QPainter p(this); + p.setPen(Qt::NoPen); + QColor color = palette().color(QPalette::Window); + color.setAlpha(transparency); + QPalette pal(this->palette()); + pal.setColor(QPalette::Window,QColor(color)); + this->setPalette(pal); + QBrush brush =QBrush(color); + p.setBrush(brush); + p.drawRoundedRect(opt.rect,0,0); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); +} diff --git a/kybackup/leftsiderbarwidget.h b/kybackup/leftsiderbarwidget.h new file mode 100644 index 0000000..2b04049 --- /dev/null +++ b/kybackup/leftsiderbarwidget.h @@ -0,0 +1,34 @@ +#ifndef LEFTSIDERBARWIDGET_H +#define LEFTSIDERBARWIDGET_H + +#include +#include +#include +#include +#include +#include +#include "functypeconverter.h" + + +class LeftsiderbarWidget : public QWidget +{ + Q_OBJECT +public: + // 启动模式,不同模式启动时展示的功能模块不同 + enum StartMode { + normal, + livecd + }; + LeftsiderbarWidget(QWidget *parent = nullptr, StartMode mode = StartMode::normal); + ~LeftsiderbarWidget(); + void paintEvent(QPaintEvent *event); +private: + int transparency = 0; + + QVBoxLayout *m_leftSideBarVLayout = nullptr; + QLabel *m_mTitleIcon = nullptr; + QLabel *m_titleLabel = nullptr; + QButtonGroup *m_funcGroup = nullptr; +}; + +#endif // LEFTSIDERBARWIDGET_H diff --git a/kybackup/main.cpp b/kybackup/main.cpp index 3f69fa1..9426980 100644 --- a/kybackup/main.cpp +++ b/kybackup/main.cpp @@ -1,11 +1,196 @@ #include "maindialog.h" #include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "qtsingleapplication/qtsingleapplication.h" +#include "../common/utils.h" +#include "xatom-helper.h" + +// 声明 +void initApp(QApplication& a); +bool isManager(); +void centerToScreen(QWidget* widget); int main(int argc, char *argv[]) { - QApplication a(argc, argv); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); +#endif + +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) + QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); +#endif + + QtSingleApplication a ("kybackup",argc, argv); + initApp(a); + + // 当前只支持管理员用户使用备份还原工具 + if (!isManager()) { + QMessageBox box(QMessageBox::Warning, QObject::tr("Warning"), QObject::tr("This tool can only be used by administrator.")); + box.setStandardButtons(QMessageBox::Ok); + box.setButtonText(QMessageBox::Ok, QObject::tr("OK")); + QIcon titleIcon = QIcon::fromTheme(THEME_YHKYLIN_BACKUP_TOOLS); + box.setWindowIcon(titleIcon); + box.exec(); + return EXIT_FAILURE; + } + + if (a.isRunning()) { + QString strUid = QString::number(getuid()); + QString ack = a.sendMessage(strUid, 3000); + + if (strUid != ack) { + QMessageBox box(QMessageBox::Critical, QObject::tr("Error"), QObject::tr("Another user had opened kybackup, you can not start it again.")); + box.setStandardButtons(QMessageBox::Ok); + box.setButtonText(QMessageBox::Ok, QObject::tr("OK")); + QIcon titleIcon = QIcon::fromTheme(THEME_YHKYLIN_BACKUP_TOOLS); + box.setWindowIcon(titleIcon); + box.exec(); + } + + return EXIT_SUCCESS; + } + MainDialog w; + // 居中窗口 + centerToScreen(&w); + // 窗口背景透明化;setWindowOpacity可使得窗口及其上控件都透明或半透明-w.setWindowOpacity(0.7); + w.setAttribute(Qt::WA_TranslucentBackground); + // 使得窗口无边框 + // w.setWindowFlag(Qt::FramelessWindowHint); + // 指示窗口管理器模糊给定窗口后面指定区域的背景(毛玻璃化背景) + KWindowEffects::enableBlurBehind(w.winId(),true); + + // 添加窗管协议 + MotifWmHints hints; + hints.flags = MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS; + hints.functions = MWM_FUNC_ALL; + hints.decorations = MWM_DECOR_BORDER; + XAtomHelper::getInstance()->setWindowMotifHint(w.winId(), hints); + + a.setWindowIcon(QIcon::fromTheme(THEME_YHKYLIN_BACKUP_TOOLS)); + a.setActivationWindow(&w, true); + + //如果是第一个实例,则绑定,方便下次调用 + QObject::connect(&a,SIGNAL(messageReceived(const QString&)),&w,SLOT(sltMessageReceived(const QString&))); + w.show(); return a.exec(); } + +/** + * @brief 应用初始化 + * @param a + * @note new出来的两个QTranslator不用删除,需要整个生命周期内有效,应用结束由操作系统收回即可 + */ +void initApp(QApplication& a) +{ + //前端向后端传递QString参数,若参数中含有中文则保证不会乱码 + QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8")); + + //区分中英文 + QString locale = QLocale::system().name(); + //QT自身标准的翻译 +#ifndef QT_NO_TRANSLATION + QString translatorFileName = QLatin1String("qt_"); + translatorFileName += locale; + QTranslator *selfTransOfQt = new QTranslator(); + if (selfTransOfQt->load(translatorFileName, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) + a.installTranslator(selfTransOfQt); + else + qDebug() << "load qt translator file failed!"; +#endif + + //应用内的翻译 + QTranslator *translator = new QTranslator(); + if (locale == "zh_CN") { + //中文需要翻译 + if (!translator->load(":/qm/images/qt_zh_CN.qm")) //qtcreator启动后看到在资源目录下 + qDebug() << "load translator file failed!"; + else + a.installTranslator(translator); + } + + // 命令行参数 + QCoreApplication::setApplicationName(QObject::tr("kybackup")); + QCoreApplication::setApplicationVersion("4.0.14"); + + /* + QCommandLineParser parser; + parser.setApplicationDescription("kybackup helper"); + parser.addHelpOption(); + parser.addVersionOption(); + // 启动后进入的页面 + QCommandLineOption functionOption(QStringList() << "r" << "restore", QCoreApplication::translate("restore", "system restore")); + parser.addOption(functionOption); + parser.process(a); + bool isRestore = parser.isSet(functionOption); + */ + + QString qsAppPath = QCoreApplication::applicationDirPath(); + Utils::initSysRootPath(qsAppPath); +} + +/** + * @brief 判断启动账号是否管理员账号 + * @return true-相当于是;false-不是 + */ +bool isManager() +{ + QString rootPath = Utils::getSysRootPath(); + // 只正常启动程序时需校验是否管理员账号启动 + if ("/" != rootPath) + return true; + + uid_t uid = getuid(); + // root用户 + if (0 == uid) + return true; + + QString sid = QString::number(uid); + QString userObject = "/org/freedesktop/Accounts/User" + sid; + + // 创建QDBusInterface + QDBusInterface iface("org.freedesktop.Accounts", userObject, "org.freedesktop.DBus.Properties", QDBusConnection::systemBus()); + if (!iface.isValid()) { + qDebug() << qPrintable(QDBusConnection::systemBus().lastError().message()); + exit(1); + } + + int result = 0; + QDBusReply reply = iface.call("Get", QString("org.freedesktop.Accounts.User"), QString("AccountType")); + if (reply.isValid()) { + QVariant val = reply.value().variant(); + result = val.toInt(); + } + + return 0 == result ? false : true; +} + +/** + * @brief 居中显示窗口 + * @param widget + */ +void centerToScreen(QWidget* widget) +{ + if (!widget) + return; + QDesktopWidget* m = QApplication::desktop(); + // QRect desk_rect = m->screenGeometry(m->screenNumber(QCursor::pos())); + QRect desk_rect = m->screenGeometry(widget); + int desk_x = desk_rect.width(); + int desk_y = desk_rect.height(); + int x = widget->width(); + int y = widget->height(); + widget->move(desk_x / 2 - x / 2 + desk_rect.left(), desk_y / 2 - y / 2 + desk_rect.top()); +} diff --git a/kybackup/maindialog.cpp b/kybackup/maindialog.cpp index 181884a..dc1bf45 100644 --- a/kybackup/maindialog.cpp +++ b/kybackup/maindialog.cpp @@ -1,11 +1,21 @@ #include "maindialog.h" #include "ui_maindialog.h" +#include +#include +#include +#include + +#include "../common/mydefine.h" +#include "module/systembackup.h" + MainDialog::MainDialog(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainDialog) { ui->setupUi(this); + initUI(); + initConnect(); } MainDialog::~MainDialog() @@ -13,3 +23,180 @@ MainDialog::~MainDialog() delete ui; } +void MainDialog::initUI() +{ + m_totalHLayout = new QHBoxLayout(this); + m_totalHLayout->setSpacing(0); + m_totalHLayout->setObjectName(QString::fromUtf8("m_totalHLayout")); + m_totalHLayout->setContentsMargins(0, 0, 0, 0); + this->setLayout(m_totalHLayout); + + m_leftSiderBarWidget = new LeftsiderbarWidget(ui->centralwidget); + m_leftSiderBarWidget->setObjectName(QString::fromUtf8("m_leftSiderBarWidget")); + m_leftSiderBarWidget->setGeometry(QRect(0, 0, 200, 640)); + + m_totalHLayout->addWidget(m_leftSiderBarWidget); + + m_rightVLayout = new QVBoxLayout(); + m_rightVLayout->setObjectName(QString::fromUtf8("m_rightVLayout")); + m_rightVLayout->setContentsMargins(0, 0, 0, 0); + + m_titleWidget = new QWidget(ui->centralwidget); + m_titleWidget->setObjectName(QString::fromUtf8("m_titleWidget")); + m_titleWidget->setGeometry(QRect(201, 0, 760, 40)); + m_rightVLayout->addWidget(m_titleWidget); + + // m_stackedWidget = new QStackedWidget(ui->centralwidget); + m_stackedWidget = new SystemBackup(ui->centralwidget); + m_stackedWidget->setObjectName(QString::fromUtf8("m_stackedWidget")); + m_stackedWidget->setGeometry(QRect(201, 40, 760, 600)); + m_rightVLayout->addWidget(m_stackedWidget); + + m_totalHLayout->addLayout(m_rightVLayout); + + initTileBar(); + initStyleSheet(); +} + +void MainDialog::initTileBar() +{ + m_titleLayout = new QHBoxLayout; + m_titleWidget->setLayout(m_titleLayout); + m_titleLayout->setContentsMargins(8, 4, 4, 0); + m_titleLayout->setSpacing(0); + + m_menuOptionBtn = new QToolButton(m_titleWidget); + m_minBtn = new QPushButton(m_titleWidget); + m_closeBtn = new QPushButton(m_titleWidget); + + m_menuOptionBtn->setToolTip(tr("Main menu")); + m_minBtn->setToolTip(tr("Minimize")); + m_closeBtn->setToolTip(tr("Close")); + + m_menuOptionBtn->setFixedSize(30, 30); + m_minBtn->setFixedSize(30, 30); + m_closeBtn->setFixedSize(30, 30); + + QMenu* backupMain = new QMenu(this); + backupMain->setObjectName("mainMenu"); + m_menuOptionBtn->setMenu(backupMain); + + QAction* backupTheme = new QAction(tr("Theme"),this); + backupMain->addAction(backupTheme); + QMenu* selectTheme = new QMenu(this); + selectTheme->setObjectName("selectTheme"); + backupTheme->setMenu(selectTheme); + QAction* defaultTheme = new QAction(tr("DefaultTheme"),this); + selectTheme->addAction(defaultTheme); + QAction* darkTheme = new QAction(tr("DarkTheme"),this); + selectTheme->addAction(darkTheme); + QAction* lightTheme = new QAction(tr("LightTheme"),this); + selectTheme->addAction(lightTheme); + + m_backupHelp = new QAction(tr("Help"),this); + backupMain->addAction(m_backupHelp); + m_backupAbout = new QAction(tr("About"),this); + backupMain->addAction(m_backupAbout); + m_backupExit = new QAction(tr("Exit"),this); + backupMain->addAction(m_backupExit); + + m_titleLayout->addStretch(); + m_titleLayout->addWidget(m_menuOptionBtn); + m_titleLayout->addSpacing(4); + m_titleLayout->addWidget(m_minBtn); + m_titleLayout->addSpacing(4); + m_titleLayout->addWidget(m_closeBtn); +} + +void MainDialog::initStyleSheet() +{ + // 主窗口 + // qApp->setWindowIcon(QIcon::fromTheme(THEME_YHKYLIN_BACKUP_TOOLS)); + this->setWindowTitle(tr("Backup and Restore")); + + // 工作区(此处代码加不加一个样,注释掉,留着学习,知道有这么回事) + //ui->centralwidget->setStyleSheet("QWidget#centralWidget{background: palette(base); border-radius: 6px;}"); + //ui->centralwidget->setAttribute(Qt::WA_TranslucentBackground); + + // 右侧title + // setStyleSheet方式設置樣式不能跟隨主題 + // m_titleWidget->setStyleSheet("QWidget#m_titleWidget{background-color:palette(base)}"); + m_titleWidget->setAutoFillBackground(true); + QPalette palette = m_titleWidget->palette(); + palette.setColor(QPalette::Background, palette.color(QPalette::Base)); + m_titleWidget->setPalette(palette); + + // m_menuOptionBtn->setStyleSheet("background-color: palette(base);"); + m_menuOptionBtn->setProperty("isWindowButton", 0x1); + m_menuOptionBtn->setProperty("useIconHighlightEffect", 0x2); + m_menuOptionBtn->setIcon(QIcon::fromTheme("open-menu-symbolic")); + m_menuOptionBtn->setAutoRaise(true); + m_menuOptionBtn->setPopupMode(QToolButton::InstantPopup); + // m_menuOptionBtn->setProperty("setIconHighlightEffectDefaultColor", m_closeBtn->palette().color(QPalette::Active, QPalette::Base)); + + m_minBtn->setProperty("isWindowButton", 0x1); + m_minBtn->setProperty("useIconHighlightEffect", 0x2); + m_minBtn->setFlat(true); + m_minBtn->setIcon(QIcon::fromTheme("window-minimize-symbolic")); + // m_minBtn->setProperty("setIconHighlightEffectDefaultColor", m_closeBtn->palette().color(QPalette::Active, QPalette::Base)); + + m_closeBtn->setProperty("isWindowButton", 0x2); + m_closeBtn->setProperty("useIconHighlightEffect", 0x8); + m_closeBtn->setFlat(true); + m_closeBtn->setIcon(QIcon::fromTheme("window-close-symbolic")); + // m_closeBtn->setProperty("setIconHighlightEffectDefaultColor", m_closeBtn->palette().color(QPalette::Active, QPalette::Base)); + + + // 右侧内容区域 + // m_stackedWidget->setStyleSheet("QStackedWidget#m_stackedWidget{background: palette(base); border-bottom-left-radius: 6px; border-bottom-right-radius: 6px;}"); + m_stackedWidget->setAutoFillBackground(true); + palette = m_stackedWidget->palette(); + palette.setColor(QPalette::Background, palette.color(QPalette::Base)); + m_stackedWidget->setPalette(palette); +} + +void MainDialog::initConnect() +{ + // 标题栏右侧按钮区域 + // 退出 + connect(m_backupExit, &QAction::triggered, this, &MainDialog::closeBtn); + + // 关于 + connect(m_backupAbout, &QAction::triggered, this, [=] { + // UkccAbout *ukcc = new UkccAbout(this); + // ukcc->exec(); + }); + + connect(m_backupHelp, &QAction::triggered, this, [=] { + QProcess process(this); + process.startDetached("kylin-user-guide -A kybackup"); + }); + + //最小化按钮 + connect(m_minBtn, &QPushButton::clicked, this, &MainDialog::showMinimized); + + //关闭按钮 + connect(m_closeBtn, &QPushButton::clicked, this, &MainDialog::closeBtn); +} + +/** + * @brief 多例启动信息槽 + * @param msg 信息,里面存放新启动实例的账户id + */ +void MainDialog::sltMessageReceived(const QString &msg) +{ + QString user = QString::number(getuid()); + if (msg == user) { +// this->setWindowFlag(Qt::WindowStaysOnTopHint,true); +// this->setWindowFlag(Qt::WindowStaysOnTopHint,false); + KWindowSystem::forceActiveWindow(this->winId()); + this->show(); + } +} + +void MainDialog::closeBtn() +{ + this->close(); + qApp->quit(); +} + diff --git a/kybackup/maindialog.h b/kybackup/maindialog.h index 6ab6e2b..cf993f3 100644 --- a/kybackup/maindialog.h +++ b/kybackup/maindialog.h @@ -2,6 +2,12 @@ #define MAINDIALOG_H #include +#include +#include +#include +#include +#include +#include "leftsiderbarwidget.h" QT_BEGIN_NAMESPACE namespace Ui { class MainDialog; } @@ -15,7 +21,35 @@ public: MainDialog(QWidget *parent = nullptr); ~MainDialog(); +public slots: + void sltMessageReceived(const QString &msg); + void closeBtn(); + private: + void initUI(); + void initTileBar(); + void initStyleSheet(); + void initConnect(); +private: + // 总体布局部分 + QHBoxLayout *m_totalHLayout = nullptr; + LeftsiderbarWidget *m_leftSiderBarWidget = nullptr; + QVBoxLayout *m_rightVLayout = nullptr; + QWidget *m_titleWidget = nullptr; + QStackedWidget *m_stackedWidget = nullptr; + + // 标题栏部分 + QHBoxLayout *m_titleLayout = nullptr; + QToolButton *m_menuOptionBtn = nullptr; + QPushButton *m_minBtn = nullptr; + QPushButton *m_closeBtn = nullptr; + + QAction *m_backupHelp = nullptr; + QAction *m_backupAbout = nullptr; + QAction *m_backupExit = nullptr; + + // 右侧内容展示部分 + Ui::MainDialog *ui; }; #endif // MAINDIALOG_H diff --git a/kybackup/maindialog.ui b/kybackup/maindialog.ui index 97e9edd..b22059b 100644 --- a/kybackup/maindialog.ui +++ b/kybackup/maindialog.ui @@ -6,16 +6,32 @@ 0 0 - 800 - 600 + 960 + 640 + + + 0 + 0 + + + + + 960 + 640 + + + + + 960 + 640 + + - MainDialog + - - diff --git a/kybackup/module/systembackup.cpp b/kybackup/module/systembackup.cpp new file mode 100644 index 0000000..82d1b12 --- /dev/null +++ b/kybackup/module/systembackup.cpp @@ -0,0 +1,80 @@ +#include "systembackup.h" +#include +#include + +#include "component/myiconlabel.h" + +SystemBackup::SystemBackup(QWidget *parent /*= nullptr*/) : + QStackedWidget(parent) +{ + initFirstWidget(); +} + +SystemBackup::~SystemBackup() +{} + +void SystemBackup::initFirstWidget() +{ + QWidget *first = new QWidget; + + QLabel *imageBackup_firstPage = new QLabel(first); + imageBackup_firstPage->setGeometry(421, 120, 300, 326); + QPixmap pixmap(":/images/system_backup.png"); + imageBackup_firstPage->setPixmap(pixmap); + imageBackup_firstPage->setScaledContents(true); + + QLabel *labelBackup_firstPage = new QLabel(tr("System Backup"), first); + // labelBackup_firstPage->setGeometry(40, 120, 300, 48); + labelBackup_firstPage->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + labelBackup_firstPage->setFixedHeight(48); + labelBackup_firstPage->move(41, 120); + // 默认水平左对齐,上下居中对齐;故不需要设置 + // labelBackup_firstPage->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + // labelBackup_firstPage->setText(tr("System Backup")); + QFont font; + font.setBold(true); + font.setPixelSize(36); + labelBackup_firstPage->setFont(font); + labelBackup_firstPage->adjustSize(); + labelBackup_firstPage->setAttribute(Qt::WA_TranslucentBackground); + + QLabel *labelNote_firstPage = new QLabel(first); + labelNote_firstPage->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + labelNote_firstPage->setFixedHeight(24); + labelNote_firstPage->move(41, 180); + labelNote_firstPage->setText(tr("Can be restored when files are damaged or lost")); + font.setBold(false); + font.setPixelSize(18); + labelNote_firstPage->setFont(font); + labelNote_firstPage->adjustSize(); + labelNote_firstPage->setAttribute(Qt::WA_TranslucentBackground); + + MyIconLabel *iconMultiBackup_firstPage = new MyIconLabel(first); + iconMultiBackup_firstPage->setGeometry(41, 244, 150, 36); + iconMultiBackup_firstPage->setThemeIcon("ukui-bf-many-spot-symbolic"); + iconMultiBackup_firstPage->setDesplayText(tr("Multi-Spot")); + iconMultiBackup_firstPage->setEnabled(false); + + MyIconLabel *iconSmallSize_firstPage = new MyIconLabel(first); + iconSmallSize_firstPage->setGeometry(201, 244, 150, 36); + iconSmallSize_firstPage->setThemeIcon("ukui-bf-many-spot-symbolic"); + iconSmallSize_firstPage->setDesplayText(tr("Small Size")); + iconSmallSize_firstPage->setEnabled(false); + + MyIconLabel *iconSecurity_firstPage = new MyIconLabel(first); + iconSecurity_firstPage->setGeometry(41, 296, 150, 36); + iconSecurity_firstPage->setThemeIcon("ukui-bf-many-spot-symbolic"); + iconSecurity_firstPage->setDesplayText(tr("Security")); + iconSecurity_firstPage->setEnabled(false); + + MyIconLabel *iconSimple_firstPage = new MyIconLabel(first); + iconSimple_firstPage->setGeometry(201, 296, 150, 36); + iconSimple_firstPage->setThemeIcon("ukui-bf-many-spot-symbolic"); + iconSimple_firstPage->setDesplayText(tr("Simple")); + iconSimple_firstPage->setEnabled(false); + + QPushButton *beginBackup = new QPushButton(this); + // beginBackup->setGeometry(); + + addWidget(first); +} diff --git a/kybackup/module/systembackup.h b/kybackup/module/systembackup.h new file mode 100644 index 0000000..6ba4bfc --- /dev/null +++ b/kybackup/module/systembackup.h @@ -0,0 +1,17 @@ +#ifndef SYSTEMBACKUP_H +#define SYSTEMBACKUP_H + +#include + +class SystemBackup : public QStackedWidget +{ + Q_OBJECT +public: + explicit SystemBackup(QWidget *parent = nullptr); + ~SystemBackup(); + +private: + void initFirstWidget(); +}; + +#endif // SYSTEMBACKUP_H diff --git a/kybackup/qtsingleapplication/qtlocalpeer.cpp b/kybackup/qtsingleapplication/qtlocalpeer.cpp new file mode 100644 index 0000000..a2bcb39 --- /dev/null +++ b/kybackup/qtsingleapplication/qtlocalpeer.cpp @@ -0,0 +1,230 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Solutions component. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "qtlocalpeer.h" +#include +#include +#include + +#if defined(Q_OS_WIN) +#include +#include +typedef BOOL(WINAPI*PProcessIdToSessionId)(DWORD,DWORD*); +static PProcessIdToSessionId pProcessIdToSessionId = 0; +#endif +#if defined(Q_OS_UNIX) +#include +#include +#include +#include +#include +#endif + +namespace QtLP_Private { +#include "qtlockedfile.cpp" +#if defined(Q_OS_WIN) +#include "qtlockedfile_win.cpp" +#else +#include "qtlockedfile_unix.cpp" +#endif +} + +const char* QtLocalPeer::ack = "uid:"; + +QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId) + : QObject(parent), id(appId) +{ + QString prefix = id; + if (id.isEmpty()) { + id = QCoreApplication::applicationFilePath(); +#if defined(Q_OS_WIN) + id = id.toLower(); +#endif + prefix = id.section(QLatin1Char('/'), -1); + } + prefix.remove(QRegExp("[^a-zA-Z]")); + // prefix.truncate(6); + + QByteArray idc = id.toUtf8(); + quint16 idNum = qChecksum(idc.constData(), idc.size()); + socketName = QLatin1String("qtsingleapp-") + prefix + + QLatin1Char('-') + QString::number(idNum, 16); + +#if defined(Q_OS_WIN) + if (!pProcessIdToSessionId) { + QLibrary lib("kernel32"); + pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId"); + } + if (pProcessIdToSessionId) { + DWORD sessionId = 0; + pProcessIdToSessionId(GetCurrentProcessId(), &sessionId); + socketName += QLatin1Char('-') + QString::number(sessionId, 16); + } +#else + // socketName += QLatin1Char('-') + QString::number(::getuid(), 16); +#endif + + server = new QLocalServer(this); + QString lockName = QDir(QDir::tempPath()).absolutePath() + + QLatin1Char('/') + socketName + + QLatin1String("-lockfile"); + createFile(lockName); + lockFile.setFileName(lockName); + lockFile.open(QIODevice::ReadWrite); +} + +bool QtLocalPeer::createFile(const QString& fileName) +{ +#if defined(Q_OS_UNIX) + QFile file(fileName); + if (file.exists()) { + chmod(fileName.toStdString().data(), S_IRWXU | S_IRWXG | S_IRWXO); + } + + int lock_file_fd = ::open(fileName.toStdString().data(), O_CREAT | O_RDWR, 0666); + if (0 > lock_file_fd) { + return false; + } + fchmod(lock_file_fd, S_IRWXU | S_IRWXG | S_IRWXO); + ::close(lock_file_fd); +#endif + return true; +} + +bool QtLocalPeer::isClient() +{ + if (lockFile.isLocked()) + return false; + + if (!lockFile.lock(QtLP_Private::QtLockedFile::WriteLock, false)) + return true; + + bool res = server->listen(socketName); +#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(4,5,0)) + // ### Workaround + if (!res && server->serverError() == QAbstractSocket::AddressInUseError) { + QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName); + res = server->listen(socketName); + } +#endif + if (!res) + qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString())); + QObject::connect(server, SIGNAL(newConnection()), SLOT(receiveConnection())); + return false; +} + + +QString QtLocalPeer::sendMessage(const QString &message, int timeout) +{ + if (!isClient()) + return ""; + + QLocalSocket socket; + bool connOk = false; + for(int i = 0; i < 2; i++) { + // Try twice, in case the other instance is just starting up + socket.connectToServer(socketName); + connOk = socket.waitForConnected(timeout/2); + if (connOk || i) + break; + int ms = 250; +#if defined(Q_OS_WIN) + Sleep(DWORD(ms)); +#else + struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 }; + nanosleep(&ts, NULL); +#endif + } + if (!connOk) + return ""; + + QByteArray uMsg(message.toUtf8()); + QDataStream ds(&socket); + ds.writeBytes(uMsg.constData(), uMsg.size()); + bool res = socket.waitForBytesWritten(timeout); + QByteArray ackMsg; + if (res) { + res &= socket.waitForReadyRead(timeout); // wait for ack + if (res) { + // res &= (socket.read(qstrlen(ack)) == ack); + ackMsg = socket.readAll(); + } + } + socket.disconnectFromServer(); + return QString::fromUtf8(ackMsg); +} + + +void QtLocalPeer::receiveConnection() +{ + QLocalSocket* socket = server->nextPendingConnection(); + if (!socket) + return; + + while (socket->bytesAvailable() < (int)sizeof(quint32)) + socket->waitForReadyRead(); + QDataStream ds(socket); + QByteArray uMsg; + quint32 remaining; + ds >> remaining; + uMsg.resize(remaining); + int got = 0; + char* uMsgBuf = uMsg.data(); + do { + got = ds.readRawData(uMsgBuf, remaining); + remaining -= got; + uMsgBuf += got; + } while (remaining && got >= 0 && socket->waitForReadyRead(2000)); + if (got < 0) { + qWarning("QtLocalPeer: Message reception failed %s", socket->errorString().toLatin1().constData()); + delete socket; + return; + } + QString message(QString::fromUtf8(uMsg)); + // socket->write(ack, qstrlen(ack)); + QString sendMsg = QString::number(::getuid()); + QByteArray msg = sendMsg.toUtf8(); + socket->write(msg); + socket->waitForBytesWritten(1000); + socket->waitForDisconnected(1000); // make sure client reads ack + delete socket; + emit messageReceived(message); //### (might take a long time to return) +} diff --git a/kybackup/qtsingleapplication/qtlocalpeer.h b/kybackup/qtsingleapplication/qtlocalpeer.h new file mode 100644 index 0000000..acc01e8 --- /dev/null +++ b/kybackup/qtsingleapplication/qtlocalpeer.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Solutions component. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTLOCALPEER_H +#define QTLOCALPEER_H + +#include +#include +#include + +#include "qtlockedfile.h" + +class QtLocalPeer : public QObject +{ + Q_OBJECT + +public: + QtLocalPeer(QObject *parent = 0, const QString &appId = QString()); + bool isClient(); + QString sendMessage(const QString &message, int timeout); + QString applicationId() const + { return id; } + +Q_SIGNALS: + void messageReceived(const QString &message); + +protected Q_SLOTS: + void receiveConnection(); + +protected: + bool createFile(const QString& fileName); + + QString id; + QString socketName; + QLocalServer* server; + QtLP_Private::QtLockedFile lockFile; + +private: + static const char* ack; +}; + +#endif // QTLOCALPEER_H diff --git a/kybackup/qtsingleapplication/qtlockedfile.cpp b/kybackup/qtsingleapplication/qtlockedfile.cpp new file mode 100644 index 0000000..c142a86 --- /dev/null +++ b/kybackup/qtsingleapplication/qtlockedfile.cpp @@ -0,0 +1,193 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Solutions component. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtlockedfile.h" + +/*! + \class QtLockedFile + + \brief The QtLockedFile class extends QFile with advisory locking + functions. + + A file may be locked in read or write mode. Multiple instances of + \e QtLockedFile, created in multiple processes running on the same + machine, may have a file locked in read mode. Exactly one instance + may have it locked in write mode. A read and a write lock cannot + exist simultaneously on the same file. + + The file locks are advisory. This means that nothing prevents + another process from manipulating a locked file using QFile or + file system functions offered by the OS. Serialization is only + guaranteed if all processes that access the file use + QLockedFile. Also, while holding a lock on a file, a process + must not open the same file again (through any API), or locks + can be unexpectedly lost. + + The lock provided by an instance of \e QtLockedFile is released + whenever the program terminates. This is true even when the + program crashes and no destructors are called. +*/ + +/*! \enum QtLockedFile::LockMode + + This enum describes the available lock modes. + + \value ReadLock A read lock. + \value WriteLock A write lock. + \value NoLock Neither a read lock nor a write lock. +*/ + +/*! + Constructs an unlocked \e QtLockedFile object. This constructor + behaves in the same way as \e QFile::QFile(). + + \sa QFile::QFile() +*/ +QtLockedFile::QtLockedFile() + : QFile() +{ +#ifdef Q_OS_WIN + wmutex = 0; + rmutex = 0; +#endif + m_lock_mode = NoLock; +} + +/*! + Constructs an unlocked QtLockedFile object with file \a name. This + constructor behaves in the same way as \e QFile::QFile(const + QString&). + + \sa QFile::QFile() +*/ +QtLockedFile::QtLockedFile(const QString &name) + : QFile(name) +{ +#ifdef Q_OS_WIN + wmutex = 0; + rmutex = 0; +#endif + m_lock_mode = NoLock; +} + +/*! + Opens the file in OpenMode \a mode. + + This is identical to QFile::open(), with the one exception that the + Truncate mode flag is disallowed. Truncation would conflict with the + advisory file locking, since the file would be modified before the + write lock is obtained. If truncation is required, use resize(0) + after obtaining the write lock. + + Returns true if successful; otherwise false. + + \sa QFile::open(), QFile::resize() +*/ +bool QtLockedFile::open(OpenMode mode) +{ + if (mode & QIODevice::Truncate) { + qWarning("QtLockedFile::open(): Truncate mode not allowed."); + return false; + } + return QFile::open(mode); +} + +/*! + Returns \e true if this object has a in read or write lock; + otherwise returns \e false. + + \sa lockMode() +*/ +bool QtLockedFile::isLocked() const +{ + return m_lock_mode != NoLock; +} + +/*! + Returns the type of lock currently held by this object, or \e + QtLockedFile::NoLock. + + \sa isLocked() +*/ +QtLockedFile::LockMode QtLockedFile::lockMode() const +{ + return m_lock_mode; +} + +/*! + \fn bool QtLockedFile::lock(LockMode mode, bool block = true) + + Obtains a lock of type \a mode. The file must be opened before it + can be locked. + + If \a block is true, this function will block until the lock is + aquired. If \a block is false, this function returns \e false + immediately if the lock cannot be aquired. + + If this object already has a lock of type \a mode, this function + returns \e true immediately. If this object has a lock of a + different type than \a mode, the lock is first released and then a + new lock is obtained. + + This function returns \e true if, after it executes, the file is + locked by this object, and \e false otherwise. + + \sa unlock(), isLocked(), lockMode() +*/ + +/*! + \fn bool QtLockedFile::unlock() + + Releases a lock. + + If the object has no lock, this function returns immediately. + + This function returns \e true if, after it executes, the file is + not locked by this object, and \e false otherwise. + + \sa lock(), isLocked(), lockMode() +*/ + +/*! + \fn QtLockedFile::~QtLockedFile() + + Destroys the \e QtLockedFile object. If any locks were held, they + are released. +*/ diff --git a/kybackup/qtsingleapplication/qtlockedfile.h b/kybackup/qtsingleapplication/qtlockedfile.h new file mode 100644 index 0000000..84c18e5 --- /dev/null +++ b/kybackup/qtsingleapplication/qtlockedfile.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Solutions component. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTLOCKEDFILE_H +#define QTLOCKEDFILE_H + +#include +#ifdef Q_OS_WIN +#include +#endif + +#if defined(Q_OS_WIN) +# if !defined(QT_QTLOCKEDFILE_EXPORT) && !defined(QT_QTLOCKEDFILE_IMPORT) +# define QT_QTLOCKEDFILE_EXPORT +# elif defined(QT_QTLOCKEDFILE_IMPORT) +# if defined(QT_QTLOCKEDFILE_EXPORT) +# undef QT_QTLOCKEDFILE_EXPORT +# endif +# define QT_QTLOCKEDFILE_EXPORT __declspec(dllimport) +# elif defined(QT_QTLOCKEDFILE_EXPORT) +# undef QT_QTLOCKEDFILE_EXPORT +# define QT_QTLOCKEDFILE_EXPORT __declspec(dllexport) +# endif +#else +# define QT_QTLOCKEDFILE_EXPORT +#endif + +namespace QtLP_Private { + +class QT_QTLOCKEDFILE_EXPORT QtLockedFile : public QFile +{ +public: + enum LockMode { NoLock = 0, ReadLock, WriteLock }; + + QtLockedFile(); + QtLockedFile(const QString &name); + ~QtLockedFile(); + + bool open(OpenMode mode); + + bool lock(LockMode mode, bool block = true); + bool unlock(); + bool isLocked() const; + LockMode lockMode() const; + +private: +#ifdef Q_OS_WIN + Qt::HANDLE wmutex; + Qt::HANDLE rmutex; + QVector rmutexes; + QString mutexname; + + Qt::HANDLE getMutexHandle(int idx, bool doCreate); + bool waitMutex(Qt::HANDLE mutex, bool doBlock); + +#endif + LockMode m_lock_mode; +}; +} +#endif diff --git a/kybackup/qtsingleapplication/qtlockedfile_unix.cpp b/kybackup/qtsingleapplication/qtlockedfile_unix.cpp new file mode 100644 index 0000000..976c1b9 --- /dev/null +++ b/kybackup/qtsingleapplication/qtlockedfile_unix.cpp @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Solutions component. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include + +#include "qtlockedfile.h" + +bool QtLockedFile::lock(LockMode mode, bool block) +{ + if (!isOpen()) { + qWarning("QtLockedFile::lock(): file is not opened"); + return false; + } + + if (mode == NoLock) + return unlock(); + + if (mode == m_lock_mode) + return true; + + if (m_lock_mode != NoLock) + unlock(); + + struct flock fl; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + fl.l_type = (mode == ReadLock) ? F_RDLCK : F_WRLCK; + int cmd = block ? F_SETLKW : F_SETLK; + int ret = fcntl(handle(), cmd, &fl); + + if (ret == -1) { + if (errno != EINTR && errno != EAGAIN) + qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); + return false; + } + + + m_lock_mode = mode; + return true; +} + + +bool QtLockedFile::unlock() +{ + if (!isOpen()) { + qWarning("QtLockedFile::unlock(): file is not opened"); + return false; + } + + if (!isLocked()) + return true; + + struct flock fl; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + fl.l_type = F_UNLCK; + int ret = fcntl(handle(), F_SETLKW, &fl); + + if (ret == -1) { + qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); + return false; + } + + m_lock_mode = NoLock; + return true; +} + +QtLockedFile::~QtLockedFile() +{ + if (isOpen()) + unlock(); +} + diff --git a/kybackup/qtsingleapplication/qtsingleapplication.cpp b/kybackup/qtsingleapplication/qtsingleapplication.cpp new file mode 100644 index 0000000..a206b79 --- /dev/null +++ b/kybackup/qtsingleapplication/qtsingleapplication.cpp @@ -0,0 +1,351 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Solutions component. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "qtsingleapplication.h" +#include "qtlocalpeer.h" +#include +#include + + +/*! + \class QtSingleApplication qtsingleapplication.h + \brief The QtSingleApplication class provides an API to detect and + communicate with running instances of an application. + + This class allows you to create applications where only one + instance should be running at a time. I.e., if the user tries to + launch another instance, the already running instance will be + activated instead. Another usecase is a client-server system, + where the first started instance will assume the role of server, + and the later instances will act as clients of that server. + + By default, the full path of the executable file is used to + determine whether two processes are instances of the same + application. You can also provide an explicit identifier string + that will be compared instead. + + The application should create the QtSingleApplication object early + in the startup phase, and call isRunning() to find out if another + instance of this application is already running. If isRunning() + returns false, it means that no other instance is running, and + this instance has assumed the role as the running instance. In + this case, the application should continue with the initialization + of the application user interface before entering the event loop + with exec(), as normal. + + The messageReceived() signal will be emitted when the running + application receives messages from another instance of the same + application. When a message is received it might be helpful to the + user to raise the application so that it becomes visible. To + facilitate this, QtSingleApplication provides the + setActivationWindow() function and the activateWindow() slot. + + If isRunning() returns true, another instance is already + running. It may be alerted to the fact that another instance has + started by using the sendMessage() function. Also data such as + startup parameters (e.g. the name of the file the user wanted this + new instance to open) can be passed to the running instance with + this function. Then, the application should terminate (or enter + client mode). + + If isRunning() returns true, but sendMessage() fails, that is an + indication that the running instance is frozen. + + Here's an example that shows how to convert an existing + application to use QtSingleApplication. It is very simple and does + not make use of all QtSingleApplication's functionality (see the + examples for that). + + \code + // Original + int main(int argc, char **argv) + { + QApplication app(argc, argv); + + MyMainWidget mmw; + mmw.show(); + return app.exec(); + } + + // Single instance + int main(int argc, char **argv) + { + QtSingleApplication app(argc, argv); + + if (app.isRunning()) + return !app.sendMessage(someDataString); + + MyMainWidget mmw; + app.setActivationWindow(&mmw); + mmw.show(); + return app.exec(); + } + \endcode + + Once this QtSingleApplication instance is destroyed (normally when + the process exits or crashes), when the user next attempts to run the + application this instance will not, of course, be encountered. The + next instance to call isRunning() or sendMessage() will assume the + role as the new running instance. + + For console (non-GUI) applications, QtSingleCoreApplication may be + used instead of this class, to avoid the dependency on the QtGui + library. + + \sa QtSingleCoreApplication +*/ + + +void QtSingleApplication::sysInit(const QString &appId) +{ + actWin = 0; + peer = new QtLocalPeer(this, appId); + connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); +} + + +/*! + Creates a QtSingleApplication object. The application identifier + will be QCoreApplication::applicationFilePath(). \a argc, \a + argv, and \a GUIenabled are passed on to the QAppliation constructor. + + If you are creating a console application (i.e. setting \a + GUIenabled to false), you may consider using + QtSingleCoreApplication instead. +*/ + +QtSingleApplication::QtSingleApplication(int &argc, char **argv, bool GUIenabled) + : QApplication(argc, argv, GUIenabled) +{ + sysInit(); +} + + +/*! + Creates a QtSingleApplication object with the application + identifier \a appId. \a argc and \a argv are passed on to the + QAppliation constructor. +*/ + +QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char **argv) + : QApplication(argc, argv) +{ + sysInit(appId); +} + +#if QT_VERSION < 0x050000 + +/*! + Creates a QtSingleApplication object. The application identifier + will be QCoreApplication::applicationFilePath(). \a argc, \a + argv, and \a type are passed on to the QAppliation constructor. +*/ +QtSingleApplication::QtSingleApplication(int &argc, char **argv, Type type) + : QApplication(argc, argv, type) +{ + sysInit(); +} + + +# if defined(Q_WS_X11) +/*! + Special constructor for X11, ref. the documentation of + QApplication's corresponding constructor. The application identifier + will be QCoreApplication::applicationFilePath(). \a dpy, \a visual, + and \a cmap are passed on to the QApplication constructor. +*/ +QtSingleApplication::QtSingleApplication(Display* dpy, Qt::HANDLE visual, Qt::HANDLE cmap) + : QApplication(dpy, visual, cmap) +{ + sysInit(); +} + +/*! + Special constructor for X11, ref. the documentation of + QApplication's corresponding constructor. The application identifier + will be QCoreApplication::applicationFilePath(). \a dpy, \a argc, \a + argv, \a visual, and \a cmap are passed on to the QApplication + constructor. +*/ +QtSingleApplication::QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap) + : QApplication(dpy, argc, argv, visual, cmap) +{ + sysInit(); +} + +/*! + Special constructor for X11, ref. the documentation of + QApplication's corresponding constructor. The application identifier + will be \a appId. \a dpy, \a argc, \a + argv, \a visual, and \a cmap are passed on to the QApplication + constructor. +*/ +QtSingleApplication::QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap) + : QApplication(dpy, argc, argv, visual, cmap) +{ + sysInit(appId); +} +# endif // Q_WS_X11 +#endif // QT_VERSION < 0x050000 + + +/*! + Returns true if another instance of this application is running; + otherwise false. + + This function does not find instances of this application that are + being run by a different user (on Windows: that are running in + another session). + + \sa sendMessage() +*/ + +bool QtSingleApplication::isRunning() +{ + return peer->isClient(); +} + + +/*! + Tries to send the text \a message to the currently running + instance. The QtSingleApplication object in the running instance + will emit the messageReceived() signal when it receives the + message. + + This function returns true if the message has been sent to, and + processed by, the current instance. If there is no instance + currently running, or if the running instance fails to process the + message within \a timeout milliseconds, this function return false. + + \sa isRunning(), messageReceived() +*/ +QString QtSingleApplication::sendMessage(const QString &message, int timeout) +{ + return peer->sendMessage(message, timeout); +} + + +/*! + Returns the application identifier. Two processes with the same + identifier will be regarded as instances of the same application. +*/ +QString QtSingleApplication::id() const +{ + return peer->applicationId(); +} + + +/*! + Sets the activation window of this application to \a aw. The + activation window is the widget that will be activated by + activateWindow(). This is typically the application's main window. + + If \a activateOnMessage is true (the default), the window will be + activated automatically every time a message is received, just prior + to the messageReceived() signal being emitted. + + \sa activateWindow(), messageReceived() +*/ + + +void QtSingleApplication::setActivationWindow(QWidget* aw, bool activateOnMessage) +{ + actWin = aw; + if (activateOnMessage) + connect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow())); + else + disconnect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow())); +} + + +/*! + Returns the applications activation window if one has been set by + calling setActivationWindow(), otherwise returns 0. + + \sa setActivationWindow() +*/ + +QWidget* QtSingleApplication::activationWindow() const +{ + return actWin; +} + + +/*! + De-minimizes, raises, and activates this application's activation window. + This function does nothing if no activation window has been set. + + This is a convenience function to show the user that this + application instance has been activated when he has tried to start + another instance. + + This function should typically be called in response to the + messageReceived() signal. By default, that will happen + automatically, if an activation window has been set. + + \sa setActivationWindow(), messageReceived(), initialize() +*/ +void QtSingleApplication::activateWindow() +{ + if (actWin) { + actWin->setWindowState(actWin->windowState() & ~Qt::WindowMinimized); + actWin->raise(); + actWin->activateWindow(); + actWin->show(); + } +} + + +/*! + \fn void QtSingleApplication::messageReceived(const QString& message) + + This signal is emitted when the current instance receives a \a + message from another instance of this application. + + \sa sendMessage(), setActivationWindow(), activateWindow() +*/ + + +/*! + \fn void QtSingleApplication::initialize(bool dummy = true) + + \obsolete +*/ diff --git a/kybackup/qtsingleapplication/qtsingleapplication.h b/kybackup/qtsingleapplication/qtsingleapplication.h new file mode 100644 index 0000000..9cd774c --- /dev/null +++ b/kybackup/qtsingleapplication/qtsingleapplication.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Solutions component. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTSINGLEAPPLICATION_H +#define QTSINGLEAPPLICATION_H + + +#include + +class QtLocalPeer; + +#if defined(Q_OS_WIN) +# if !defined(QT_QTSINGLEAPPLICATION_EXPORT) && !defined(QT_QTSINGLEAPPLICATION_IMPORT) +# define QT_QTSINGLEAPPLICATION_EXPORT +# elif defined(QT_QTSINGLEAPPLICATION_IMPORT) +# if defined(QT_QTSINGLEAPPLICATION_EXPORT) +# undef QT_QTSINGLEAPPLICATION_EXPORT +# endif +# define QT_QTSINGLEAPPLICATION_EXPORT __declspec(dllimport) +# elif defined(QT_QTSINGLEAPPLICATION_EXPORT) +# undef QT_QTSINGLEAPPLICATION_EXPORT +# define QT_QTSINGLEAPPLICATION_EXPORT __declspec(dllexport) +# endif +#else +# define QT_QTSINGLEAPPLICATION_EXPORT +#endif + +class QT_QTSINGLEAPPLICATION_EXPORT QtSingleApplication : public QApplication +{ + Q_OBJECT + +public: + QtSingleApplication(int &argc, char **argv, bool GUIenabled = true); + QtSingleApplication(const QString &id, int &argc, char **argv); +#if QT_VERSION < 0x050000 + QtSingleApplication(int &argc, char **argv, Type type); +# if defined(Q_WS_X11) + QtSingleApplication(Display* dpy, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0); + QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap= 0); + QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0); +# endif // Q_WS_X11 +#endif // QT_VERSION < 0x050000 + + bool isRunning(); + QString id() const; + + void setActivationWindow(QWidget* aw, bool activateOnMessage = true); + QWidget* activationWindow() const; + + // Obsolete: + void initialize(bool dummy = true) + { isRunning(); Q_UNUSED(dummy) } + +public Q_SLOTS: + QString sendMessage(const QString &message, int timeout = 5000); + void activateWindow(); + + +Q_SIGNALS: + void messageReceived(const QString &message); + + +private: + void sysInit(const QString &appId = QString()); + QtLocalPeer *peer; + QWidget *actWin; +}; + +#endif // QTSINGLEAPPLICATION_H diff --git a/kybackup/resource/images/data_backup.svg b/kybackup/resource/images/data_backup.svg new file mode 100644 index 0000000..cbeefc4 --- /dev/null +++ b/kybackup/resource/images/data_backup.svg @@ -0,0 +1,12 @@ + + + pic_sjbf + + + + + + + + + \ No newline at end of file diff --git a/kybackup/resource/images/data_restore.svg b/kybackup/resource/images/data_restore.svg new file mode 100644 index 0000000..1385777 --- /dev/null +++ b/kybackup/resource/images/data_restore.svg @@ -0,0 +1,12 @@ + + + pic_sjhy + + + + + + + + + \ No newline at end of file diff --git a/kybackup/resource/images/ghost_image.svg b/kybackup/resource/images/ghost_image.svg new file mode 100644 index 0000000..4192f30 --- /dev/null +++ b/kybackup/resource/images/ghost_image.svg @@ -0,0 +1,12 @@ + + + pic_Ghost + + + + + + + + + \ No newline at end of file diff --git a/kybackup/resource/images/sysem_restore.svg b/kybackup/resource/images/sysem_restore.svg new file mode 100644 index 0000000..353b105 --- /dev/null +++ b/kybackup/resource/images/sysem_restore.svg @@ -0,0 +1,12 @@ + + + pic_xthy + + + + + + + + + \ No newline at end of file diff --git a/kybackup/resource/images/system_backup.png b/kybackup/resource/images/system_backup.png new file mode 100644 index 0000000000000000000000000000000000000000..1f000875d41d8406b9ab392d7cc84619d70bec28 GIT binary patch literal 72461 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

dO6I;j*GQ?t`+9;dE_pktD@eom6gUxvnZ&pN># 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. + * + * Authors: Yue Lan + * + */ + +#include "xatom-helper.h" + +#include +#include +#include +#include +#include + +static XAtomHelper* global_instance = nullptr; + +XAtomHelper* XAtomHelper::getInstance() +{ + if (!global_instance) + global_instance = new XAtomHelper; + return global_instance; +} + +bool XAtomHelper::isFrameLessWindow(int winId) +{ + auto hints = getInstance()->getWindowMotifHint(winId); + if (hints.flags == MWM_HINTS_DECORATIONS && hints.functions == 1) { + return true; + } + return false; +} + +bool XAtomHelper::isWindowDecorateBorderOnly(int winId) +{ + return isWindowMotifHintDecorateBorderOnly(getInstance()->getWindowMotifHint(winId)); +} + +bool XAtomHelper::isWindowMotifHintDecorateBorderOnly(const MotifWmHints& hint) +{ + bool isDeco = false; + if (hint.flags & MWM_HINTS_DECORATIONS && hint.flags != MWM_HINTS_DECORATIONS) { + if (hint.decorations == MWM_DECOR_BORDER) + isDeco = true; + } + return isDeco; +} + +bool XAtomHelper::isUKUICsdSupported() +{ + // fixme: + return false; +} + +bool XAtomHelper::isUKUIDecorationWindow(int winId) +{ + if (m_ukuiDecorationAtion == None) + return false; + + Atom type; + int format; + ulong nitems; + ulong bytes_after; + uchar* data; + + bool isUKUIDecoration = false; + + XGetWindowProperty(QX11Info::display(), winId, m_ukuiDecorationAtion, + 0, LONG_MAX, false, + m_ukuiDecorationAtion, &type, + &format, &nitems, + &bytes_after, &data); + + if (type == m_ukuiDecorationAtion) { + if (nitems == 1) { + isUKUIDecoration = data[0]; + } + } + + return isUKUIDecoration; +} + +UnityCorners XAtomHelper::getWindowBorderRadius(int winId) +{ + UnityCorners corners; + + Atom type; + int format; + ulong nitems; + ulong bytes_after; + uchar* data; + + if (m_unityBorderRadiusAtom != None) { + XGetWindowProperty(QX11Info::display(), winId, m_unityBorderRadiusAtom, + 0, LONG_MAX, false, + XA_CARDINAL, &type, + &format, &nitems, + &bytes_after, &data); + + if (type == XA_CARDINAL) { + if (nitems == 4) { + corners.topLeft = static_cast(data[0]); + corners.topRight = static_cast(data[1 * sizeof(ulong)]); + corners.bottomLeft = static_cast(data[2 * sizeof(ulong)]); + corners.bottomRight = static_cast(data[3 * sizeof(ulong)]); + } + XFree(data); + } + } + + return corners; +} + +void XAtomHelper::setWindowBorderRadius(int winId, const UnityCorners& data) +{ + if (m_unityBorderRadiusAtom == None) + return; + + ulong corners[4] = { data.topLeft, data.topRight, data.bottomLeft, data.bottomRight }; + + XChangeProperty(QX11Info::display(), winId, m_unityBorderRadiusAtom, XA_CARDINAL, + 32, XCB_PROP_MODE_REPLACE, (const unsigned char*)&corners, sizeof(corners) / sizeof(corners[0])); +} + +void XAtomHelper::setWindowBorderRadius(int winId, int topLeft, int topRight, int bottomLeft, int bottomRight) +{ + if (m_unityBorderRadiusAtom == None) + return; + + ulong corners[4] = { (ulong)topLeft, (ulong)topRight, (ulong)bottomLeft, (ulong)bottomRight }; + + XChangeProperty(QX11Info::display(), winId, m_unityBorderRadiusAtom, XA_CARDINAL, + 32, XCB_PROP_MODE_REPLACE, (const unsigned char*)&corners, sizeof(corners) / sizeof(corners[0])); +} + +void XAtomHelper::setUKUIDecoraiontHint(int winId, bool set) +{ + if (m_ukuiDecorationAtion == None) + return; + + XChangeProperty(QX11Info::display(), winId, m_ukuiDecorationAtion, m_ukuiDecorationAtion, 32, XCB_PROP_MODE_REPLACE, (const unsigned char*)&set, 1); +} + +void XAtomHelper::setWindowMotifHint(int winId, const MotifWmHints& hints) +{ + if (m_unityBorderRadiusAtom == None) + return; + + XChangeProperty(QX11Info::display(), winId, m_motifWMHintsAtom, m_motifWMHintsAtom, + 32, XCB_PROP_MODE_REPLACE, (const unsigned char*)&hints, sizeof(MotifWmHints) / sizeof(ulong)); +} + +MotifWmHints XAtomHelper::getWindowMotifHint(int winId) +{ + MotifWmHints hints; + + if (m_unityBorderRadiusAtom == None) + return hints; + + uchar* data; + Atom type; + int format; + ulong nitems; + ulong bytes_after; + + XGetWindowProperty(QX11Info::display(), winId, m_motifWMHintsAtom, + 0, sizeof(MotifWmHints) / sizeof(long), false, AnyPropertyType, &type, + &format, &nitems, &bytes_after, &data); + + if (type == None) { + return hints; + } else { + hints = *(MotifWmHints*)data; + XFree(data); + } + return hints; +} + +XAtomHelper::XAtomHelper(QObject* parent) + : QObject(parent) +{ + if (!QX11Info::isPlatformX11()) + return; + + m_motifWMHintsAtom = XInternAtom(QX11Info::display(), "_MOTIF_WM_HINTS", true); + m_unityBorderRadiusAtom = XInternAtom(QX11Info::display(), "_UNITY_GTK_BORDER_RADIUS", false); + m_ukuiDecorationAtion = XInternAtom(QX11Info::display(), "_KWIN_UKUI_DECORAION", false); +} + +Atom XAtomHelper::registerUKUICsdNetWmSupportAtom() +{ + // fixme: + return None; +} + +void XAtomHelper::unregisterUKUICsdNetWmSupportAtom() +{ + // fixme: +} diff --git a/kybackup/xatom-helper.h b/kybackup/xatom-helper.h new file mode 100644 index 0000000..8fc9421 --- /dev/null +++ b/kybackup/xatom-helper.h @@ -0,0 +1,107 @@ +/* + * KWin Style UKUI + * + * Copyright (C) 2020, KylinSoft Co., Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Authors: Yue Lan + * + */ + +#ifndef XATOMHELPER_H +#define XATOMHELPER_H + +#include + +struct UnityCorners { + ulong topLeft = 0; + ulong topRight = 0; + ulong bottomLeft = 0; + ulong bottomRight = 0; +}; + +typedef struct { + ulong flags = 0; + ulong functions = 0; + ulong decorations = 0; + long input_mode = 0; + ulong status = 0; +} MotifWmHints, MwmHints; + +#define MWM_HINTS_FUNCTIONS (1L << 0) +#define MWM_HINTS_DECORATIONS (1L << 1) +#define MWM_HINTS_INPUT_MODE (1L << 2) +#define MWM_HINTS_STATUS (1L << 3) + +#define MWM_FUNC_ALL (1L << 0) +#define MWM_FUNC_RESIZE (1L << 1) +#define MWM_FUNC_MOVE (1L << 2) +#define MWM_FUNC_MINIMIZE (1L << 3) +#define MWM_FUNC_MAXIMIZE (1L << 4) +#define MWM_FUNC_CLOSE (1L << 5) + +#define MWM_DECOR_ALL (1L << 0) +#define MWM_DECOR_BORDER (1L << 1) +#define MWM_DECOR_RESIZEH (1L << 2) +#define MWM_DECOR_TITLE (1L << 3) +#define MWM_DECOR_MENU (1L << 4) +#define MWM_DECOR_MINIMIZE (1L << 5) +#define MWM_DECOR_MAXIMIZE (1L << 6) + +#define MWM_INPUT_MODELESS 0 +#define MWM_INPUT_PRIMARY_APPLICATION_MODAL 1 +#define MWM_INPUT_SYSTEM_MODAL 2 +#define MWM_INPUT_FULL_APPLICATION_MODAL 3 +#define MWM_INPUT_APPLICATION_MODAL MWM_INPUT_PRIMARY_APPLICATION_MODAL + +#define MWM_TEAROFF_WINDOW (1L << 0) + +namespace UKUI { +class Decoration; +} + +class XAtomHelper : public QObject { + friend class UKUI::Decoration; + Q_OBJECT +public: + static XAtomHelper* getInstance(); + + static bool isFrameLessWindow(int winId); + + bool isWindowDecorateBorderOnly(int winId); + bool isWindowMotifHintDecorateBorderOnly(const MotifWmHints& hint); + bool isUKUICsdSupported(); + bool isUKUIDecorationWindow(int winId); + + UnityCorners getWindowBorderRadius(int winId); + void setWindowBorderRadius(int winId, const UnityCorners& data); + void setWindowBorderRadius(int winId, int topLeft, int topRight, int bottomLeft, int bottomRight); + void setUKUIDecoraiontHint(int winId, bool set = true); + + void setWindowMotifHint(int winId, const MotifWmHints& hints); + MotifWmHints getWindowMotifHint(int winId); + +private: + explicit XAtomHelper(QObject* parent = nullptr); + + unsigned long registerUKUICsdNetWmSupportAtom(); + void unregisterUKUICsdNetWmSupportAtom(); + + unsigned long m_motifWMHintsAtom = 0l; + unsigned long m_unityBorderRadiusAtom = 0l; + unsigned long m_ukuiDecorationAtion = 0l; +}; + +#endif // XATOMHELPER_H diff --git a/yhkylin-backup-tool.pro.user b/yhkylin-backup-tool.pro.user index d30b5e5..2186499 100644 --- a/yhkylin-backup-tool.pro.user +++ b/yhkylin-backup-tool.pro.user @@ -1,6 +1,6 @@ - + EnvironmentId @@ -64,7 +64,7 @@ {5440484b-1916-43f8-99de-3fbc4fe13c66} 0 0 - 0 + 1 /home/zhaominyong/workspace/kylin-backup-tools-new/build-yhkylin-backup-tool-unknown-Debug