commit 2fd08bfafc075a8755ade8b4861a216d588702c0 Author: sleepwithoutbz Date: Tue Sep 16 00:33:46 2025 +0800 Init commit. diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..19fa6ca --- /dev/null +++ b/.clang-format @@ -0,0 +1,37 @@ +BasedOnStyle: LLVM + +# 语言: None, Cpp, Java, JavaScript, ObjC, Proto, TableGen, TextProto +Language: Cpp + +# tab宽度 +TabWidth: 4 + +# 使用tab字符: Never, ForIndentation, ForContinuationAndIndentation, Always +UseTab: Never + +# 每行字符的限制,0表示没有限制 +ColumnLimit: 100 + +# 访问说明符(public、private等)的偏移 +AccessModifierOffset: -4 + +# 缩进case标签 +IndentCaseLabels: false + +# 要使用的预处理器指令缩进样式 +IndentPPDirectives: AfterHash + +# 缩进宽度 +IndentWidth: 4 + +# 函数返回类型换行时,缩进函数声明或函数定义的函数名 +IndentWrappedFunctionNames: false + +# 连续赋值时,对齐所有等号 +AlignConsecutiveAssignments: true + +# 连续声明时,对齐所有声明的变量名 +AlignConsecutiveDeclarations: true + +# 用于在使用反斜杠换行中对齐反斜杠的选项 +AlignEscapedNewlines: Left diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4b7653d --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +build/ +.cache/ +.vs/ +.vscode/ + +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f98d6bb --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,51 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(systray LANGUAGES CXX) + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) + +qt_standard_project_setup() + +# 导出src下的cpp文件 +file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS src/*.cpp) +qt_add_executable(systray ${SRC_FILES}) + +set_target_properties(systray PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(systray PRIVATE + Qt6::Core + Qt6::Gui + Qt6::Widgets +) + +# Resources: +set(systray_resource_files + "images/bad.png" + "images/heart.png" + "images/trash.png" +) + +qt_add_resources(systray "systray" + PREFIX + "/" + FILES + ${systray_resource_files} +) + +install(TARGETS systray + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_generate_deploy_app_script( + TARGET systray + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR +) +install(SCRIPT ${deploy_script}) diff --git a/CMakeLists.txt.user b/CMakeLists.txt.user new file mode 100644 index 0000000..11404e8 --- /dev/null +++ b/CMakeLists.txt.user @@ -0,0 +1,418 @@ + + + + + + EnvironmentId + {fd82fbfc-f75d-4f93-a8b6-cde9869ae40a} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + true + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 0 + 80 + true + true + 1 + 0 + false + true + false + 2 + true + true + 0 + 8 + true + false + 1 + true + true + true + *.md, *.MD, Makefile + false + true + true + + + + ProjectExplorer.Project.PluginSettings + + + true + false + true + true + true + true + + false + + + 0 + true + + true + true + Builtin.DefaultTidyAndClazy + 8 + true + + + + true + + + + + ProjectExplorer.Project.Target.0 + + Desktop + true + Desktop Qt 6.9.2 MinGW 64-bit + Desktop Qt 6.9.2 MinGW 64-bit + qt.qt6.692.win64_mingw_kit + 0 + 0 + 0 + + Debug + 2 + false + + -DCMAKE_GENERATOR:STRING=Ninja +-DQT_MAINTENANCE_TOOL:FILEPATH=D:/Programs/Qt/MaintenanceTool.exe +-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} +-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} +-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake +-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DCMAKE_BUILD_TYPE:STRING=Debug +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} + 0 + D:\Programs\Qt\Examples\Qt-6.9.2\widgets\desktop\systray\build\Desktop_Qt_6_9_2_MinGW_64_bit-Debug + + + + + all + + false + + true + 构建 + CMakeProjectManager.MakeStep + + 1 + 构建 + 构建 + ProjectExplorer.BuildSteps.Build + + + + + + clean + + false + + true + 构建 + CMakeProjectManager.MakeStep + + 1 + 清除 + 清除 + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Debug + CMakeProjectManager.CMakeBuildConfiguration + 0 + 0 + + + 0 + 部署 + 部署 + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + + + + + + + + false + + true + ApplicationManagerPlugin.Deploy.CMakePackageStep + + + install-package --acknowledge + true + Install Application Manager package + ApplicationManagerPlugin.Deploy.InstallPackageStep + + + + + + + + 2 + 部署 + 部署 + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ApplicationManagerPlugin.Deploy.Configuration + + 2 + + true + true + 0 + true + + 2 + + false + -e cpu-cycles --call-graph "dwarf,4096" -F 250 + systray + CMakeProjectManager.CMakeRunConfiguration. + systray + false + true + true + true + D:/Programs/Qt/Examples/Qt-6.9.2/widgets/desktop/systray/build/Desktop_Qt_6_9_2_MinGW_64_bit-Debug + + 1 + + + Release + 2 + false + + -DCMAKE_GENERATOR:STRING=Ninja +-DQT_MAINTENANCE_TOOL:FILEPATH=D:/Programs/Qt/MaintenanceTool.exe +-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} +-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} +-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake +-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DCMAKE_BUILD_TYPE:STRING=Release +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} + D:\Programs\Qt\Examples\Qt-6.9.2\widgets\desktop\systray\build\Desktop_Qt_6_9_2_MinGW_64_bit-Release + + + + + all + + false + + true + CMakeProjectManager.MakeStep + + 1 + 构建 + 构建 + ProjectExplorer.BuildSteps.Build + + + + + + clean + + false + + true + CMakeProjectManager.MakeStep + + 1 + 清除 + 清除 + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Release + CMakeProjectManager.CMakeBuildConfiguration + 0 + -1 + + + 0 + 部署 + 部署 + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + + + + + + install + + false + + true + ApplicationManagerPlugin.Deploy.CMakePackageStep + + + install-package --acknowledge + true + Install Application Manager package + ApplicationManagerPlugin.Deploy.InstallPackageStep + + + + + + + + 2 + 部署 + 部署 + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ApplicationManagerPlugin.Deploy.Configuration + + 2 + 0 + + 2 + + + 0 + 部署 + 部署 + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + + + + + + + + false + + true + ApplicationManagerPlugin.Deploy.CMakePackageStep + + + install-package --acknowledge + true + Install Application Manager package + ApplicationManagerPlugin.Deploy.InstallPackageStep + + + + + + + + 2 + 部署 + 部署 + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ApplicationManagerPlugin.Deploy.Configuration + + 2 + + true + true + 0 + true + + 2 + + false + -e cpu-cycles --call-graph "dwarf,4096" -F 250 + systray + CMakeProjectManager.CMakeRunConfiguration. + systray + false + true + true + true + D:/Programs/Qt/Examples/Qt-6.9.2/widgets/desktop/systray/build/Desktop_Qt_6_9_2_MinGW_64_bit-Debug + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.FileVersion + 22 + + + Version + 22 + + diff --git a/images/bad.png b/images/bad.png new file mode 100644 index 0000000..b09de85 Binary files /dev/null and b/images/bad.png differ diff --git a/images/heart.png b/images/heart.png new file mode 100644 index 0000000..5d466b1 Binary files /dev/null and b/images/heart.png differ diff --git a/images/trash.png b/images/trash.png new file mode 100644 index 0000000..454074f Binary files /dev/null and b/images/trash.png differ diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..367b027 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,46 @@ +#include + +#ifndef QT_NO_SYSTEMTRAYICON + +# include "window.h" +# include + +int main(int argc, char *argv[]) { + QApplication app(argc, argv); + + if (!QSystemTrayIcon::isSystemTrayAvailable()) { + auto choice = + QMessageBox::critical(nullptr, QObject::tr("Systray"), + QObject::tr("I couldn't detect any system tray on this system."), + QMessageBox::Close | QMessageBox::Ignore); + if (choice == QMessageBox::Close) + return 1; + // Otherwise "lurk": if a system tray is started later, the icon will + // appear. + } + QApplication::setQuitOnLastWindowClosed(false); + + Window window; + window.show(); + return app.exec(); +} + +#else + +# include +# include + +int main(int argc, char *argv[]) { + QApplication app(argc, argv); + QString text("QSystemTrayIcon is not supported on this platform"); + + QLabel *label = new QLabel(text); + label->setWordWrap(true); + + label->show(); + qDebug() << text; + + app.exec(); +} + +#endif diff --git a/src/window.cpp b/src/window.cpp new file mode 100644 index 0000000..ac62606 --- /dev/null +++ b/src/window.cpp @@ -0,0 +1,227 @@ +#include "window.h" + +#ifndef QT_NO_SYSTEMTRAYICON + +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include + +//! [0] +Window::Window() { + createIconGroupBox(); + createMessageGroupBox(); + + iconLabel->setMinimumWidth(durationLabel->sizeHint().width()); + + createActions(); + createTrayIcon(); + + connect(showMessageButton, &QAbstractButton::clicked, this, &Window::showMessage); + connect(showIconCheckBox, &QAbstractButton::toggled, trayIcon, &QSystemTrayIcon::setVisible); + connect(iconComboBox, &QComboBox::currentIndexChanged, this, &Window::setIcon); + connect(trayIcon, &QSystemTrayIcon::messageClicked, this, &Window::messageClicked); + connect(trayIcon, &QSystemTrayIcon::activated, this, &Window::iconActivated); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addWidget(iconGroupBox); + mainLayout->addWidget(messageGroupBox); + setLayout(mainLayout); + + iconComboBox->setCurrentIndex(1); + trayIcon->show(); + + setWindowTitle(tr("Systray")); + resize(400, 300); +} +//! [0] + +//! [1] +void Window::setVisible(bool visible) { + minimizeAction->setEnabled(visible); + maximizeAction->setEnabled(!isMaximized()); + restoreAction->setEnabled(isMaximized() || !visible); + QDialog::setVisible(visible); +} +//! [1] + +//! [2] +void Window::closeEvent(QCloseEvent *event) { + if (!event->spontaneous() || !isVisible()) + return; + if (trayIcon->isVisible()) { + QMessageBox::information(this, tr("Systray"), + tr("The program will keep running in the " + "system tray. To terminate the program, " + "choose Quit in the context menu " + "of the system tray entry.")); + hide(); + event->ignore(); + } +} +//! [2] + +//! [3] +void Window::setIcon(int index) { + QIcon icon = iconComboBox->itemIcon(index); + trayIcon->setIcon(icon); + setWindowIcon(icon); + + trayIcon->setToolTip(iconComboBox->itemText(index)); +} +//! [3] + +//! [4] +void Window::iconActivated(QSystemTrayIcon::ActivationReason reason) { + switch (reason) { + case QSystemTrayIcon::Trigger: + case QSystemTrayIcon::DoubleClick: + iconComboBox->setCurrentIndex((iconComboBox->currentIndex() + 1) % iconComboBox->count()); + break; + case QSystemTrayIcon::MiddleClick: + showMessage(); + break; + default:; + } +} +//! [4] + +//! [5] +void Window::showMessage() { + showIconCheckBox->setChecked(true); + int selectedIcon = typeComboBox->itemData(typeComboBox->currentIndex()).toInt(); + QSystemTrayIcon::MessageIcon msgIcon = QSystemTrayIcon::MessageIcon(selectedIcon); + + if (selectedIcon == -1) { // custom icon + QIcon icon(iconComboBox->itemIcon(iconComboBox->currentIndex())); + trayIcon->showMessage(titleEdit->text(), bodyEdit->toPlainText(), icon, + durationSpinBox->value() * 1000); + } else { + trayIcon->showMessage(titleEdit->text(), bodyEdit->toPlainText(), msgIcon, + durationSpinBox->value() * 1000); + } +} +//! [5] + +//! [6] +void Window::messageClicked() { + QMessageBox::information(nullptr, tr("Systray"), + tr("Sorry, I already gave what help I could.\n" + "Maybe you should try asking a human?")); +} +//! [6] + +void Window::createIconGroupBox() { + iconGroupBox = new QGroupBox(tr("Tray Icon")); + + iconLabel = new QLabel("Icon:"); + + iconComboBox = new QComboBox; + iconComboBox->addItem(QIcon(":/images/bad.png"), tr("Bad")); + iconComboBox->addItem(QIcon(":/images/heart.png"), tr("Heart")); + iconComboBox->addItem(QIcon(":/images/trash.png"), tr("Trash")); + + showIconCheckBox = new QCheckBox(tr("Show icon")); + showIconCheckBox->setChecked(true); + + QHBoxLayout *iconLayout = new QHBoxLayout; + iconLayout->addWidget(iconLabel); + iconLayout->addWidget(iconComboBox); + iconLayout->addStretch(); + iconLayout->addWidget(showIconCheckBox); + iconGroupBox->setLayout(iconLayout); +} + +void Window::createMessageGroupBox() { + messageGroupBox = new QGroupBox(tr("Balloon Message")); + + typeLabel = new QLabel(tr("Type:")); + + typeComboBox = new QComboBox; + typeComboBox->addItem(tr("None"), QSystemTrayIcon::NoIcon); + typeComboBox->addItem(style()->standardIcon(QStyle::SP_MessageBoxInformation), + tr("Information"), QSystemTrayIcon::Information); + typeComboBox->addItem(style()->standardIcon(QStyle::SP_MessageBoxWarning), tr("Warning"), + QSystemTrayIcon::Warning); + typeComboBox->addItem(style()->standardIcon(QStyle::SP_MessageBoxCritical), tr("Critical"), + QSystemTrayIcon::Critical); + typeComboBox->addItem(QIcon(), tr("Custom icon"), -1); + typeComboBox->setCurrentIndex(1); + + durationLabel = new QLabel(tr("Duration:")); + + durationSpinBox = new QSpinBox; + durationSpinBox->setRange(5, 60); + durationSpinBox->setSuffix(" s"); + durationSpinBox->setValue(15); + + durationWarningLabel = new QLabel(tr("(some systems might ignore this " + "hint)")); + durationWarningLabel->setIndent(10); + + titleLabel = new QLabel(tr("Title:")); + + titleEdit = new QLineEdit(tr("Cannot connect to network")); + + bodyLabel = new QLabel(tr("Body:")); + + bodyEdit = new QTextEdit; + bodyEdit->setPlainText(tr("Don't believe me. Honestly, I don't have a " + "clue.\nClick this balloon for details.")); + + showMessageButton = new QPushButton(tr("Show Message")); + showMessageButton->setDefault(true); + + QGridLayout *messageLayout = new QGridLayout; + messageLayout->addWidget(typeLabel, 0, 0); + messageLayout->addWidget(typeComboBox, 0, 1, 1, 2); + messageLayout->addWidget(durationLabel, 1, 0); + messageLayout->addWidget(durationSpinBox, 1, 1); + messageLayout->addWidget(durationWarningLabel, 1, 2, 1, 3); + messageLayout->addWidget(titleLabel, 2, 0); + messageLayout->addWidget(titleEdit, 2, 1, 1, 4); + messageLayout->addWidget(bodyLabel, 3, 0); + messageLayout->addWidget(bodyEdit, 3, 1, 2, 4); + messageLayout->addWidget(showMessageButton, 5, 4); + messageLayout->setColumnStretch(3, 1); + messageLayout->setRowStretch(4, 1); + messageGroupBox->setLayout(messageLayout); +} + +void Window::createActions() { + minimizeAction = new QAction(tr("Mi&nimize"), this); + connect(minimizeAction, &QAction::triggered, this, &QWidget::hide); + + maximizeAction = new QAction(tr("Ma&ximize"), this); + connect(maximizeAction, &QAction::triggered, this, &QWidget::showMaximized); + + restoreAction = new QAction(tr("&Restore"), this); + connect(restoreAction, &QAction::triggered, this, &QWidget::showNormal); + + quitAction = new QAction(tr("&Quit"), this); + connect(quitAction, &QAction::triggered, qApp, &QCoreApplication::quit); +} + +void Window::createTrayIcon() { + trayIconMenu = new QMenu(this); + trayIconMenu->addAction(minimizeAction); + trayIconMenu->addAction(maximizeAction); + trayIconMenu->addAction(restoreAction); + trayIconMenu->addSeparator(); + trayIconMenu->addAction(quitAction); + + trayIcon = new QSystemTrayIcon(this); + trayIcon->setContextMenu(trayIconMenu); +} + +#endif diff --git a/src/window.h b/src/window.h new file mode 100644 index 0000000..c1cca5f --- /dev/null +++ b/src/window.h @@ -0,0 +1,73 @@ +#pragma once + +#include + +#ifndef QT_NO_SYSTEMTRAYICON + +# include + +QT_BEGIN_NAMESPACE +class QAction; +class QCheckBox; +class QComboBox; +class QGroupBox; +class QLabel; +class QLineEdit; +class QMenu; +class QPushButton; +class QSpinBox; +class QTextEdit; +QT_END_NAMESPACE + +//! [0] +class Window : public QDialog { + Q_OBJECT + +public: + Window(); + + void setVisible(bool visible) override; + +protected: + void closeEvent(QCloseEvent *event) override; + +private slots: + void setIcon(int index); + void iconActivated(QSystemTrayIcon::ActivationReason reason); + void showMessage(); + void messageClicked(); + +private: + void createIconGroupBox(); + void createMessageGroupBox(); + void createActions(); + void createTrayIcon(); + + QGroupBox *iconGroupBox; + QLabel *iconLabel; + QComboBox *iconComboBox; + QCheckBox *showIconCheckBox; + + QGroupBox *messageGroupBox; + QLabel *typeLabel; + QLabel *durationLabel; + QLabel *durationWarningLabel; + QLabel *titleLabel; + QLabel *bodyLabel; + QComboBox *typeComboBox; + QSpinBox *durationSpinBox; + QLineEdit *titleEdit; + QTextEdit *bodyEdit; + QPushButton *showMessageButton; + + QAction *minimizeAction; + QAction *maximizeAction; + QAction *restoreAction; + QAction *quitAction; + + QSystemTrayIcon *trayIcon; + QMenu *trayIconMenu; +}; +//! [0] + +#endif // QT_NO_SYSTEMTRAYICON diff --git a/systray.pro b/systray.pro new file mode 100644 index 0000000..e0708ea --- /dev/null +++ b/systray.pro @@ -0,0 +1,11 @@ +HEADERS = window.h +SOURCES = main.cpp \ + window.cpp +RESOURCES = systray.qrc + +QT += widgets +requires(qtConfig(combobox)) + +# install +target.path = $$[QT_INSTALL_EXAMPLES]/widgets/desktop/systray +INSTALLS += target diff --git a/systray.qrc b/systray.qrc new file mode 100644 index 0000000..a8b6535 --- /dev/null +++ b/systray.qrc @@ -0,0 +1,7 @@ + + + images/bad.png + images/heart.png + images/trash.png + +