commit 7814bccdc9b17c8cb8349f38570c584fc55f8fd5 Author: cutefishd Date: Wed Mar 24 19:59:30 2021 +0800 Init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..733eeec --- /dev/null +++ b/.gitignore @@ -0,0 +1,54 @@ +# C++ objects and libs +*.slo +*.lo +*.o +*.a +*.la +*.lai +*.so +*.so.* +*.dll +*.dylib + +# Qt-es +object_script.*.Release +object_script.*.Debug +*_plugin_import.cpp +/.qmake.cache +/.qmake.stash +*.pro.user +*.pro.user.* +*.qbs.user +*.qbs.user.* +*.moc +moc_*.cpp +moc_*.h +qrc_*.cpp +ui_*.h +*.qmlc +*.jsc +Makefile* +*build-* +*.qm +*.prl + +# Qt unit tests +target_wrapper.* + +# QtCreator +*.autosave + +# QtCreator Qml +*.qmlproject.user +*.qmlproject.user.* + +# QtCreator CMake +CMakeLists.txt.user* + +# QtCreator 4.8< compilation database +compile_commands.json + +# QtCreator local machine specific files for imported projects +*creator.user* + +build/* diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..db09bce --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,79 @@ +cmake_minimum_required(VERSION 3.14) + +project(statusbar LANGUAGES CXX) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Qt5 CONFIG REQUIRED Widgets DBus X11Extras Concurrent Svg LinguistTools QuickControls2) +find_package(KF5WindowSystem REQUIRED) +find_package(dbusmenu-qt5 REQUIRED) +find_package(MeuiKit REQUIRED) + +set(SRCS + src/main.cpp + src/statusbar.cpp + src/controlcenterdialog.cpp + src/brightness.cpp + src/battery.cpp + src/appearance.cpp + src/volume.cpp + src/processprovider.cpp + + src/systemtray/statusnotifieritemjob.cpp + src/systemtray/statusnotifieritemsource.cpp + src/systemtray/systemtraytypes.cpp + src/systemtray/systemtraytypedefs.h + src/systemtray/systemtraymodel.cpp + src/systemtray/statusnotifierwatcher.cpp + + qml.qrc +) + +set(statusnotifierwatcher_xml src/systemtray/org.kde.StatusNotifierWatcher.xml) +qt5_add_dbus_interface(SRCS ${statusnotifierwatcher_xml} statusnotifierwatcher_interface) +qt5_add_dbus_interface(SRCS src/systemtray/org.freedesktop.DBus.Properties.xml dbusproperties) + +set(statusnotifieritem_xml src/systemtray/org.kde.StatusNotifierItem.xml) +set_source_files_properties(${statusnotifieritem_xml} PROPERTIES + NO_NAMESPACE false + INCLUDE "src/systemtray/systemtraytypes.h" + CLASSNAME OrgKdeStatusNotifierItem +) +qt5_add_dbus_interface(SRCS ${statusnotifieritem_xml} statusnotifieritem_interface) + +qt5_add_dbus_adaptor(SRCS src/systemtray/org.kde.StatusNotifierWatcher.xml + src/systemtray/statusnotifierwatcher.h StatusNotifierWatcher) + +add_executable(cutefish-statusbar ${SRCS}) + +target_link_libraries(cutefish-statusbar + PRIVATE + Qt5::Core + Qt5::Widgets + Qt5::Quick + Qt5::QuickControls2 + Qt5::X11Extras + Qt5::Concurrent + Qt5::DBus + Qt5::Svg + + MeuiKit + + KF5::WindowSystem + dbusmenu-qt5 +) + +file(GLOB TS_FILES translations/*.ts) +qt5_create_translation(QM_FILES ${TS_FILES}) +add_custom_target(translations DEPENDS ${QM_FILES} SOURCES ${TS_FILES}) +add_dependencies(cutefish-statusbar translations) + +install(TARGETS cutefish-statusbar RUNTIME DESTINATION /usr/bin) +install(FILES ${QM_FILES} DESTINATION /usr/share/cutefish-statusbar/translations) diff --git a/images/dark/audio-volume-high-symbolic.svg b/images/dark/audio-volume-high-symbolic.svg new file mode 100644 index 0000000..b151449 --- /dev/null +++ b/images/dark/audio-volume-high-symbolic.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/images/dark/audio-volume-low-symbolic.svg b/images/dark/audio-volume-low-symbolic.svg new file mode 100644 index 0000000..935d1fe --- /dev/null +++ b/images/dark/audio-volume-low-symbolic.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/images/dark/audio-volume-medium-symbolic.svg b/images/dark/audio-volume-medium-symbolic.svg new file mode 100644 index 0000000..06ff169 --- /dev/null +++ b/images/dark/audio-volume-medium-symbolic.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/images/dark/audio-volume-muted-symbolic.svg b/images/dark/audio-volume-muted-symbolic.svg new file mode 100644 index 0000000..208e22e --- /dev/null +++ b/images/dark/audio-volume-muted-symbolic.svg @@ -0,0 +1,17 @@ + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/images/dark/battery-level-0-charging-symbolic.svg b/images/dark/battery-level-0-charging-symbolic.svg new file mode 100644 index 0000000..4b9b263 --- /dev/null +++ b/images/dark/battery-level-0-charging-symbolic.svg @@ -0,0 +1,68 @@ + + + + + + image/svg+xml + + + + + + + Paper Symbolic Icon Theme + + + + diff --git a/images/dark/battery-level-0-symbolic.svg b/images/dark/battery-level-0-symbolic.svg new file mode 100644 index 0000000..ba82892 --- /dev/null +++ b/images/dark/battery-level-0-symbolic.svg @@ -0,0 +1,63 @@ + + + + + + image/svg+xml + + + + + + + Paper Symbolic Icon Theme + + + diff --git a/images/dark/battery-level-10-charging-symbolic.svg b/images/dark/battery-level-10-charging-symbolic.svg new file mode 100644 index 0000000..1daaf02 --- /dev/null +++ b/images/dark/battery-level-10-charging-symbolic.svg @@ -0,0 +1,9 @@ + + Paper Symbolic Icon Theme + + + + + + + diff --git a/images/dark/battery-level-10-symbolic.svg b/images/dark/battery-level-10-symbolic.svg new file mode 100644 index 0000000..82883bc --- /dev/null +++ b/images/dark/battery-level-10-symbolic.svg @@ -0,0 +1,8 @@ + + Paper Symbolic Icon Theme + + + + + + diff --git a/images/dark/battery-level-100-charging-symbolic.svg b/images/dark/battery-level-100-charging-symbolic.svg new file mode 100644 index 0000000..d4ed5dd --- /dev/null +++ b/images/dark/battery-level-100-charging-symbolic.svg @@ -0,0 +1,11 @@ + + Paper Symbolic Icon Theme + + + + + + + + + diff --git a/images/dark/battery-level-100-symbolic.svg b/images/dark/battery-level-100-symbolic.svg new file mode 100644 index 0000000..46c7a87 --- /dev/null +++ b/images/dark/battery-level-100-symbolic.svg @@ -0,0 +1,8 @@ + + Paper Symbolic Icon Theme + + + + + + diff --git a/images/dark/battery-level-20-charging-symbolic.svg b/images/dark/battery-level-20-charging-symbolic.svg new file mode 100644 index 0000000..0a4453f --- /dev/null +++ b/images/dark/battery-level-20-charging-symbolic.svg @@ -0,0 +1,80 @@ + + + + + + image/svg+xml + + + + + + + Paper Symbolic Icon Theme + + + + + + + diff --git a/images/dark/battery-level-20-symbolic.svg b/images/dark/battery-level-20-symbolic.svg new file mode 100644 index 0000000..2d1877f --- /dev/null +++ b/images/dark/battery-level-20-symbolic.svg @@ -0,0 +1,71 @@ + + + + + + image/svg+xml + + + + + + + Paper Symbolic Icon Theme + + + + diff --git a/images/dark/battery-level-30-charging-symbolic.svg b/images/dark/battery-level-30-charging-symbolic.svg new file mode 100644 index 0000000..60ce649 --- /dev/null +++ b/images/dark/battery-level-30-charging-symbolic.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/images/dark/battery-level-30-symbolic.svg b/images/dark/battery-level-30-symbolic.svg new file mode 100644 index 0000000..fdf956f --- /dev/null +++ b/images/dark/battery-level-30-symbolic.svg @@ -0,0 +1,70 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/images/dark/battery-level-40-charging-symbolic.svg b/images/dark/battery-level-40-charging-symbolic.svg new file mode 100644 index 0000000..60ce649 --- /dev/null +++ b/images/dark/battery-level-40-charging-symbolic.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/images/dark/battery-level-40-symbolic.svg b/images/dark/battery-level-40-symbolic.svg new file mode 100644 index 0000000..122ac0f --- /dev/null +++ b/images/dark/battery-level-40-symbolic.svg @@ -0,0 +1,69 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/images/dark/battery-level-50-charging-symbolic.svg b/images/dark/battery-level-50-charging-symbolic.svg new file mode 100644 index 0000000..60ce649 --- /dev/null +++ b/images/dark/battery-level-50-charging-symbolic.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/images/dark/battery-level-50-symbolic.svg b/images/dark/battery-level-50-symbolic.svg new file mode 100644 index 0000000..9cf18ef --- /dev/null +++ b/images/dark/battery-level-50-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/images/dark/battery-level-60-charging-symbolic.svg b/images/dark/battery-level-60-charging-symbolic.svg new file mode 100644 index 0000000..238b45b --- /dev/null +++ b/images/dark/battery-level-60-charging-symbolic.svg @@ -0,0 +1,78 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/images/dark/battery-level-60-symbolic.svg b/images/dark/battery-level-60-symbolic.svg new file mode 100644 index 0000000..2ce5a18 --- /dev/null +++ b/images/dark/battery-level-60-symbolic.svg @@ -0,0 +1,69 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/images/dark/battery-level-70-charging-symbolic.svg b/images/dark/battery-level-70-charging-symbolic.svg new file mode 100644 index 0000000..238b45b --- /dev/null +++ b/images/dark/battery-level-70-charging-symbolic.svg @@ -0,0 +1,78 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/images/dark/battery-level-70-symbolic.svg b/images/dark/battery-level-70-symbolic.svg new file mode 100644 index 0000000..2ce5a18 --- /dev/null +++ b/images/dark/battery-level-70-symbolic.svg @@ -0,0 +1,69 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/images/dark/battery-level-80-charging-symbolic.svg b/images/dark/battery-level-80-charging-symbolic.svg new file mode 100644 index 0000000..e5facea --- /dev/null +++ b/images/dark/battery-level-80-charging-symbolic.svg @@ -0,0 +1,11 @@ + + Paper Symbolic Icon Theme + + + + + + + + + diff --git a/images/dark/battery-level-80-symbolic.svg b/images/dark/battery-level-80-symbolic.svg new file mode 100644 index 0000000..778503b --- /dev/null +++ b/images/dark/battery-level-80-symbolic.svg @@ -0,0 +1,8 @@ + + Paper Symbolic Icon Theme + + + + + + diff --git a/images/dark/battery-level-90-charging-symbolic.svg b/images/dark/battery-level-90-charging-symbolic.svg new file mode 100644 index 0000000..e5facea --- /dev/null +++ b/images/dark/battery-level-90-charging-symbolic.svg @@ -0,0 +1,11 @@ + + Paper Symbolic Icon Theme + + + + + + + + + diff --git a/images/dark/battery-level-90-symbolic.svg b/images/dark/battery-level-90-symbolic.svg new file mode 100644 index 0000000..46be4f0 --- /dev/null +++ b/images/dark/battery-level-90-symbolic.svg @@ -0,0 +1,72 @@ + + + + + + image/svg+xml + + + + + + + Paper Symbolic Icon Theme + + + + diff --git a/images/dark/bluetooth-symbolic.svg b/images/dark/bluetooth-symbolic.svg new file mode 100644 index 0000000..f6dcc96 --- /dev/null +++ b/images/dark/bluetooth-symbolic.svg @@ -0,0 +1,61 @@ + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/images/dark/brightness.svg b/images/dark/brightness.svg new file mode 100644 index 0000000..909fbdc --- /dev/null +++ b/images/dark/brightness.svg @@ -0,0 +1,16 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/images/dark/close_normal.svg b/images/dark/close_normal.svg new file mode 100644 index 0000000..73e27b2 --- /dev/null +++ b/images/dark/close_normal.svg @@ -0,0 +1,23 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/images/dark/control.svg b/images/dark/control.svg new file mode 100644 index 0000000..833f9e8 --- /dev/null +++ b/images/dark/control.svg @@ -0,0 +1,17 @@ + + + + + + diff --git a/images/dark/dark-mode.svg b/images/dark/dark-mode.svg new file mode 100644 index 0000000..18dc84b --- /dev/null +++ b/images/dark/dark-mode.svg @@ -0,0 +1,59 @@ + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/images/dark/media-playback-pause-symbolic.svg b/images/dark/media-playback-pause-symbolic.svg new file mode 100644 index 0000000..3e90132 --- /dev/null +++ b/images/dark/media-playback-pause-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/images/dark/media-playback-start-symbolic.svg b/images/dark/media-playback-start-symbolic.svg new file mode 100644 index 0000000..81730c4 --- /dev/null +++ b/images/dark/media-playback-start-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/images/dark/media-skip-backward-symbolic.svg b/images/dark/media-skip-backward-symbolic.svg new file mode 100644 index 0000000..e35c9d5 --- /dev/null +++ b/images/dark/media-skip-backward-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/images/dark/media-skip-forward-symbolic.svg b/images/dark/media-skip-forward-symbolic.svg new file mode 100644 index 0000000..a2c94d7 --- /dev/null +++ b/images/dark/media-skip-forward-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/images/dark/minimize_normal.svg b/images/dark/minimize_normal.svg new file mode 100644 index 0000000..7adacee --- /dev/null +++ b/images/dark/minimize_normal.svg @@ -0,0 +1,22 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/images/dark/network-wired-activated.svg b/images/dark/network-wired-activated.svg new file mode 100644 index 0000000..8fb6024 --- /dev/null +++ b/images/dark/network-wired-activated.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/images/dark/network-wired.svg b/images/dark/network-wired.svg new file mode 100644 index 0000000..fe8e676 --- /dev/null +++ b/images/dark/network-wired.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/images/dark/network-wireless-connected-00.svg b/images/dark/network-wireless-connected-00.svg new file mode 100644 index 0000000..77a2b43 --- /dev/null +++ b/images/dark/network-wireless-connected-00.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/images/dark/network-wireless-connected-100.svg b/images/dark/network-wireless-connected-100.svg new file mode 100644 index 0000000..34f0a62 --- /dev/null +++ b/images/dark/network-wireless-connected-100.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/images/dark/network-wireless-connected-25.svg b/images/dark/network-wireless-connected-25.svg new file mode 100644 index 0000000..ab60d64 --- /dev/null +++ b/images/dark/network-wireless-connected-25.svg @@ -0,0 +1,19 @@ + + + + + + + diff --git a/images/dark/network-wireless-connected-50.svg b/images/dark/network-wireless-connected-50.svg new file mode 100644 index 0000000..aae1995 --- /dev/null +++ b/images/dark/network-wireless-connected-50.svg @@ -0,0 +1,19 @@ + + + + + + + diff --git a/images/dark/network-wireless-connected-75.svg b/images/dark/network-wireless-connected-75.svg new file mode 100644 index 0000000..3c0d028 --- /dev/null +++ b/images/dark/network-wireless-connected-75.svg @@ -0,0 +1,19 @@ + + + + + + + diff --git a/images/dark/restore_normal.svg b/images/dark/restore_normal.svg new file mode 100644 index 0000000..57a8657 --- /dev/null +++ b/images/dark/restore_normal.svg @@ -0,0 +1,26 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/images/dark/settings.svg b/images/dark/settings.svg new file mode 100644 index 0000000..9b3dff6 --- /dev/null +++ b/images/dark/settings.svg @@ -0,0 +1,67 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/images/dark/system-shutdown-symbolic.svg b/images/dark/system-shutdown-symbolic.svg new file mode 100644 index 0000000..99ba7f0 --- /dev/null +++ b/images/dark/system-shutdown-symbolic.svg @@ -0,0 +1,60 @@ + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/images/light/audio-volume-high-symbolic.svg b/images/light/audio-volume-high-symbolic.svg new file mode 100644 index 0000000..0e998d7 --- /dev/null +++ b/images/light/audio-volume-high-symbolic.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/images/light/audio-volume-low-symbolic.svg b/images/light/audio-volume-low-symbolic.svg new file mode 100644 index 0000000..ea3cb7d --- /dev/null +++ b/images/light/audio-volume-low-symbolic.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/images/light/audio-volume-medium-symbolic.svg b/images/light/audio-volume-medium-symbolic.svg new file mode 100644 index 0000000..01ca6fa --- /dev/null +++ b/images/light/audio-volume-medium-symbolic.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/images/light/audio-volume-muted-symbolic.svg b/images/light/audio-volume-muted-symbolic.svg new file mode 100644 index 0000000..173170f --- /dev/null +++ b/images/light/audio-volume-muted-symbolic.svg @@ -0,0 +1,17 @@ + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/images/light/battery-level-0-charging-symbolic.svg b/images/light/battery-level-0-charging-symbolic.svg new file mode 100644 index 0000000..4b9b263 --- /dev/null +++ b/images/light/battery-level-0-charging-symbolic.svg @@ -0,0 +1,68 @@ + + + + + + image/svg+xml + + + + + + + Paper Symbolic Icon Theme + + + + diff --git a/images/light/battery-level-0-symbolic.svg b/images/light/battery-level-0-symbolic.svg new file mode 100644 index 0000000..ba82892 --- /dev/null +++ b/images/light/battery-level-0-symbolic.svg @@ -0,0 +1,63 @@ + + + + + + image/svg+xml + + + + + + + Paper Symbolic Icon Theme + + + diff --git a/images/light/battery-level-10-charging-symbolic.svg b/images/light/battery-level-10-charging-symbolic.svg new file mode 100644 index 0000000..972d4c1 --- /dev/null +++ b/images/light/battery-level-10-charging-symbolic.svg @@ -0,0 +1,9 @@ + + Paper Symbolic Icon Theme + + + + + + + diff --git a/images/light/battery-level-10-symbolic.svg b/images/light/battery-level-10-symbolic.svg new file mode 100644 index 0000000..82883bc --- /dev/null +++ b/images/light/battery-level-10-symbolic.svg @@ -0,0 +1,8 @@ + + Paper Symbolic Icon Theme + + + + + + diff --git a/images/light/battery-level-100-charging-symbolic.svg b/images/light/battery-level-100-charging-symbolic.svg new file mode 100644 index 0000000..52309c8 --- /dev/null +++ b/images/light/battery-level-100-charging-symbolic.svg @@ -0,0 +1,11 @@ + + Paper Symbolic Icon Theme + + + + + + + + + diff --git a/images/light/battery-level-100-symbolic.svg b/images/light/battery-level-100-symbolic.svg new file mode 100644 index 0000000..681ffce --- /dev/null +++ b/images/light/battery-level-100-symbolic.svg @@ -0,0 +1,8 @@ + + Paper Symbolic Icon Theme + + + + + + diff --git a/images/light/battery-level-20-charging-symbolic.svg b/images/light/battery-level-20-charging-symbolic.svg new file mode 100644 index 0000000..303898f --- /dev/null +++ b/images/light/battery-level-20-charging-symbolic.svg @@ -0,0 +1,80 @@ + + + + + + image/svg+xml + + + + + + + Paper Symbolic Icon Theme + + + + + + + diff --git a/images/light/battery-level-20-symbolic.svg b/images/light/battery-level-20-symbolic.svg new file mode 100644 index 0000000..2d1877f --- /dev/null +++ b/images/light/battery-level-20-symbolic.svg @@ -0,0 +1,71 @@ + + + + + + image/svg+xml + + + + + + + Paper Symbolic Icon Theme + + + + diff --git a/images/light/battery-level-30-charging-symbolic.svg b/images/light/battery-level-30-charging-symbolic.svg new file mode 100644 index 0000000..8f6dbef --- /dev/null +++ b/images/light/battery-level-30-charging-symbolic.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/images/light/battery-level-30-symbolic.svg b/images/light/battery-level-30-symbolic.svg new file mode 100644 index 0000000..accd40c --- /dev/null +++ b/images/light/battery-level-30-symbolic.svg @@ -0,0 +1,70 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/images/light/battery-level-40-charging-symbolic.svg b/images/light/battery-level-40-charging-symbolic.svg new file mode 100644 index 0000000..8f6dbef --- /dev/null +++ b/images/light/battery-level-40-charging-symbolic.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/images/light/battery-level-40-symbolic.svg b/images/light/battery-level-40-symbolic.svg new file mode 100644 index 0000000..b36b58e --- /dev/null +++ b/images/light/battery-level-40-symbolic.svg @@ -0,0 +1,69 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/images/light/battery-level-50-charging-symbolic.svg b/images/light/battery-level-50-charging-symbolic.svg new file mode 100644 index 0000000..8f6dbef --- /dev/null +++ b/images/light/battery-level-50-charging-symbolic.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/images/light/battery-level-50-symbolic.svg b/images/light/battery-level-50-symbolic.svg new file mode 100644 index 0000000..0650da8 --- /dev/null +++ b/images/light/battery-level-50-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/images/light/battery-level-60-charging-symbolic.svg b/images/light/battery-level-60-charging-symbolic.svg new file mode 100644 index 0000000..413e32e --- /dev/null +++ b/images/light/battery-level-60-charging-symbolic.svg @@ -0,0 +1,78 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/images/light/battery-level-60-symbolic.svg b/images/light/battery-level-60-symbolic.svg new file mode 100644 index 0000000..98f92d9 --- /dev/null +++ b/images/light/battery-level-60-symbolic.svg @@ -0,0 +1,69 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/images/light/battery-level-70-charging-symbolic.svg b/images/light/battery-level-70-charging-symbolic.svg new file mode 100644 index 0000000..413e32e --- /dev/null +++ b/images/light/battery-level-70-charging-symbolic.svg @@ -0,0 +1,78 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/images/light/battery-level-70-symbolic.svg b/images/light/battery-level-70-symbolic.svg new file mode 100644 index 0000000..98f92d9 --- /dev/null +++ b/images/light/battery-level-70-symbolic.svg @@ -0,0 +1,69 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/images/light/battery-level-80-charging-symbolic.svg b/images/light/battery-level-80-charging-symbolic.svg new file mode 100644 index 0000000..2700546 --- /dev/null +++ b/images/light/battery-level-80-charging-symbolic.svg @@ -0,0 +1,11 @@ + + Paper Symbolic Icon Theme + + + + + + + + + diff --git a/images/light/battery-level-80-symbolic.svg b/images/light/battery-level-80-symbolic.svg new file mode 100644 index 0000000..4df07a7 --- /dev/null +++ b/images/light/battery-level-80-symbolic.svg @@ -0,0 +1,8 @@ + + Paper Symbolic Icon Theme + + + + + + diff --git a/images/light/battery-level-90-charging-symbolic.svg b/images/light/battery-level-90-charging-symbolic.svg new file mode 100644 index 0000000..2700546 --- /dev/null +++ b/images/light/battery-level-90-charging-symbolic.svg @@ -0,0 +1,11 @@ + + Paper Symbolic Icon Theme + + + + + + + + + diff --git a/images/light/battery-level-90-symbolic.svg b/images/light/battery-level-90-symbolic.svg new file mode 100644 index 0000000..6702735 --- /dev/null +++ b/images/light/battery-level-90-symbolic.svg @@ -0,0 +1,72 @@ + + + + + + image/svg+xml + + + + + + + Paper Symbolic Icon Theme + + + + diff --git a/images/light/bluetooth-symbolic.svg b/images/light/bluetooth-symbolic.svg new file mode 100644 index 0000000..e5c5f2f --- /dev/null +++ b/images/light/bluetooth-symbolic.svg @@ -0,0 +1,62 @@ + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/images/light/brightness.svg b/images/light/brightness.svg new file mode 100644 index 0000000..487abe9 --- /dev/null +++ b/images/light/brightness.svg @@ -0,0 +1,16 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/images/light/close_normal.svg b/images/light/close_normal.svg new file mode 100644 index 0000000..82ae50e --- /dev/null +++ b/images/light/close_normal.svg @@ -0,0 +1,23 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/images/light/control.svg b/images/light/control.svg new file mode 100644 index 0000000..d2d4a24 --- /dev/null +++ b/images/light/control.svg @@ -0,0 +1,17 @@ + + + + + + diff --git a/images/light/dark-mode.svg b/images/light/dark-mode.svg new file mode 100644 index 0000000..7082359 --- /dev/null +++ b/images/light/dark-mode.svg @@ -0,0 +1,59 @@ + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/images/light/media-playback-pause-symbolic.svg b/images/light/media-playback-pause-symbolic.svg new file mode 100644 index 0000000..4290326 --- /dev/null +++ b/images/light/media-playback-pause-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/images/light/media-playback-start-symbolic.svg b/images/light/media-playback-start-symbolic.svg new file mode 100644 index 0000000..8d57c19 --- /dev/null +++ b/images/light/media-playback-start-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/images/light/media-skip-backward-symbolic.svg b/images/light/media-skip-backward-symbolic.svg new file mode 100644 index 0000000..1b85972 --- /dev/null +++ b/images/light/media-skip-backward-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/images/light/media-skip-forward-symbolic.svg b/images/light/media-skip-forward-symbolic.svg new file mode 100644 index 0000000..1193e44 --- /dev/null +++ b/images/light/media-skip-forward-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/images/light/minimize_normal.svg b/images/light/minimize_normal.svg new file mode 100644 index 0000000..c5562db --- /dev/null +++ b/images/light/minimize_normal.svg @@ -0,0 +1,22 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/images/light/network-wired-activated.svg b/images/light/network-wired-activated.svg new file mode 100644 index 0000000..62a3edc --- /dev/null +++ b/images/light/network-wired-activated.svg @@ -0,0 +1,4 @@ + + + + diff --git a/images/light/network-wired.svg b/images/light/network-wired.svg new file mode 100644 index 0000000..22b2547 --- /dev/null +++ b/images/light/network-wired.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/images/light/network-wireless-connected-00.svg b/images/light/network-wireless-connected-00.svg new file mode 100644 index 0000000..b2cc080 --- /dev/null +++ b/images/light/network-wireless-connected-00.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/images/light/network-wireless-connected-100.svg b/images/light/network-wireless-connected-100.svg new file mode 100644 index 0000000..438ec59 --- /dev/null +++ b/images/light/network-wireless-connected-100.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/images/light/network-wireless-connected-25.svg b/images/light/network-wireless-connected-25.svg new file mode 100644 index 0000000..eae90db --- /dev/null +++ b/images/light/network-wireless-connected-25.svg @@ -0,0 +1,19 @@ + + + + + + + diff --git a/images/light/network-wireless-connected-50.svg b/images/light/network-wireless-connected-50.svg new file mode 100644 index 0000000..63e1de0 --- /dev/null +++ b/images/light/network-wireless-connected-50.svg @@ -0,0 +1,19 @@ + + + + + + + diff --git a/images/light/network-wireless-connected-75.svg b/images/light/network-wireless-connected-75.svg new file mode 100644 index 0000000..7d28375 --- /dev/null +++ b/images/light/network-wireless-connected-75.svg @@ -0,0 +1,19 @@ + + + + + + + diff --git a/images/light/restore_normal.svg b/images/light/restore_normal.svg new file mode 100644 index 0000000..c6680a0 --- /dev/null +++ b/images/light/restore_normal.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/images/light/settings.svg b/images/light/settings.svg new file mode 100644 index 0000000..5481d44 --- /dev/null +++ b/images/light/settings.svg @@ -0,0 +1,67 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/images/light/system-shutdown-symbolic.svg b/images/light/system-shutdown-symbolic.svg new file mode 100644 index 0000000..dd97cbf --- /dev/null +++ b/images/light/system-shutdown-symbolic.svg @@ -0,0 +1,64 @@ + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/images/media-playback-pause-symbolic.svg b/images/media-playback-pause-symbolic.svg new file mode 100644 index 0000000..4290326 --- /dev/null +++ b/images/media-playback-pause-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/images/media-playback-start-symbolic.svg b/images/media-playback-start-symbolic.svg new file mode 100644 index 0000000..8d57c19 --- /dev/null +++ b/images/media-playback-start-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/images/media-skip-backward-symbolic.svg b/images/media-skip-backward-symbolic.svg new file mode 100644 index 0000000..1b85972 --- /dev/null +++ b/images/media-skip-backward-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/images/media-skip-forward-symbolic.svg b/images/media-skip-forward-symbolic.svg new file mode 100644 index 0000000..1193e44 --- /dev/null +++ b/images/media-skip-forward-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/qml.qrc b/qml.qrc new file mode 100644 index 0000000..b972a1e --- /dev/null +++ b/qml.qrc @@ -0,0 +1,105 @@ + + + qml/main.qml + qml/StandardItem.qml + images/dark/audio-volume-high-symbolic.svg + images/dark/audio-volume-low-symbolic.svg + images/dark/audio-volume-medium-symbolic.svg + images/dark/audio-volume-muted-symbolic.svg + images/dark/battery-level-0-charging-symbolic.svg + images/dark/battery-level-0-symbolic.svg + images/dark/battery-level-10-charging-symbolic.svg + images/dark/battery-level-10-symbolic.svg + images/dark/battery-level-100-charging-symbolic.svg + images/dark/battery-level-100-symbolic.svg + images/dark/battery-level-20-charging-symbolic.svg + images/dark/battery-level-20-symbolic.svg + images/dark/battery-level-30-charging-symbolic.svg + images/dark/battery-level-30-symbolic.svg + images/dark/battery-level-40-charging-symbolic.svg + images/dark/battery-level-40-symbolic.svg + images/dark/battery-level-50-charging-symbolic.svg + images/dark/battery-level-50-symbolic.svg + images/dark/battery-level-60-charging-symbolic.svg + images/dark/battery-level-60-symbolic.svg + images/dark/battery-level-70-charging-symbolic.svg + images/dark/battery-level-70-symbolic.svg + images/dark/battery-level-80-charging-symbolic.svg + images/dark/battery-level-80-symbolic.svg + images/dark/battery-level-90-charging-symbolic.svg + images/dark/battery-level-90-symbolic.svg + images/dark/bluetooth-symbolic.svg + images/dark/brightness.svg + images/dark/close_normal.svg + images/dark/control.svg + images/dark/dark-mode.svg + images/dark/media-playback-pause-symbolic.svg + images/dark/media-playback-start-symbolic.svg + images/dark/media-skip-backward-symbolic.svg + images/dark/media-skip-forward-symbolic.svg + images/dark/minimize_normal.svg + images/dark/network-wired-activated.svg + images/dark/network-wired.svg + images/dark/network-wireless-connected-00.svg + images/dark/network-wireless-connected-100.svg + images/dark/network-wireless-connected-25.svg + images/dark/network-wireless-connected-50.svg + images/dark/network-wireless-connected-75.svg + images/dark/restore_normal.svg + images/dark/settings.svg + images/dark/system-shutdown-symbolic.svg + images/light/audio-volume-high-symbolic.svg + images/light/audio-volume-low-symbolic.svg + images/light/audio-volume-medium-symbolic.svg + images/light/audio-volume-muted-symbolic.svg + images/light/battery-level-0-charging-symbolic.svg + images/light/battery-level-0-symbolic.svg + images/light/battery-level-10-charging-symbolic.svg + images/light/battery-level-10-symbolic.svg + images/light/battery-level-100-charging-symbolic.svg + images/light/battery-level-100-symbolic.svg + images/light/battery-level-20-charging-symbolic.svg + images/light/battery-level-20-symbolic.svg + images/light/battery-level-30-charging-symbolic.svg + images/light/battery-level-30-symbolic.svg + images/light/battery-level-40-charging-symbolic.svg + images/light/battery-level-40-symbolic.svg + images/light/battery-level-50-charging-symbolic.svg + images/light/battery-level-50-symbolic.svg + images/light/battery-level-60-charging-symbolic.svg + images/light/battery-level-60-symbolic.svg + images/light/battery-level-70-charging-symbolic.svg + images/light/battery-level-70-symbolic.svg + images/light/battery-level-80-charging-symbolic.svg + images/light/battery-level-80-symbolic.svg + images/light/battery-level-90-charging-symbolic.svg + images/light/battery-level-90-symbolic.svg + images/light/bluetooth-symbolic.svg + images/light/brightness.svg + images/light/close_normal.svg + images/light/control.svg + images/light/dark-mode.svg + images/light/media-playback-pause-symbolic.svg + images/light/media-playback-start-symbolic.svg + images/light/media-skip-backward-symbolic.svg + images/light/media-skip-forward-symbolic.svg + images/light/minimize_normal.svg + images/light/network-wired-activated.svg + images/light/network-wired.svg + images/light/network-wireless-connected-00.svg + images/light/network-wireless-connected-100.svg + images/light/network-wireless-connected-25.svg + images/light/network-wireless-connected-50.svg + images/light/network-wireless-connected-75.svg + images/light/restore_normal.svg + images/light/settings.svg + images/light/system-shutdown-symbolic.svg + images/media-playback-pause-symbolic.svg + images/media-playback-start-symbolic.svg + images/media-skip-backward-symbolic.svg + images/media-skip-forward-symbolic.svg + qml/ControlDialog.qml + qml/CardItem.qml + qml/IconButton.qml + + diff --git a/qml/CardItem.qml b/qml/CardItem.qml new file mode 100644 index 0000000..b5585a6 --- /dev/null +++ b/qml/CardItem.qml @@ -0,0 +1,120 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtGraphicalEffects 1.0 +import MeuiKit 1.0 as Meui + +Item { + id: control + + property bool checked: false + property alias icon: _image.source + property alias label: _titleLabel.text + property alias text: _label.text + + signal clicked + + property var hoverColor: Meui.Theme.darkMode ? Qt.lighter(Meui.Theme.secondBackgroundColor, 2) + : Qt.darker(Meui.Theme.secondBackgroundColor, 1.3) + property var pressedColor: Meui.Theme.darkMode ? Qt.lighter(Meui.Theme.secondBackgroundColor, 1.8) + : Qt.darker(Meui.Theme.secondBackgroundColor, 1.5) + + property var highlightHoverColor: Meui.Theme.darkMode ? Qt.lighter(Meui.Theme.highlightColor, 1.1) + : Qt.darker(Meui.Theme.highlightColor, 1.1) + property var highlightPressedColor: Meui.Theme.darkMode ? Qt.lighter(Meui.Theme.highlightColor, 1.1) + : Qt.darker(Meui.Theme.highlightColor, 1.2) + + MouseArea { + id: _mouseArea + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.LeftButton + onClicked: control.clicked() + + onPressedChanged: { + control.scale = pressed ? 0.95 : 1.0 + } + } + + Behavior on scale { + NumberAnimation { + duration: 100 + } + } + + Meui.RoundedRect { + anchors.fill: parent + radius: Meui.Theme.bigRadius + backgroundOpacity: control.checked ? 0.9 : 0.3 + animationEnabled: false + + color: { + if (control.checked) { + if (_mouseArea.pressed) + return highlightPressedColor + else if (_mouseArea.containsMouse) + return highlightHoverColor + else + return Meui.Theme.highlightColor + } else { + if (_mouseArea.pressed) + return pressedColor + else if (_mouseArea.containsMouse) + return hoverColor + else + return Meui.Theme.secondBackgroundColor + } + } + } + + ColumnLayout { + anchors.fill: parent + anchors.leftMargin: Meui.Theme.smallRadius + anchors.rightMargin: Meui.Theme.smallRadius + + Image { + id: _image + Layout.preferredWidth: control.height * 0.3 + Layout.preferredHeight: control.height * 0.3 + sourceSize: Qt.size(width, height) + asynchronous: true + Layout.alignment: Qt.AlignCenter + Layout.topMargin: Meui.Units.largeSpacing + +// ColorOverlay { +// anchors.fill: _image +// source: _image +// color: control.checked ? Meui.Theme.highlightedTextColor : Meui.Theme.disabledTextColor +// } + } + + Item { + Layout.fillHeight: true + } + + Label { + id: _titleLabel + color: control.checked ? Meui.Theme.highlightedTextColor : Meui.Theme.textColor + Layout.preferredHeight: control.height * 0.15 + Layout.alignment: Qt.AlignHCenter + } + + Item { + Layout.fillHeight: true + } + + Label { + id: _label + color: control.checked ? Meui.Theme.highlightedTextColor : Meui.Theme.textColor + elide: Label.ElideRight + Layout.preferredHeight: control.height * 0.1 + Layout.alignment: Qt.AlignHCenter + // Layout.fillWidth: true + Layout.bottomMargin: Meui.Units.largeSpacing + } + + Item { + Layout.fillHeight: true + } + } +} diff --git a/qml/ControlDialog.qml b/qml/ControlDialog.qml new file mode 100644 index 0000000..d34a92d --- /dev/null +++ b/qml/ControlDialog.qml @@ -0,0 +1,356 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Window 2.12 +import QtQuick.Layouts 1.12 +import QtGraphicalEffects 1.0 + +import Cutefish.StatusBar 1.0 +import MeuiKit 1.0 as Meui +import Cutefish.Accounts 1.0 as Accounts + +ControlCenterDialog { + id: control + width: 500 + height: _mainLayout.implicitHeight + Meui.Units.largeSpacing * 4 + + minimumWidth: 500 + maximumWidth: 500 + minimumHeight: _mainLayout.implicitHeight + Meui.Units.largeSpacing * 4 + maximumHeight: _mainLayout.implicitHeight + Meui.Units.largeSpacing * 4 + + property point position: Qt.point(0, 0) + + onWidthChanged: adjustCorrectLocation() + onHeightChanged: adjustCorrectLocation() + onPositionChanged: adjustCorrectLocation() + + color: "transparent" + + Appearance { + id: appearance + } + + function adjustCorrectLocation() { + var posX = control.position.x + var posY = control.position.y + + // left + if (posX < 0) + posX = Meui.Units.largeSpacing + + // top + if (posY < 0) + posY = Meui.Units.largeSpacing + + // right + if (posX + control.width > Screen.width) + posX = Screen.width - control.width + + // bottom + if (posY > control.height > Screen.width) + posY = Screen.width - control.width - Meui.Units.largeSpacing + + control.x = posX + control.y = posY + } + + Brightness { + id: brightness + } + + Accounts.UserAccount { + id: currentUser + } + + Meui.WindowBlur { + view: control + geometry: Qt.rect(control.x, control.y, control.width, control.height) + windowRadius: _background.radius + enabled: true + } + + Meui.RoundedRect { + id: _background + anchors.fill: parent + radius: control.height * 0.05 + color: Meui.Theme.backgroundColor + backgroundOpacity: Meui.Theme.darkMode ? 0.3 : 0.4 + } + + Meui.WindowShadow { + view: control + geometry: Qt.rect(control.x, control.y, control.width, control.height) + radius: _background.radius + } + + ColumnLayout { + id: _mainLayout + anchors.fill: parent + anchors.leftMargin: Meui.Units.largeSpacing * 2 + anchors.topMargin: Meui.Units.largeSpacing * 1.5 + anchors.rightMargin: Meui.Units.largeSpacing * 2 + anchors.bottomMargin: Meui.Units.largeSpacing + spacing: Meui.Units.largeSpacing + + Item { + id: topItem + Layout.fillWidth: true + height: 50 + + RowLayout { + id: topItemLayout + anchors.fill: parent + spacing: Meui.Units.largeSpacing + + Image { + id: userIcon + Layout.fillHeight: true + width: height + sourceSize: Qt.size(width, height) + source: currentUser.iconFileName ? "file:///" + currentUser.iconFileName : "image://icontheme/default-user" + + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Item { + width: userIcon.width + height: userIcon.height + + Rectangle { + anchors.fill: parent + radius: parent.height / 2 + } + } + } + } + + Label { + id: userLabel + text: currentUser.userName + Layout.fillHeight: true + Layout.fillWidth: true + elide: Label.ElideRight + } + + IconButton { + id: settingsButton + implicitWidth: topItem.height * 0.8 + implicitHeight: topItem.height * 0.8 + Layout.alignment: Qt.AlignTop + source: "qrc:/images/" + (Meui.Theme.darkMode ? "dark/" : "light/") + "settings.svg" + onLeftButtonClicked: { + control.visible = false + process.startDetached("cutefish-settings") + } + } + + IconButton { + id: shutdownButton + implicitWidth: topItem.height * 0.8 + implicitHeight: topItem.height * 0.8 + Layout.alignment: Qt.AlignTop + source: "qrc:/images/" + (Meui.Theme.darkMode ? "dark/" : "light/") + "system-shutdown-symbolic.svg" + onLeftButtonClicked: { + control.visible = false + process.startDetached("cutefish-shutdown") + } + } + } + } + + Item { + id: controlItem + Layout.fillWidth: true + height: 120 + visible: wirelessItem.visible || bluetoothItem.visible + + RowLayout { + anchors.fill: parent + spacing: Meui.Units.largeSpacing + + CardItem { + id: wirelessItem + Layout.fillHeight: true + Layout.preferredWidth: contentItem.width / 3 - Meui.Units.largeSpacing * 2 + icon: Meui.Theme.darkMode || checked ? "qrc:/images/dark/network-wireless-connected-100.svg" + : "qrc:/images/light/network-wireless-connected-100.svg" + visible: network.wirelessHardwareEnabled + checked: network.wirelessEnabled + label: qsTr("Wi-Fi") + text: network.wirelessEnabled ? network.wirelessConnectionName ? + network.wirelessConnectionName : + qsTr("On") : qsTr("Off") + onClicked: network.wirelessEnabled = !network.wirelessEnabled + } + + CardItem { + id: bluetoothItem + Layout.fillHeight: true + Layout.preferredWidth: contentItem.width / 3 - Meui.Units.largeSpacing * 2 + icon: Meui.Theme.darkMode || checked ? "qrc:/images/dark/bluetooth-symbolic.svg" + : "qrc:/images/light/bluetooth-symbolic.svg" + checked: false + label: qsTr("Bluetooth") + text: qsTr("Off") + } + + CardItem { + id: darkModeItem + Layout.fillHeight: true + Layout.preferredWidth: contentItem.width / 3 - Meui.Units.largeSpacing * 2 + icon: Meui.Theme.darkMode || checked ? "qrc:/images/dark/dark-mode.svg" + : "qrc:/images/light/dark-mode.svg" + checked: Meui.Theme.darkMode + label: qsTr("Dark Mode") + text: Meui.Theme.darkMode ? qsTr("On") : qsTr("Off") + onClicked: appearance.switchDarkMode(!Meui.Theme.darkMode) + } + + Item { + Layout.fillWidth: true + } + } + } + + Item { + id: brightnessItem + Layout.fillWidth: true + height: 50 + visible: brightness.enabled + + Meui.RoundedRect { + id: brightnessItemBg + anchors.fill: parent + anchors.margins: 0 + radius: Meui.Theme.bigRadius + color: Meui.Theme.backgroundColor + backgroundOpacity: 0.3 + } + + RowLayout { + anchors.fill: brightnessItemBg + anchors.margins: Meui.Units.largeSpacing + spacing: Meui.Units.largeSpacing + + Image { + width: parent.height * 0.6 + height: parent.height * 0.6 + sourceSize: Qt.size(width, height) + source: "qrc:/images/" + (Meui.Theme.darkMode ? "dark" : "light") + "/brightness.svg" + } + + Slider { + id: brightnessSlider + from: 0 + to: 100 + stepSize: 1 + value: brightness.value + Layout.fillWidth: true + Layout.fillHeight: true + + onMoved: { + brightness.setValue(brightnessSlider.value) + } + } + } + } + + Item { + id: volumeItem + Layout.fillWidth: true + height: 50 + visible: volume.isValid + + Meui.RoundedRect { + id: volumeItemBg + anchors.fill: parent + anchors.margins: 0 + radius: Meui.Theme.bigRadius + color: Meui.Theme.backgroundColor + backgroundOpacity: 0.3 + } + + RowLayout { + anchors.fill: volumeItemBg + anchors.margins: Meui.Units.largeSpacing + spacing: Meui.Units.largeSpacing + + Image { + width: parent.height * 0.6 + height: parent.height * 0.6 + sourceSize: Qt.size(width, height) + source: "qrc:/images/" + (Meui.Theme.darkMode ? "dark" : "light") + "/" + volume.iconName + ".svg" + } + + Slider { + id: slider + from: 0 + to: 100 + stepSize: 1 + value: volume.volume + Layout.fillWidth: true + Layout.fillHeight: true + + onValueChanged: { + volume.setVolume(value) + + if (volume.isMute && value > 0) + volume.setMute(false) + } + } + } + } + + RowLayout { + Label { + id: timeLabel + + Timer { + interval: 1000 + repeat: true + running: true + triggeredOnStart: true + onTriggered: { + timeLabel.text = new Date().toLocaleString(Qt.locale(), Locale.ShortFormat) + } + } + } + + Item { + Layout.fillWidth: true + } + + StandardItem { + width: batteryLayout.implicitWidth + Meui.Units.largeSpacing + height: batteryLayout.implicitHeight + Meui.Units.largeSpacing + + onClicked: { + control.visible = false + process.startDetached("cutefish-settings", ["-m", "battery"]) + } + + RowLayout { + id: batteryLayout + anchors.fill: parent + visible: battery.available + spacing: 0 + + Image { + id: batteryIcon + width: 22 + height: 16 + sourceSize: Qt.size(width, height) + source: "qrc:/images/" + (Meui.Theme.darkMode ? "dark/" : "light/") + battery.iconSource + asynchronous: true + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + } + + Label { + text: battery.chargePercent + "%" + color: Meui.Theme.textColor + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + } + } + } + } + } +} diff --git a/qml/IconButton.qml b/qml/IconButton.qml new file mode 100644 index 0000000..06c6dc9 --- /dev/null +++ b/qml/IconButton.qml @@ -0,0 +1,57 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import MeuiKit 1.0 as Meui + +Item { + id: control + + property url source + property real size: 24 + property string popupText + + signal leftButtonClicked + signal rightButtonClicked + + MouseArea { + id: mouseArea + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + hoverEnabled: control.visible ? true : false + + onClicked: { + if (mouse.button === Qt.LeftButton) + control.leftButtonClicked() + else if (mouse.button === Qt.RightButton) + control.rightButtonClicked() + } + } + + Rectangle { + anchors.fill: parent + anchors.margins: 1 + radius: parent.height * 0.2 + + color: { + if (mouseArea.containsMouse) { + if (mouseArea.containsPress) + return (Meui.Theme.darkMode) ? Qt.rgba(255, 255, 255, 0.3) : Qt.rgba(0, 0, 0, 0.3) + else + return (Meui.Theme.darkMode) ? Qt.rgba(255, 255, 255, 0.2) : Qt.rgba(0, 0, 0, 0.2) + } + + return "transparent" + } + } + + Image { + id: iconImage + anchors.centerIn: parent + width: parent.height * 0.8 + height: width + sourceSize.width: width + sourceSize.height: height + source: control.source + asynchronous: true + } +} diff --git a/qml/StandardItem.qml b/qml/StandardItem.qml new file mode 100644 index 0000000..fabec38 --- /dev/null +++ b/qml/StandardItem.qml @@ -0,0 +1,68 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtGraphicalEffects 1.0 + +import MeuiKit 1.0 as Meui +import Cutefish.StatusBar 1.0 + +Item { + id: control + + property var popupText: "" + + signal clicked + signal rightClicked + + MouseArea { + id: _mouseArea + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + hoverEnabled: true + + onClicked: { + if (mouse.button == Qt.LeftButton) + control.clicked() + else if (mouse.button == Qt.RightButton) + control.rightClicked() + } + + onPressed: { + popupTips.hide() + } + + onContainsMouseChanged: { + if (containsMouse && control.popupText !== "") { + popupTips.popupText = control.popupText + popupTips.position = Qt.point(control.mapToGlobal(0, 0).x + (control.width / 2 - popupTips.width / 2), + control.height + 1) + popupTips.show() + } else { + popupTips.hide() + } + } + } + + Rectangle { + anchors.fill: parent + radius: Meui.Theme.smallRadius + + color: { + if (_mouseArea.containsMouse) { + if (_mouseArea.containsPress) + return (Meui.Theme.darkMode) ? Qt.rgba(255, 255, 255, 0.3) : Qt.rgba(0, 0, 0, 0.2) + else + return (Meui.Theme.darkMode) ? Qt.rgba(255, 255, 255, 0.2) : Qt.rgba(0, 0, 0, 0.1) + } + + return "transparent" + } + + Behavior on color { + ColorAnimation { + duration: 125 + easing.type: Easing.InOutCubic + } + } + } +} diff --git a/qml/main.qml b/qml/main.qml new file mode 100644 index 0000000..c76c5f3 --- /dev/null +++ b/qml/main.qml @@ -0,0 +1,169 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +import Cutefish.StatusBar 1.0 +import Cyber.NetworkManagement 1.0 as NM +import MeuiKit 1.0 as Meui + +Item { + id: rootItem + + Rectangle { + id: background + anchors.fill: parent + color: Meui.Theme.backgroundColor + opacity: 0.6 + } + + Meui.PopupTips { + id: popupTips + backgroundColor: Meui.Theme.backgroundColor + backgroundOpacity: Meui.Theme.darkMode ? 0.3 : 0.4 + } + + RowLayout { + anchors.fill: parent + anchors.leftMargin: Meui.Units.smallSpacing + anchors.rightMargin: Meui.Units.smallSpacing + + Item { + Layout.fillWidth: true + } + + ListView { + id: trayView + + orientation: Qt.Horizontal + layoutDirection: Qt.RightToLeft + interactive: false + clip: true + spacing: 0 + + property var itemSize: rootItem.height * 0.8 + property var itemWidth: itemSize + Meui.Units.largeSpacing + + Layout.fillHeight: true + Layout.preferredWidth: itemWidth * count + + model: SystemTrayModel { + id: trayModel + } + + delegate: StandardItem { + width: trayView.itemWidth + height: ListView.view.height + + Image { + anchors.centerIn: parent + width: parent.height * 0.7 + height: width + sourceSize: Qt.size(width, height) + source: model.iconName ? "image://icontheme/" + model.iconName : "" + } + + onClicked: trayModel.leftButtonClick(model.id) + onRightClicked: trayModel.rightButtonClick(model.id) + popupText: model.toolTip ? model.toolTip : model.title + } + } + + StandardItem { + id: controler + + Layout.fillHeight: true + Layout.preferredWidth: _controlerLayout.implicitWidth + + onClicked: { + if (controlDialog.visible) + controlDialog.visible = false + else { + // 先初始化,用户可能会通过Alt鼠标左键移动位置 + controlDialog.position = Qt.point(0, 0) + controlDialog.visible = true + controlDialog.position = Qt.point(mapToGlobal(0, 0).x, mapToGlobal(0, 0).y) + } + } + + RowLayout { + id: _controlerLayout + anchors.fill: parent + + spacing: Meui.Units.largeSpacing + + Image { + id: volumeIcon + visible: volume.isValid && status === Image.Ready + source: "qrc:/images/" + (Meui.Theme.darkMode ? "dark/" : "light/") + volume.iconName + ".svg" + width: 16 + height: width + sourceSize: Qt.size(width, height) + asynchronous: true + Layout.alignment: Qt.AlignCenter + } + + Image { + id: wirelessIcon + width: 16 + height: width + sourceSize: Qt.size(width, height) + source: network.wirelessIconName ? "qrc:/images/" + (Meui.Theme.darkMode ? "dark/" : "light/") + network.wirelessIconName + ".svg" : "" + asynchronous: true + Layout.alignment: Qt.AlignCenter + visible: network.enabled && + network.wirelessEnabled && + network.wirelessConnectionName !== "" && + wirelessIcon.status === Image.Ready + } + + Image { + id: batteryIcon + visible: battery.available && status === Image.Ready + height: 16 + width: height + 6 + sourceSize: Qt.size(width, height) + source: "qrc:/images/" + (Meui.Theme.darkMode ? "dark/" : "light/") + battery.iconSource + Layout.alignment: Qt.AlignCenter + asynchronous: true + } + + Label { + id: timeLabel + Layout.alignment: Qt.AlignCenter + font.pixelSize: rootItem.height * 0.5 + + Timer { + interval: 1000 + repeat: true + running: true + triggeredOnStart: true + onTriggered: { + timeLabel.text = new Date().toLocaleTimeString(Qt.locale(), Locale.ShortFormat) + } + } + } + } + } + } + + // Components + ControlDialog { + id: controlDialog + } + + Volume { + id: volume + } + + Battery { + id: battery + } + + NM.ConnectionIcon { + id: connectionIconProvider + } + + NM.Network { + id: network + } +} diff --git a/src/appearance.cpp b/src/appearance.cpp new file mode 100644 index 0000000..c0eea45 --- /dev/null +++ b/src/appearance.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2021 CutefishOS Team. + * + * Author: revenmartin + * + * 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 + * 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 . + */ + +#include "appearance.h" + +#include +#include +#include +#include +#include + +Appearance::Appearance(QObject *parent) + : QObject(parent) + , m_interface("org.cutefish.Settings", + "/Theme", + "org.cutefish.Theme", + QDBusConnection::sessionBus()) + , m_dockSettings(new QSettings(QSettings::UserScope, "cutefishos", "dock")) + , m_dockConfigWacher(new QFileSystemWatcher(this)) + , m_dockIconSize(0) + , m_dockDirection(0) + , m_fontPointSize(11) +{ + m_dockIconSize = m_dockSettings->value("IconSize").toInt(); + m_dockDirection = m_dockSettings->value("Direction").toInt(); + + m_dockConfigWacher->addPath(m_dockSettings->fileName()); + connect(m_dockConfigWacher, &QFileSystemWatcher::fileChanged, this, [=] { + m_dockSettings->sync(); + m_dockIconSize = m_dockSettings->value("IconSize").toInt(); + m_dockDirection = m_dockSettings->value("Direction").toInt(); + m_dockConfigWacher->addPath(m_dockSettings->fileName()); + emit dockIconSizeChanged(); + emit dockDirectionChanged(); + }); + + // Init + if (m_interface.isValid()) { + m_fontPointSize = m_interface.property("systemFontPointSize").toInt(); + + connect(&m_interface, SIGNAL(darkModeDimsWallpaerChanged()), this, SIGNAL(dimsWallpaperChanged())); + } +} + +void Appearance::switchDarkMode(bool darkMode) +{ + if (m_interface.isValid()) { + m_interface.call("setDarkMode", darkMode); + } +} + +bool Appearance::dimsWallpaper() const +{ + return m_interface.property("darkModeDimsWallpaer").toBool(); +} + +void Appearance::setDimsWallpaper(bool value) +{ + m_interface.call("setDarkModeDimsWallpaer", value); +} + +int Appearance::dockIconSize() const +{ + return m_dockIconSize; +} + +void Appearance::setDockIconSize(int dockIconSize) +{ + if (m_dockIconSize == dockIconSize) + return; + + m_dockIconSize = dockIconSize; + m_dockSettings->setValue("IconSize", m_dockIconSize); +} + +int Appearance::dockDirection() const +{ + return m_dockDirection; +} + +void Appearance::setDockDirection(int dockDirection) +{ + if (m_dockDirection == dockDirection) + return; + + m_dockDirection = dockDirection; + m_dockSettings->setValue("Direction", m_dockDirection); +} + +void Appearance::setGenericFontFamily(const QString &name) +{ + if (name.isEmpty()) + return; + + QDBusInterface iface("org.cutefish.Settings", + "/Theme", + "org.cutefish.Theme", + QDBusConnection::sessionBus(), this); + if (iface.isValid()) { + iface.call("setSystemFont", name); + } +} + +void Appearance::setFixedFontFamily(const QString &name) +{ + if (name.isEmpty()) + return; + + QDBusInterface iface("org.cutefish.Settings", + "/Theme", + "org.cutefish.Theme", + QDBusConnection::sessionBus(), this); + if (iface.isValid()) { + iface.call("setSystemFixedFont", name); + } +} + +int Appearance::fontPointSize() const +{ + return m_fontPointSize; +} + +void Appearance::setFontPointSize(int fontPointSize) +{ + m_fontPointSize = fontPointSize; + + QDBusInterface iface("org.cutefish.Settings", + "/Theme", + "org.cutefish.Theme", + QDBusConnection::sessionBus(), this); + if (iface.isValid()) { + iface.call("setSystemFontPointSize", m_fontPointSize * 1.0); + } +} + +void Appearance::setAccentColor(int accentColor) +{ + QDBusInterface iface("org.cutefish.Settings", + "/Theme", + "org.cutefish.Theme", + QDBusConnection::sessionBus(), this); + if (iface.isValid()) { + iface.call("setAccentColor", accentColor); + } +} + +double Appearance::devicePixelRatio() const +{ + return m_interface.property("devicePixelRatio").toDouble(); +} + +void Appearance::setDevicePixelRatio(double value) +{ + QDBusInterface iface("org.cutefish.Settings", + "/Theme", + "org.cutefish.Theme", + QDBusConnection::sessionBus(), this); + if (iface.isValid()) { + iface.call("setDevicePixelRatio", value); + } +} diff --git a/src/appearance.h b/src/appearance.h new file mode 100644 index 0000000..c495bf2 --- /dev/null +++ b/src/appearance.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2021 CutefishOS Team. + * + * Author: revenmartin + * + * 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 + * 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 . + */ + +#ifndef APPEARANCE_H +#define APPEARANCE_H + +#include +#include +#include +#include + +class Appearance : public QObject +{ + Q_OBJECT + Q_PROPERTY(int dockIconSize READ dockIconSize WRITE setDockIconSize NOTIFY dockIconSizeChanged) + Q_PROPERTY(int dockDirection READ dockDirection WRITE setDockDirection NOTIFY dockDirectionChanged) + Q_PROPERTY(int fontPointSize READ fontPointSize WRITE setFontPointSize NOTIFY fontPointSizeChanged) + Q_PROPERTY(bool dimsWallpaper READ dimsWallpaper WRITE setDimsWallpaper NOTIFY dimsWallpaperChanged) + Q_PROPERTY(double devicePixelRatio READ devicePixelRatio WRITE setDevicePixelRatio NOTIFY devicePixelRatioChanged) + +public: + explicit Appearance(QObject *parent = nullptr); + + Q_INVOKABLE void switchDarkMode(bool darkMode); + + bool dimsWallpaper() const; + Q_INVOKABLE void setDimsWallpaper(bool value); + + int dockIconSize() const; + Q_INVOKABLE void setDockIconSize(int dockIconSize); + + int dockDirection() const; + Q_INVOKABLE void setDockDirection(int dockDirection); + + Q_INVOKABLE void setGenericFontFamily(const QString &name); + Q_INVOKABLE void setFixedFontFamily(const QString &name); + + int fontPointSize() const; + Q_INVOKABLE void setFontPointSize(int fontPointSize); + + Q_INVOKABLE void setAccentColor(int accentColor); + + double devicePixelRatio() const; + Q_INVOKABLE void setDevicePixelRatio(double value); + +signals: + void dockIconSizeChanged(); + void dockDirectionChanged(); + void fontPointSizeChanged(); + void dimsWallpaperChanged(); + void devicePixelRatioChanged(); + +private: + QDBusInterface m_interface; + QSettings *m_dockSettings; + QFileSystemWatcher *m_dockConfigWacher; + + int m_dockIconSize; + int m_dockDirection; + int m_fontPointSize; +}; + +#endif // APPEARANCE_H diff --git a/src/battery.cpp b/src/battery.cpp new file mode 100644 index 0000000..1f93406 --- /dev/null +++ b/src/battery.cpp @@ -0,0 +1,130 @@ +#include "battery.h" + +static const QString s_sServer = "org.cutefish.Settings"; +static const QString s_sPath = "/PrimaryBattery"; +static const QString s_sInterface = "org.cutefish.PrimaryBattery"; + +Battery::Battery(QObject *parent) + : QObject(parent) + , m_upowerInterface("org.freedesktop.UPower", + "/org/freedesktop/UPower", + "org.freedesktop.UPower", + QDBusConnection::systemBus()) + , m_interface("org.cutefish.Settings", + "/PrimaryBattery", + "org.cutefish.PrimaryBattery", + QDBusConnection::sessionBus()) + , m_available(false) + , m_onBattery(false) +{ + m_available = m_interface.isValid() && !m_interface.lastError().isValid(); + + if (m_available) { + QDBusConnection::sessionBus().connect(s_sServer, s_sPath, s_sInterface, "chargeStateChanged", this, SLOT(chargeStateChanged(int))); + QDBusConnection::sessionBus().connect(s_sServer, s_sPath, s_sInterface, "chargePercentChanged", this, SLOT(chargePercentChanged(int))); + QDBusConnection::sessionBus().connect(s_sServer, s_sPath, s_sInterface, "lastChargedPercentChanged", this, SLOT(lastChargedPercentChanged())); + QDBusConnection::sessionBus().connect(s_sServer, s_sPath, s_sInterface, "capacityChanged", this, SLOT(capacityChanged(int))); + QDBusConnection::sessionBus().connect(s_sServer, s_sPath, s_sInterface, "remainingTimeChanged", this, SLOT(remainingTimeChanged(qlonglong))); + + // Update Icon + QDBusConnection::sessionBus().connect(s_sServer, s_sPath, s_sInterface, "chargePercentChanged", this, SLOT(iconSourceChanged())); + + QDBusInterface interface("org.freedesktop.UPower", "/org/freedesktop/UPower", + "org.freedesktop.UPower", QDBusConnection::systemBus()); + + QDBusConnection::systemBus().connect("org.freedesktop.UPower", "/org/freedesktop/UPower", + "org.freedesktop.DBus.Properties", + "PropertiesChanged", this, + SLOT(onPropertiesChanged(QString, QVariantMap, QStringList))); + + if (interface.isValid()) { + m_onBattery = interface.property("OnBattery").toBool(); + } + + emit validChanged(); + } +} + +bool Battery::available() const +{ + return m_available; +} + +bool Battery::onBattery() const +{ + return m_onBattery; +} + +int Battery::chargeState() const +{ + return m_interface.property("chargeState").toInt(); +} + +int Battery::chargePercent() const +{ + return m_interface.property("chargePercent").toInt(); +} + +int Battery::lastChargedPercent() const +{ + return m_interface.property("lastChargedPercent").toInt(); +} + +int Battery::capacity() const +{ + return m_interface.property("capacity").toInt(); +} + +QString Battery::statusString() const +{ + return m_interface.property("statusString").toString(); +} + +QString Battery::iconSource() const +{ + int percent = this->chargePercent(); + int range = 0; + + if (percent >= 95) + range = 100; + else if (percent >= 85) + range = 90; + else if (percent>= 75) + range = 80; + else if (percent >= 65) + range = 70; + else if (percent >= 55) + range = 60; + else if (percent >= 45) + range = 50; + else if (percent >= 35) + range = 40; + else if (percent >= 25) + range = 30; + else if (percent >= 15) + range = 20; + else if (percent >= 5) + range = 10; + else + range = 0; + + if (m_onBattery) + return QString("battery-level-%1-symbolic.svg").arg(range); + + return QString("battery-level-%1-charging-symbolic.svg").arg(range); +} + +void Battery::onPropertiesChanged(const QString &ifaceName, const QVariantMap &changedProps, const QStringList &invalidatedProps) +{ + Q_UNUSED(ifaceName); + Q_UNUSED(changedProps); + Q_UNUSED(invalidatedProps); + + bool onBattery = m_upowerInterface.property("OnBattery").toBool(); + if (onBattery != m_onBattery) { + m_onBattery = onBattery; + m_interface.call("refresh"); + emit onBatteryChanged(); + emit iconSourceChanged(); + } +} diff --git a/src/battery.h b/src/battery.h new file mode 100644 index 0000000..349e2b6 --- /dev/null +++ b/src/battery.h @@ -0,0 +1,53 @@ +#ifndef BATTERY_H +#define BATTERY_H + +#include +#include + +class Battery : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool available READ available NOTIFY validChanged) + Q_PROPERTY(int chargeState READ chargeState NOTIFY chargeStateChanged) + Q_PROPERTY(int chargePercent READ chargePercent NOTIFY chargePercentChanged) + Q_PROPERTY(int lastChargedPercent READ lastChargedPercent NOTIFY lastChargedPercentChanged) + Q_PROPERTY(int capacity READ capacity NOTIFY capacityChanged) + Q_PROPERTY(QString statusString READ statusString NOTIFY remainingTimeChanged) + Q_PROPERTY(bool onBattery READ onBattery NOTIFY onBatteryChanged) + Q_PROPERTY(QString iconSource READ iconSource NOTIFY iconSourceChanged) + +public: + explicit Battery(QObject *parent = nullptr); + + bool available() const; + bool onBattery() const; + + int chargeState() const; + int chargePercent() const; + int lastChargedPercent() const; + int capacity() const; + QString statusString() const; + + QString iconSource() const; + +signals: + void validChanged(); + void chargeStateChanged(int); + void chargePercentChanged(int); + void capacityChanged(int); + void remainingTimeChanged(qlonglong time); + void onBatteryChanged(); + void lastChargedPercentChanged(); + void iconSourceChanged(); + +private slots: + void onPropertiesChanged(const QString &ifaceName, const QVariantMap &changedProps, const QStringList &invalidatedProps); + +private: + QDBusInterface m_upowerInterface; + QDBusInterface m_interface; + bool m_available; + bool m_onBattery; +}; + +#endif // BATTERY_H diff --git a/src/brightness.cpp b/src/brightness.cpp new file mode 100644 index 0000000..c04a491 --- /dev/null +++ b/src/brightness.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2021 CutefishOS Team. + * + * Author: revenmartin + * + * 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 + * 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 . + */ + +#include "brightness.h" + +Brightness::Brightness(QObject *parent) + : QObject(parent) + , m_dbusConnection(QDBusConnection::sessionBus()) + , m_iface("org.cutefish.Settings", + "/Brightness", + "org.cutefish.Brightness", m_dbusConnection) + , m_value(0) + , m_enabled(false) +{ + if (!m_iface.isValid()) + return; + + m_value = m_iface.property("brightness").toInt(); + m_enabled = m_iface.property("brightnessEnabled").toBool(); +} + +void Brightness::setValue(int value) +{ + m_iface.call("setValue", value); +} + +int Brightness::value() const +{ + return m_value; +} + +bool Brightness::enabled() const +{ + return m_enabled; +} diff --git a/src/brightness.h b/src/brightness.h new file mode 100644 index 0000000..6a80554 --- /dev/null +++ b/src/brightness.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2021 CutefishOS Team. + * + * Author: revenmartin + * + * 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 + * 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 . + */ + +#ifndef BRIGHTNESS_H +#define BRIGHTNESS_H + +#include +#include + +class Brightness : public QObject +{ + Q_OBJECT + Q_PROPERTY(int value READ value NOTIFY valueChanged) + Q_PROPERTY(bool enabled READ enabled CONSTANT) + +public: + explicit Brightness(QObject *parent = nullptr); + + Q_INVOKABLE void setValue(int value); + + int value() const; + bool enabled() const; + +signals: + void valueChanged(); + +private: + QDBusConnection m_dbusConnection; + QDBusInterface m_iface; + int m_value; + bool m_enabled; +}; + +#endif // BRIGHTNESS_H diff --git a/src/controlcenterdialog.cpp b/src/controlcenterdialog.cpp new file mode 100644 index 0000000..efac8df --- /dev/null +++ b/src/controlcenterdialog.cpp @@ -0,0 +1,24 @@ +#include "controlcenterdialog.h" +#include + +ControlCenterDialog::ControlCenterDialog(QQuickView *parent) + : QQuickView(parent) +{ + setFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); + + connect(this, &QQuickView::activeChanged, this, [=] { + if (!isActive()) + hide(); + }); +} + +void ControlCenterDialog::showEvent(QShowEvent *event) +{ + KWindowSystem::setState(winId(), NET::SkipTaskbar | NET::SkipPager | NET::SkipSwitcher); + QQuickView::showEvent(event); +} + +void ControlCenterDialog::hideEvent(QHideEvent *event) +{ + QQuickView::hideEvent(event); +} diff --git a/src/controlcenterdialog.h b/src/controlcenterdialog.h new file mode 100644 index 0000000..adeb18e --- /dev/null +++ b/src/controlcenterdialog.h @@ -0,0 +1,19 @@ +#ifndef CONTROLCENTERDIALOG_H +#define CONTROLCENTERDIALOG_H + +#include +#include + +class ControlCenterDialog : public QQuickView +{ + Q_OBJECT + +public: + ControlCenterDialog(QQuickView *view = nullptr); + +protected: + void showEvent(QShowEvent *event) override; + void hideEvent(QHideEvent *event) override; +}; + +#endif // CONTROLCENTERDIALOG_H diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..2e879b8 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,40 @@ +#include +#include +#include + +#include "statusbar.h" +#include "controlcenterdialog.h" +#include "systemtray/systemtraymodel.h" + +#include "appearance.h" +#include "brightness.h" +#include "battery.h" +#include "volume.h" + +int main(int argc, char *argv[]) +{ + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QApplication app(argc, argv); + + QString qmFilePath = QString("%1/%2.qm").arg("/usr/share/cutefish-statusbar/translations/").arg(QLocale::system().name()); + if (QFile::exists(qmFilePath)) { + QTranslator *translator = new QTranslator(QApplication::instance()); + if (translator->load(qmFilePath)) { + QGuiApplication::installTranslator(translator); + } else { + translator->deleteLater(); + } + } + + const char *uri = "Cutefish.StatusBar"; + qmlRegisterType(uri, 1, 0, "SystemTrayModel"); + qmlRegisterType(uri, 1, 0, "ControlCenterDialog"); + qmlRegisterType(uri, 1, 0, "Appearance"); + qmlRegisterType(uri, 1, 0, "Brightness"); + qmlRegisterType(uri, 1, 0, "Battery"); + qmlRegisterType(uri, 1, 0, "Volume"); + + StatusBar bar; + + return app.exec(); +} diff --git a/src/processprovider.cpp b/src/processprovider.cpp new file mode 100644 index 0000000..90003b2 --- /dev/null +++ b/src/processprovider.cpp @@ -0,0 +1,16 @@ +#include "processprovider.h" +#include + +ProcessProvider::ProcessProvider(QObject *parent) + : QObject(parent) +{ + +} + +bool ProcessProvider::startDetached(const QString &exec, QStringList args) +{ + QProcess process; + process.setProgram(exec); + process.setArguments(args); + return process.startDetached(); +} diff --git a/src/processprovider.h b/src/processprovider.h new file mode 100644 index 0000000..8d07bc1 --- /dev/null +++ b/src/processprovider.h @@ -0,0 +1,16 @@ +#ifndef PROCESSPROVIDER_H +#define PROCESSPROVIDER_H + +#include + +class ProcessProvider : public QObject +{ + Q_OBJECT + +public: + explicit ProcessProvider(QObject *parent = nullptr); + + Q_INVOKABLE bool startDetached(const QString &exec, QStringList args = QStringList()); +}; + +#endif // PROCESSPROVIDER_H diff --git a/src/statusbar.cpp b/src/statusbar.cpp new file mode 100644 index 0000000..517c2d1 --- /dev/null +++ b/src/statusbar.cpp @@ -0,0 +1,66 @@ +#include "statusbar.h" +#include "processprovider.h" + +#include +#include + +#include +#include + +#include +#include +#include + +StatusBar::StatusBar(QQuickView *parent) + : QQuickView(parent) +{ + setFlags(Qt::FramelessWindowHint | Qt::WindowDoesNotAcceptFocus); + setColor(Qt::transparent); + + KWindowSystem::setOnDesktop(winId(), NET::OnAllDesktops); + KWindowSystem::setType(winId(), NET::Dock); + KWindowEffects::slideWindow(winId(), KWindowEffects::TopEdge); + + engine()->rootContext()->setContextProperty("process", new ProcessProvider); + + setSource(QUrl(QStringLiteral("qrc:/qml/main.qml"))); + setResizeMode(QQuickView::SizeRootObjectToView); + setScreen(qApp->primaryScreen()); + updateGeometry(); + setVisible(true); + + connect(qApp->primaryScreen(), &QScreen::virtualGeometryChanged, this, &StatusBar::updateGeometry); + connect(qApp->primaryScreen(), &QScreen::geometryChanged, this, &StatusBar::updateGeometry); +} + +void StatusBar::updateGeometry() +{ + const QRect rect = qApp->primaryScreen()->geometry(); + QRect windowRect = QRect(rect.x(), rect.y(), rect.width(), 30); + setGeometry(windowRect); + updateViewStruts(); +} + +void StatusBar::updateViewStruts() +{ + const QRect windowRect = geometry(); + NETExtendedStrut strut; + + strut.top_width = windowRect.height(); + strut.top_start = x(); + strut.top_end = x() + windowRect.width(); + + KWindowSystem::setExtendedStrut(winId(), + strut.left_width, + strut.left_start, + strut.left_end, + strut.right_width, + strut.right_start, + strut.right_end, + strut.top_width, + strut.top_start, + strut.top_end, + strut.bottom_width, + strut.bottom_start, + strut.bottom_end); +} diff --git a/src/statusbar.h b/src/statusbar.h new file mode 100644 index 0000000..9a709c8 --- /dev/null +++ b/src/statusbar.h @@ -0,0 +1,17 @@ +#ifndef STATUSBAR_H +#define STATUSBAR_H + +#include + +class StatusBar : public QQuickView +{ + Q_OBJECT + +public: + explicit StatusBar(QQuickView *parent = nullptr); + + void updateGeometry(); + void updateViewStruts(); +}; + +#endif // STATUSBAR_H diff --git a/src/systemtray/CMakeLists.txt b/src/systemtray/CMakeLists.txt new file mode 100644 index 0000000..1b659b7 --- /dev/null +++ b/src/systemtray/CMakeLists.txt @@ -0,0 +1,18 @@ +set(TRAY_SRCS + statusnotifieritemjob.cpp + statusnotifieritemsource.cpp + systemtraytypes.cpp + systemtraytypedefs.h +) + +set(statusnotifierwatcher_xml org.kde.StatusNotifierWatcher.xml) +qt5_add_dbus_interface(SRCS ${statusnotifierwatcher_xml} statusnotifierwatcher_interface) +qt5_add_dbus_interface(SRCS org.freedesktop.DBus.Properties.xml dbusproperties) + +set(statusnotifieritem_xml org.kde.StatusNotifierItem.xml) +set_source_files_properties(${statusnotifieritem_xml} PROPERTIES + NO_NAMESPACE false + INCLUDE "systemtraytypes.h" + CLASSNAME OrgKdeStatusNotifierItem +) +qt5_add_dbus_interface(SRCS ${statusnotifieritem_xml} statusnotifieritem_interface) \ No newline at end of file diff --git a/src/systemtray/org.freedesktop.DBus.Properties.xml b/src/systemtray/org.freedesktop.DBus.Properties.xml new file mode 100644 index 0000000..3bbf826 --- /dev/null +++ b/src/systemtray/org.freedesktop.DBus.Properties.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/systemtray/org.kde.StatusNotifierItem.xml b/src/systemtray/org.kde.StatusNotifierItem.xml new file mode 100644 index 0000000..d378c74 --- /dev/null +++ b/src/systemtray/org.kde.StatusNotifierItem.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/systemtray/org.kde.StatusNotifierWatcher.xml b/src/systemtray/org.kde.StatusNotifierWatcher.xml new file mode 100644 index 0000000..2eb1a7a --- /dev/null +++ b/src/systemtray/org.kde.StatusNotifierWatcher.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/systemtray/statusnotifieritemjob.cpp b/src/systemtray/statusnotifieritemjob.cpp new file mode 100644 index 0000000..e198a65 --- /dev/null +++ b/src/systemtray/statusnotifieritemjob.cpp @@ -0,0 +1,25 @@ +#include "statusnotifieritemjob.h" + +StatusNotifierItemJob::StatusNotifierItemJob(StatusNotifierItemSource *source, QObject *parent) + : QObject(parent) + , m_source(source) +{ + // Queue connection, so that all 'deleteLater' are performed before we use updated menu. + connect(source, SIGNAL(contextMenuReady(QMenu *)), this, SLOT(contextMenuReady(QMenu *)), Qt::QueuedConnection); + connect(source, &StatusNotifierItemSource::activateResult, this, &StatusNotifierItemJob::activateCallback); +} + +void StatusNotifierItemJob::start() +{ + +} + +void StatusNotifierItemJob::activateCallback(bool success) +{ + +} + +void StatusNotifierItemJob::contextMenuReady(QMenu *menu) +{ + +} diff --git a/src/systemtray/statusnotifieritemjob.h b/src/systemtray/statusnotifieritemjob.h new file mode 100644 index 0000000..19bb572 --- /dev/null +++ b/src/systemtray/statusnotifieritemjob.h @@ -0,0 +1,27 @@ +#ifndef STATUSNOTIFIERITEMJOB_H +#define STATUSNOTIFIERITEMJOB_H + +#include +#include + +#include "statusnotifieritemsource.h" + +class StatusNotifierItemJob : public QObject +{ + Q_OBJECT + +public: + explicit StatusNotifierItemJob(StatusNotifierItemSource *source, QObject *parent = nullptr); + +protected: + void start(); + +private Q_SLOTS: + void activateCallback(bool success); + void contextMenuReady(QMenu *menu); + +private: + StatusNotifierItemSource *m_source; +}; + +#endif // STATUSNOTIFIERITEMJOB_H diff --git a/src/systemtray/statusnotifieritemsource.cpp b/src/systemtray/statusnotifieritemsource.cpp new file mode 100644 index 0000000..d40cc92 --- /dev/null +++ b/src/systemtray/statusnotifieritemsource.cpp @@ -0,0 +1,355 @@ +#include "statusnotifieritemsource.h" +#include "systemtraytypes.h" + +#include +#include +#include + +class MenuImporter : public DBusMenuImporter +{ +public: + using DBusMenuImporter::DBusMenuImporter; + +protected: + QIcon iconForName(const QString & name) override { + return QIcon::fromTheme(name); + } +}; + +StatusNotifierItemSource::StatusNotifierItemSource(const QString ¬ifierItemId, QObject *parent) + : QObject(parent) + , m_menuImporter(nullptr) + , m_refreshing(false) + , m_needsReRefreshing(false) + , m_titleUpdate(true) + , m_iconUpdate(true) + , m_tooltipUpdate(true) + , m_statusUpdate(true) + , m_id(notifierItemId) +{ + setObjectName(notifierItemId); + + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + + m_name = notifierItemId; + + int slash = notifierItemId.indexOf('/'); + if (slash == -1) { + qWarning() << "Invalid notifierItemId:" << notifierItemId; + m_valid = false; + m_statusNotifierItemInterface = nullptr; + return; + } + + QString service = notifierItemId.left(slash); + QString path = notifierItemId.mid(slash); + + m_statusNotifierItemInterface = new org::kde::StatusNotifierItem(service, path, QDBusConnection::sessionBus(), this); + + m_refreshTimer.setSingleShot(true); + m_refreshTimer.setInterval(10); + connect(&m_refreshTimer, &QTimer::timeout, this, &StatusNotifierItemSource::performRefresh); + + m_valid = !service.isEmpty() && m_statusNotifierItemInterface->isValid(); + + if (m_valid) { + connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewTitle, this, &StatusNotifierItemSource::refreshTitle); + connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewIcon, this, &StatusNotifierItemSource::refreshIcons); + connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewAttentionIcon, this, &StatusNotifierItemSource::refreshIcons); + connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewOverlayIcon, this, &StatusNotifierItemSource::refreshIcons); + connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewToolTip, this, &StatusNotifierItemSource::refreshToolTip); + connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewStatus, this, &StatusNotifierItemSource::syncStatus); + refresh(); + } +} + +StatusNotifierItemSource::~StatusNotifierItemSource() +{ + delete m_statusNotifierItemInterface; +} + +QString StatusNotifierItemSource::id() const +{ + return m_id; +} + +QString StatusNotifierItemSource::title() const +{ + return m_title; +} + +QString StatusNotifierItemSource::tooltip() const +{ + return m_tooltip; +} + +QString StatusNotifierItemSource::subtitle() const +{ + return m_subTitle; +} + +QString StatusNotifierItemSource::iconName() const +{ + return m_iconName; +} + +QIcon StatusNotifierItemSource::icon() const +{ + return m_icon; +} + +void StatusNotifierItemSource::activate(int x, int y) +{ + if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) { + QDBusMessage message = QDBusMessage::createMethodCall(m_statusNotifierItemInterface->service(), + m_statusNotifierItemInterface->path(), + m_statusNotifierItemInterface->interface(), + QStringLiteral("Activate")); + + message << x << y; + QDBusPendingCall call = m_statusNotifierItemInterface->connection().asyncCall(message); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); + connect(watcher, &QDBusPendingCallWatcher::finished, this, &StatusNotifierItemSource::activateCallback); + } +} + +void StatusNotifierItemSource::secondaryActivate(int x, int y) +{ + if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) { + m_statusNotifierItemInterface->call(QDBus::NoBlock, QStringLiteral("SecondaryActivate"), x, y); + } +} + +void StatusNotifierItemSource::scroll(int delta, const QString &direction) +{ + if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) { + m_statusNotifierItemInterface->call(QDBus::NoBlock, QStringLiteral("Scroll"), delta, direction); + } +} + +void StatusNotifierItemSource::contextMenu(int x, int y) +{ + if (m_menuImporter) { + m_menuImporter->updateMenu(); + + // Popup menu + if (m_menuImporter->menu()) { + m_menuImporter->menu()->popup(QPoint(x, y)); + } + } else { + qWarning() << "Could not find DBusMenu interface, falling back to calling ContextMenu()"; + if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) { + m_statusNotifierItemInterface->call(QDBus::NoBlock, QStringLiteral("ContextMenu"), x, y); + } + } +} + +void StatusNotifierItemSource::contextMenuReady() +{ + emit contextMenuReady(m_menuImporter->menu()); +} + +void StatusNotifierItemSource::refreshTitle() +{ + m_titleUpdate = true; + refresh(); +} + +void StatusNotifierItemSource::refreshIcons() +{ + m_iconUpdate = true; + refresh(); +} + +void StatusNotifierItemSource::refreshToolTip() +{ + m_tooltipUpdate = true; + refresh(); +} + +void StatusNotifierItemSource::refresh() +{ + if (!m_refreshTimer.isActive()) { + m_refreshTimer.start(); + } +} + +void StatusNotifierItemSource::performRefresh() +{ + if (m_refreshing) { + m_needsReRefreshing = true; + return; + } + + m_refreshing = true; + QDBusMessage message = QDBusMessage::createMethodCall(m_statusNotifierItemInterface->service(), + m_statusNotifierItemInterface->path(), + QStringLiteral("org.freedesktop.DBus.Properties"), + QStringLiteral("GetAll")); + + message << m_statusNotifierItemInterface->interface(); + QDBusPendingCall call = m_statusNotifierItemInterface->connection().asyncCall(message); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); + connect(watcher, &QDBusPendingCallWatcher::finished, this, &StatusNotifierItemSource::refreshCallback); +} + +void StatusNotifierItemSource::syncStatus(QString) +{ + +} + +void StatusNotifierItemSource::refreshCallback(QDBusPendingCallWatcher *call) +{ + m_refreshing = false; + if (m_needsReRefreshing) { + m_needsReRefreshing = false; + performRefresh(); + call->deleteLater(); + return; + } + + QDBusPendingReply reply = *call; + if (reply.isError()) { + m_valid = false; + } else { + QVariantMap properties = reply.argumentAt<0>(); + QString path = properties[QStringLiteral("IconThemePath")].toString(); + + m_title = properties[QStringLiteral("Title")].toString(); + m_iconName = properties[QStringLiteral("IconName")].toString(); + + // ToolTip + KDbusToolTipStruct toolTip; + properties[QStringLiteral("ToolTip")].value() >> toolTip; + m_tooltip = toolTip.title; + + // Icon + KDbusImageVector image; + properties[QStringLiteral("IconPixmap")].value() >> image; + if (!image.isEmpty()) { + m_icon = imageVectorToPixmap(image); + } + +// QString newTitle; +// QString newIconName; +// QString newToolTip; + +// QString overlayIconName = properties[QStringLiteral("OverlayIconName")].toString(); +// QString iconName = properties[QStringLiteral("IconName")].toString(); + +// bool changed = false; + +// newTitle = properties[QStringLiteral("Title")].toString(); + +// if (!overlayIconName.isEmpty()) +// newIconName = iconName; +// if (!iconName.isEmpty()) +// newIconName = iconName; + +// KDbusToolTipStruct toolTip; +// properties[QStringLiteral("ToolTip")].value() >> toolTip; +// // newToolTip = !toolTip.title.isEmpty() ? toolTip.title : toolTip.subTitle; + +// if (newTitle != m_title) { +// m_title = newTitle; +// changed = true; +// } + +// if (newIconName != m_iconName) { +// m_iconName = iconName; +// changed = true; +// } + +// if (newToolTip != m_tooltip) { +// m_tooltip = newToolTip; +// changed = true; +// } + +// // Icon +// KDbusImageVector image; +// properties[QStringLiteral("AttentionIconPixmap")].value() >> image; +// if (!image.isEmpty()) { +// m_icon = imageVectorToPixmap(image); +// changed = true; +// } + +// properties[QStringLiteral("IconPixmap")].value() >> image; +// if (!image.isEmpty()) { +// m_icon = imageVectorToPixmap(image); +// changed = true; +// } + + // Menu + if (!m_menuImporter) { + QString menuObjectPath = properties[QStringLiteral("Menu")].value().path(); + if (!menuObjectPath.isEmpty()) { + if (menuObjectPath.startsWith(QLatin1String("/NO_DBUSMENU"))) { + // This is a hack to make it possible to disable DBusMenu in an + // application. The string "/NO_DBUSMENU" must be the same as in + // KStatusNotifierItem::setContextMenu(). + qWarning() << "DBusMenu disabled for this application"; + } else { + m_menuImporter = new MenuImporter(m_statusNotifierItemInterface->service(), + menuObjectPath, this); + } + } + } + + // qDebug() << newTitle << newIconName << newToolTip << image.isEmpty(); + + emit updated(this); + } + + call->deleteLater(); +} + +void StatusNotifierItemSource::activateCallback(QDBusPendingCallWatcher *call) +{ + QDBusPendingReply reply = *call; + emit activateResult(!reply.isError()); + call->deleteLater(); +} + +QPixmap StatusNotifierItemSource::KDbusImageStructToPixmap(const KDbusImageStruct &image) const +{ + // swap from network byte order if we are little endian + if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) { + uint *uintBuf = (uint *)image.data.data(); + for (uint i = 0; i < image.data.size() / sizeof(uint); ++i) { + *uintBuf = ntohl(*uintBuf); + ++uintBuf; + } + } + if (image.width == 0 || image.height == 0) { + return QPixmap(); + } + + // avoid a deep copy of the image data + // we need to keep a reference to the image.data alive for the lifespan of the image, even if the image is copied + // we create a new QByteArray with a shallow copy of the original data on the heap, then delete this in the QImage cleanup + auto dataRef = new QByteArray(image.data); + + QImage iconImage( + reinterpret_cast(dataRef->data()), + image.width, + image.height, + QImage::Format_ARGB32, + [](void *ptr) { + delete static_cast(ptr); + }, + dataRef); + return QPixmap::fromImage(iconImage); +} + +QIcon StatusNotifierItemSource::imageVectorToPixmap(const KDbusImageVector &vector) const +{ + QIcon icon; + + for (int i = 0; i < vector.size(); ++i) { + icon.addPixmap(KDbusImageStructToPixmap(vector[i])); + } + + return icon; +} diff --git a/src/systemtray/statusnotifieritemsource.h b/src/systemtray/statusnotifieritemsource.h new file mode 100644 index 0000000..166fdca --- /dev/null +++ b/src/systemtray/statusnotifieritemsource.h @@ -0,0 +1,72 @@ +#ifndef STATUSNOTIFIERITEMSOURCE_H +#define STATUSNOTIFIERITEMSOURCE_H + +#include +#include +#include + +#include "statusnotifieritem_interface.h" + +class DBusMenuImporter; +class StatusNotifierItemSource : public QObject +{ + Q_OBJECT + +public: + explicit StatusNotifierItemSource(const QString &service, QObject *parent = nullptr); + ~StatusNotifierItemSource(); + + QString id() const; + QString title() const; + QString tooltip() const; + QString subtitle() const; + QString iconName() const; + QIcon icon() const; + + void activate(int x, int y); + void secondaryActivate(int x, int y); + void scroll(int delta, const QString &direction); + void contextMenu(int x, int y); + +signals: + void contextMenuReady(QMenu *menu); + void activateResult(bool success); + void updated(StatusNotifierItemSource *); + +private slots: + void contextMenuReady(); + void refreshTitle(); + void refreshIcons(); + void refreshToolTip(); + void refresh(); + void performRefresh(); + void syncStatus(QString); + void refreshCallback(QDBusPendingCallWatcher *); + void activateCallback(QDBusPendingCallWatcher *); + +private: + QPixmap KDbusImageStructToPixmap(const KDbusImageStruct &image) const; + QIcon imageVectorToPixmap(const KDbusImageVector &vector) const; + +private: + bool m_valid; + QString m_name; + QTimer m_refreshTimer; + DBusMenuImporter *m_menuImporter; + org::kde::StatusNotifierItem *m_statusNotifierItemInterface; + bool m_refreshing : 1; + bool m_needsReRefreshing : 1; + bool m_titleUpdate : 1; + bool m_iconUpdate : 1; + bool m_tooltipUpdate : 1; + bool m_statusUpdate : 1; + + QString m_id; + QString m_title; + QString m_tooltip; + QString m_subTitle; + QString m_iconName; + QIcon m_icon; +}; + +#endif // STATUSNOTIFIERITEMSOURCE_H diff --git a/src/systemtray/statusnotifierwatcher.cpp b/src/systemtray/statusnotifierwatcher.cpp new file mode 100644 index 0000000..90f501d --- /dev/null +++ b/src/systemtray/statusnotifierwatcher.cpp @@ -0,0 +1,104 @@ +#include "statusnotifierwatcher.h" +#include "statusnotifieritem_interface.h" +#include "statusnotifierwatcheradaptor.h" + +#include +#include +#include + +StatusNotifierWatcher::StatusNotifierWatcher(QObject *parent) + : QObject(parent) +{ + new StatusNotifierWatcherAdaptor(this); + QDBusConnection dbus = QDBusConnection::sessionBus(); + dbus.registerObject(QStringLiteral("/StatusNotifierWatcher"), this); + dbus.registerService(QStringLiteral("org.kde.StatusNotifierWatcher")); + + m_serviceWatcher = new QDBusServiceWatcher(this); + m_serviceWatcher->setConnection(dbus); + m_serviceWatcher->setWatchMode(QDBusServiceWatcher::WatchForUnregistration); + + connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &StatusNotifierWatcher::serviceUnregistered); +} + +StatusNotifierWatcher::~StatusNotifierWatcher() +{ + QDBusConnection dbus = QDBusConnection::sessionBus(); + dbus.unregisterService(QStringLiteral("org.kde.StatusNotifierWatcher")); +} + +QStringList StatusNotifierWatcher::RegisteredStatusNotifierItems() const +{ + return m_registeredServices; +} + +bool StatusNotifierWatcher::IsStatusNotifierHostRegistered() const +{ + return !m_statusNotifierHostServices.isEmpty(); +} + +void StatusNotifierWatcher::RegisterStatusNotifierItem(const QString &serviceOrPath) +{ + QString service; + QString path; + if (serviceOrPath.startsWith(QLatin1Char('/'))) { + service = message().service(); + path = serviceOrPath; + } else { + service = serviceOrPath; + path = QStringLiteral("/StatusNotifierItem"); + } + QString notifierItemId = service + path; + if (m_registeredServices.contains(notifierItemId)) { + return; + } + m_serviceWatcher->addWatchedService(service); + if (QDBusConnection::sessionBus().interface()->isServiceRegistered(service).value()) { + // check if the service has registered a SystemTray object + org::kde::StatusNotifierItem trayclient(service, path, QDBusConnection::sessionBus()); + if (trayclient.isValid()) { + qDebug() << "Registering" << notifierItemId << "to system tray"; + m_registeredServices.append(notifierItemId); + emit StatusNotifierItemRegistered(notifierItemId); + } else { + m_serviceWatcher->removeWatchedService(service); + } + } else { + m_serviceWatcher->removeWatchedService(service); + } +} + +void StatusNotifierWatcher::RegisterStatusNotifierHost(const QString &service) +{ + if (service.contains(QLatin1String("org.kde.StatusNotifierHost-")) && QDBusConnection::sessionBus().interface()->isServiceRegistered(service).value() + && !m_statusNotifierHostServices.contains(service)) { + qDebug() << "Registering" << service << "as system tray"; + + m_statusNotifierHostServices.insert(service); + m_serviceWatcher->addWatchedService(service); + emit StatusNotifierHostRegistered(); + } +} + +void StatusNotifierWatcher::serviceUnregistered(const QString &name) +{ + qDebug() << "Service " << name << "unregistered"; + m_serviceWatcher->removeWatchedService(name); + + QString match = name + QLatin1Char('/'); + QStringList::Iterator it = m_registeredServices.begin(); + while (it != m_registeredServices.end()) { + if (it->startsWith(match)) { + QString name = *it; + it = m_registeredServices.erase(it); + emit StatusNotifierItemUnregistered(name); + } else { + ++it; + } + } + + if (m_statusNotifierHostServices.contains(name)) { + m_statusNotifierHostServices.remove(name); + emit StatusNotifierHostUnregistered(); + } +} diff --git a/src/systemtray/statusnotifierwatcher.h b/src/systemtray/statusnotifierwatcher.h new file mode 100644 index 0000000..7376f8e --- /dev/null +++ b/src/systemtray/statusnotifierwatcher.h @@ -0,0 +1,45 @@ +#ifndef STATUSNOTIFIERWATCHER_H +#define STATUSNOTIFIERWATCHER_H + +#include +#include +#include +#include + +class QDBusServiceWatcher; +class StatusNotifierWatcher : public QObject, protected QDBusContext +{ + Q_OBJECT + Q_SCRIPTABLE Q_PROPERTY(bool IsStatusNotifierHostRegistered READ IsStatusNotifierHostRegistered) + Q_SCRIPTABLE Q_PROPERTY(int ProtocolVersion READ protocolVersion) + Q_SCRIPTABLE Q_PROPERTY(QStringList RegisteredStatusNotifierItems READ RegisteredStatusNotifierItems) + +public: + explicit StatusNotifierWatcher(QObject *parent = nullptr); + ~StatusNotifierWatcher(); + + QStringList RegisteredStatusNotifierItems() const; + bool IsStatusNotifierHostRegistered() const; + int protocolVersion() const { return 0; } + +public slots: + void RegisterStatusNotifierItem(const QString &service); + void RegisterStatusNotifierHost(const QString &service); + +protected Q_SLOTS: + void serviceUnregistered(const QString &name); + +Q_SIGNALS: + void StatusNotifierItemRegistered(const QString &service); + // TODO: decide if this makes sense, the systray itself could notice the vanishing of items, but looks complete putting it here + void StatusNotifierItemUnregistered(const QString &service); + void StatusNotifierHostRegistered(); + void StatusNotifierHostUnregistered(); + +private: + QDBusServiceWatcher *m_serviceWatcher = nullptr; + QStringList m_registeredServices; + QSet m_statusNotifierHostServices; +}; + +#endif // STATUSNOTIFIERWATCHER_H diff --git a/src/systemtray/systemtraymodel.cpp b/src/systemtray/systemtraymodel.cpp new file mode 100644 index 0000000..1bd22c5 --- /dev/null +++ b/src/systemtray/systemtraymodel.cpp @@ -0,0 +1,144 @@ +#include "systemtraymodel.h" + +#include +#include + +SystemTrayModel::SystemTrayModel(QObject *parent) + : QAbstractListModel(parent) +{ + m_hostName = "org.kde.StatusNotifierHost-" + QString::number(QCoreApplication::applicationPid()); + QDBusConnection::sessionBus().interface()->registerService(m_hostName, QDBusConnectionInterface::DontQueueService); + + m_watcher = new StatusNotifierWatcher; + m_watcher->RegisterStatusNotifierHost(m_hostName); + m_watcher->moveToThread(QApplication::instance()->thread()); + + connect(m_watcher, &StatusNotifierWatcher::StatusNotifierItemRegistered, this, &SystemTrayModel::onItemAdded); + connect(m_watcher, &StatusNotifierWatcher::StatusNotifierItemUnregistered, this, &SystemTrayModel::onItemRemoved); +} + +SystemTrayModel::~SystemTrayModel() +{ + QDBusConnection::sessionBus().unregisterService(m_hostName); + + delete m_watcher; +} + +int SystemTrayModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return m_items.size(); +} + +QHash SystemTrayModel::roleNames() const +{ + QHash roles; + roles[IdRole] = "id"; + roles[IconNameRole] = "iconName"; + roles[IconRole] = "icon"; + roles[TitleRole] = "title"; + roles[ToolTipRole] = "toolTip"; + return roles; +} + +QVariant SystemTrayModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + StatusNotifierItemSource *item = m_items.at(index.row()); + + switch (role) { + case IdRole: + return item->id(); + case IconNameRole: + return item->iconName(); + case IconRole: { + if (!item->icon().isNull()) + return item->icon(); + else + return QVariant(); + } + case TitleRole: + return item->title(); + case ToolTipRole: + return item->tooltip(); + } + + return QVariant(); +} + +int SystemTrayModel::indexOf(const QString &id) +{ + for (StatusNotifierItemSource *item : m_items) { + if (item->id() == id) + return m_items.indexOf(item); + } + + return -1; +} + +StatusNotifierItemSource *SystemTrayModel::findItemById(const QString &id) +{ + int index = indexOf(id); + + if (index == -1) + return nullptr; + + return m_items.at(index); +} + +void SystemTrayModel::leftButtonClick(const QString &id) +{ + StatusNotifierItemSource *item = findItemById(id); + + if (item) { + QPoint p(QCursor::pos()); + item->activate(p.x(), p.y()); + } +} + +void SystemTrayModel::rightButtonClick(const QString &id) +{ + StatusNotifierItemSource *item = findItemById(id); + if (item) { + QPoint p(QCursor::pos()); + item->contextMenu(p.x(), p.y()); + } +} + +void SystemTrayModel::onItemAdded(const QString &service) +{ + StatusNotifierItemSource *source = new StatusNotifierItemSource(service, this); + + connect(source, &StatusNotifierItemSource::updated, this, &SystemTrayModel::updated); + + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + m_items.append(source); + endInsertRows(); +} + +void SystemTrayModel::onItemRemoved(const QString &service) +{ + int index = indexOf(service); + + if (index != -1) { + beginRemoveRows(QModelIndex(), index, index); + StatusNotifierItemSource *item = m_items.at(index); + m_items.removeAll(item); + endRemoveRows(); + } +} + +void SystemTrayModel::updated(StatusNotifierItemSource *item) +{ + if (!item) + return; + + int idx = indexOf(item->id()); + + // update + if (idx != -1) { + dataChanged(index(idx, 0), index(idx, 0)); + } +} diff --git a/src/systemtray/systemtraymodel.h b/src/systemtray/systemtraymodel.h new file mode 100644 index 0000000..3142e2c --- /dev/null +++ b/src/systemtray/systemtraymodel.h @@ -0,0 +1,47 @@ +#ifndef SYSTEMTRAYMODEL_H +#define SYSTEMTRAYMODEL_H + +#include + +#include "statusnotifierwatcher.h" +#include "statusnotifieritemsource.h" + +class SystemTrayModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + IdRole = Qt::UserRole + 1, + IconNameRole, + IconRole, + TitleRole, + ToolTipRole + }; + + explicit SystemTrayModel(QObject *parent = nullptr); + ~SystemTrayModel(); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QHash roleNames() const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + int indexOf(const QString &id); + StatusNotifierItemSource *findItemById(const QString &id); + + Q_INVOKABLE void leftButtonClick(const QString &id); + Q_INVOKABLE void rightButtonClick(const QString &id); + +private slots: + void onItemAdded(const QString &service); + void onItemRemoved(const QString &service); + void updated(StatusNotifierItemSource *item); + +private: + StatusNotifierWatcher *m_watcher; + QList m_items; + QString m_hostName; +}; + +#endif // SYSTEMTRAYMODEL_H diff --git a/src/systemtray/systemtraytypedefs.h b/src/systemtray/systemtraytypedefs.h new file mode 100644 index 0000000..0024f39 --- /dev/null +++ b/src/systemtray/systemtraytypedefs.h @@ -0,0 +1,48 @@ +/*************************************************************************** + * * + * Copyright (C) 2009 Marco Martin * + * * + * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#ifndef SYSTEMTRAYTYPEDEFS_H +#define SYSTEMTRAYTYPEDEFS_H + +#include +#include +#include +#include + +struct KDbusImageStruct { + int width; + int height; + QByteArray data; +}; + +typedef QVector KDbusImageVector; + +struct KDbusToolTipStruct { + QString icon; + KDbusImageVector image; + QString title; + QString subTitle; +}; + +Q_DECLARE_METATYPE(KDbusImageStruct) +Q_DECLARE_METATYPE(KDbusImageVector) +Q_DECLARE_METATYPE(KDbusToolTipStruct) + +#endif \ No newline at end of file diff --git a/src/systemtray/systemtraytypes.cpp b/src/systemtray/systemtraytypes.cpp new file mode 100644 index 0000000..c690c58 --- /dev/null +++ b/src/systemtray/systemtraytypes.cpp @@ -0,0 +1,128 @@ +/*************************************************************************** + * * + * Copyright (C) 2009 Marco Martin * + * * + * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include +#include "systemtraytypes.h" + +// Marshall the ImageStruct data into a D-BUS argument +const QDBusArgument &operator<<(QDBusArgument &argument, const KDbusImageStruct &icon) +{ + argument.beginStructure(); + argument << icon.width; + argument << icon.height; + argument << icon.data; + argument.endStructure(); + return argument; +} + +// Retrieve the ImageStruct data from the D-BUS argument +const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusImageStruct &icon) +{ + qint32 width = 0; + qint32 height = 0; + QByteArray data; + + if (argument.currentType() == QDBusArgument::StructureType) { + argument.beginStructure(); + // qCDebug(DATAENGINE_SNI)() << "begun structure"; + argument >> width; + // qCDebug(DATAENGINE_SNI)() << width; + argument >> height; + // qCDebug(DATAENGINE_SNI)() << height; + argument >> data; + // qCDebug(DATAENGINE_SNI)() << data.size(); + argument.endStructure(); + } + + icon.width = width; + icon.height = height; + icon.data = data; + + return argument; +} + +// Marshall the ImageVector data into a D-BUS argument +const QDBusArgument &operator<<(QDBusArgument &argument, const KDbusImageVector &iconVector) +{ + argument.beginArray(qMetaTypeId()); + for (int i = 0; i < iconVector.size(); ++i) { + argument << iconVector[i]; + } + argument.endArray(); + return argument; +} + +// Retrieve the ImageVector data from the D-BUS argument +const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusImageVector &iconVector) +{ + iconVector.clear(); + + if (argument.currentType() == QDBusArgument::ArrayType) { + argument.beginArray(); + + while (!argument.atEnd()) { + KDbusImageStruct element; + argument >> element; + iconVector.append(element); + } + + argument.endArray(); + } + + return argument; +} + +// Marshall the ToolTipStruct data into a D-BUS argument +const QDBusArgument &operator<<(QDBusArgument &argument, const KDbusToolTipStruct &toolTip) +{ + argument.beginStructure(); + argument << toolTip.icon; + argument << toolTip.image; + argument << toolTip.title; + argument << toolTip.subTitle; + argument.endStructure(); + + return argument; +} + +// Retrieve the ToolTipStruct data from the D-BUS argument +const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusToolTipStruct &toolTip) +{ + QString icon; + KDbusImageVector image; + QString title; + QString subTitle; + + if (argument.currentType() == QDBusArgument::StructureType) { + argument.beginStructure(); + argument >> icon; + argument >> image; + argument >> title; + argument >> subTitle; + argument.endStructure(); + } + + toolTip.icon = icon; + toolTip.image = image; + toolTip.title = title; + toolTip.subTitle = subTitle; + + return argument; +} diff --git a/src/systemtray/systemtraytypes.h b/src/systemtray/systemtraytypes.h new file mode 100644 index 0000000..8e1b64f --- /dev/null +++ b/src/systemtray/systemtraytypes.h @@ -0,0 +1,37 @@ +/*************************************************************************** + * * + * Copyright (C) 2009 Marco Martin * + * * + * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#ifndef SYSTEMTRAYTYPES_H +#define SYSTEMTRAYTYPES_H + +#include + +#include "systemtraytypedefs.h" + +const QDBusArgument &operator<<(QDBusArgument &argument, const KDbusImageStruct &icon); +const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusImageStruct &icon); + +const QDBusArgument &operator<<(QDBusArgument &argument, const KDbusImageVector &iconVector); +const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusImageVector &iconVector); + +const QDBusArgument &operator<<(QDBusArgument &argument, const KDbusToolTipStruct &toolTip); +const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusToolTipStruct &toolTip); + +#endif diff --git a/src/volume.cpp b/src/volume.cpp new file mode 100644 index 0000000..183cc35 --- /dev/null +++ b/src/volume.cpp @@ -0,0 +1,151 @@ +#include "volume.h" + +#include +#include +#include +#include +#include + +static const QString Service = "org.cutefish.Settings"; +static const QString ObjectPath = "/Audio"; +static const QString Interface = "org.cutefish.Audio"; + +static VolumeManager *SELF = nullptr; + +VolumeManager *VolumeManager::self() +{ + if (!SELF) + SELF = new VolumeManager; + + return SELF; +} + +VolumeManager::VolumeManager(QObject *parent) + : QObject(parent) + , m_isValid(false) + , m_isMute(false) + , m_volume(0) +{ + QDBusServiceWatcher *watcher = new QDBusServiceWatcher(this); + watcher->setConnection(QDBusConnection::sessionBus()); + watcher->addWatchedService(Service); + + init(); + + connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, &VolumeManager::init); +} + +bool VolumeManager::isValid() const +{ + return m_isValid; +} + +void VolumeManager::initStatus() +{ + QDBusInterface iface(Service, ObjectPath, Interface, QDBusConnection::sessionBus(), this); + + m_isValid = iface.isValid() && !iface.lastError().isValid(); + + if (m_isValid) { + int volume = iface.property("volume").toInt(); + bool mute = iface.property("mute").toBool(); + + if (m_volume != volume) { + m_volume = volume; + emit volumeChanged(); + } + + if (m_isMute != mute) { + m_isMute = mute; + emit muteChanged(); + } + } + + emit validChanged(); +} + +void VolumeManager::connectDBusSignals() +{ + QDBusInterface iface(Service, ObjectPath, Interface, QDBusConnection::sessionBus(), this); + + if (iface.isValid()) { + QDBusConnection::sessionBus().connect(Service, ObjectPath, Interface, "volumeChanged", + this, SLOT(onDBusVolumeChanged(int))); + QDBusConnection::sessionBus().connect(Service, ObjectPath, Interface, "muteChanged", + this, SLOT(onDBusMuteChanged(bool))); + } +} + +void VolumeManager::onDBusVolumeChanged(int volume) +{ + if (m_volume != volume) { + m_volume = volume; + emit volumeChanged(); + } +} + +void VolumeManager::onDBusMuteChanged(bool mute) +{ + if (m_isMute != mute) { + m_isMute = mute; + emit muteChanged(); + + // Need to update the icon. + emit volumeChanged(); + } +} + +int VolumeManager::volume() const +{ + return m_volume; +} + +QString VolumeManager::iconName() const +{ + if (m_volume <= 0 || m_isMute) + return QStringLiteral("audio-volume-muted-symbolic"); + else if (m_volume <= 25) + return QStringLiteral("audio-volume-low-symbolic"); + else if (m_volume <= 75) + return QStringLiteral("audio-volume-medium-symbolic"); + else + return QStringLiteral("audio-volume-high-symbolic"); +} + +void VolumeManager::toggleMute() +{ + QDBusInterface iface(Service, ObjectPath, Interface, QDBusConnection::sessionBus(), this); + + if (iface.isValid()) { + iface.call("toggleMute"); + } +} + +void VolumeManager::setMute(bool mute) +{ + QDBusInterface iface(Service, ObjectPath, Interface, QDBusConnection::sessionBus(), this); + + if (iface.isValid()) { + iface.call("setMute", QVariant::fromValue(mute)); + } +} + +void VolumeManager::setVolume(int value) +{ + QDBusInterface iface(Service, ObjectPath, Interface, QDBusConnection::sessionBus(), this); + + if (iface.isValid()) { + iface.call("setVolume", QVariant::fromValue(value)); + } +} + +void VolumeManager::init() +{ + initStatus(); + connectDBusSignals(); +} + +bool VolumeManager::isMute() const +{ + return m_isMute; +} diff --git a/src/volume.h b/src/volume.h new file mode 100644 index 0000000..f1e31f1 --- /dev/null +++ b/src/volume.h @@ -0,0 +1,49 @@ +#ifndef VOLUMEMANAGER_H +#define VOLUMEMANAGER_H + +#include + +class VolumeManager : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool isValid READ isValid NOTIFY validChanged) + Q_PROPERTY(bool isMute READ isMute NOTIFY muteChanged) + Q_PROPERTY(int volume READ volume NOTIFY volumeChanged) + Q_PROPERTY(QString iconName READ iconName NOTIFY volumeChanged) + +public: + static VolumeManager *self(); + + explicit VolumeManager(QObject *parent = nullptr); + + bool isValid() const; + bool isMute() const; + int volume() const; + + QString iconName() const; + + Q_INVOKABLE void toggleMute(); + Q_INVOKABLE void setMute(bool mute); + Q_INVOKABLE void setVolume(int value); + +signals: + void validChanged(); + void muteChanged(); + void volumeChanged(); + +private: + void init(); + void initStatus(); + void connectDBusSignals(); + +private slots: + void onDBusVolumeChanged(int volume); + void onDBusMuteChanged(bool mute); + +private: + bool m_isValid; + bool m_isMute; + int m_volume; +}; + +#endif // VOLUMEMANAGER_H diff --git a/translations/en_US.ts b/translations/en_US.ts new file mode 100644 index 0000000..17b97e5 --- /dev/null +++ b/translations/en_US.ts @@ -0,0 +1,35 @@ + + + + + ControlDialog + + + Wi-Fi + + + + + + On + + + + + + + Off + + + + + Bluetooth + + + + + Dark Mode + + + + diff --git a/translations/zh_CN.ts b/translations/zh_CN.ts new file mode 100644 index 0000000..7298122 --- /dev/null +++ b/translations/zh_CN.ts @@ -0,0 +1,35 @@ + + + + + ControlDialog + + + Wi-Fi + 无线网络 + + + + + On + 打开 + + + + + + Off + 关闭 + + + + Bluetooth + 蓝牙 + + + + Dark Mode + 深色模式 + + +