ukui-volume-control/0000775000175000017500000000000015171074757013456 5ustar fengfengukui-volume-control/ukui-shortcut-audio/0000775000175000017500000000000015171074712017372 5ustar fengfengukui-volume-control/ukui-shortcut-audio/config/0000775000175000017500000000000015171074677020651 5ustar fengfengukui-volume-control/ukui-shortcut-audio/config/qmldir0000664000175000017500000000007215171074677022063 0ustar fengfengmodule org.ukui.shortcut.audio plugin ukui-shortcut-audio ukui-volume-control/ukui-shortcut-audio/res/0000775000175000017500000000000015171074677020175 5ustar fengfengukui-volume-control/ukui-shortcut-audio/res/res.qrc0000664000175000017500000000005115171074677021471 0ustar fengfeng ukui-volume-control/ukui-shortcut-audio/CMakeLists.txt0000664000175000017500000000644115171074712022137 0ustar fengfengcmake_minimum_required(VERSION 3.14) #定义项目名称和版本号 project(ukui-shortcut-audio) set(VERSION_MAJOR 1) set(VERSION_MINOR 0) set(VERSION_MICRO 0) set(UKUI_SHORTCUT_AUDIO_VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_MICRO}) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) #查找Qt库 find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Gui Widgets Quick DBus LinguistTools REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Gui Widgets Quick DBus LinguistTools REQUIRED) include_directories(src/common/) include_directories(src/control/) include_directories(src/extra/) include_directories(src/model/) include_directories(src/utils/) include_directories(config/) include_directories(res/) include_directories(translations/) include_directories(qml/) #设置数据目录和翻译目录 set(UKUI_SHORTCUT_AUDIO_DATA_DIR "/usr/share/ukui/widgets/org.ukui.shortcut.audio") set(UKUI_SHORTCUT_AUDIO_TRANSLATION_DIR "${UKUI_SHORTCUT_AUDIO_DATA_DIR}/translations") set(SOURCE_FILES src/ukui-audio-main.cpp src/ukui-audio-main.h src/common/common.cpp src/common/common.h src/control/ukui-audio-control.cpp src/control/ukui-audio-control.h src/control/ukui-audio-sink-control.cpp src/control/ukui-audio-sink-control.h src/control/ukui-audio-source-control.cpp src/control/ukui-audio-source-control.h src/model/ukui-audio-sink-model.cpp src/model/ukui-audio-sink-model.h src/model/ukui-audio-source-model.cpp src/model/ukui-audio-source-model.h ../backend/Util.h src/extra/DBusClient.cpp src/extra/DBusClient.h src/extra/BaseType.h ) set(CONFIG_FILES config/qmldir ) set(QML_META_FILES qml/metadata.json ) set(QRC_FILES qml/qml.qrc res/res.qrc ) file(GLOB TS_FILES translations/*.ts) if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) qt6_create_translation(QM_FILES ${CMAKE_CURRENT_SOURCE_DIR} ${TS_FILES} OPTIONS -no-obsolete -no-ui-lines) else() qt5_create_translation(QM_FILES ${CMAKE_CURRENT_SOURCE_DIR} ${TS_FILES} OPTIONS -no-obsolete -no-ui-lines) endif() add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${CONFIG_FILES} ${QML_META_FILES} ${QRC_FILES} ${QM_FILES}) target_compile_definitions(${PROJECT_NAME} PRIVATE $<$:QT_QML_DEBUG>) target_link_libraries(${PROJECT_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Quick Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::DBus ) target_include_directories(${PROJECT_NAME} PUBLIC ../backend ) message("UKUI_SHORTCUT_AUDIO_DATA_DIR:" ${UKUI_SHORTCUT_AUDIO_DATA_DIR}) message("UKUI_SHORTCUT_AUDIO_TRANSLATION_DIR:" ${UKUI_SHORTCUT_AUDIO_TRANSLATION_DIR}) install(DIRECTORY "qml/" DESTINATION ${UKUI_SHORTCUT_AUDIO_DATA_DIR}) install(DIRECTORY "res/" DESTINATION "${UKUI_SHORTCUT_AUDIO_DATA_DIR}/res") install(FILES ${QM_FILES} DESTINATION ${UKUI_SHORTCUT_AUDIO_TRANSLATION_DIR}) install(FILES ${CONFIG_FILES} DESTINATION "/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}/qt${QT_VERSION_MAJOR}/qml/org/ukui/shortcut/audio") install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION "/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}/qt${QT_VERSION_MAJOR}/qml/org/ukui/shortcut/audio") ukui-volume-control/ukui-shortcut-audio/translations/0000775000175000017500000000000015171074712022113 5ustar fengfengukui-volume-control/ukui-shortcut-audio/translations/ukui-shortcut-audio_zh_HK.ts0000664000175000017500000000535515171074712027503 0ustar fengfeng AudioListArea ( ) DeviceDelegate EmptyDelegate HeaderDelegate MoreAudioSettingArea More sound settings 更多聲音設置 QObject Output 輸出 No output sound card detected 未檢測到輸出聲卡 There are currently no applications that play sound 暫無播放聲音的應用 Application volume 應用音量 Input 輸入 UKUIAudioSinkModel Multi Bluetooth Output 多藍牙輸出 ukui-volume-control/ukui-shortcut-audio/translations/ukui-shortcut-audio_zh_CN.ts0000664000175000017500000000535515171074712027501 0ustar fengfeng AudioListArea ( ) DeviceDelegate EmptyDelegate HeaderDelegate MoreAudioSettingArea More sound settings 更多声音设置 QObject Output 输出 No output sound card detected 未检测到输出声卡 There are currently no applications that play sound 暂无播放声音的应用 Application volume 应用音量 Input 输入 UKUIAudioSinkModel Multi Bluetooth Output 多蓝牙输出 ukui-volume-control/ukui-shortcut-audio/translations/ukui-shortcut-audio_ky.ts0000664000175000017500000000570115171074712027116 0ustar fengfeng AudioListArea ( ) DeviceDelegate EmptyDelegate HeaderDelegate MoreAudioSettingArea More sound settings داعى ەلە كۅپ دووش قۇرۇۇ ، اچۇۇ ، باشتوو جاسوو ،اتقارۇۇ QObject Output چىعۇۇ No output sound card detected ۅندۉرۉش دووش كارتاسىن تەكشۈرمىگەن There are currently no applications that play sound ازىرچا قويۇلباعان دووشتۇن قولدونۇۇسۇ Application volume قولدونۇشچان دووش Input كىرگىز UKUIAudioSinkModel Multi Bluetooth Output كۅپ بليۇتۇز ۅندۉرۉش ukui-volume-control/ukui-shortcut-audio/translations/ukui-shortcut-audio_mn.ts0000664000175000017500000000621015171074712027101 0ustar fengfeng AudioListArea ( ) DeviceDelegate EmptyDelegate HeaderDelegate MoreAudioSettingArea More sound settings ᠨᠡᠩ ᠣᠯᠠᠨ ᠳᠠᠭᠤ ᠵᠢ ᠳᠤᠬᠢᠷᠠᠭᠤᠯᠬᠤ QObject Output ᠭᠠᠷᠭᠠᠬᠤ No output sound card detected ᠭᠠᠷᠭᠠᠬᠤ ᠳᠠᠭᠤᠨ ᠤ᠋ ᠺᠠᠷᠲ ᠵᠢ ᠬᠢᠨᠠᠨ ᠬᠡᠮᠵᠢᠵᠤ ᠤᠯᠤᠭᠰᠠᠨ ᠥᠬᠡᠢ There are currently no applications that play sound ᠲᠦᠷ ᠨᠡᠪᠳᠡᠷᠡᠬᠦᠯᠬᠦ ᠳᠠᠭᠤᠨ ᠤ᠋ ᠬᠡᠷᠡᠭᠯᠡᠭᠡ ᠪᠠᠢᠬᠤ ᠥᠬᠡᠢ Application volume ᠬᠡᠷᠡᠭᠯᠡᠭᠡᠨ ᠤ᠋ ᠳᠠᠭᠤᠨ ᠤ᠋ ᠬᠡᠮᠵᠢᠶ᠎ᠡ Input ᠣᠷᠣᠭᠤᠯᠬᠤ UKUIAudioSinkModel Multi Bluetooth Output ᠣᠯᠠᠨ ᠯᠠᠨᠶᠠ ᠭᠠᠷᠭᠠᠯᠳᠠ ukui-volume-control/ukui-shortcut-audio/translations/ukui-shortcut-audio_bo_CN.ts0000664000175000017500000000630415171074712027453 0ustar fengfeng AudioListArea ( ) DeviceDelegate EmptyDelegate HeaderDelegate MoreAudioSettingArea More sound settings སྐད་སྒྲ་དེ་བས་མང་བ་བཀོད་སྒྲིག་བྱེད་དགོས། QObject Output ཕྱིར་འདྲེན། No output sound card detected ཞིབ་དཔྱད་ཚད་ལེན་མ་བྱས་པར་སྒྲའི་བྱང་བུ་ཕྱིར་གཏོང་བྱེད་པ། There are currently no applications that play sound གནས་སྐབས་སུ་སྐད་སྒྲ་མ་གཏོང་། Application volume ཉེར་སྤྱོད་སྒྲ་ཚད། Input མ་དངུལ་འཇོག་པ། UKUIAudioSinkModel Multi Bluetooth Output སོ་སྔོན་པོ་མང་པོ་ཕྱིར་གཏོང་། ukui-volume-control/ukui-shortcut-audio/translations/ukui-shortcut-audio_ug.ts0000664000175000017500000000563215171074712027111 0ustar fengfeng AudioListArea ( ) DeviceDelegate EmptyDelegate HeaderDelegate MoreAudioSettingArea More sound settings تېخىمۇ كۆپ ئاۋاز تەسىس قىلىش QObject Output چىقىرىش No output sound card detected چىقىرىش ئاۋاز كارتىسىنى تەكشۈرمىگەن There are currently no applications that play sound ھازىرچە قويۇلمىغان ئاۋازنىڭ قوللىنىشى Application volume قوللىنىشچان ئاۋاز Input كىرگۈز UKUIAudioSinkModel Multi Bluetooth Output كۆپ كۆك چىش چىقىرىش ukui-volume-control/ukui-shortcut-audio/translations/ukui-shortcut-audio_kk.ts0000664000175000017500000000564315171074712027105 0ustar fengfeng AudioListArea ( ) DeviceDelegate EmptyDelegate HeaderDelegate MoreAudioSettingArea More sound settings الٸدە كوپ اۋا ورنالاسترعان ەتۋ QObject Output جاريالاۋ No output sound card detected جاريالاۋ اۋا كارتوشكاسىن تەكسەرمەگەن There are currently no applications that play sound قازىرشا قويىلماعان داۋىستىڭ قولدانىلۋى Application volume قولدانعىش اۋا Input كىرگىزۋ UKUIAudioSinkModel Multi Bluetooth Output كوپ بليۇتووت جاريالاۋ ukui-volume-control/ukui-shortcut-audio/src/0000775000175000017500000000000015171074677020173 5ustar fengfengukui-volume-control/ukui-shortcut-audio/src/model/0000775000175000017500000000000015171074712021261 5ustar fengfengukui-volume-control/ukui-shortcut-audio/src/model/ukui-audio-source-model.h0000664000175000017500000001143315171074712026104 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: zhouyong * */ #ifndef UKUIAUDIOSOURCEMODEL_H #define UKUIAUDIOSOURCEMODEL_H #include #include #include "../extra/DBusClient.h" class UKUIAudioSourceModel : public QAbstractListModel { Q_OBJECT public: // 定义角色(暴露给 QML 的属性) enum SourceInfoRoles { ItemTypeRole = Qt::UserRole + 1, TitleRole, // 标题内容(仅 Header 使用) CardNameRole, CardDescRole, PortNameRole, PortLabelRole, PriorityRole, DirectionRole, AvailableRole, PortEnabledRole, PortTypeRole, PortIconRole, IsActivePortRole, StreamNameRole, StreamIndexRole, StreamVolumeRole, StreamRole, StreamIconNameRole }; Q_ENUM(SourceInfoRoles) // 定义数据项类型 enum ItemType { Invaild = -1, HeaderOutput, // 输出设备标题 HeaderAppVolume, // 应用音量标题 DeviceItem, // 设备项 StreamItem, // 应用音量项 EmptyDeviceItem, // 空设备项 EmptyStreamItem // 空应用音量项 }; Q_ENUM(ItemType) // 内部数据结构 struct SourceInfo { ItemType itemType = Invaild; //列表item类型 ItemType QString title = ""; // 标题文案 QString cardName; // 声卡名称 QString cardDesc; // 声卡描述 QString portName; // 设备端口名称 QString portLabel; // 设备端口描述 uint32_t priority; // 端口优先级 quint32 direction; // 端口方向 input or output quint32 available; // 端口可用状态 bool enabled = true; // 端口是否启用 int32_t type; // 端口的类型 QString portIcon = ""; // 设备图标 bool isActivePort = false; friend const ClientDeviceInfo& operator>>(const ClientDeviceInfo& argument, SourceInfo& info) { info.itemType = DeviceItem; info.cardName = argument.cardName; info.cardDesc = argument.cardDesc; info.portName = argument.portName; info.portLabel = argument.portLabel; info.priority = argument.priority; info.direction = argument.direction; info.available = argument.available; // info.enabled = argument.enabled; // info.type = argument.type; return argument; } QString streamName; // 流媒体名称 quint32 streamIndex; // 流媒体索引 uint32_t streamVolume; // 流媒体音量 QString streamRole; // 流媒体角色 QString streamIconName; // 流媒体图标名称 friend const StreamInfo& operator>>(const StreamInfo& argument, SourceInfo& info) { info.itemType = StreamItem; info.streamName = argument.name; info.streamIndex = argument.index; info.streamVolume = argument.volume; info.streamRole = argument.role; info.streamIconName = argument.iconName; return argument; } }; static UKUIAudioSourceModel& getInstance(); void initData(); void initSlots(); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QHash roleNames() const override; void addDeviceHeader(); void addDevice(ClientDeviceInfo deviceInfo); bool setDeviceActivePort(QString portName, QString cardName); void removeDevice(int index); void clearDevice(); void addStreamHeader(); void addStream(StreamInfo streamInfo); void removeStream(int index); void clearStream(); int getStreamCount(); void releaseModel(); private Q_SLOTS: private: UKUIAudioSourceModel() = default; UKUIAudioSourceModel(const UKUIAudioSourceModel&) = delete; UKUIAudioSourceModel(UKUIAudioSourceModel&&) = delete; ~UKUIAudioSourceModel(); public: private: QList m_sourceInfos; }; #endif // UKUIAUDIOSOURCEMODEL_H ukui-volume-control/ukui-shortcut-audio/src/model/ukui-audio-sink-model.h0000664000175000017500000001307415171074712025553 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: zhouyong * */ #ifndef UKUIAUDIOSINKMODEL_H #define UKUIAUDIOSINKMODEL_H #include #include #include #include "../extra/DBusClient.h" class UKUIAudioSinkModel : public QAbstractListModel { Q_OBJECT public: // 定义角色(暴露给 QML 的属性) enum SinkInfoRoles { ItemTypeRole = Qt::UserRole + 1, TitleRole, // 标题内容(仅 Header 使用) CardNameRole, CardDescRole, PortNameRole, PortLabelRole, PriorityRole, DirectionRole, AvailableRole, PortEnabledRole, PortTypeRole, PortIconRole, IsActivePortRole, StreamNameRole, StreamIndexRole, StreamVolumeRole, StreamRole, StreamIconNameRole }; Q_ENUM(SinkInfoRoles) // 定义数据项类型 enum ItemType { Invaild = -1, HeaderOutput, // 输出设备标题 HeaderAppVolume, // 应用音量标题 DeviceItem, // 设备项 StreamItem, // 应用音量项 EmptyDeviceItem, // 空设备项 EmptyStreamItem // 空应用音量项 }; Q_ENUM(ItemType) // 内部数据结构 struct SinkInfo { ItemType itemType = Invaild; //列表item类型 ItemType QString title = ""; // 标题文案 QString cardName; // 声卡名称 QString cardDesc; // 声卡描述 QString portName; // 设备端口名称 QString portLabel; // 设备端口描述 uint32_t priority; // 端口优先级 quint32 direction; // 端口方向 input or output quint32 available; // 端口可用状态 bool enabled = true; // 端口是否启用 int32_t type; // 端口的类型 QString portIcon = ""; // 设备图标 bool isActivePort = false; friend const ClientDeviceInfo& operator>>(const ClientDeviceInfo& argument, SinkInfo& info) { info.itemType = DeviceItem; info.cardName = argument.cardName; info.cardDesc = argument.cardDesc; info.portName = argument.portName; info.portLabel = argument.portLabel; info.priority = argument.priority; info.direction = argument.direction; info.available = argument.available; info.enabled = argument.enabled; info.type = argument.type; return argument; } friend const ClientDefaultDeviceInfo& operator>>(const ClientDefaultDeviceInfo& argument, SinkInfo& info) { info.itemType = DeviceItem; info.cardName = argument.cardName; info.cardDesc = argument.activePortDesc; info.portName = argument.activePortName; info.portLabel = tr("Multi Bluetooth Output"); info.priority = -1; info.direction = 0; info.available = 0; info.enabled = 1; info.type = 0; return argument; } QString streamName; // 流媒体名称 quint32 streamIndex; // 流媒体索引 uint32_t streamVolume; // 流媒体音量 QString streamRole; // 流媒体角色 QString streamIconName; // 流媒体图标名称 friend const StreamInfo& operator>>(const StreamInfo& argument, SinkInfo& info) { info.itemType = StreamItem; info.streamName = argument.name; info.streamIndex = argument.index; info.streamVolume = argument.volume; info.streamRole = argument.role; info.streamIconName = argument.iconName; return argument; } }; static UKUIAudioSinkModel& getInstance(); void initData(); void initSlots(); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QHash roleNames() const override; void addDeviceHeader(); void addEmptyDevice(); void addDevice(ClientDeviceInfo deviceInfo); void addDevice(ClientDefaultDeviceInfo deviceInfo); bool setDeviceActivePort(QString portName, QString cardName); void removeDevice(int index); void clearDevice(); int getDeviceCount(); int getEnabledDeviceCount(); int getInsertDeviceIndex(); void addStreamHeader(); void addEmptyStream(); void addStream(StreamInfo streamInfo); void removeStream(int index); void clearStream(); int getStreamCount(); int getStreamIndexByStreamIconName(QString iconName); void releaseModel(); signals: private Q_SLOTS: private: UKUIAudioSinkModel() = default; UKUIAudioSinkModel(const UKUIAudioSinkModel&) = delete; UKUIAudioSinkModel(UKUIAudioSinkModel&&) = delete; ~UKUIAudioSinkModel(); public: private: QList m_sinkInfos; }; #endif // UKUIAUDIOSINKMODEL_H ukui-volume-control/ukui-shortcut-audio/src/model/ukui-audio-source-model.cpp0000664000175000017500000001715315171074712026444 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: zhouyong * */ #include "ukui-audio-source-model.h" #include UKUIAudioSourceModel& UKUIAudioSourceModel::getInstance() { static UKUIAudioSourceModel instance; return instance; } void UKUIAudioSourceModel::initData() { } void UKUIAudioSourceModel::initSlots() { } int UKUIAudioSourceModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return m_sourceInfos.size(); } QVariant UKUIAudioSourceModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() >= m_sourceInfos.size()) { return QVariant(); } const SourceInfo& sourceInfo = m_sourceInfos.at(index.row()); switch (role) { case ItemTypeRole: return sourceInfo.itemType; case TitleRole: return sourceInfo.title; case CardNameRole: return sourceInfo.cardName; case CardDescRole: return sourceInfo.cardDesc; case PortNameRole: return sourceInfo.portName; case PortLabelRole:return sourceInfo.portLabel; case PriorityRole:return sourceInfo.priority; case DirectionRole: return sourceInfo.direction; case AvailableRole: return sourceInfo.available; case PortEnabledRole: return sourceInfo.enabled; case PortTypeRole: return sourceInfo.type; case PortIconRole: return sourceInfo.portIcon; case IsActivePortRole: return sourceInfo.isActivePort; case StreamNameRole: return sourceInfo.streamName; case StreamIndexRole: return sourceInfo.streamIndex; case StreamVolumeRole: return sourceInfo.streamVolume; case StreamRole: return sourceInfo.streamRole; case StreamIconNameRole: return sourceInfo.streamIconName; default: return QVariant(); } } QHash UKUIAudioSourceModel::roleNames() const { return { {ItemTypeRole, "itemType"}, {TitleRole, "title"}, {CardNameRole, "cardName"}, {CardDescRole, "cardDesc"}, {PortNameRole, "portName"}, {PortLabelRole, "portLabel"}, {PriorityRole, "priority"}, {DirectionRole, "direction"}, {AvailableRole, "available"}, {PortEnabledRole, "portEnabled"}, {PortTypeRole, "portType"}, {PortIconRole, "portIcon"}, {IsActivePortRole, "isActivePort"}, {StreamNameRole, "streamName"}, {StreamIndexRole, "streamIndex"}, {StreamVolumeRole, "streamVolume"}, {StreamRole, "streamRole"}, {StreamIconNameRole, "streamIconName"} }; } void UKUIAudioSourceModel::addDeviceHeader() { beginInsertRows(QModelIndex(), rowCount(), rowCount()); m_sourceInfos.append({HeaderOutput, QObject::tr("Input"), "", "", "", "", 0, 0, 0, true, -1, "", false, "", 0, 0, "", ""}); endInsertRows(); } void UKUIAudioSourceModel::addDevice(ClientDeviceInfo deviceInfo) { SourceInfo SourceInfo; deviceInfo >> SourceInfo; beginInsertRows(QModelIndex(), rowCount(), rowCount()); m_sourceInfos.append(SourceInfo); endInsertRows(); } bool UKUIAudioSourceModel::setDeviceActivePort(QString portName, QString cardName) { qDebug() << __func__ << "enter" << "portName:" << portName << "cardName:" << cardName; bool ret = false; int index = 0; for (; index < m_sourceInfos.size(); ++index) { if (m_sourceInfos.at(index).itemType != DeviceItem) { continue; } qDebug() << __func__ << "index:" << index << "cardName:" << m_sourceInfos.at(index).cardName << "portName:" << m_sourceInfos.at(index).portName; if ((m_sourceInfos.at(index).cardName.compare(cardName) == 0) && (m_sourceInfos.at(index).portName.compare(portName) == 0)) { m_sourceInfos[index].isActivePort = true; QModelIndex topLeft = createIndex(index, 0, nullptr); QModelIndex bottomRight = createIndex(index, 0, nullptr); emit dataChanged(topLeft, bottomRight); ret = true; } else { m_sourceInfos[index].isActivePort = false; QModelIndex topLeft = createIndex(index, 0, nullptr); QModelIndex bottomRight = createIndex(index, 0, nullptr); emit dataChanged(topLeft, bottomRight); } } return ret; } void UKUIAudioSourceModel::removeDevice(int index) { if (index < 0 || index >= m_sourceInfos.size()) { qDebug() << __func__ << "index is invaild" << index; return; } if (m_sourceInfos.at(index).itemType != DeviceItem) { qDebug() << __func__ << "this index no DeviceItem" << index; return; } beginRemoveRows(QModelIndex(), index, index); m_sourceInfos.removeAt(index); endRemoveRows(); } void UKUIAudioSourceModel::clearDevice() { int index; for (index = 0; index < m_sourceInfos.size(); ++index) { if (m_sourceInfos.at(index).itemType == DeviceItem) { beginRemoveRows(QModelIndex(), index, index); m_sourceInfos.removeAt(index); endRemoveRows(); } } } void UKUIAudioSourceModel::addStreamHeader() { beginInsertRows(QModelIndex(), rowCount(), rowCount()); m_sourceInfos.append({HeaderAppVolume, QObject::tr("Application volume"), "", "", "", "", 0, 0, 0, true, -1, "", false, "", 0, 0, "", ""}); endInsertRows(); } void UKUIAudioSourceModel::addStream(StreamInfo streamInfo) { SourceInfo SourceInfo; streamInfo >> SourceInfo; beginInsertRows(QModelIndex(), rowCount(), rowCount()); m_sourceInfos.append(SourceInfo); endInsertRows(); } void UKUIAudioSourceModel::removeStream(int index) { if (index < 0 || index >= m_sourceInfos.size()) { qDebug() << __func__ << "index is invaild" << index; return; } if (m_sourceInfos.at(index).itemType != StreamItem) { qDebug() << __func__ << "this index no StreamItem" << index; return; } beginRemoveRows(QModelIndex(), index, index); m_sourceInfos.removeAt(index); endRemoveRows(); } void UKUIAudioSourceModel::clearStream() { int index; for (index = 0; index < m_sourceInfos.size(); ++index) { if (m_sourceInfos.at(index).itemType == StreamItem) { beginRemoveRows(QModelIndex(), index, index); m_sourceInfos.removeAt(index); endRemoveRows(); } } } int UKUIAudioSourceModel::getStreamCount() { int index = 0; int count = 0; for (; index < m_sourceInfos.size(); ++index) { if (m_sourceInfos.at(index).itemType == StreamItem) { count++; } } qDebug() << __func__ << "count:" << count; return count; } void UKUIAudioSourceModel::releaseModel() { qDebug() << "UKUIAudioSourceModel::releaseModel" << "enter"; // beginResetModel(); m_sourceInfos.clear(); // endResetModel(); qDebug() << "UKUIAudioSourceModel::releaseModel" << "leave"; } UKUIAudioSourceModel::~UKUIAudioSourceModel() { qDebug() << __func__ << "enter"; releaseModel(); } ukui-volume-control/ukui-shortcut-audio/src/model/ukui-audio-sink-model.cpp0000664000175000017500000003125515171074712026107 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: zhouyong * */ #include "ukui-audio-sink-model.h" #include UKUIAudioSinkModel& UKUIAudioSinkModel::getInstance() { static UKUIAudioSinkModel instance; return instance; } void UKUIAudioSinkModel::initData() { } void UKUIAudioSinkModel::initSlots() { } int UKUIAudioSinkModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return m_sinkInfos.size(); } QVariant UKUIAudioSinkModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() >= m_sinkInfos.size()) { qDebug() << __func__ << "Invaild index:" << index; return QVariant(); } const SinkInfo& sinkInfo = m_sinkInfos.at(index.row()); switch (role) { case ItemTypeRole: return sinkInfo.itemType; case TitleRole: return sinkInfo.title; case CardNameRole: return sinkInfo.cardName; case CardDescRole: return sinkInfo.cardDesc; case PortNameRole: return sinkInfo.portName; case PortLabelRole:return sinkInfo.portLabel; case PriorityRole:return sinkInfo.priority; case DirectionRole: return sinkInfo.direction; case AvailableRole: return sinkInfo.available; case PortEnabledRole: return sinkInfo.enabled; case PortTypeRole: return sinkInfo.type; case PortIconRole: return sinkInfo.portIcon; case IsActivePortRole: return sinkInfo.isActivePort; case StreamNameRole: return sinkInfo.streamName; case StreamIndexRole: return sinkInfo.streamIndex; case StreamVolumeRole: return sinkInfo.streamVolume; case StreamRole: return sinkInfo.streamRole; case StreamIconNameRole: return sinkInfo.streamIconName; default: return QVariant(); } } QHash UKUIAudioSinkModel::roleNames() const { return { {ItemTypeRole, "itemType"}, {TitleRole, "title"}, {CardNameRole, "cardName"}, {CardDescRole, "cardDesc"}, {PortNameRole, "portName"}, {PortLabelRole, "portLabel"}, {PriorityRole, "priority"}, {DirectionRole, "direction"}, {AvailableRole, "available"}, {PortEnabledRole, "portEnabled"}, {PortTypeRole, "portType"}, {PortIconRole, "portIcon"}, {IsActivePortRole, "isActivePort"}, {StreamNameRole, "streamName"}, {StreamIndexRole, "streamIndex"}, {StreamVolumeRole, "streamVolume"}, {StreamRole, "streamRole"}, {StreamIconNameRole, "streamIconName"} }; } void UKUIAudioSinkModel::addDeviceHeader() { beginInsertRows(QModelIndex(), rowCount(), rowCount()); m_sinkInfos.append({HeaderOutput, QObject::tr("Output"), "", "", "", "", 0, 0, 0, true, -1, "", false, "", 0, 0, "", ""}); endInsertRows(); } void UKUIAudioSinkModel::addEmptyDevice() { qDebug() << __func__ << "enter insert 1 index:" << (m_sinkInfos.size() - 1); beginInsertRows(QModelIndex(), (m_sinkInfos.size() - 1), (m_sinkInfos.size() - 1)); m_sinkInfos.insert(1, {EmptyDeviceItem, QObject::tr("No output sound card detected"), "", "", "", "", 0, 0, 0, true, -1, "", false, "", 0, 0, "", ""}); endInsertRows(); } void UKUIAudioSinkModel::addDevice(ClientDeviceInfo deviceInfo) { qDebug() << __func__ << QString("Add card: %1 port: %2 to device list").arg(deviceInfo.cardName).arg(deviceInfo.portName); if ((m_sinkInfos.size() - 1) < 0) { qDebug() << __func__ << "m_sinkInfos.size invaild"; return; } SinkInfo sinkInfo; deviceInfo >> sinkInfo; if (sinkInfo.portName.contains(QString("headphone"), Qt::CaseInsensitive)) { sinkInfo.portIcon = "audio-headphones-symbolic"; } else if (sinkInfo.portName.contains(QString("headset"), Qt::CaseInsensitive)) { sinkInfo.portIcon = "audio-headset-symbolic"; } else { sinkInfo.portIcon = "audio-speakers-symbolic"; } beginInsertRows(QModelIndex(), getInsertDeviceIndex(), getInsertDeviceIndex()); m_sinkInfos.insert(getInsertDeviceIndex(), sinkInfo); endInsertRows(); } void UKUIAudioSinkModel::addDevice(ClientDefaultDeviceInfo deviceInfo) { qDebug() << __func__ << QString("Add card: %1 port: %2 to device list").arg(deviceInfo.cardName).arg(deviceInfo.activePortName); if ((m_sinkInfos.size() - 1) < 0) { qDebug() << __func__ << "m_sinkInfos.size invaild"; return; } SinkInfo sinkInfo; deviceInfo >> sinkInfo; if (sinkInfo.portName.contains(QString("headphone"), Qt::CaseInsensitive)) { sinkInfo.portIcon = "audio-headphones-symbolic"; } else if (sinkInfo.portName.contains(QString("headset"), Qt::CaseInsensitive)) { sinkInfo.portIcon = "audio-headset-symbolic"; } else if (sinkInfo.portName.contains(QString("combine"), Qt::CaseInsensitive)) { sinkInfo.portIcon = "audio-speakers-bluetooth-symbolic"; } else { sinkInfo.portIcon = "audio-speakers-symbolic"; } beginInsertRows(QModelIndex(), getInsertDeviceIndex(), getInsertDeviceIndex()); m_sinkInfos.insert(getInsertDeviceIndex(), sinkInfo); endInsertRows(); } bool UKUIAudioSinkModel::setDeviceActivePort(QString portName, QString cardName) { qDebug() << __func__ << "enter" << "portName:" << portName << "cardName:" << cardName; bool ret = false; int index = 0; beginResetModel(); for (; index < m_sinkInfos.size(); ++index) { if (m_sinkInfos.at(index).itemType != DeviceItem) { continue; } // qDebug() << __func__ << "index:" << index << "cardName:" << m_sinkInfos.at(index).cardName // << "portName:" << m_sinkInfos.at(index).portName; if ((m_sinkInfos.at(index).cardName.compare(cardName) == 0) && (m_sinkInfos.at(index).portName.compare(portName) == 0)) { m_sinkInfos[index].isActivePort = true; QModelIndex topLeft = createIndex(index, 0, nullptr); QModelIndex bottomRight = createIndex(index, 0, nullptr); // emit dataChanged(topLeft, bottomRight); ret = true; } else { m_sinkInfos[index].isActivePort = false; QModelIndex topLeft = createIndex(index, 0, nullptr); QModelIndex bottomRight = createIndex(index, 0, nullptr); // emit dataChanged(topLeft, bottomRight); } } endResetModel(); return ret; } void UKUIAudioSinkModel::removeDevice(int index) { if (index < 0 || index >= m_sinkInfos.size()) { qDebug() << __func__ << "index is invaild" << index; return; } if (m_sinkInfos.at(index).itemType != DeviceItem) { qDebug() << __func__ << "this index no DeviceItem" << index; return; } qDebug() << __func__ << QString("Remove %1 device.").arg(index); beginRemoveRows(QModelIndex(), index, index); m_sinkInfos.removeAt(index); endRemoveRows(); } void UKUIAudioSinkModel::clearDevice() { int index = m_sinkInfos.size() - 1; for (; index >= 0; --index) { // qDebug() << __func__ << "index:" << index << "itemType:" << m_sinkInfos.at(index).itemType << "size:" << m_sinkInfos.size(); if (m_sinkInfos.at(index).itemType == DeviceItem || m_sinkInfos.at(index).itemType == EmptyDeviceItem) { beginRemoveRows(QModelIndex(), index, index); m_sinkInfos.removeAt(index); endRemoveRows(); } } } int UKUIAudioSinkModel::getDeviceCount() { int index = 0; int count = 0; for (; index < m_sinkInfos.size(); ++index) { if (m_sinkInfos.at(index).itemType == DeviceItem) { count++; } } qDebug() << __func__ << "count:" << count; return count; } int UKUIAudioSinkModel::getEnabledDeviceCount() { int index = 0; int count = 0; for (; index < m_sinkInfos.size(); ++index) { if ((m_sinkInfos.at(index).itemType == DeviceItem) && (m_sinkInfos.at(index).enabled)) { count++; } } qDebug() << __func__ << "count:" << count; return count; } int UKUIAudioSinkModel::getInsertDeviceIndex() { int index = 1; for (; index < m_sinkInfos.size(); ++index) { if (m_sinkInfos.at(index).itemType == HeaderAppVolume) { break; } } // qDebug() << __func__ << "index:" << index; return index; } void UKUIAudioSinkModel::addStreamHeader() { beginInsertRows(QModelIndex(), rowCount(), rowCount()); m_sinkInfos.append({HeaderAppVolume, QObject::tr("Application volume"), "", "", "", "", 0, 0, 0, true, -1, "", false, "", 0, 0, "", ""}); endInsertRows(); } void UKUIAudioSinkModel::addEmptyStream() { qDebug() << __func__ << "enter index:" << m_sinkInfos.size(); beginInsertRows(QModelIndex(), m_sinkInfos.size(), m_sinkInfos.size()); m_sinkInfos.insert(m_sinkInfos.size(), {EmptyStreamItem, QObject::tr("There are currently no applications that play sound"), "", "", "", "", 0, 0, 0, true, -1, "", false, "", 0, 0, "", ""}); endInsertRows(); } void UKUIAudioSinkModel::addStream(StreamInfo streamInfo) { qDebug() << __func__ << "enter streamInfo.name:" << streamInfo.name << "streamInfo.index:" << streamInfo.index << "streamInfo.volume:" << streamInfo.volume << "streamInfo.role:" << streamInfo.role << "streamInfo.iconName:" << streamInfo.iconName; if (m_sinkInfos.size() <= 0) { qDebug() << __func__ << "m_sinkInfos.size invaild"; return; } if ((streamInfo.role.compare(QString("filter")) == 0) || (streamInfo.role.compare(QString("abstract")) == 0) || (streamInfo.role.compare(QString("event")) == 0)) { qDebug() << __func__ << streamInfo.role << "abandoned"; return; } SinkInfo sinkInfo; streamInfo >> sinkInfo; beginInsertRows(QModelIndex(), m_sinkInfos.size(), m_sinkInfos.size()); m_sinkInfos.insert(m_sinkInfos.size(), sinkInfo); endInsertRows(); } void UKUIAudioSinkModel::removeStream(int index) { if (index < 0 || index >= m_sinkInfos.size()) { qDebug() << __func__ << "index is invaild" << index; return; } if (m_sinkInfos.at(index).itemType != StreamItem) { qDebug() << __func__ << "this index no StreamItem" << index; return; } beginRemoveRows(QModelIndex(), index, index); m_sinkInfos.removeAt(index); endRemoveRows(); } void UKUIAudioSinkModel::clearStream() { int index = m_sinkInfos.size() - 1; for (; index >= 0; --index) { // qDebug() << __func__ << "index:" << index << "itemType:" << m_sinkInfos.at(index).itemType << "size:" << m_sinkInfos.size(); if (m_sinkInfos.at(index).itemType == StreamItem || m_sinkInfos.at(index).itemType == EmptyStreamItem) { beginRemoveRows(QModelIndex(), index, index); m_sinkInfos.removeAt(index); endRemoveRows(); } } } int UKUIAudioSinkModel::getStreamCount() { int index = 0; int count = 0; for (; index < m_sinkInfos.size(); ++index) { if (m_sinkInfos.at(index).itemType == StreamItem) { count++; } } qDebug() << __func__ << "count:" << count; return count; } int UKUIAudioSinkModel::getStreamIndexByStreamIconName(QString iconName) { int index = 0; int streamIndex = -1; for (; index < m_sinkInfos.size(); ++index) { if (m_sinkInfos.at(index).itemType == StreamItem && m_sinkInfos.at(index).streamIconName.compare(iconName) == 0) { streamIndex = m_sinkInfos.at(index).streamIndex; break; } } qDebug() << __func__ << "streamIndex:" << streamIndex; return streamIndex; } void UKUIAudioSinkModel::releaseModel() { qDebug() << "UKUIAudioSinkModel::releaseModel" << "enter"; // beginResetModel(); m_sinkInfos.clear(); // endResetModel(); qDebug() << "UKUIAudioSinkModel::releaseModel" << "leave"; } UKUIAudioSinkModel::~UKUIAudioSinkModel() { qDebug() << __func__ << "enter"; releaseModel(); } ukui-volume-control/ukui-shortcut-audio/src/control/0000775000175000017500000000000015171074712021641 5ustar fengfengukui-volume-control/ukui-shortcut-audio/src/control/ukui-audio-source-control.cpp0000664000175000017500000002315115171074712027377 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: zhouyong * */ #include "ukui-audio-source-control.h" #include "Util.h" #include "../model/ukui-audio-source-model.h" using namespace UkuiAudioFramwork; UKUIAudioSourceControl& UKUIAudioSourceControl::getInstance() { static UKUIAudioSourceControl instance; return instance; } bool UKUIAudioSourceControl::initData() { bool ret = true; return ret; } bool UKUIAudioSourceControl::initSlots() { bool ret = true; ret = connect(&DBusClient::getInstance(), SIGNAL(volumeChangedSignal(int, int, const QDBusVariant&)), this, SLOT(volumeChangedSlots(int, int, const QDBusVariant&))); return ret; } void UKUIAudioSourceControl::volumeChangedSlots(int type, int idx, const QDBusVariant& value) { qDebug() << __func__ << "type:" << type << "idx:" << idx << "value:" << value.variant(); if (type == EnumToInt(NodeType::INPUT)) { setSourceVolumeValue(value.variant().toInt()); } } void UKUIAudioSourceControl::deviceChangedSlots(int type, const QString& portName, const QString& cardName) { qDebug() << __func__ << "type:" << type << "portName:" << portName << "cardName:" << cardName; switch (IntToEnum(type)) { case NodeType::INPUT: { emit sourceDeviceChanged(portName, cardName); } break; case NodeType::OUTPUT: case NodeType::SINK_INPUT: case NodeType::SOURCE_OUTPUT: default: break; } } void UKUIAudioSourceControl::deviceAdjustSlots(int type) { qDebug() << __func__ << "type:" << type; switch (IntToEnum(type)) { case NodeType::INPUT: { emit sourceDeviceAdjust(); } break; case NodeType::OUTPUT: case NodeType::SINK_INPUT: case NodeType::SOURCE_OUTPUT: default: break; } } bool UKUIAudioSourceControl::getSourceVolume(int index, QVariant& retValue) { qDebug() << __func__ << "index" << index; bool ret = true; QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getVolume", EnumToInt(NodeType::INPUT), index); if (reply.arguments().isEmpty()) { qDebug() << __func__ << "arguments is nullptr..."; ret = false; return ret; } qDebug() << __func__ << "reply" << reply.arguments().value(0); retValue.setValue(reply.arguments()); return ret; } void UKUIAudioSourceControl::updateSourceVolume(QVariant retValue) { qDebug() << __func__ << "enter"; if (!retValue.isValid()) { qDebug() << __func__ << "retValue is invalid..."; return; } setSourceVolumeValue(retValue.toInt()); } bool UKUIAudioSourceControl::setSourceVolume(int index, int value) { qDebug() << __func__ << "index" << index << "value" << value; setSourceVolumeValue(value); bool ret = true; DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "setVolume", EnumToInt(NodeType::INPUT), index, value); return ret; } bool UKUIAudioSourceControl::getSourceAvailablePortList(QVariant& retValue) { qDebug() << __func__ << "enter"; bool ret = true; QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getAvailablePortList", EnumToInt(NodeType::INPUT)); if (reply.arguments().isEmpty()) { qDebug() << __func__ << "arguments is nullptr..."; ret = false; return ret; } retValue.setValue(reply.arguments()); return ret; } void UKUIAudioSourceControl::updateSourceAvailablePortList(QVariant retValue) { qDebug() << __func__ << "enter"; if (!retValue.isValid()) { qDebug() << __func__ << "retValue is invalid..."; return; } UKUIAudioSourceModel::getInstance().clearDevice(); UKUIAudioSourceModel::getInstance().addDeviceHeader(); const QDBusArgument& dbusArgs = retValue.value>().at(0).value(); dbusArgs.beginArray(); while (!dbusArgs.atEnd()) { ClientDeviceInfo info; dbusArgs >> info; UKUIAudioSourceModel::getInstance().addDevice(info); } dbusArgs.endArray(); } bool UKUIAudioSourceControl::getSourceOutputList(QVariant& retValue) { qDebug() << __func__ << "enter"; bool ret = true; QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getSourceOutputList"); if (reply.arguments().isEmpty()) { qDebug() << __func__ << "arguments is nullptr..."; ret = false; return ret; } retValue.setValue(reply.arguments()); return ret; } void UKUIAudioSourceControl::updateSourceInputList(QVariant retValue) { qDebug() << __func__ << "enter"; if (!retValue.isValid()) { qDebug() << __func__ << "retValue is invalid..."; return; } UKUIAudioSourceModel::getInstance().clearStream(); UKUIAudioSourceModel::getInstance().addStreamHeader(); const QDBusArgument& dbusArgs = retValue.value>().at(0).value(); dbusArgs.beginArray(); while (!dbusArgs.atEnd()) { StreamInfo info; dbusArgs >> info; UKUIAudioSourceModel::getInstance().addStream(info); } dbusArgs.endArray(); } bool UKUIAudioSourceControl::getSourceDefaultDevice(int index, QVariant& retValue) { qDebug() << __func__ << "enter"; bool ret = true; QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getDefaultDevice", EnumToInt(NodeType::INPUT), index); if (reply.arguments().isEmpty()) { qDebug() << __func__ << "arguments is nullptr..."; ret = false; return ret; } retValue.setValue(reply.arguments()); return ret; } void UKUIAudioSourceControl::updateSourceDefaultDevice(QVariant retValue) { qDebug() << __func__ << "enter"; if (!retValue.isValid()) { qDebug() << __func__ << "retValue is invalid..."; return; } const QDBusArgument& dbusArgs = retValue.value>().at(0).value(); ClientDefaultDeviceInfo info; dbusArgs >> info; UKUIAudioSourceModel::getInstance().setDeviceActivePort(info.activePortName, info.cardName); } void UKUIAudioSourceControl::updateSourceDefaultDevice(const QString& portName, const QString& cardName) { qDebug() << __func__ << "enter" << "portName:" << portName << "cardName:" << cardName; UKUIAudioSourceModel::getInstance().setDeviceActivePort(portName, cardName); } bool UKUIAudioSourceControl::setSourceDefaultDevice(int index, const QString& portName, const QString& cardName) { qDebug() << __func__ << "enter"; bool ret = true; DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "setDefaultDevice", EnumToInt(NodeType::INPUT), index, portName, cardName); return ret; } void UKUIAudioSourceControl::releaseData() { qDebug() << "UKUIAudioSourceControl::releaseData" << "enter"; m_sourceVolumeValue = -1; UKUIAudioSourceModel::getInstance().releaseModel(); qDebug() << "UKUIAudioSourceControl::releaseData" << "leave"; } void UKUIAudioSourceControl::releaseSlots() { qDebug() << "UKUIAudioSourceControl::releaseSlots" << "enter"; disconnect(&DBusClient::getInstance(), SIGNAL(volumeChangedSignal(int, int, const QDBusVariant&)), this, SLOT(volumeChangedSlots(int, int, const QDBusVariant&))); qDebug() << "UKUIAudioSourceControl::releaseSlots" << "leave"; } UKUIAudioSourceControl::~UKUIAudioSourceControl() { qDebug() << __func__ << "enter"; releaseSlots(); releaseData(); } ukui-volume-control/ukui-shortcut-audio/src/control/ukui-audio-sink-control.cpp0000664000175000017500000005340515171074712027050 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: zhouyong * */ #include "ukui-audio-sink-control.h" #include "Util.h" #include "../model/ukui-audio-sink-model.h" #include "./ukui-audio-control.h" #include "../common/common.h" using namespace UkuiAudioFramwork; UKUIAudioSinkControl& UKUIAudioSinkControl::getInstance() { static UKUIAudioSinkControl instance; return instance; } bool UKUIAudioSinkControl::initData() { bool ret = true; return ret; } bool UKUIAudioSinkControl::initSlots() { bool ret = true; ret = connect(&DBusClient::getInstance(), SIGNAL(volumeChangedSignal(int, int, const QDBusVariant&)), this, SLOT(volumeChangedSlots(int, int, const QDBusVariant&))); ret = connect(&DBusClient::getInstance(), SIGNAL(muteChangedSignal(int, int, bool)), this, SLOT(muteChangedSlots(int, int, bool))); ret = connect(&DBusClient::getInstance(), SIGNAL(deviceChangedSignal(int, const QString&, const QString&)), this, SLOT(deviceChangedSlots(int, const QString&, const QString&))); ret = connect(&DBusClient::getInstance(), SIGNAL(deviceAdjustSignal(int)), this, SLOT(deviceAdjustSlots(int))); ret = connect(&DBusClient::getInstance(), SIGNAL(addStreamSignal(int, const QString&, const QString&)), this, SLOT(addStreamSlots(int, const QString&, const QString&))); ret = connect(&DBusClient::getInstance(), SIGNAL(removeStreamSignal(int)), this, SLOT(removeStreamSlots(int))); ret = connect(&DBusClient::getInstance(), SIGNAL(settingsChangedSignal(QString,QDBusVariant)), this, SLOT(settingsChangedSlots(QString,QDBusVariant))); //start ret = QDBusConnection::sessionBus().connect(QString(), QString("/"), QString("org.kylin.music"), QString("sinkInputVolumeChanged"), this, SLOT(streamVolumeChangedSlots(QString,int,bool))); ret = QDBusConnection::sessionBus().connect(QString(), QString("/"), QString("org.kylin.video"), QString("sinkInputVolumeChanged"), this, SLOT(streamVolumeChangedSlots(QString,int,bool))); //end return ret; } bool UKUIAudioSinkControl::initView() { bool ret = true; UKUIAudioSinkModel::getInstance().addDeviceHeader(); UKUIAudioSinkModel::getInstance().addStreamHeader(); return ret; } void UKUIAudioSinkControl::volumeChangedSlots(int type, int idx, const QDBusVariant& value) { qDebug() << __func__ << "type:" << type << "idx:" << idx << "value:" << value.variant(); if (type == EnumToInt(NodeType::OUTPUT)) { setSinkVolumeValue(value.variant().toInt()); } } void UKUIAudioSinkControl::muteChangedSlots(int type, int index, bool mute) { qDebug() << __func__ << "type:" << type << "index:" << index << "mute:" << mute; if (type == EnumToInt(NodeType::OUTPUT)) { setSinkMuteStatus(mute); } } void UKUIAudioSinkControl::deviceChangedSlots(int type, const QString& portName, const QString& cardName) { qDebug() << __func__ << "type:" << type << "portName:" << portName << "cardName:" << cardName; switch (IntToEnum(type)) { case NodeType::OUTPUT: { emit sinkDeviceChanged(portName, cardName); } break; case NodeType::INPUT: case NodeType::SINK_INPUT: case NodeType::SOURCE_OUTPUT: default: break; } } void UKUIAudioSinkControl::deviceAdjustSlots(int type) { qDebug() << __func__ << "type:" << type; switch (IntToEnum(type)) { case NodeType::OUTPUT: { emit sinkDeviceAdjust(); } break; case NodeType::INPUT: case NodeType::SINK_INPUT: case NodeType::SOURCE_OUTPUT: default: break; } } void UKUIAudioSinkControl::addStreamSlots(int idx, const QString& iconName, const QString& descName) { qDebug() << __func__ << "idx:" << idx << "iconName:" << iconName << "descName:" << descName; } void UKUIAudioSinkControl::removeStreamSlots(int idx) { qDebug() << __func__ << "idx:" << idx; } void UKUIAudioSinkControl::settingsChangedSlots(const QString& key, const QDBusVariant& value) { qDebug() << __func__ << "key:" << key << "value:" << value.variant(); if (key.compare(VOLUME_BOOST_KEY) == 0) { setSinkVolumeBoostStatus(value.variant().toBool()); } } void UKUIAudioSinkControl::streamVolumeChangedSlots(QString name, int value, bool muteStatus) { qDebug() << __func__ << "name" << name << "value" << value << "muteStatus" << muteStatus; m_sinkInputName = name; m_sinkInputValue = value; m_sinkInputMuteStatus = muteStatus; if (nullptr == m_debounceTimer) { m_debounceTimer = new QTimer(this); m_debounceTimer->setSingleShot(true); QObject::connect(m_debounceTimer, &QTimer::timeout, this, &UKUIAudioSinkControl::debounceTimerSlots); } else { m_debounceTimer->stop(); m_debounceTimer->start(200); } } void UKUIAudioSinkControl::debounceTimerSlots() { qDebug() << __func__ << "m_sinkInputName" << m_sinkInputName << "m_sinkInputValue" << m_sinkInputValue << "m_sinkInputMuteStatus" << m_sinkInputMuteStatus; int streamIndex = UKUIAudioSinkModel::getInstance().getStreamIndexByStreamIconName(m_sinkInputName); if (streamIndex == -1) { qDebug() << __func__ << "streamIndex invaild ."; return; } UKUIAudioControl::getInstance().startTask(Common::TaskID_SetSinkInputVolume, streamIndex, m_sinkInputValue); } bool UKUIAudioSinkControl::getSinkVolume(int index, QVariant& retValue) { qDebug() << __func__ << "index" << index; bool ret = true; QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getVolume", EnumToInt(NodeType::OUTPUT), index); if (reply.arguments().isEmpty()) { qDebug() << __func__ << "arguments is nullptr..."; ret = false; return ret; } retValue.setValue(reply.arguments()); return ret; } void UKUIAudioSinkControl::updateSinkVolume(QVariant retValue) { qDebug() << __func__ << "enter"; if (!retValue.isValid()) { qDebug() << __func__ << "retValue is invalid..."; return; } setSinkVolumeValue(retValue.value>().at(0).toInt()); } bool UKUIAudioSinkControl::setSinkVolume(int index, int value) { qDebug() << __func__ << "index" << index << "value" << value; setSinkVolumeValue(value); bool ret = true; DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "setVolume", EnumToInt(NodeType::OUTPUT), index, value); return ret; } bool UKUIAudioSinkControl::getSinkMute(int index, QVariant& retValue) { qDebug() << __func__ << "index" << index; bool ret = true; QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getMute", EnumToInt(NodeType::OUTPUT), index); if (reply.arguments().isEmpty()) { qDebug() << __func__ << "arguments is nullptr..."; ret = false; return ret; } retValue.setValue(reply.arguments()); return ret; } void UKUIAudioSinkControl::updateSinkMute(QVariant retValue) { qDebug() << __func__ << "enter"; if (!retValue.isValid()) { qDebug() << __func__ << "retValue is invalid..."; return; } setSinkMuteStatus(retValue.value>().at(0).toBool()); } bool UKUIAudioSinkControl::setSinkMute(int index, bool muted) { qDebug() << __func__ << "index" << index << "muted" << muted; setSinkMuteStatus(muted); bool ret = true; DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "setMute", EnumToInt(NodeType::OUTPUT), index, muted); return ret; } bool UKUIAudioSinkControl::getSinkAvailablePortList(QVariant& retValue) { qDebug() << __func__ << "enter"; bool ret = true; QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getAvailablePortList", EnumToInt(NodeType::OUTPUT)); if (reply.arguments().isEmpty()) { qDebug() << __func__ << "arguments is nullptr..."; ret = false; return ret; } retValue.setValue(reply.arguments()); isExitMultiAudioDevice(); return ret; } void UKUIAudioSinkControl::updateSinkAvailablePortList(QVariant retValue) { qDebug() << __func__ << "enter"; if (!retValue.isValid()) { qDebug() << __func__ << "retValue is invalid..."; return; } UKUIAudioSinkModel::getInstance().clearDevice(); const QDBusArgument& dbusArgs = retValue.value>().at(0).value(); dbusArgs.beginArray(); while (!dbusArgs.atEnd()) { ClientDeviceInfo info; dbusArgs >> info; UKUIAudioSinkModel::getInstance().addDevice(info); } dbusArgs.endArray(); if (UKUIAudioSinkModel::getInstance().getDeviceCount() <= 0) { UKUIAudioSinkModel::getInstance().addEmptyDevice(); } else if (UKUIAudioSinkModel::getInstance().getEnabledDeviceCount() <= 0) { UKUIAudioSinkModel::getInstance().addEmptyDevice(); } if (m_isExitMultiAudioDevice) { UKUIAudioSinkModel::getInstance().addDevice(m_SinkMutiAudioDeviceInfo); } } bool UKUIAudioSinkControl::getSinkInputList(QVariant& retValue) { qDebug() << __func__ << "enter"; bool ret = true; QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getSinkInputList"); if (reply.arguments().isEmpty()) { qDebug() << __func__ << "arguments is nullptr..."; ret = false; return ret; } retValue.setValue(reply.arguments()); return ret; } void UKUIAudioSinkControl::updateSinkInputList(QVariant retValue) { qDebug() << __func__ << "enter"; if (!retValue.isValid()) { qDebug() << __func__ << "retValue is invalid..."; return; } UKUIAudioSinkModel::getInstance().clearStream(); const QDBusArgument& dbusArgs = retValue.value>().at(0).value(); dbusArgs.beginArray(); while (!dbusArgs.atEnd()) { StreamInfo info; dbusArgs >> info; UKUIAudioSinkModel::getInstance().addStream(info); } dbusArgs.endArray(); if (UKUIAudioSinkModel::getInstance().getStreamCount() <= 0) { UKUIAudioSinkModel::getInstance().addEmptyStream(); } } void UKUIAudioSinkControl::updateSinkInputVolume(QVariant retValue) { qDebug() << __func__ << "enter"; if (!retValue.isValid()) { qDebug() << __func__ << "retValue is invalid..."; return; } } bool UKUIAudioSinkControl::setSinkInputVolume(int index, int value) { qDebug() << __func__ << "index" << index << "value" << value; bool ret = true; DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "setVolume", EnumToInt(NodeType::SINK_INPUT), index, value); return ret; } bool UKUIAudioSinkControl::sendSinkInputVolumeChanged(const QString& iconName, int value, bool muteStatus) { qDebug() << __func__ << "iconName" << iconName << "value" << value << "muteStatus" << muteStatus; bool ret = true; if ((iconName.compare(QString("kylin-music")) == 0) || (iconName.compare(QString("kylin-video")) == 0)) { QDBusMessage msg = QDBusMessage::createSignal(QString("/"), QString("org.ukui.media"), QString("sinkVolumeChanged")); msg << iconName << value << muteStatus; QDBusConnection::sessionBus().send(msg); qDebug() << __func__ << "iconName:" << iconName << "value:" << value; } return ret; } bool UKUIAudioSinkControl::getSinkDefaultDevice(int index, QVariant& retValue) { qDebug() << __func__ << "enter" << index; bool ret = true; QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getDefaultDevice", EnumToInt(NodeType::OUTPUT), index); if (reply.arguments().isEmpty()) { qDebug() << __func__ << "arguments is nullptr..."; ret = false; return ret; } retValue.setValue(reply.arguments()); return ret; } void UKUIAudioSinkControl::updateSinkDefaultDevice(QVariant retValue) { qDebug() << __func__ << "enter"; if (!retValue.isValid()) { qDebug() << __func__ << "retValue is invalid..."; return; } const QDBusArgument& dbusArgs = retValue.value>().at(0).value(); ClientDefaultDeviceInfo info; dbusArgs >> info; UKUIAudioSinkModel::getInstance().setDeviceActivePort(info.activePortName, info.cardName); } void UKUIAudioSinkControl::updateSinkDefaultDevice(const QString& portName, const QString& cardName) { qDebug() << __func__ << "enter" << "portName:" << portName << "cardName:" << cardName; UKUIAudioSinkModel::getInstance().setDeviceActivePort(portName, cardName); } bool UKUIAudioSinkControl::setSinkDefaultDevice(int index, const QString &portName, const QString&cardName) { qDebug() << __func__ << "enter" << index << "portName:" << portName << "cardName:" << cardName; bool ret = true; DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "setDefaultDevice", EnumToInt(NodeType::OUTPUT), index, portName, cardName); return ret; } bool UKUIAudioSinkControl::getSinkVolumeBoostStatus(QVariant& retValue) { qDebug() << __func__ << "enter"; bool ret = true; QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, SETTINGS_INTERFACE, "getVolumeBoostStatus"); if (reply.arguments().isEmpty()) { qDebug() << __func__ << "arguments is nullptr..."; ret = false; return ret; } retValue.setValue(reply.arguments()); return ret; } void UKUIAudioSinkControl::updateSinkVolumeBoostStatus(QVariant retValue) { qDebug() << __func__ << "enter"; if (!retValue.isValid()) { qDebug() << __func__ << "retValue is invalid..."; return; } setSinkVolumeBoostStatus(retValue.value>().at(0).toBool()); } bool UKUIAudioSinkControl::getSinkVolumeBoostValue(QVariant& retValue) { qDebug() << __func__ << "enter"; bool ret = true; QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, SETTINGS_INTERFACE, "getVolumeBoostValue"); if (reply.arguments().isEmpty()) { qDebug() << __func__ << "arguments is nullptr..."; ret = false; return ret; } retValue.setValue(reply.arguments()); return ret; } void UKUIAudioSinkControl::updateSinkVolumeBoostValue(QVariant retValue) { qDebug() << __func__ << "enter"; if (!retValue.isValid()) { qDebug() << __func__ << "retValue is invalid..."; return; } setSinkVolumeBoostValue(retValue.value>().at(0).toInt()); } bool UKUIAudioSinkControl::isExitMultiAudioDevice() { m_isExitMultiAudioDevice = false; auto status = getSinkMultiAudioCombineStatus(); if (status) { auto sinkList = getSinkList(); auto it = std::find_if(sinkList.begin(), sinkList.end(), [](const ClientDefaultDeviceInfo& info) { return info.name.contains("combine"); }); if (it != sinkList.end()) { m_isExitMultiAudioDevice = true; m_SinkMutiAudioDeviceInfo = *it; qDebug() << __func__ << "Add Multiple Bluetooth outputs, (*it).activePortName " << (*it).activePortName << "(*it).cardName" << (*it).cardName; } } return m_isExitMultiAudioDevice; } bool UKUIAudioSinkControl::getSinkMultiAudioCombineStatus() const { QDBusReply reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getMultiAudioCombineStatus", EnumToInt(NodeType::OUTPUT)); return reply; } QList UKUIAudioSinkControl::getSinkList() const { QList list; QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getSinkList"); if (reply.arguments().isEmpty()) { qDebug() << __func__ << "empty sinklist"; return {}; } const QDBusArgument& dbusArgs = reply.arguments().at(0).value(); dbusArgs.beginArray(); while (!dbusArgs.atEnd()) { ClientDefaultDeviceInfo info; dbusArgs >> info; list.push_back(info); } dbusArgs.endArray(); return list; } int UKUIAudioSinkControl::getSinkDeviceCount() { return UKUIAudioSinkModel::getInstance().getDeviceCount(); } int UKUIAudioSinkControl::getEnabledSinkDeviceCount() { return UKUIAudioSinkModel::getInstance().getEnabledDeviceCount(); } void UKUIAudioSinkControl::releaseData() { qDebug() << "UKUIAudioSinkControl::releaseData" << "enter"; m_sinkVolumeValue = -1; m_sinkMuteStatus = false; m_sinkVolumeStatusIcon = ""; m_sinkVolumeBoostStatus = false; m_sinkVolumeBoostValue = -1; m_isExitMultiAudioDevice = false; UKUIAudioSinkModel::getInstance().releaseModel(); qDebug() << "UKUIAudioSinkControl::releaseData" << "leave"; } void UKUIAudioSinkControl::releaseSlots() { qDebug() << "UKUIAudioSinkControl::releaseSlots" << "enter"; disconnect(&DBusClient::getInstance(), SIGNAL(volumeChangedSignal(int, int, const QDBusVariant&)), this, SLOT(volumeChangedSlots(int, int, const QDBusVariant&))); disconnect(&DBusClient::getInstance(), SIGNAL(muteChangedSignal(int, int, bool)), this, SLOT(muteChangedSlots(int, int, bool))); disconnect(&DBusClient::getInstance(), SIGNAL(deviceChangedSignal(int, const QString&, const QString&)), this, SLOT(deviceChangedSlots(int, const QString&, const QString&))); disconnect(&DBusClient::getInstance(), SIGNAL(deviceAdjustSignal(int)), this, SLOT(deviceAdjustSlots(int))); disconnect(&DBusClient::getInstance(), SIGNAL(addStreamSignal(int, const QString&, const QString&)), this, SLOT(addStreamSlots(int, const QString&, const QString&))); disconnect(&DBusClient::getInstance(), SIGNAL(removeStreamSignal(int)), this, SLOT(removeStreamSlots(int))); qDebug() << "UKUIAudioSinkControl::releaseSlots" << "leave"; } UKUIAudioSinkControl::~UKUIAudioSinkControl() { qDebug() << __func__ << "enter"; releaseSlots(); releaseData(); if (m_debounceTimer) { m_debounceTimer->deleteLater(); m_debounceTimer = nullptr; } } ukui-volume-control/ukui-shortcut-audio/src/control/ukui-audio-sink-control.h0000664000175000017500000002026015171074712026506 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: zhouyong * */ #ifndef UKUIAUDIOSINKCONTROL_H #define UKUIAUDIOSINKCONTROL_H #include #include #include #include "../extra/DBusClient.h" #include "../extra/BaseType.h" class UKUIAudioSinkControl : public QObject { Q_OBJECT Q_PROPERTY(int sinkVolumeValue READ sinkVolumeValue WRITE setSinkVolumeValue NOTIFY sinkVolumeValueChanged) Q_PROPERTY(int sinkMuteStatus READ sinkMuteStatus WRITE setSinkMuteStatus NOTIFY sinkMuteStatusChanged) Q_PROPERTY(QString sinkVolumeStatusIcon READ sinkVolumeStatusIcon WRITE setSinkVolumeStatusIcon NOTIFY sinkVolumeStatusIconChanged) Q_PROPERTY(bool sinkVolumeBoostStatus READ sinkVolumeBoostStatus WRITE setSinkVolumeBoostStatus NOTIFY sinkVolumeBoostStatusChanged) Q_PROPERTY(int sinkVolumeBoostValue READ sinkVolumeBoostValue WRITE setSinkVolumeBoostValue NOTIFY sinkVolumeBoostValueChanged) public: static UKUIAudioSinkControl& getInstance(); bool initData(); bool initSlots(); bool initView(); int sinkVolumeValue() const { return m_sinkVolumeValue;} void setSinkVolumeValue(int sinkVolumeValue) { qDebug() << __func__ << "sinkVolumeValue:" << sinkVolumeValue << "m_sinkVolumeValue:" << m_sinkVolumeValue; //start no sink device if (getEnabledSinkDeviceCount() <= 0) { sinkVolumeValue = 0; } //end m_sinkVolumeValue = sinkVolumeValue; emit sinkVolumeValueChanged(m_sinkVolumeValue); QString sinkVolumeStatusIcon; if (sinkMuteStatus() || (sinkVolumeValue == 0)) { sinkVolumeStatusIcon = "audio-volume-muted-symbolic"; } else if (sinkVolumeValue > 66) { sinkVolumeStatusIcon = "audio-volume-high-symbolic"; } else if (sinkVolumeValue > 33) { sinkVolumeStatusIcon = "audio-volume-medium-symbolic"; } else { sinkVolumeStatusIcon = "audio-volume-low-symbolic"; } setSinkVolumeStatusIcon(sinkVolumeStatusIcon); } int sinkMuteStatus() const { return m_sinkMuteStatus;} void setSinkMuteStatus(bool sinkMuteStatus) { qDebug() << __func__ << "sinkMuteStatus:" << sinkMuteStatus << "m_sinkMuteStatus:" << m_sinkMuteStatus; // if (sinkMuteStatus != m_sinkMuteStatus) { m_sinkMuteStatus = sinkMuteStatus; emit sinkMuteStatusChanged(m_sinkMuteStatus); QString sinkVolumeStatusIcon; if (sinkMuteStatus || (sinkVolumeValue() == 0)) { sinkVolumeStatusIcon = "audio-volume-muted-symbolic"; } else if (sinkVolumeValue() > 66) { sinkVolumeStatusIcon = "audio-volume-high-symbolic"; } else if (sinkVolumeValue() > 33) { sinkVolumeStatusIcon = "audio-volume-medium-symbolic"; } else { sinkVolumeStatusIcon = "audio-volume-low-symbolic"; } setSinkVolumeStatusIcon(sinkVolumeStatusIcon); // } } QString sinkVolumeStatusIcon() const { return m_sinkVolumeStatusIcon;} void setSinkVolumeStatusIcon(QString sinkVolumeStatusIcon) { qDebug() << __func__ << "sinkVolumeStatusIcon:" << sinkVolumeStatusIcon << "m_sinkVolumeStatusIcon:" << m_sinkVolumeStatusIcon; // if (sinkVolumeStatusIcon != m_sinkVolumeStatusIcon) { m_sinkVolumeStatusIcon = sinkVolumeStatusIcon; emit sinkVolumeStatusIconChanged(m_sinkVolumeStatusIcon); // } } bool sinkVolumeBoostStatus() const {return m_sinkVolumeBoostStatus;} void setSinkVolumeBoostStatus(bool sinkVolumeBoostStatus) { qDebug() << __func__ << "sinkVolumeBoostStatus:" << sinkVolumeBoostStatus << "m_sinkVolumeBoostStatus:" << m_sinkVolumeBoostStatus; // if (sinkVolumeBoostStatus != m_sinkVolumeBoostStatus) { m_sinkVolumeBoostStatus = sinkVolumeBoostStatus; emit sinkVolumeBoostStatusChanged(m_sinkVolumeBoostStatus); // } } int sinkVolumeBoostValue() const { return m_sinkVolumeBoostValue;} void setSinkVolumeBoostValue(int sinkVolumeBoostValue) { qDebug() << __func__ << "sinkVolumeBoostValue:" << sinkVolumeBoostValue << "m_sinkVolumeBoostValue:" << m_sinkVolumeBoostValue; // if (sinkVolumeBoostValue != m_sinkVolumeBoostValue) { m_sinkVolumeBoostValue = sinkVolumeBoostValue; emit sinkVolumeBoostValueChanged(m_sinkVolumeBoostValue); // } } signals: void sinkVolumeValueChanged(int sinkVolumeValue); void sinkMuteStatusChanged(bool sinkMuteStatus); void sinkVolumeStatusIconChanged(QString sinkVolumeStatusIcon); void sinkVolumeBoostStatusChanged(bool sinkVolumeBoostStatus); void sinkVolumeBoostValueChanged(int sinkVolumeBoostValue); signals: void sinkDeviceChanged(QString, QString); void sinkDeviceAdjust(); private Q_SLOTS: void volumeChangedSlots(int, int, const QDBusVariant&); void muteChangedSlots(int, int, bool); void deviceChangedSlots(int, const QString&, const QString&); void deviceAdjustSlots(int); void addStreamSlots(int, const QString&, const QString&); void removeStreamSlots(int); void settingsChangedSlots(const QString&, const QDBusVariant&); void streamVolumeChangedSlots(QString, int, bool); void debounceTimerSlots(); public: bool getSinkVolume(int, QVariant& retValue); Q_INVOKABLE void updateSinkVolume(QVariant retValue); bool setSinkVolume(int, int); bool getSinkMute(int, QVariant& retValue); Q_INVOKABLE void updateSinkMute(QVariant retValue); bool setSinkMute(int, bool); bool getSinkAvailablePortList(QVariant& retValue); Q_INVOKABLE void updateSinkAvailablePortList(QVariant retValue); bool getSinkInputList(QVariant& retValue); Q_INVOKABLE void updateSinkInputList(QVariant retValue); Q_INVOKABLE void updateSinkInputVolume(QVariant retValue); bool setSinkInputVolume(int, int); bool sendSinkInputVolumeChanged(const QString&, int, bool); bool getSinkDefaultDevice(int, QVariant& retValue); Q_INVOKABLE void updateSinkDefaultDevice(QVariant retValue); Q_INVOKABLE void updateSinkDefaultDevice(const QString&, const QString&); bool setSinkDefaultDevice(int, const QString&, const QString&); bool getSinkVolumeBoostStatus(QVariant& retValue); Q_INVOKABLE void updateSinkVolumeBoostStatus(QVariant retValue); bool getSinkVolumeBoostValue(QVariant& retValue); Q_INVOKABLE void updateSinkVolumeBoostValue(QVariant retValue); bool isExitMultiAudioDevice(); bool getSinkMultiAudioCombineStatus() const; QList getSinkList() const; Q_INVOKABLE int getSinkDeviceCount(); Q_INVOKABLE int getEnabledSinkDeviceCount(); Q_INVOKABLE void releaseData(); Q_INVOKABLE void releaseSlots(); private: UKUIAudioSinkControl() = default; UKUIAudioSinkControl(const UKUIAudioSinkControl&) = delete; UKUIAudioSinkControl(UKUIAudioSinkControl&&) = delete; UKUIAudioSinkControl operator=(const UKUIAudioSinkControl&) = delete; UKUIAudioSinkControl operator=(UKUIAudioSinkControl&&) = delete; ~UKUIAudioSinkControl(); private: int m_sinkVolumeValue = -1; bool m_sinkMuteStatus = false; QString m_sinkVolumeStatusIcon = ""; bool m_sinkVolumeBoostStatus = false; int m_sinkVolumeBoostValue = -1; bool m_isExitMultiAudioDevice = false; ClientDefaultDeviceInfo m_SinkMutiAudioDeviceInfo; QTimer* m_debounceTimer = nullptr; QString m_sinkInputName = ""; int m_sinkInputValue = 0; bool m_sinkInputMuteStatus = false; }; #endif // UKUIAUDIOSINKCONTROL_H ukui-volume-control/ukui-shortcut-audio/src/control/ukui-audio-control.h0000664000175000017500000000421715171074712025550 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: zhouyong * */ #ifndef UKUIAUDIOCONTROL_H #define UKUIAUDIOCONTROL_H #include #include "../extra/DBusClient.h" #include #include #include class UKUIAudioControl : public QObject { Q_OBJECT public: static UKUIAudioControl& getInstance(); bool initData(); bool initSlots(); bool initDbusConnect(); bool jumpControlPanelAudio(); public: Q_INVOKABLE void initMainThreadEvent(); Q_INVOKABLE void startTask(const int taskID, QVariant var1 = QVariant() , QVariant var2 = QVariant(), QVariant var3 = QVariant()); Q_INVOKABLE void setLoadedStatus() { m_isLoaded = true; qDebug() << "getLoadStatus m_isLoaded:" << m_isLoaded; } Q_INVOKABLE bool getLoadStatus() { qDebug() << "getLoadStatus m_isLoaded:" << m_isLoaded; return m_isLoaded; } private Q_SLOTS: signals: void taskCompleted(int taskID, QVariant var1 = QVariant()); void taskError(int taskID); public: Q_INVOKABLE void releaseData(); Q_INVOKABLE void releaseSlots(); private: UKUIAudioControl() = default; UKUIAudioControl(const UKUIAudioControl&) = delete; UKUIAudioControl(UKUIAudioControl&&) = delete; UKUIAudioControl operator=(const UKUIAudioControl&) = delete; UKUIAudioControl operator=(UKUIAudioControl&&) = delete; ~UKUIAudioControl(); private: QMutex m_taskMutex; bool m_isLoaded = false; }; #endif // UKUIAUDIOCONTROL_H ukui-volume-control/ukui-shortcut-audio/src/control/ukui-audio-source-control.h0000664000175000017500000000611215171074712027042 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: zhouyong * */ #ifndef UKUIAUDIOSOURCECONTROL_H #define UKUIAUDIOSOURCECONTROL_H #include #include #include "../extra/DBusClient.h" class UKUIAudioSourceControl : public QObject { Q_OBJECT Q_PROPERTY(int sourceVolumeValue READ sourceVolumeValue WRITE setSourceVolumeValue NOTIFY sourceVolumeValueChanged) public: static UKUIAudioSourceControl& getInstance(); bool initData(); bool initSlots(); int sourceVolumeValue() const { return m_sourceVolumeValue;} void setSourceVolumeValue(int sourceVolumeValue) { qDebug() << __func__ << "sourceVolumeValue:" << sourceVolumeValue << "m_sourceVolumeValue:" << m_sourceVolumeValue; if (sourceVolumeValue != m_sourceVolumeValue) { m_sourceVolumeValue = sourceVolumeValue; emit sourceVolumeValueChanged(m_sourceVolumeValue); } } signals: void sourceVolumeValueChanged(int sourceVolumeValue); signals: void sourceDeviceChanged(QString, QString); void sourceDeviceAdjust(); private Q_SLOTS: void volumeChangedSlots(int, int, const QDBusVariant&); void deviceChangedSlots(int, const QString&, const QString&); void deviceAdjustSlots(int); public: bool getSourceVolume(int index, QVariant& retValue); Q_INVOKABLE void updateSourceVolume(QVariant retValue); bool setSourceVolume(int index, int); bool getSourceAvailablePortList(QVariant& retValue); Q_INVOKABLE void updateSourceAvailablePortList(QVariant retValue); bool getSourceOutputList(QVariant& retValue); Q_INVOKABLE void updateSourceInputList(QVariant retValue); bool getSourceDefaultDevice(int index, QVariant& retValue); Q_INVOKABLE void updateSourceDefaultDevice(QVariant retValue); Q_INVOKABLE void updateSourceDefaultDevice(const QString&, const QString&); bool setSourceDefaultDevice(int index, const QString&, const QString&); Q_INVOKABLE void releaseData(); Q_INVOKABLE void releaseSlots(); private: UKUIAudioSourceControl() = default; UKUIAudioSourceControl(const UKUIAudioSourceControl&) = delete; UKUIAudioSourceControl(UKUIAudioSourceControl&&) = delete; UKUIAudioSourceControl operator=(const UKUIAudioSourceControl&) = delete; UKUIAudioSourceControl operator=(UKUIAudioSourceControl&&) = delete; ~UKUIAudioSourceControl(); private: int m_sourceVolumeValue = -1; }; #endif // UKUIAUDIOSOURCECONTROL_H ukui-volume-control/ukui-shortcut-audio/src/control/ukui-audio-control.cpp0000664000175000017500000001753415171074712026111 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: zhouyong * */ #include "ukui-audio-control.h" #include #include #include #include #include "common.h" #include "ukui-audio-sink-control.h" #include "ukui-audio-source-control.h" #include "ukui-audio-sink-model.h" UKUIAudioControl& UKUIAudioControl::getInstance() { static UKUIAudioControl instance; return instance; } bool UKUIAudioControl::initData() { bool ret = true; return ret; } bool UKUIAudioControl::initSlots() { bool ret = true; return ret; } bool UKUIAudioControl::initDbusConnect() { bool ret = true; ret = DBusClient::getInstance().initDbusConnect(); return ret; } bool UKUIAudioControl::jumpControlPanelAudio() { qDebug() << __func__ << "enter"; int ret = true; QProcess* process = new QProcess(); connect(process, QOverload::of(&QProcess::finished), [process](int, QProcess::ExitStatus) { if (process) { process->deleteLater(); } }); process->start("ukui-control-center", {"-m", "Audio"}); ret = process->waitForStarted(); if (!ret) { if (process) { process->deleteLater(); } } qDebug() << __func__ << "leave ret" << ret; return ret; } void UKUIAudioControl::initMainThreadEvent() { QThread* mainThread = QCoreApplication::instance()->thread(); this->moveToThread(mainThread); UKUIAudioSinkControl::getInstance().moveToThread(mainThread); UKUIAudioSourceControl::getInstance().moveToThread(mainThread); UKUIAudioSinkModel::getInstance().moveToThread(mainThread); this->initSlots(); this->initDbusConnect(); UKUIAudioSinkControl::getInstance().initSlots(); UKUIAudioSourceControl::getInstance().initSlots(); UKUIAudioSinkControl::getInstance().initView(); } void UKUIAudioControl::startTask(int taskID, QVariant var1, QVariant var2, QVariant var3) { qDebug() << __func__ << "enter taskID:" << taskID << "var1:" << var1 << "var2" << var2; QMutex& taskMutex = m_taskMutex; QFuture future = QtConcurrent::run([taskID,var1,var2,var3,&taskMutex,this]() { qDebug() << "UKUIAudioControl::startTask run taskID:" << taskID; QMetaEnum metaEnum = QMetaEnum::fromType(); if (metaEnum.isValid()) { const char* enumName = metaEnum.valueToKey(taskID); qDebug() << "UKUIAudioControl::startTask run taskID:" << taskID << "enumName:" << (enumName ? enumName : "unvalid"); } bool ret = true; if (taskMutex.tryLock(3000)) { QVariant retValue; switch (taskID) { case Common::TaskID_Init: { initData(); UKUIAudioSinkControl::getInstance().initData(); UKUIAudioSourceControl::getInstance().initData(); } break; case Common::TaskID_GetSinkVolume: { ret = UKUIAudioSinkControl::getInstance().getSinkVolume(var1.toInt(), retValue); } break; case Common::TaskID_SetSinkVolume: { ret = UKUIAudioSinkControl::getInstance().setSinkVolume(var1.toInt(), var2.toInt()); } break; case Common::TaskID_GetSinkMute: { ret = UKUIAudioSinkControl::getInstance().getSinkMute(var1.toInt(), retValue); } break; case Common::TaskID_SetSinkMute: { ret = UKUIAudioSinkControl::getInstance().setSinkMute(var1.toInt(), var2.toBool()); } break; case Common::TaskID_SetSinkInputVolume: { ret = UKUIAudioSinkControl::getInstance().setSinkInputVolume(var1.toInt(), var2.toInt()); } break; case Common::TaskID_GetSourceVolume: { ret = UKUIAudioSourceControl::getInstance().getSourceVolume(var1.toInt(), retValue); } break; case Common::TaskID_SetSourceVolume: { ret = UKUIAudioSourceControl::getInstance().setSourceVolume(var1.toInt(), var2.toInt()); } break; case Common::TaskID_GetSinkAvailablePortList: { ret = UKUIAudioSinkControl::getInstance().getSinkAvailablePortList(retValue); } break; case Common::TaskID_GetSourceAvailablePortList: { ret = UKUIAudioSourceControl::getInstance().getSourceAvailablePortList(retValue); } break; case Common::TaskID_GetSinkInputList: { ret = UKUIAudioSinkControl::getInstance().getSinkInputList(retValue); } break; case Common::TaskID_GetSourceInputList: { ret = UKUIAudioSourceControl::getInstance().getSourceOutputList(retValue); } break; case Common::TaskID_GetSinkDefaultDevice: { ret = UKUIAudioSinkControl::getInstance().getSinkDefaultDevice(var1.toInt(), retValue); } break; case Common::TaskID_SetSinkDefaultDevice: { ret = UKUIAudioSinkControl::getInstance().setSinkDefaultDevice(var1.toInt(), var2.toString(), var3.toString()); } break; case Common::TaskID_GetSourceDefaultDevice: { } break; case Common::TaskID_SetSourceDefaultDevice: { } break; case Common::TaskID_JumpControlPanelAudio: { jumpControlPanelAudio(); } break; case Common::TaskID_GetSinkVolumeBoostStatus: { ret = UKUIAudioSinkControl::getInstance().getSinkVolumeBoostStatus(retValue); } break; case Common::TaskID_GetSinkVolumeBoostValue: { ret = UKUIAudioSinkControl::getInstance().getSinkVolumeBoostValue(retValue); } break; case Common::TaskID_SendSinkInputVolumeChanged: { ret = UKUIAudioSinkControl::getInstance().sendSinkInputVolumeChanged(var1.toString(), var2.toInt(), var3.toBool()); } break; default: { } break; } if (ret) { emit taskCompleted(taskID, retValue); } else { emit taskError(taskID); } taskMutex.unlock(); } else { qDebug() << "UKUIAudioControl::startTask tryLock timeout give up taskID:" << taskID; ret = false; emit taskError(taskID); } }); } void UKUIAudioControl::releaseData() { qDebug() << __func__ << "enter"; UKUIAudioSinkControl::getInstance().releaseData(); UKUIAudioSourceControl::getInstance().releaseData(); qDebug() << __func__ << "leave"; } void UKUIAudioControl::releaseSlots() { qDebug() << __func__ << "enter"; UKUIAudioSinkControl::getInstance().releaseSlots(); UKUIAudioSourceControl::getInstance().releaseSlots(); qDebug() << __func__ << "leave"; } UKUIAudioControl::~UKUIAudioControl() { qDebug() << __func__ << "enter"; releaseSlots(); releaseData(); } ukui-volume-control/ukui-shortcut-audio/src/common/0000775000175000017500000000000015171074712021451 5ustar fengfengukui-volume-control/ukui-shortcut-audio/src/common/common.cpp0000664000175000017500000000206615171074677023463 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: zhouyong * */ #include "common.h" #include #include Common& Common::getInstance() { static Common instance; return instance; } QVariant Common::getThemePropertyByName(QString name) { qDebug() << __func__ << name << qApp->property(name.toStdString().c_str()); return qApp->property(name.toStdString().c_str()); } ukui-volume-control/ukui-shortcut-audio/src/common/common.h0000664000175000017500000000375415171074712023123 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: zhouyong * */ #ifndef COMMON_H #define COMMON_H #include #include class Common : public QObject { Q_OBJECT public: static Common& getInstance(); enum TaskID { TaskID_Init = 0, TaskID_GetSinkVolume, TaskID_SetSinkVolume, TaskID_GetSinkMute, TaskID_SetSinkMute, TaskID_SetSinkInputVolume, TaskID_GetSourceVolume, TaskID_SetSourceVolume, TaskID_SetSourceOutputVolume, TaskID_GetSinkAvailablePortList, TaskID_GetSourceAvailablePortList, TaskID_GetSinkInputList, TaskID_GetSourceInputList, TaskID_GetSinkDefaultDevice, TaskID_SetSinkDefaultDevice, TaskID_GetSourceDefaultDevice, TaskID_SetSourceDefaultDevice, TaskID_JumpControlPanelAudio, TaskID_GetSinkVolumeBoostStatus, TaskID_GetSinkVolumeBoostValue, TaskID_SendSinkInputVolumeChanged, TaskID_End = 999 }; Q_ENUM(TaskID) public: Q_INVOKABLE QVariant getThemePropertyByName(QString); private: Common() = default; Common(const Common&) = delete; Common(Common&&) = delete; Common operator=(const Common&) = delete; Common operator=(Common&&) = delete; ~Common() = default; private: }; #endif // COMMON_H ukui-volume-control/ukui-shortcut-audio/src/ukui-audio-main.h0000664000175000017500000000201715171074677023342 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: zhouyong * */ #ifndef UKUIAUDIOMAIN_H #define UKUIAUDIOMAIN_H #include class UKUIAudioMain : public QQmlExtensionPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) public: void registerTypes(const char *uri) override; }; #endif // UKUIAUDIOMAIN_H ukui-volume-control/ukui-shortcut-audio/src/extra/0000775000175000017500000000000015171074712021304 5ustar fengfengukui-volume-control/ukui-shortcut-audio/src/extra/DBusClient.cpp0000664000175000017500000001217415171074712024011 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "DBusClient.h" #include //#include "ClientMethod.h" DBusClient& DBusClient::getInstance() { static DBusClient instance; return instance; } bool DBusClient::initDbusConnect() { bool ret = true; if(!QDBusConnection::sessionBus().connect(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "volumeChanged", this, SLOT(volumeChangedSlots(int, int, const QDBusVariant&)))) { qDebug() << "Audio framework interface error, connect " << "volumeChanged" << "failed!"; qDebug() << QDBusConnection::sessionBus().lastError().message(); ret = false; } if(!QDBusConnection::sessionBus().connect(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "muteChanged", this, SLOT(muteChangedSlots(int, int, bool)))) { qDebug() << "Audio framework interface error, connect " << "muteChanged" << "failed!"; qDebug() << QDBusConnection::sessionBus().lastError().message(); ret = false; } if(!QDBusConnection::sessionBus().connect(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "deviceChanged", this, SLOT(deviceChangedSlots(int, const QString&, const QString&)))) { qDebug() << "Audio framework interface error, connect " << "deviceChanged" << "failed!"; qDebug() << QDBusConnection::sessionBus().lastError().message(); ret = false; } if(!QDBusConnection::sessionBus().connect(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "deviceAdjust", this, SLOT(deviceAdjustSlots(int)))) { qDebug() << "Audio framework interface error, connect " << "deviceChanged" << "failed!"; qDebug() << QDBusConnection::sessionBus().lastError().message(); ret = false; } if(!QDBusConnection::sessionBus().connect(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, SETTINGS_INTERFACE, "changed", this, SLOT(settingsChangedSlots(const QString&, const QDBusVariant&)))) { qDebug() << "Audio framework interface error, connect " << "changed" << "failed!" ; qDebug() << QDBusConnection::sessionBus().lastError().message(); ret = false; } if(!QDBusConnection::sessionBus().connect(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "addStream", this, SLOT(addStreamSlots(int, int, const QString&, const QString&)))) { qDebug() << "Audio framework interface error, connect " << "addStream" << "failed!" ; qDebug() << QDBusConnection::sessionBus().lastError().message(); ret = false; } if(!QDBusConnection::sessionBus().connect(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "removeStream", this, SLOT(removeStreamSlots(int)))) { qDebug() << "Audio framework interface error, connect " << "removeStream" << "failed!" ; qDebug() << QDBusConnection::sessionBus().lastError().message(); ret = false; } return ret; } void DBusClient::volumeChangedSlots(int type, int idx, const QDBusVariant& v) { Q_EMIT volumeChangedSignal(type, idx, v); } void DBusClient::muteChangedSlots(int type, int index, bool mute) { Q_EMIT muteChangedSignal(type, index, mute); } void DBusClient::deviceChangedSlots(int type, const QString& portName, const QString& cardName) { Q_EMIT deviceChangedSignal(type, portName, cardName); } void DBusClient::deviceAdjustSlots(int type) { Q_EMIT deviceAdjustSignal(type); } void DBusClient::settingsChangedSlots(const QString& key, const QDBusVariant& v) { qDebug() << "DBusClient::settingsChangedSlots, key:" << key << " value: " << v.variant(); Q_EMIT settingsChangedSignal(key, v); } void DBusClient::addStreamSlots(int idx, int value, const QString& iconName, const QString& descName) { Q_EMIT addStreamSignal(idx, value, iconName, descName); } void DBusClient::removeStreamSlots(int idx) { Q_EMIT removeStreamSignal(idx); } ukui-volume-control/ukui-shortcut-audio/src/extra/BaseType.h0000664000175000017500000000266315171074712023200 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 BASETYPE_H #define BASETYPE_H //#include //#include //#include #include //#include "simple_logger/Formatter.h" enum class Module { Service = 0, Tray, ControlCenter }; // enum class BackendType { PULSEAUDIO = 0, PIPEWIRE, ALSA, JACK, UNKNOWN }; enum class VolumeType { BASE_VOLUME = 0, BALANCE_VOLUME }; enum class NodeType { INPUT = 0, OUTPUT, SINK_INPUT, SOURCE_OUTPUT }; enum class PaDataType { CARD = 0, SINK, SOURCE, SINK_INPUT, SOURCE_OUTPUT, MODULE }; typedef struct NodeInfo { uint32_t index; uint8_t channels; }NodeInfo; #endif // BASETYPE_H ukui-volume-control/ukui-shortcut-audio/src/extra/DBusClient.h0000664000175000017500000002064115171074712023454 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 DBUSCLIENT_H #define DBUSCLIENT_H #include #include #include #include #include #include #include #include "Util.h" // 注册 ClientDeviceInfo 类型 typedef struct ClientDeviceInfo { QString cardName; // 声卡名称 QString cardDesc; // 声卡描述 QString portName; // 设备端口名称 QString portLabel; // 设备端口描述 uint32_t priority; // 端口优先级 uint32_t direction; // 端口方向 input or output uint32_t available; // 端口可用状态 bool enabled = true; // 端口是否启用 int32_t type; // 端口的类型 friend const QDBusArgument& operator>>(const QDBusArgument& argument, ClientDeviceInfo& info) { argument.beginStructure(); argument >> info.cardName; argument >> info.cardDesc; argument >> info.portName; argument >> info.portLabel; argument >> info.priority; argument >> info.direction; argument >> info.available; argument >> info.enabled; // argument >> info.type; argument.endStructure(); return argument; } friend const QDBusArgument &operator<<(QDBusArgument& argument, const ClientDeviceInfo& info) { argument.beginStructure(); argument << info.cardName; argument << info.cardDesc; argument << info.portName; argument << info.portLabel; argument << info.priority; argument << info.direction; argument << info.available; argument << info.enabled; // argument << info.type; argument.endStructure(); return argument; } } ClientDeviceInfo; Q_DECLARE_METATYPE(ClientDeviceInfo); // 注册 ClientDefaultDeviceInfo 类型 typedef struct ClientDefaultDeviceInfo { quint32 idx; // 默认设备的索引 QString name; // 默认设备的名称(sink/source 名称) QString cardName; // 默认设备所在的声卡名称 QString activePortName; // 默认设备的活跃端口名称,可能为空 QString activePortDesc; // 默认设备的活跃端口描述 friend const QDBusArgument& operator>>(const QDBusArgument& argument, ClientDefaultDeviceInfo& info) { argument.beginStructure(); argument >> info.idx; argument >> info.name; argument >> info.cardName; argument >> info.activePortName; argument >> info.activePortDesc; argument.endStructure(); return argument; } friend const QDBusArgument &operator<<(QDBusArgument& argument, const ClientDefaultDeviceInfo& info) { argument.beginStructure(); argument << info.idx; argument << info.name; argument << info.cardName; argument << info.activePortName; argument << info.activePortDesc; argument.endStructure(); return argument; } } ClientDefaultDeviceInfo; Q_DECLARE_METATYPE(ClientDefaultDeviceInfo); // 注册 StreamInfo 类型 typedef struct StreamInfo { QString name; // 流媒体名称 quint32 index; // 流媒体索引 uint32_t volume; // 流媒体音量 QString role; // 流媒体角色 QString iconName; // 流媒体图标名称 friend const QDBusArgument& operator>>(const QDBusArgument& argument, StreamInfo& info) { argument.beginStructure(); argument >> info.name; argument >> info.index; argument >> info.volume; argument >> info.role; argument >> info.iconName; argument.endStructure(); return argument; } friend const QDBusArgument &operator<<(QDBusArgument& argument, const StreamInfo& info) { argument.beginStructure(); argument << info.name; argument << info.index; argument << info.volume; argument << info.role; argument << info.iconName; argument.endStructure(); return argument; } } StreamInfo; Q_DECLARE_METATYPE(StreamInfo); // 注册 ClientSoundThemeInfo 类型 typedef struct ClientSoundThemeInfo { QString name; // 音效主题的名称 QString description; // 音效主题的描述 friend const QDBusArgument& operator>>(const QDBusArgument& argument, ClientSoundThemeInfo& info) { argument.beginStructure(); argument >> info.name; argument >> info.description; argument.endStructure(); return argument; } friend const QDBusArgument &operator<<(QDBusArgument& argument, const ClientSoundThemeInfo& info) { argument.beginStructure(); argument << info.name; argument << info.description; argument.endStructure(); return argument; } } ClientSoundThemeInfo; Q_DECLARE_METATYPE(ClientSoundThemeInfo); // 注册 ClientSoundEffectFileInfo 类型 typedef struct ClientSoundEffectFileInfo { QString name; // 音效主题的名称 QString description; // 音效主题的描述 friend const QDBusArgument& operator>>(const QDBusArgument& argument, ClientSoundEffectFileInfo& info) { argument.beginStructure(); argument >> info.name; argument >> info.description; argument.endStructure(); return argument; } friend const QDBusArgument &operator<<(QDBusArgument& argument, const ClientSoundEffectFileInfo& info) { argument.beginStructure(); argument << info.name; argument << info.description; argument.endStructure(); return argument; } } ClientSoundEffectFileInfo; Q_DECLARE_METATYPE(ClientSoundEffectFileInfo); class DBusClient : public QObject { Q_OBJECT public: static DBusClient& getInstance(); public: bool initDbusConnect(); template QDBusMessage dbusMethodCall(const QString&, const QString&, const QString&, const QString&, Args &&...); template QDBusPendingCall dbusMethodAsyncCall(const QString&, const QString&, const QString&, const QString&, Args &&...); Q_SIGNALS: void volumeChangedSignal(int, int, const QDBusVariant&); void muteChangedSignal(int, int, bool); void deviceChangedSignal(int, const QString&, const QString&); void deviceAdjustSignal(int); void settingsChangedSignal(const QString&, const QDBusVariant&); void addStreamSignal(int, int, const QString&, const QString&); void removeStreamSignal(int); private Q_SLOTS: void volumeChangedSlots(int, int, const QDBusVariant&); void muteChangedSlots(int, int, bool); void deviceChangedSlots(int, const QString&, const QString&); void deviceAdjustSlots(int); void settingsChangedSlots(const QString&, const QDBusVariant&); void addStreamSlots(int, int, const QString&, const QString&); void removeStreamSlots(int); private: DBusClient() = default; DBusClient(const DBusClient&) = delete; DBusClient(DBusClient&&) = delete; DBusClient operator=(const DBusClient&) = delete; DBusClient operator=(DBusClient&&) = delete; ~DBusClient() = default; }; template QDBusMessage DBusClient::dbusMethodCall(const QString& destination, const QString& path, const QString& interface, const QString& method, Args&&... args) { QDBusInterface mediaInterface(destination, path, interface); return mediaInterface.call(method, std::forward(args)...); } template QDBusPendingCall DBusClient::dbusMethodAsyncCall(const QString& destination, const QString& path, const QString& interface, const QString& method, Args&&... args) { QDBusInterface mediaInterface(destination, path, interface); return mediaInterface.asyncCall(method, std::forward(args)...); } #endif // DBUSCLIENT_H ukui-volume-control/ukui-shortcut-audio/src/ukui-audio-main.cpp0000664000175000017500000000503615171074677023701 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: zhouyong * */ #include "ukui-audio-main.h" #include #include #include "common/common.h" #include "control/ukui-audio-control.h" #include "control/ukui-audio-sink-control.h" #include "control/ukui-audio-source-control.h" #include "model/ukui-audio-sink-model.h" #include "model/ukui-audio-source-model.h" void UKUIAudioMain::registerTypes(const char *uri) { Q_ASSERT(QString(uri) == QLatin1String("org.ukui.shortcut.audio")); qDebug() << __func__ << uri; qmlRegisterSingletonType("org.ukui.audio.common", 1, 0, "Common", [] (QQmlEngine *qmlEngine, QJSEngine *jsEngine) -> QObject* { Common* common = &Common::getInstance(); return common; }); qmlRegisterSingletonType("org.ukui.audio.sinkcontrol", 1, 0, "UKUIAudioSinkControl", [] (QQmlEngine *qmlEngine, QJSEngine *jsEngine) -> QObject* { UKUIAudioSinkControl* sinkControl = &UKUIAudioSinkControl::getInstance(); return sinkControl; }); qmlRegisterSingletonType("org.ukui.audio.sourcecontrol", 1, 0, "UKUIAudioSourceControl", [] (QQmlEngine *qmlEngine, QJSEngine *jsEngine) -> QObject* { UKUIAudioSourceControl* sourceControl = &UKUIAudioSourceControl::getInstance(); return sourceControl; }); qmlRegisterSingletonType("org.ukui.audio.sinkmodel", 1, 0, "UKUIAudioSinkModel", [] (QQmlEngine *qmlEngine, QJSEngine *jsEngine) -> QObject* { UKUIAudioSinkModel* sinkModel = &UKUIAudioSinkModel::getInstance(); return sinkModel; }); qmlRegisterSingletonType("org.ukui.shortcut.audio", 1, 0, "UKUIAudioControl", [] (QQmlEngine *qmlEngine, QJSEngine *jsEngine) -> QObject* { UKUIAudioControl* control = &UKUIAudioControl::getInstance(); return control; }); } ukui-volume-control/ukui-shortcut-audio/qml/0000775000175000017500000000000015171074712020163 5ustar fengfengukui-volume-control/ukui-shortcut-audio/qml/qml.qrc0000664000175000017500000000063015171074677021474 0ustar fengfeng main.qml AudioArea.qml AudioListArea.qml VolumeDelegate.qml DeviceDelegate.qml HeaderDelegate.qml EmptyDelegate.qml SystemVolumeArea.qml MoreAudioSettingArea.qml ukui-volume-control/ukui-shortcut-audio/qml/VolumeDelegate.qml0000664000175000017500000000577715171074712023620 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 . */ import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 import org.ukui.quick.items 1.0 as UkuiItems Item { id: volumeDelegate width: volumeDelegateWidth height: volumeDelegateHeight property real volumeDelegateWidth: 396 property real volumeDelegateHeight: 56 property real volumeDelegateTopMargin: 0 property alias volumeValue: volumeDelegateSlider.value property alias volumeDelegateImgSource: volumeDelegateImg.source signal valueChanged(real value) ItemDelegate { anchors.fill: parent background: Item { } contentItem: Item { anchors.fill: parent UkuiItems.ThemeIcon { id: volumeDelegateImg width: 32 height: 32 anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.leftMargin: 24 source: "" visible: volumeDelegateImg.source === "" ? false : true } UkuiItems.TouchSlider { // Slider { id: volumeDelegateSlider width: 268 height: 16 anchors.verticalCenter: parent.verticalCenter anchors.left: volumeDelegateImg.right anchors.leftMargin: 8 from: 0 to: 100 stepSize: 1 property var sliderType: "touchSlider" onValueChanged: { debounceTimer.restart() } Timer { id: debounceTimer interval: 200 onTriggered: { console.log("VolumeDelegate.qml Slider debounceTimer:", volumeDelegateSlider.value) volumeDelegate.valueChanged(volumeDelegateSlider.value) } } } Label { width: 48 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter anchors.left: volumeDelegateSlider.right anchors.verticalCenter: parent.verticalCenter text: qsTr(volumeValue +"%") } } } } ukui-volume-control/ukui-shortcut-audio/qml/SystemVolumeArea.qml0000664000175000017500000001030215171074712024137 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 . */ import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 import org.ukui.quick.items 1.0 as UkuiItems import org.ukui.quick.platform 1.0 as Platform Item { id: systemVolume width: systemVolumeWidth height: systemVolumeHeight property real systemVolumeWidth: 396 property real systemVolumeHeight: 56 property real systemVolumeTopMargin: 0 property bool systemVolumeMuteStatus: false property bool isFirstSetVolume: true property alias systemVolumeValue: systemVolumeSlider.value property alias systemVolumeSliderEnabled: systemVolumeSlider.enabled property alias systemVolumeSliderRange: systemVolumeSlider.to property alias systemVolumeBtnSource: systemVolumeBtn.icon.source property alias systemVolumeBtnEnabled: systemVolumeBtn.enabled signal systemVolumeBtnClick() signal valueChanged(real value) UkuiItems.DtThemeButton { id: systemVolumeBtn width: 36 height: 36 Layout.preferredWidth: 36 Layout.preferredHeight: 36 Layout.alignment: Qt.AlignVCenter anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.leftMargin: 24 enabled: true background.radius: width/2 property var kTransparent : Platform.GradientColor { gradientDeg : 0 gradientStart : Qt.rgba(0/255, 0/255, 0/255, 0) gradientEnd : Qt.rgba(0/255, 0/255, 0/255, 0) gradientBackground : Qt.rgba(0/255, 0/255, 0/255, 0) } background.backgroundColor: containsPress ? Platform.GlobalTheme.kContainAlphaClick : containsMouse ? Platform.GlobalTheme.kContainAlphaHover : kTransparent icon.source: "audio-volume-medium-symbolic" icon.mode: UkuiItems.Icon.AutoHighlight onClicked: { systemVolume.systemVolumeBtnClick() } onEnabledChanged: { console.log("systemVolume.qml DtThemeButton onEnabledChanged:", enabled) systemVolumeBtn.icon.mode = (enabled ? UkuiItems.Icon.AutoHighlight : UkuiItems.Icon.Disabled) } } UkuiItems.TouchSlider { // Slider { id: systemVolumeSlider width: 268 height: 16 anchors.verticalCenter: parent.verticalCenter anchors.left: systemVolumeBtn.right anchors.leftMargin: 6 from: 0 to: 100 stepSize: 1 enabled: true property var sliderType: "touchSlider" onValueChanged: { console.log("systemVolume.qml Slider onValueChanged:", systemVolumeSlider.value, isFirstSetVolume) if (!isFirstSetVolume) { debounceTimer.restart() } isFirstSetVolume = false } Timer { id: debounceTimer interval: 300 onTriggered: { console.log("systemVolume.qml Slider debounceTimer:", systemVolumeSlider.value) systemVolume.valueChanged(systemVolumeSlider.value) } } } Label { width: 48 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter anchors.left: systemVolumeSlider.right anchors.leftMargin: 5 anchors.verticalCenter: parent.verticalCenter text: qsTr(systemVolumeValue +"%") enabled: systemVolumeBtn.enabled } } ukui-volume-control/ukui-shortcut-audio/qml/AudioListArea.qml0000664000175000017500000001222515171074712023366 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 . */ import QtQuick 2.15 import QtQuick.Controls 2.15 import org.ukui.shortcut.audio 1.0 import org.ukui.audio.common 1.0 import org.ukui.audio.sinkcontrol 1.0 import org.ukui.audio.sinkmodel 1.0 Item { id: audioList width: audioListWidth height: audioListHeight property real audioListWidth: 396 property real audioListHeight: 305 ListView { id: listView anchors.fill: parent model: UKUIAudioSinkModel clip: true ScrollBar.vertical: ScrollBar { id: verticalScrollBar policy: listView.contentHeight > listView.height ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff } delegate: Loader { width: parent.width property var modelData: model sourceComponent: { console.log("AudioListArea.qml Item type:",model.itemType ,model.title ,model.portName ,modelData.portIcon ,modelData.cardDesc ,modelData.isActivePort ,model.streamName ,model.streamIconName ,model.streamVolume ,model.streamIndex) switch(model.itemType) { case UKUIAudioSinkModel.HeaderOutput: case UKUIAudioSinkModel.HeaderAppVolume: return headerComponent case UKUIAudioSinkModel.EmptyDeviceItem: case UKUIAudioSinkModel.EmptyStreamItem: return emptyDelegateComponent case UKUIAudioSinkModel.DeviceItem: return deviceComponent case UKUIAudioSinkModel.StreamItem: return appVolumeComponent } } } // 标题组件 Component { id: headerComponent HeaderDelegate { headerDelegateTextStr: modelData.title headerDelegateTopMargin: 4 headerDelegateBtnVisible: modelData.itemType === UKUIAudioSinkModel.HeaderAppVolume ? true : false headerDelegateBtnEnabled: modelData.itemType === UKUIAudioSinkModel.HeaderAppVolume ? true : false headerDelegateBtnSource: modelData.itemType === UKUIAudioSinkModel.HeaderAppVolume ? "application-system-symbolic" : "" onHeaderDelegateBtnClick: { console.log("AudioListArea.qml onHeaderDelegateBtnClick") UKUIAudioControl.startTask(Common.TaskID_JumpControlPanelAudio) } } } // 设备项组件 Component { id: deviceComponent DeviceDelegate { deviceDelegateBtnSource: modelData.portIcon deviceDelegatePortDesTextStr: modelData.portLabel + qsTr(" ( ") + modelData.cardDesc + qsTr(" )") deviceDelegateBtnHighLighted: modelData.isActivePort deviceDelegatePortEnabled: modelData.portEnabled MouseArea { anchors.fill: parent onClicked: { console.log("AudioListArea.qml onClicked portName cardName portIcon:", modelData.portName, modelData.portIcon, modelData.cardName) UKUIAudioControl.startTask(Common.TaskID_SetSinkDefaultDevice, -1, modelData.portName, modelData.cardName) } } } } // 应用音量项组件 Component { id: appVolumeComponent VolumeDelegate { volumeValue: modelData.streamVolume volumeDelegateImgSource: "image://imageProvider/" + modelData.streamIconName onValueChanged: { console.log("AudioListArea.qml onValueChanged value:",value) UKUIAudioControl.startTask(Common.TaskID_SetSinkInputVolume, modelData.streamIndex, value) UKUIAudioControl.startTask(Common.TaskID_SendSinkInputVolumeChanged, modelData.streamIconName, value, false) } } } // 空项组件 Component { id: emptyDelegateComponent EmptyDelegate { emptyDelegateTextStr: modelData.title } } } Component.onCompleted: { console.log("AudioListArea.qml onCompleted") UKUIAudioControl.startTask(Common.TaskID_GetSinkVolume, -1) UKUIAudioControl.startTask(Common.TaskID_GetSinkMute, -1) UKUIAudioControl.startTask(Common.TaskID_GetSinkVolumeBoostStatus) } } ukui-volume-control/ukui-shortcut-audio/qml/MoreAudioSettingArea.qml0000664000175000017500000000377015171074712024720 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 . */ import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 import org.ukui.audio.common 1.0 Item { id: moreAudioSetting width: moreAudioSettingWidth height: moreAudioSettingHeight property real moreAudioSettingWidth: 396 property real moreAudioSettingHeight: 56 signal moreAudioSettingClick() Rectangle { id: moreAudioSettingRec width: parent.width height: parent.height color: "#00000000" Label { id: moreAudioSettingText height: 22 anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.leftMargin: 24 text: qsTr("More sound settings") color: Common.getThemePropertyByName("kfont-strong") MouseArea { anchors.fill: parent hoverEnabled: true onEntered: parent.color = Common.getThemePropertyByName("highlight-active") onExited: parent.color = Common.getThemePropertyByName("kfont-strong") onClicked: { console.log("MoreAudioSettingArea.qml onClicked") moreAudioSetting.moreAudioSettingClick() } } } } } ukui-volume-control/ukui-shortcut-audio/qml/main.qml0000664000175000017500000000432315171074677021636 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: zhouyong * */ import QtQuick 2.15 import QtQuick.Layouts 1.15 import org.ukui.quick.widgets 1.0 import org.ukui.shortcut.audio 1.0 import org.ukui.audio.common 1.0 WidgetItem { Layout.fillWidth: true Layout.fillHeight: true clip: true Loader { id: mainPage anchors.fill: parent onStatusChanged: { if (status === Loader.Ready) { console.log("main.qml Loader onStatusChanged loaded suc") } else if (status === Loader.Error) { console.log("main.qml Loader onStatusChanged error loading") } } } Component.onCompleted: { console.log("main.qml onCompleted") UKUIAudioControl.initMainThreadEvent() UKUIAudioControl.startTask(Common.TaskID_Init) } Component.onDestruction: { console.log("main.qml onDestruction") UKUIAudioControl.releaseSlots(); UKUIAudioControl.releaseData(); } Connections { target: UKUIAudioControl function onTaskCompleted(taskID, retValue) { if (taskID === Common.TaskID_Init) { console.log("main.qml onTaskCompleted TaskID_Init") if (mainPage.status !== Loader.Ready) { mainPage.source = "AudioArea.qml" } } } function onTaskError(taskID) { if (taskID === Common.TaskID_Init) { console.log("main.qml onTaskError TaskID_Init") } } } } ukui-volume-control/ukui-shortcut-audio/qml/DeviceDelegate.qml0000664000175000017500000000563415171074712023540 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 . */ import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 import org.ukui.quick.items 1.0 as UkuiItems Item { id: deviceDelegate width: deviceDelegateWidth height: deviceDelegateHeight anchors.top: parent.top anchors.topMargin: deviceDelegateTopMargin property real deviceDelegateWidth: 396 property real deviceDelegateHeight: deviceDelegate.visible ? 56 : 0 property real deviceDelegateTopMargin: 0 property string deviceDelegateBtnSource: "audio-speakers-symbolic" property alias deviceDelegateBtnHighLighted: deviceDelegateBtn.isHighLight property alias deviceDelegatePortDesTextStr: deviceDelegatePortDesText.text property alias deviceDelegatePortEnabled: deviceDelegate.visible ItemDelegate { anchors.fill: parent background: Item { } contentItem: Item { anchors.fill: parent UkuiItems.IconButton { id: deviceDelegateBtn width: 36 height: 36 Layout.preferredWidth: 36 Layout.preferredHeight: 36 Layout.alignment: Qt.AlignVCenter anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.leftMargin: 24 radius: width/2 iconSource: deviceDelegateBtnSource isHighLight: false } Label { id: deviceDelegatePortDesText width: 306 anchors.left: deviceDelegateBtn.right anchors.leftMargin: 12 anchors.verticalCenter: parent.verticalCenter elide: Text.ElideRight wrapMode: Text.NoWrap text: qsTr("") ToolTip { text: deviceDelegatePortDesText.text visible: hoverHandler.hovered && deviceDelegatePortDesText.truncated delay: 500 property var maxWidth: 500 property var wrapMode: Text.WrapAnywhere } HoverHandler { id: hoverHandler} } } } } ukui-volume-control/ukui-shortcut-audio/qml/EmptyDelegate.qml0000664000175000017500000000325715171074712023436 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 . */ import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 import org.ukui.audio.common 1.0 Item { id: emptyDelegate width: emptyDelegateWidth height: emptyDelegateHeight property real emptyDelegateWidth: 396 property real emptyDelegateHeight: 64 property alias emptyDelegateTextStr: emptyDelegateText.text Label { id: emptyDelegateText width: 306 anchors.centerIn: parent elide: Text.ElideRight horizontalAlignment: Text.AlignHCenter wrapMode: Text.NoWrap color: Common.getThemePropertyByName("kfont-secondary") text: qsTr("") ToolTip { text: emptyDelegateText.text visible: hoverHandler.hovered && emptyDelegateText.truncated delay: 500 property var maxWidth: 500 property var wrapMode: Text.WrapAnywhere } HoverHandler { id: hoverHandler} } } ukui-volume-control/ukui-shortcut-audio/qml/AudioArea.qml0000664000175000017500000002111615171074712022531 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: zhouyong * */ import QtQuick 2.15 import QtQuick.Controls 2.15 import org.ukui.shortcut.audio 1.0 import org.ukui.audio.common 1.0 import org.ukui.audio.sinkcontrol 1.0 import org.ukui.audio.sourcecontrol 1.0 Item { id: qmlAudioAreaItem anchors.fill: parent anchors.top: parent.top anchors.topMargin: 56 property bool qml_sinkVolumeBoostStatus: false SystemVolumeArea { id: systemVolume anchors.top: parent.top onSystemVolumeBtnClick: { console.log("AudioArea.qml onSystemVolumeBtnClick systemVolumeMuteStatus:",systemVolumeMuteStatus) UKUIAudioControl.startTask(Common.TaskID_SetSinkMute, -1 , !systemVolumeMuteStatus) } onValueChanged: { console.log("AudioArea.qml onValueChanged value:",value) if (systemVolumeMuteStatus) { UKUIAudioControl.startTask(Common.TaskID_SetSinkMute, -1, false) } UKUIAudioControl.startTask(Common.TaskID_SetSinkVolume, -1, value) } } Rectangle { id: lineTop width: parent.width height: 1 anchors.top: systemVolume.bottom color: Common.getThemePropertyByName("kline-normal") } AudioListArea { id: audioList anchors.top: lineTop.bottom audioListHeight: (parent.height - systemVolume.height - lineTop.height - lineBottom.height - moreAudioSetting.height) } Rectangle { id: lineBottom width: parent.width height: 1 anchors.bottom: moreAudioSetting.top color: Common.getThemePropertyByName("kline-normal") } MoreAudioSettingArea { id: moreAudioSetting anchors.bottom: parent.bottom onMoreAudioSettingClick: { console.log("AudioArea.qml onMoreAudioSettingClick") UKUIAudioControl.startTask(Common.TaskID_JumpControlPanelAudio) } } Component.onCompleted: { console.log("AudioArea.qml onCompleted") UKUIAudioControl.startTask(Common.TaskID_GetSinkAvailablePortList) UKUIAudioControl.startTask(Common.TaskID_GetSinkInputList) } Connections { target: UKUIAudioSinkControl function onSinkVolumeValueChanged(sinkVolumeValue) { console.log("AudioArea.qml onSinkVolumeValueChanged sinkVolumeValue:",sinkVolumeValue,systemVolume.systemVolumeValue) if (sinkVolumeValue !== systemVolume.systemVolumeValue) { systemVolume.systemVolumeValue = sinkVolumeValue } } function onSinkMuteStatusChanged(sinkMuteStatus) { console.log("AudioArea.qml onSinkMuteStatusChanged sinkMuteStatus:",sinkMuteStatus) systemVolume.systemVolumeMuteStatus = sinkMuteStatus } function onSinkVolumeStatusIconChanged(sinkVolumeStatusIcon) { console.log("AudioArea.qml onSinkVolumeStatusIconChanged sinkVolumeStatusIcon:",sinkVolumeStatusIcon) systemVolume.systemVolumeBtnSource = sinkVolumeStatusIcon } function onSinkVolumeBoostValueChanged(sinkVolumeBoostValue) { console.log("AudioArea.qml onSinkVolumeBoostValueChanged sinkVolumeBoostValue:" ,qml_sinkVolumeBoostStatus,sinkVolumeBoostValue) if (qml_sinkVolumeBoostStatus) { systemVolume.systemVolumeSliderRange = sinkVolumeBoostValue UKUIAudioControl.startTask(Common.TaskID_GetSinkVolume) } } function onSinkVolumeBoostStatusChanged(sinkVolumeBoostStatus) { console.log("AudioArea.qml onSinkVolumeBoostStatusChanged sinkVolumeBoostStatus:",sinkVolumeBoostStatus) qml_sinkVolumeBoostStatus = sinkVolumeBoostStatus; if (sinkVolumeBoostStatus) { UKUIAudioControl.startTask(Common.TaskID_GetSinkVolumeBoostValue) } } function onSinkDeviceChanged(portName, cardName) { console.log("AudioArea.qml onSinkDeviceChanged:",portName,cardName) UKUIAudioSinkControl.updateSinkDefaultDevice(portName, cardName) UKUIAudioControl.startTask(Common.TaskID_GetSinkVolume) } function onSinkDeviceAdjust() { console.log("AudioArea.qml onSinkDeviceAdjust") UKUIAudioControl.startTask(Common.TaskID_GetSinkAvailablePortList) } } Connections { target: UKUIAudioControl function onTaskCompleted(taskID, retValue) { console.log("AudioArea.qml onTaskCompleted taskID:",taskID) if (taskID === Common.TaskID_Init) { } else if (taskID === Common.TaskID_GetSinkVolume) { UKUIAudioSinkControl.updateSinkVolume(retValue) } else if (taskID === Common.TaskID_SetSinkVolume) { } else if (taskID === Common.TaskID_GetSinkMute) { UKUIAudioSinkControl.updateSinkMute(retValue) } else if (taskID === Common.TaskID_SetSinkMute) { } else if (taskID === Common.TaskID_SetSinkInputVolume) { } else if (taskID === Common.TaskID_GetSourceVolume) { } else if (taskID === Common.TaskID_SetSourceVolume) { } else if (taskID === Common.TaskID_GetSinkAvailablePortList) { UKUIAudioSinkControl.updateSinkAvailablePortList(retValue) UKUIAudioControl.startTask(Common.TaskID_GetSinkDefaultDevice) if (UKUIAudioSinkControl.getEnabledSinkDeviceCount() > 0) { systemVolume.systemVolumeBtnEnabled = true systemVolume.systemVolumeSliderEnabled = true } else { systemVolume.systemVolumeBtnEnabled = false systemVolume.systemVolumeSliderEnabled = false } } else if (taskID === Common.TaskID_GetSourceAvailablePortList) { } else if (taskID === Common.TaskID_GetSinkInputList) { UKUIAudioSinkControl.updateSinkInputList(retValue) } else if (taskID === Common.TaskID_GetSourceInputList) { } else if (taskID === Common.TaskID_GetSinkDefaultDevice) { UKUIAudioSinkControl.updateSinkDefaultDevice(retValue) } else if (taskID === Common.TaskID_SetSinkDefaultDevice) { } else if (taskID === Common.TaskID_GetSourceDefaultDevice) { } else if (taskID === Common.TaskID_SetSourceDefaultDevice) { } else if (taskID === Common.TaskID_JumpControlPanelAudio) { } else if (taskID === Common.TaskID_GetSinkVolumeBoostValue) { UKUIAudioSinkControl.updateSinkVolumeBoostValue(retValue) } else if (taskID === Common.TaskID_GetSinkVolumeBoostStatus) { UKUIAudioSinkControl.updateSinkVolumeBoostStatus(retValue) } } function onTaskError(taskID) { console.log("AudioArea.qml onTaskError taskID:",taskID) if (taskID === Common.TaskID_Init) { } else if (taskID === Common.TaskID_GetSinkVolume) { } else if (taskID === Common.TaskID_SetSinkVolume) { } else if (taskID === Common.TaskID_GetSourceVolume) { } else if (taskID === Common.TaskID_SetSourceVolume) { } else if (taskID === Common.TaskID_GetSinkAvailablePortList) { } else if (taskID === Common.TaskID_GetSourceAvailablePortList) { } else if (taskID === Common.TaskID_GetSinkInputList) { } else if (taskID === Common.TaskID_GetSourceInputList) { } else if (taskID === Common.TaskID_GetSinkDefaultDevice) { } else if (taskID === Common.TaskID_SetSinkDefaultDevice) { } else if (taskID === Common.TaskID_GetSourceDefaultDevice) { } else if (taskID === Common.TaskID_SetSourceDefaultDevice) { } else if (taskID === Common.TaskID_JumpControlPanelAudio) { } } } } ukui-volume-control/ukui-shortcut-audio/qml/HeaderDelegate.qml0000664000175000017500000000560515171074712023527 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 . */ import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 import org.ukui.quick.items 1.0 as UkuiItems import org.ukui.quick.platform 1.0 as Platform Item { id: headerDelegate width: headerDelegateWidth height: headerDelegateHeight anchors.top: parent.top anchors.topMargin: headerDelegateTopMargin property real headerDelegateWidth: 396 property real headerDelegateHeight: 32 property real headerDelegateTopMargin: 0 property string headerDelegateBtnSource: "" property alias headerDelegateBtnVisible: headerDelegateBtn.visible property alias headerDelegateBtnEnabled: headerDelegateBtn.enabled property alias headerDelegateTextStr: headerDelegateText.text signal headerDelegateBtnClick() Label { id: headerDelegateText anchors.left: parent.left anchors.leftMargin: 24 anchors.verticalCenter: parent.verticalCenter text: qsTr("") } UkuiItems.DtThemeButton { id: headerDelegateBtn width: 32 height: 32 Layout.preferredWidth: 32 Layout.preferredHeight: 32 Layout.alignment: Qt.AlignVCenter anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right anchors.rightMargin: 16 background.radius: 6 property var kTransparent : Platform.GradientColor { gradientDeg : 0 gradientStart : Qt.rgba(0/255, 0/255, 0/255, 0) gradientEnd : Qt.rgba(0/255, 0/255, 0/255, 0) gradientBackground : Qt.rgba(0/255, 0/255, 0/255, 0) } background.backgroundColor: containsPress ? Platform.GlobalTheme.kContainAlphaClick : containsMouse ? Platform.GlobalTheme.kContainAlphaHover : kTransparent icon.source: headerDelegateBtnSource icon.mode: UkuiItems.Icon.AutoHighlight visible: false enabled: false onClicked: { console.log("HeaderDelegate.qml headerDelegateBtn onClicked") headerDelegate.headerDelegateBtnClick() } } } ukui-volume-control/ukui-shortcut-audio/qml/metadata.json0000775000175000017500000000076615171074677022664 0ustar fengfeng{ "Authors": [ { "Name": "zhouyong", "Email": "zhouyong01@kylinos.cn" } ], "Id": "org.ukui.shortcut.audio", "Icon": "", "Name": "ukui-shortcut-audio", "Name[zh_CN]": "快捷面板声音插件", "Tooltip": "audio", "Tooltip[zh_CN]": "声音", "Description": "audio", "Description[zh_CN]": "声音", "Version": "1.0", "ShowIn": "Shortcut", "WidgetType": "Widget", "Contents": { "Main": "main.qml", "I18n": "translations/ukui-shortcut-audio" } } ukui-volume-control/README.md0000664000175000017500000000756715171074712014743 0ustar fengfeng# ukui-volume-control `ukui-volume-control` provides the audio service and volume control components used by UKUI. The project covers backend audio management, the UKUI Control Center audio plugin, and the shortcut audio widget used by the desktop shell. ## Overview The repository is organized into several functional parts: - `backend/` Builds `ukui-audio-service`, the background service that manages audio devices, streams, volume, mute state, and session-related audio behavior. - `audio/` Builds the shared library used by the UKUI Control Center audio module. - `tray/` Contains the tray volume control application source code. - `ukui-shortcut-audio/` Provides the QML plugin and widget resources for the shortcut audio panel. - `common/` Shared helper code used by multiple components. - `data/` Desktop integration files such as the user service unit and autostart entry. - `debian/` Debian and Ubuntu Kylin packaging metadata. ## Features - Audio device detection and stream management - Volume and mute control for input and output devices - Application stream control - PipeWire and PulseAudio integration - UKUI Control Center integration - UKUI shortcut widget and desktop session integration - Translation resources for multiple locales ## Build Requirements The project is intended for UKUI desktop environments and depends on Qt 6 plus several Linux desktop and audio libraries. On Debian-based systems, the package set below matches the current packaging metadata closely: ```bash sudo apt install \ cmake intltool \ libasound2-dev libcanberra-dev libdbus-1-dev libdconf-dev \ libglib2.0-dev libgsettings-qt6-dev libkf6windowsystem-dev \ libkysdk-applications-dev libkysdk-system-dev \ libpipewire-0.3-dev libpugixml-dev libpulse-dev libsndfile1-dev \ libukcc-dev libxml2-dev nlohmann-json3-dev \ qt6-base-dev qt6-base-dev-tools qt6-declarative-dev \ qt6-svg-dev qt6-tools-dev qt6-tools-dev-tools ``` If the source package is already known to your build environment, you can also install dependencies from packaging metadata with: ```bash sudo apt build-dep . ``` You will also need the UKUI runtime environment and related desktop libraries when running the built binaries. ## Development Build For local development and testing, configure and build the project with CMake: ```bash cmake -S . -B build -DCMAKE_BUILD_TYPE=Release cmake --build build -j"$(nproc)" ``` To install into the system prefix: ```bash sudo cmake --install build ``` ## Main Outputs The current build installs these primary artifacts: - `ukui-audio-service` to `/usr/bin` - The control center audio plugin to `/usr/lib//ukui-control-center/` - The shortcut audio QML plugin to `/usr/lib//qt/qml/org/ukui/shortcut/audio` - Widget resources to `/usr/share/ukui/widgets/org.ukui.shortcut.audio` - Translation files under `/usr/share/ukui-media/translations/audio` - A user service unit under `/usr/lib/systemd/user/` ## Debian Packaging For Debian and Ubuntu Kylin package creation, packaging files are maintained in `debian/`. This package uses the `3.0 (quilt)` source format. When building a source package, make sure the upstream tarball is available as: ```text ../ukui-volume-control_.orig.tar.gz ``` The upstream tarball can be fetched according to `debian/watch` with: ```bash uscan --download-current-version --force-download --rename ``` To build a source package for review or upload: ```bash debuild -S -sa ``` To build binary packages locally: ```bash debuild -b -uc -us ``` You can also use `dpkg-buildpackage` directly if preferred. Build artifacts such as `.dsc`, `.changes`, `.buildinfo`, and `.deb` files are normally generated in the parent directory. ## License Most of this project is licensed under GPL-3.0-or-later. See `COPYING` for the full GPL text and `debian/copyright` for packaged copyright and third-party license details. ukui-volume-control/common/0000775000175000017500000000000015171074712014735 5ustar fengfengukui-volume-control/common/DBusClient.cpp0000664000175000017500000001272015171074712017437 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "DBusClient.h" #include //#include "ClientMethod.h" DBusClient& DBusClient::getInstance() { static DBusClient instance; return instance; } void DBusClient::initDbusConnect() { if (!QDBusConnection::sessionBus().connect(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "volumeChanged", this, SLOT(volumeChangedSlots(int, int, const QDBusVariant&)))) { qDebug() << "Audio framework interface error, connect " << "volumeChanged" << "failed!"; qDebug() << QDBusConnection::sessionBus().lastError().message(); } if (!QDBusConnection::sessionBus().connect(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "muteChanged", this, SLOT(muteChangedSlots(int, int, bool)))) { qDebug() << "Audio framework interface error, connect " << "muteChanged" << "failed!"; qDebug() << QDBusConnection::sessionBus().lastError().message(); } if (!QDBusConnection::sessionBus().connect(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "volumeChanged", this, SLOT(volumeChangedSlots(int, int, const QDBusVariant&)))) { qDebug() << "Audio framework interface error, connect " << "volumeChanged" << "failed!"; qDebug() << QDBusConnection::sessionBus().lastError().message(); } if (!QDBusConnection::sessionBus().connect(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "deviceChanged", this, SLOT(deviceChangedSlots(int, const QString&, const QString&)))) { qDebug() << "Audio framework interface error, connect " << "deviceChanged" << "failed!"; qDebug() << QDBusConnection::sessionBus().lastError().message(); } if (!QDBusConnection::sessionBus().connect(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "deviceAdjust", this, SLOT(deviceAdjustSlots(int)))) { qDebug() << "Audio framework interface error, connect " << "deviceChanged" << "failed!"; qDebug() << QDBusConnection::sessionBus().lastError().message(); } if (!QDBusConnection::sessionBus().connect(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, SETTINGS_INTERFACE, "changed", this, SLOT(settingsChangedSlots(const QString&, const QDBusVariant&)))) { qDebug() << "Audio framework interface error, connect " << "changed" << "failed!" ; qDebug() << QDBusConnection::sessionBus().lastError().message(); } if (!QDBusConnection::sessionBus().connect(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "addStream", this, SLOT(addStreamSlots(int, int, int, bool, const QString&, const QString&)))) { qDebug() << "Audio framework interface error, connect " << "addStream" << "failed!" ; qDebug() << QDBusConnection::sessionBus().lastError().message(); } if (!QDBusConnection::sessionBus().connect(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "removeStream", this, SLOT(removeStreamSlots(int, int)))) { qDebug() << "Audio framework interface error, connect " << "removeStream" << "failed!" ; qDebug() << QDBusConnection::sessionBus().lastError().message(); } } void DBusClient::volumeChangedSlots(int type, int idx, const QDBusVariant& v) { Q_EMIT volumeChangedSignal(type, idx, v); } void DBusClient::muteChangedSlots(int type, int idx, bool mute) { Q_EMIT muteChangedSignal(type, idx, mute); } void DBusClient::deviceChangedSlots(int type, const QString& portName, const QString& cardName) { Q_EMIT deviceChangedSignal(type, portName, cardName); } void DBusClient::deviceAdjustSlots(int type) { Q_EMIT deviceAdjustSignal(type); } void DBusClient::settingsChangedSlots(const QString& key, const QDBusVariant& v) { qDebug() << "DBusClient::settingsChangedSlots, key:" << key << " value: " << v.variant(); Q_EMIT settingsChangedSignal(key, v); } void DBusClient::addStreamSlots(int dire, int idx, int value, bool mute, const QString& iconName, const QString& descName) { Q_EMIT addStreamSignal(dire, idx, value, mute, iconName, descName); } void DBusClient::removeStreamSlots(int dire, int idx) { Q_EMIT removeStreamSignal(dire, idx); } ukui-volume-control/common/Ukui4CustomControl.h0000664000175000017500000000465715171074712020657 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 UKUI4CUSTOMCONTROL_H #define UKUI4CUSTOMCONTROL_H #include #include #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #include #endif #include //文本长自动省略并添加悬浮 class AutoOmitLabel : public QLabel { Q_OBJECT public: explicit AutoOmitLabel(QWidget* = nullptr); explicit AutoOmitLabel(const QString&, QWidget* = nullptr); ~AutoOmitLabel() = default; public: void setText(const QString&, bool = true); protected: void paintEvent(QPaintEvent*) override; private: QString m_fullText; }; class UkmediaVolumeSlider : public kdk::KSlider { Q_OBJECT public: UkmediaVolumeSlider(QWidget* = nullptr); ~UkmediaVolumeSlider(); Q_SIGNALS: void silderPressSignal(); void silderReleaseSignal(); protected: void mousePressEvent(QMouseEvent*) override; void mouseReleaseEvent(QMouseEvent*) override; void mouseMoveEvent(QMouseEvent*) override; void wheelEvent(QWheelEvent*) override; void keyReleaseEvent(QKeyEvent*) override; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) void enterEvent(QEnterEvent*) override; #else void enterEvent(QEvent*) override; #endif void leaveEvent(QEvent*) override; private: void initStyleOption(QStyleOptionSlider*); void updateValue(QMouseEvent*); private: bool m_bMousePress = false; }; class UkBalanceVolumeSlider : public kdk::KSlider { Q_OBJECT public: UkBalanceVolumeSlider(QWidget* = nullptr); ~UkBalanceVolumeSlider(); protected: virtual bool eventFilter(QObject*, QEvent*) override; private: void initStyleOption(QStyleOptionSlider*); void showTooltip(); }; #endif // UKUI4CUSTOMCONTROL_H ukui-volume-control/common/DBusClient.h0000664000175000017500000002465215171074712017113 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 DBUSCLIENT_H #define DBUSCLIENT_H #include #include #include #include #include #include #include #include "Util.h" // 注册 ClientDeviceInfo 类型 typedef struct ClientDeviceInfo { QString cardName; // 声卡名称 QString cardDesc; // 声卡描述 QString portName; // 设备端口名称 QString portLabel; // 设备端口描述 quint32 priority; // 设备端口优先级 quint32 direction; // 端口方向 input or output quint32 available; // 端口可用状态 bool enabled; // 端口启用状态 friend const QDBusArgument& operator>>(const QDBusArgument& argument, ClientDeviceInfo& info) { argument.beginStructure(); argument >> info.cardName; argument >> info.cardDesc; argument >> info.portName; argument >> info.portLabel; argument >> info.priority; argument >> info.direction; argument >> info.available; argument >> info.enabled; argument.endStructure(); return argument; } friend const QDBusArgument &operator<<(QDBusArgument& argument, const ClientDeviceInfo& info) { argument.beginStructure(); argument << info.cardName; argument << info.cardDesc; argument << info.portName; argument << info.portLabel; argument << info.priority; argument << info.direction; argument << info.available; argument << info.enabled; argument.endStructure(); return argument; } } ClientDeviceInfo; Q_DECLARE_METATYPE(ClientDeviceInfo) // 注册 ClientDefaultDeviceInfo 类型 typedef struct ClientDefaultDeviceInfo { quint32 idx; // 默认设备的索引 QString name; // 默认设备的名称(sink/source 名称) QString cardName; // 默认设备所在的声卡名称 // QString cardDesc; // 默认设备所在的声卡描述 QString activePortName; // 默认设备的活跃端口名称,可能为空 QString activePortDesc; // 默认设备的活跃端口描述 friend const QDBusArgument& operator>>(const QDBusArgument& argument, ClientDefaultDeviceInfo& info) { argument.beginStructure(); argument >> info.idx; argument >> info.name; argument >> info.cardName; // argument >> info.cardDesc; argument >> info.activePortName; argument >> info.activePortDesc; argument.endStructure(); return argument; } friend const QDBusArgument &operator<<(QDBusArgument& argument, const ClientDefaultDeviceInfo& info) { argument.beginStructure(); argument << info.idx; argument << info.name; argument << info.cardName; // argument << info.cardDesc; argument << info.activePortName; argument << info.activePortDesc; argument.endStructure(); return argument; } } ClientDefaultDeviceInfo; Q_DECLARE_METATYPE(ClientDefaultDeviceInfo) // 注册 StreamInfo 类型 typedef struct StreamInfo { QString name; // 流媒体名称 quint32 index; // 流媒体索引 uint32_t volume; // 流媒体音量 QString role; // 流媒体角色 QString iconName; // 流媒体图标名称 friend const QDBusArgument& operator>>(const QDBusArgument& argument, StreamInfo& info) { argument.beginStructure(); argument >> info.name; argument >> info.index; argument >> info.volume; argument >> info.role; argument >> info.iconName; argument.endStructure(); return argument; } friend const QDBusArgument &operator<<(QDBusArgument& argument, const StreamInfo& info) { argument.beginStructure(); argument << info.name; argument << info.index; argument << info.volume; argument << info.role; argument << info.iconName; argument.endStructure(); return argument; } } StreamInfo; Q_DECLARE_METATYPE(StreamInfo) typedef struct ClientMediaInfo { uint32_t direction; // 流媒体的direction, input or output uint32_t index; // 流媒体索引 uint32_t volume; // 流媒体音量值 bool mute; // 流媒体静音状态 QString name; // 流媒体名称 friend const QDBusArgument& operator>>(const QDBusArgument& argument, ClientMediaInfo& info) { argument.beginStructure(); argument >> info.direction; argument >> info.index; argument >> info.volume; argument >> info.mute; argument >> info.name; argument.endStructure(); return argument; } friend const QDBusArgument &operator<<(QDBusArgument& argument, const ClientMediaInfo& info) { argument.beginStructure(); argument << info.direction; argument << info.index; argument << info.volume; argument << info.mute; argument << info.name; argument.endStructure(); return argument; } } ClientMediaInfo; Q_DECLARE_METATYPE(ClientMediaInfo) typedef struct ClientStreamMediaInfo { QString mediaRole; // 流媒体角色 QString iconName; // 流媒体应用图标名称 QString binary; // 流媒体对应的二进制名称 std::list mediaInfoList; // 流媒体信息 friend const QDBusArgument& operator>>(const QDBusArgument& argument, ClientStreamMediaInfo& info) { argument.beginStructure(); argument >> info.mediaRole; argument >> info.iconName; argument >> info.binary; argument.beginArray(); while (!argument.atEnd()) { ClientMediaInfo element; argument >> element; info.mediaInfoList.push_back(element); } argument.endArray(); argument.endStructure(); return argument; } friend const QDBusArgument &operator<<(QDBusArgument& argument, const ClientStreamMediaInfo& info) { argument.beginStructure(); argument << info.mediaRole; argument << info.iconName; argument << info.binary; argument.beginArray(); for (const auto& media : info.mediaInfoList) { argument << media; } argument.endArray(); argument.endStructure(); return argument; } } ClientStreamMediaInfo; Q_DECLARE_METATYPE(ClientStreamMediaInfo) // 注册 ClientSoundThemeInfo 类型 typedef struct ClientSoundThemeInfo { QString name; // 音效主题的名称 QString description; // 音效主题的描述 friend const QDBusArgument& operator>>(const QDBusArgument& argument, ClientSoundThemeInfo& info) { argument.beginStructure(); argument >> info.name; argument >> info.description; argument.endStructure(); return argument; } friend const QDBusArgument &operator<<(QDBusArgument& argument, const ClientSoundThemeInfo& info) { argument.beginStructure(); argument << info.name; argument << info.description; argument.endStructure(); return argument; } } ClientSoundThemeInfo; Q_DECLARE_METATYPE(ClientSoundThemeInfo) // 注册 ClientSoundEffectFileInfo 类型 typedef struct ClientSoundEffectFileInfo { QString name; // 音效主题的名称 QString description; // 音效主题的描述 friend const QDBusArgument& operator>>(const QDBusArgument& argument, ClientSoundEffectFileInfo& info) { argument.beginStructure(); argument >> info.name; argument >> info.description; argument.endStructure(); return argument; } friend const QDBusArgument &operator<<(QDBusArgument& argument, const ClientSoundEffectFileInfo& info) { argument.beginStructure(); argument << info.name; argument << info.description; argument.endStructure(); return argument; } } ClientSoundEffectFileInfo; Q_DECLARE_METATYPE(ClientSoundEffectFileInfo) class DBusClient : public QObject { Q_OBJECT public: static DBusClient& getInstance(); public: void initDbusConnect(); template QDBusMessage dbusMethodCall(const QString&, const QString&, const QString&, const QString&, Args &&...); Q_SIGNALS: void volumeChangedSignal(int, int, const QDBusVariant&); void muteChangedSignal(int, int, bool); void deviceChangedSignal(int, const QString&, const QString&); void deviceAdjustSignal(int); void settingsChangedSignal(const QString&, const QDBusVariant&); void addStreamSignal(int, int, int, bool, const QString&, const QString&); void removeStreamSignal(int, int); private Q_SLOTS: void volumeChangedSlots(int, int, const QDBusVariant&); void muteChangedSlots(int, int, bool); void deviceChangedSlots(int, const QString&, const QString&); void deviceAdjustSlots(int); void settingsChangedSlots(const QString&, const QDBusVariant&); void addStreamSlots(int, int, int, bool, const QString&, const QString&); void removeStreamSlots(int, int); private: DBusClient() = default; DBusClient(const DBusClient&) = delete; DBusClient(DBusClient&&) = delete; DBusClient operator=(const DBusClient&) = delete; DBusClient operator=(DBusClient&&) = delete; ~DBusClient() = default; }; template QDBusMessage DBusClient::dbusMethodCall(const QString& destination, const QString& path, const QString& interface, const QString& method, Args&&... args) { QDBusInterface mediaInterface(destination, path, interface); return mediaInterface.call(method, std::forward(args)...); } #endif // DBUSCLIENT_H ukui-volume-control/common/Ukui4CustomControl.cpp0000664000175000017500000001266315171074712021206 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "Ukui4CustomControl.h" #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #include #endif AutoOmitLabel::AutoOmitLabel(QWidget* parent) : QLabel(parent) { } AutoOmitLabel::AutoOmitLabel(const QString& text, QWidget* parent) : QLabel(parent) { setText(text); } void AutoOmitLabel::paintEvent(QPaintEvent* event) { QFontMetrics fontMetrics(font()); #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) int fontSize = fontMetrics.horizontalAdvance(m_fullText); #else int fontSize = fontMetrics.width(m_fullText); #endif if (fontSize > width()) { setText(fontMetrics.elidedText(m_fullText, Qt::ElideRight, width()), false); setToolTip(m_fullText); } else { setText(m_fullText, false); setToolTip(""); } QLabel::paintEvent(event); } void AutoOmitLabel::setText(const QString& text, bool save) { if (save) m_fullText = text; QLabel::setText(text); } UkmediaVolumeSlider::UkmediaVolumeSlider(QWidget* parent) : kdk::KSlider(parent) { Q_UNUSED(parent); setSliderType(kdk::SmoothSlider); setNodeVisible(false); } void UkmediaVolumeSlider::updateValue(QMouseEvent* e) { int value = 0; int currentX = e->pos().x(); double per = currentX * 1.0 / this->width(); if ((this->maximum() - this->minimum()) >= 50) { value = qRound(per*(this->maximum() - this->minimum())) + this->minimum(); if (value <= (this->maximum() / 2 - this->maximum() / 10 + this->minimum() / 10)) { value = qRound(per*(this->maximum() - this->minimum() - 1)) + this->minimum(); } else if (value > (this->maximum() / 2 + this->maximum() / 10 + this->minimum() / 10)) { value = qRound(per*(this->maximum() - this->minimum() + 1)) + this->minimum(); } else { value = qRound(per*(this->maximum() - this->minimum())) + this->minimum(); } } else { value = qRound(per*(this->maximum() - this->minimum())) + this->minimum(); } this->setValue(value); } void UkmediaVolumeSlider::mousePressEvent(QMouseEvent* e) { m_bMousePress = true; // 向父窗口发送自定义事件event type,这样就可以在父窗口中捕获这个事件进行处理 QEvent evEvent(static_cast(QEvent::User + 1)); QCoreApplication::sendEvent(parentWidget(), &evEvent); return KSlider::mousePressEvent(e); } void UkmediaVolumeSlider::mouseReleaseEvent(QMouseEvent* e) { m_bMousePress = false; return KSlider::mouseReleaseEvent(e); } void UkmediaVolumeSlider::mouseMoveEvent(QMouseEvent* e) { return KSlider::mouseMoveEvent(e); } void UkmediaVolumeSlider::wheelEvent(QWheelEvent* e) { return KSlider::wheelEvent(e); } void UkmediaVolumeSlider::keyReleaseEvent(QKeyEvent* e) { return KSlider::keyReleaseEvent(e); } void UkmediaVolumeSlider::initStyleOption(QStyleOptionSlider* option) { return KSlider::initStyleOption(option); } void UkmediaVolumeSlider::leaveEvent(QEvent* e) { return KSlider::leaveEvent(e); } #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) void UkmediaVolumeSlider::enterEvent(QEnterEvent* e) { return KSlider::enterEvent(e); } #else void UkmediaVolumeSlider::enterEvent(QEvent* e) { return KSlider::enterEvent(e); } #endif UkmediaVolumeSlider::~UkmediaVolumeSlider() { // delete m_pTiplabel; } UkBalanceVolumeSlider::UkBalanceVolumeSlider(QWidget* parent) : kdk::KSlider(parent) { Q_UNUSED(parent); setSliderType(kdk::SingleSelectSlider); setRange(-100,100); setSingleStep(100); setTickInterval(100); setOrientation(Qt::Horizontal); setFocusPolicy(Qt::StrongFocus); this->installEventFilter(this); } void UkBalanceVolumeSlider::showTooltip() { QString percent = QString::number(this->value()); percent.append("%"); QStyleOptionSlider opt; this->initStyleOption(&opt); QRect handleRect = this->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this); QPoint point = this->mapToGlobal(handleRect.topLeft()); QFontMetrics fontMetrics = QFontMetrics(this->font()); QRect fontRect = fontMetrics.boundingRect(percent); QToolTip::showText(point - QPoint(fontRect.width()/2 +3, fontRect.height() + 40), percent); return; } bool UkBalanceVolumeSlider::eventFilter(QObject* watched, QEvent* event) { if (watched == this) { if (event->type() == QEvent::HoverEnter) showTooltip(); } return kdk::KSlider::eventFilter(watched, event); } void UkBalanceVolumeSlider::initStyleOption(QStyleOptionSlider* option) { kdk::KSlider::initStyleOption(option); } UkBalanceVolumeSlider::~UkBalanceVolumeSlider() { } ukui-volume-control/backend/0000775000175000017500000000000015171074712015034 5ustar fengfengukui-volume-control/backend/PipewireBackend.cpp0000664000175000017500000000166615171074712020605 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "PipewireBackend.h" namespace UkuiAudioFramwork { PipewireBackend::PipewireBackend() { } void PipewireBackend::init() { } bool PipewireBackend::detect() { return {}; } } ukui-volume-control/backend/AudioMethod.cpp0000664000175000017500000011762015171074712017751 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "AudioMethod.h" #include #include #include #include #include "InitVolumeModule.h" #include "AudioContext.h" #include "MediaControler.h" #include "MediaAutoPauseModule.h" #include "MultiAudioCombineModule.h" #include "DeviceManagerModule.h" #include "Util.h" namespace UkuiAudioFramwork { AudioMethod& AudioMethod::getInstance() { static AudioMethod instance; return instance; } void AudioMethod::setManager(std::shared_ptr manager) { m_pAudioManager = manager; } void AudioMethod::init() { // init dbus m_dbus.initialize(); // init settings for (auto& [k, v] : m_keys) { switch (k) { case SettingType::AUDIO_SETTING: { v = std::make_shared(); break; } case SettingType::GLOBALTHEME_SETTING: { v = std::make_shared(); break; } case SettingType::SESSIONMANAGER_SETTING: { v = std::make_shared(); break; } case SettingType::SOUDNTHEMEPLAYER_SETTING: { v = std::make_shared(); break; } default: break; } } // 初始化音量 auto initSystemVolume = [this]() { auto inputDev = getDefaultDevice(NodeType::INPUT, -1); initVolume(NodeType::INPUT, inputDev.activePortName, inputDev.cardName); auto outputDev = getDefaultDevice(NodeType::OUTPUT, -1); initVolume(NodeType::OUTPUT, outputDev.activePortName, outputDev.cardName); }; std::thread(initSystemVolume).detach(); // auto initModule = [this]() { // for (const auto& [key, value] : m_settingsMap) { // const auto& [module, param] = value; // bool status = m_statusFunctions[key](); // if (status) { // loadModule(module, param); // } // } // }; // std::thread(initModule).detach(); initJson(); DeviceManagerModule::getInstance().setManager(m_pAudioManager); DeviceManagerModule::getInstance().setJson(m_pRestoreVolumeJson); MediaAutoPauseModule::getInstance().setJson(m_pRestoreVolumeJson); MultiAudioCombineModule::getInstance().setJson(m_pMultiAudioJson); } void AudioMethod::run() { m_dbus.run(); } std::optional AudioMethod::getFieldValue(const std::string& line, const std::string_view& fieldName) { if (line.find(fieldName) != std::string::npos) { auto start = line.find('=') + 1; if (line[start] == '"') { ++start; auto end = line.find('"', start); if (end != std::string::npos) { return line.substr(start, end - start); } } } return std::nullopt; } void AudioMethod::initJson() { m_pRestoreVolumeJson = std::make_shared(); m_pMultiAudioJson = std::make_shared(); } SystemVersionInfo AudioMethod::getSystemVersionInfo() { SystemVersionInfo info; const auto filePath = std::filesystem::path("/etc/os-release"); try { if (std::ifstream osReleaseFile(filePath); osReleaseFile.is_open()) { std::string line; while (std::getline(osReleaseFile, line)) { if (!info.prettyName) { info.prettyName = getFieldValue(line, "PRETTY_NAME"); } if (info.prettyName && !info.releaseId) { info.releaseId = getFieldValue(line, "KYLIN_RELEASE_ID"); } } } else { std::cerr << "can not open " << filePath << std::endl; } } catch (const std::filesystem::filesystem_error& e) { std::cerr << "file system error: " << e.what() << std::endl; } return info; } DesktopEnvironmentVersion AudioMethod::getTypeFromSystemVersionInfo(const SystemVersionInfo& info) const { if (auto prettyName = info.prettyName; prettyName) { if (prettyName.value().find("openKylin") != std::string::npos) { return DesktopEnvironmentVersion::DESKTOP_ENVIRONMENT_VERSION_UKUI4; } else if (prettyName.value().find("Kylin") != std::string::npos) { if (auto releaseId = info.releaseId; releaseId) { if (releaseId.value().find("2107") != std::string_view::npos) { return DesktopEnvironmentVersion::DESKTOP_ENVIRONMENT_VERSION_UKUI2; } else if (releaseId.value().find("2203") != std::string_view::npos || releaseId.value().find("2303") != std::string_view::npos || releaseId.value().find("2403") != std::string_view::npos) { return DesktopEnvironmentVersion::DESKTOP_ENVIRONMENT_VERSION_UKUI3; } } } } return DesktopEnvironmentVersion::DESKTOP_ENVIRONMENT_VERSION_UNKNOWN; } DesktopEnvironmentVersion AudioMethod::getDesktopEnvironmentVersion() { return getTypeFromSystemVersionInfo(getSystemVersionInfo()); } std::string AudioMethod::getStreamDescription(const std::string& streamName) { if ("" == streamName) return {}; AUDIO_DEBUG("getStreamDescription streamName: {} .",streamName); GError** error = nullptr; GKeyFileFlags flags = G_KEY_FILE_NONE; GKeyFile* keyfile = g_key_file_new(); std::string path = "/usr/share/applications/"; std::string kaimingPath = "/opt/kaiming/share/applications/"; std::string description = ""; //start adapter application auto it = m_adapterStreamNameMap.find(streamName); if (it != m_adapterStreamNameMap.end()) { path += it->second; kaimingPath += it->second; } else { path += streamName; kaimingPath += streamName; } //end path += ".desktop"; kaimingPath += ".desktop"; gboolean ret = g_key_file_load_from_file(keyfile, path.c_str(), flags, error); AUDIO_DEBUG("getStreamDescription ret: {} , path: {} .",ret , path); if (!ret) { ret = g_key_file_load_from_file(keyfile, kaimingPath.c_str(), flags, error); AUDIO_DEBUG("getStreamDescription ret: {} , kaimingPath: {} .",ret , kaimingPath); } char* name = g_key_file_get_locale_string(keyfile, "Desktop Entry", "Name", nullptr, nullptr); description = name ? name : streamName; g_key_file_free(keyfile); return description; } void AudioMethod::initVolume(NodeType type, const std::string& portName, const std::string& cardName) { if (InitVolumeModule::getInstance().isNeedInitVolume(portName, cardName)) { if (cardName.starts_with("bluez")) { m_pAudioManager->waitBluezReady(3000); m_pAudioManager->setBluezReadyStatus(false); } auto volume = 17; if (NodeType::INPUT == type) { volume = 100; } else if (NodeType::OUTPUT == type) { auto portType = getDevicePortType(cardName, portName); volume = m_outputVolumeMap[portType]; } AUDIO_DEBUG("When the sound card: {} port: {} is connected for the first time, the initial volume is {}.", cardName, portName, volume); InitVolumeModule::getInstance().insert(portName, cardName, volume); setVolume(type, -1, volume); } } void AudioMethod::loadModule(const std::string& name, const std::string& param) { AUDIO_DEBUG("load module: {} with param: {}", name, param); m_pAudioManager->loadModule(name, param); } void AudioMethod::unloadModule(const std::string& name) { AUDIO_DEBUG("unload module: {}", name); m_pAudioManager->unloadModule(name); } std::string AudioMethod::getBackend() const { // return getBackendName(BackendType::PULSEAUDIO); return m_pAudioManager->getBackend(); } void AudioMethod::setBackend(const BackendType& type) { m_pAudioManager->setBackend(type); } void AudioMethod::setDetailVolume(const NodeType& type, const std::string& name, const std::string& dev, int volume) { AUDIO_DEBUG("Set {} detail volume, card: {} port: {} valume changed to {}.", getNodeTypeName(type), dev, name, volume); m_pAudioManager->setDetailVolume(type, name, dev, volume); } int AudioMethod::getDetailVolume(const NodeType& type, const std::string& name, const std::string& dev) const { auto volume = m_pAudioManager->getDetailVolume(type, name, dev); AUDIO_DEBUG("Get {} detail volume, card: {} port: {} volume: {}", getNodeTypeName(type), dev, name, volume); return volume; } void AudioMethod::setVolume(const NodeType& type, int idx, int volume) { AUDIO_DEBUG("Set {} volume, {} volume changed to {}.", getNodeTypeName(type), idx, volume); m_pAudioManager->setVolume(type, idx, volume); } int AudioMethod::getVolume(const NodeType& type, int idx) const { auto volume = m_pAudioManager->getVolume(type, idx); AUDIO_DEBUG("Get {} volume, {} volume: {}.", getNodeTypeName(type), idx, volume); return volume; } void AudioMethod::setBalance(const NodeType& type, double v) { AUDIO_DEBUG("Set {} balance value to {}.", getNodeTypeName(type), v); m_pAudioManager->setBalance(type, v); } double AudioMethod::getBalance(const NodeType& type) const { auto balance = m_pAudioManager->getBalance(type); AUDIO_DEBUG("Get {} balance, balance {}.", getNodeTypeName(type), balance); return balance; } void AudioMethod::setDetailMute(const NodeType& type, const std::string& name, const std::string& dev, bool mute) { AUDIO_DEBUG("Set {} detail mute, card: {} port: {} mute changed to {}.", getNodeTypeName(type), dev, name, mute); m_pAudioManager->setDetailMute(type, name, dev, mute); } bool AudioMethod::getDetailMute(const NodeType& type, const std::string& name, const std::string& dev) const { auto mute = m_pAudioManager->getDetailMute(type, name, dev); AUDIO_DEBUG("Get {} detail mute, card: {} port: {} mute: {}", getNodeTypeName(type), dev, name, mute); return mute; } void AudioMethod::setMute(const NodeType& type, int idx, bool mute) { AUDIO_DEBUG("Set {} mute, {} mute changed to {}.", getNodeTypeName(type), idx, mute); m_pAudioManager->setMute(type, idx, mute); } bool AudioMethod::getMute(const NodeType& type, int idx) const { auto mute = m_pAudioManager->getMute(type, idx); AUDIO_DEBUG("Get {} mute, {} mute: {}.", getNodeTypeName(type), idx, mute); return mute; } DefaultDeviceInfo AudioMethod::getDefaultDevice(const NodeType& type, int idx) const { auto defaultDevice = m_pAudioManager->getDefaultDevice(type, idx); AUDIO_DEBUG("Get {} default device, idx: {}, default device: {}", getNodeTypeName(type), idx, defaultDevice.name); return defaultDevice; } void AudioMethod::setDefaultDevice(const NodeType& type, int idx, const std::string& name, const std::string& dev) { AUDIO_DEBUG("Set {} default device, default device change to card: {} port: {}.", getNodeTypeName(type), dev, name); m_pAudioManager->setDefaultDevice(type, idx, name, dev); } std::vector AudioMethod::getDeviceList(const NodeType& type) const { auto list = m_pAudioManager->getDeviceList(type); AUDIO_DEBUG("Get {} device list."); return list; } std::list AudioMethod::getSinkList() const { auto list = m_pAudioManager->getSinkList(); std::string sl = "["; for (const auto& s : list) { sl += "("; sl += s.toString(); sl += "),"; } sl += "]"; AUDIO_DEBUG("Get sink list, sinks: {}.", sl); return list; } std::list AudioMethod::getSourceList() const { auto list = m_pAudioManager->getSourceList(); std::string sl = "["; for (const auto& s : list) { sl += "("; sl += s.toString(); sl += "),"; } sl += "]"; AUDIO_DEBUG("Get source list, sources: {}.", sl); return list; } std::list> AudioMethod::getAvailablePortList(const NodeType& type) const { auto list = m_pAudioManager->getAvailablePortList(type); std::string pl = "["; for (const auto& p : list) { pl += "("; pl += p->toString(); pl += "),"; } pl += "]"; AUDIO_DEBUG("Get {} available port, ports: {}.", getNodeTypeName(type), pl); return list; } std::list AudioMethod::getSinkInputList() const { auto list = m_pAudioManager->getSinkInputList(); std::string sil = "["; for (const auto& si : list) { sil += "("; sil += si.toString(); sil += "),"; } sil += "]"; AUDIO_DEBUG("Get sink inputs, inputs: {}.", sil); return list; } std::list AudioMethod::getSourceOutputList() const { auto list = m_pAudioManager->getSourceOutputList(); std::string sol = "["; for (const auto& so : list) { sol += "("; sol += so.toString(); sol += "),"; } sol += "]"; AUDIO_DEBUG("Get source outputs, outputs: {}.", sol); return list; } std::list AudioMethod::getStreamMediaList() const { return m_pAudioManager->getStreamMediaList(); } bool AudioMethod::isValidDevice(const NodeType& type) const { auto ret = m_pAudioManager->isValidDevice(type); AUDIO_DEBUG("{} has {} device.", getNodeTypeName(type), ret ? "vaild" : "invaild"); return ret; } bool AudioMethod::getVolumeBoostStatus() const { if (auto key = m_keys.find(SettingType::AUDIO_SETTING); key != m_keys.end()) { auto status = g_variant_get_boolean(key->second->getValue(VOLUME_BOOST_KEY)); AUDIO_DEBUG("Get volume boost status, status: {}.", status); return status; } AUDIO_ERROR("Not found SettingType::AUDIO_SETTING keys, get volume boost status failed."); return false; } void AudioMethod::setVolumeBoostStatus(bool status) { AUDIO_DEBUG("Set volume boost status to {}.", status); if (auto key = m_keys.find(SettingType::AUDIO_SETTING); key != m_keys.end()) key->second->setValue(VOLUME_BOOST_KEY, g_variant_new("b", status)); else AUDIO_ERROR("Not found SettingType::AUDIO_SETTING keys, set volume boost status failed."); if (!status) { auto sinkList = getSinkList(); std::for_each(sinkList.begin(), sinkList.end(), [this](const DefaultDeviceInfo& dev) { if (dev.volume > PA_VOLUME_NORMAL) { AUDIO_DEBUG("When Volume-Increase is turned off, device: {} needs to be set volume: {} to 65536", dev.name, dev.volume); setVolume(NodeType::OUTPUT, dev.idx, UKMEDIA_VOLUME_NORMAL); } }); } } int AudioMethod::getVolumeBoostValue() const { if (auto key = m_keys.find(SettingType::AUDIO_SETTING); key != m_keys.end()) { auto value = g_variant_get_int32(key->second->getValue(VOLUME_BOOST_VOLUME_KEY)); AUDIO_DEBUG("Get volume boost value, value: {}.", value); return value; } AUDIO_ERROR("Not found SettingType::AUDIO_SETTING keys, get volume boost value failed."); return DEFAULT_VOLUME_BOOST_VALUE; } void AudioMethod::setVolumeBoostValue(int value) { AUDIO_DEBUG("Set volume boost value to {}.", value); if (auto key = m_keys.find(SettingType::AUDIO_SETTING); key != m_keys.end()) key->second->setValue(VOLUME_BOOST_VOLUME_KEY, g_variant_new("i", value)); else AUDIO_ERROR("Not found SettingType::AUDIO_SETTING keys, set volume boost value failed."); } bool AudioMethod::getMonoStatus() const { if (auto key = m_keys.find(SettingType::AUDIO_SETTING); key != m_keys.end()) { auto status = g_variant_get_boolean(key->second->getValue(MONO_AUDIO_KEY)); AUDIO_DEBUG("Get mono status, status: {}.", status); return status; } AUDIO_ERROR("Not found SettingType::AUDIO_SETTING keys, get mono status failed."); return false; } void AudioMethod::setMonoStatus(bool status) { AUDIO_DEBUG("Set mono status to {}.", status); if (auto key = m_keys.find(SettingType::AUDIO_SETTING); key != m_keys.end()) key->second->setValue(MONO_AUDIO_KEY, g_variant_new("b", status)); else AUDIO_ERROR("Not found SettingType::AUDIO_SETTING keys, set mono status failed."); } bool AudioMethod::getEchoCancelStatus() const { if (auto key = m_keys.find(SettingType::AUDIO_SETTING); key != m_keys.end()) { auto status = g_variant_get_boolean(key->second->getValue(NOISE_REDUCTION_KEY)); AUDIO_DEBUG("Get echo cancel status, status: {}.", status); return status; } AUDIO_ERROR("Not found SettingType::AUDIO_SETTING keys, get noise resduction status failed."); return false; } void AudioMethod::setEchoCancelStatus(bool status) { AUDIO_DEBUG("Set echo cancel status to {}.", status); if (auto key = m_keys.find(SettingType::AUDIO_SETTING); key != m_keys.end()) key->second->setValue(NOISE_REDUCTION_KEY, g_variant_new("b", status)); else AUDIO_ERROR("Not found SettingType::AUDIO_SETTING keys, set noise resduction status failed."); } bool AudioMethod::getLoopbackStatus() const { if (auto key = m_keys.find(SettingType::AUDIO_SETTING); key != m_keys.end()) { auto status = g_variant_get_boolean(key->second->getValue(LOOPBACK_KEY)); AUDIO_DEBUG("Get loopback status, status: {}.", status); return status; } AUDIO_ERROR("Not found SettingType::AUDIO_SETTING keys, get loopback status failed."); return false; } void AudioMethod::setLoopbackStatus(bool status) { AUDIO_DEBUG("Set loopback status to {}.", status); if (auto key = m_keys.find(SettingType::AUDIO_SETTING); key != m_keys.end()) key->second->setValue(LOOPBACK_KEY, g_variant_new("b", status)); else AUDIO_ERROR("Not found SettingType::AUDIO_SETTING keys, set loopback status failed."); } std::string AudioMethod::getSoundThemeName() const { if (auto key = m_keys.find(SettingType::AUDIO_SETTING); key != m_keys.end()) { gsize length; auto name = g_variant_get_string(key->second->getValue(THEME_NAME_KEY), &length); AUDIO_DEBUG("Get sound theme name, name: {}.", name); return name; } AUDIO_ERROR("Not found SettingType::AUDIO_SETTING keys, get theme name failed."); return {}; } void AudioMethod::setSoundThemeName(const std::string& name) { AUDIO_DEBUG("Set sound theme name to {}.", name); if (auto key = m_keys.find(SettingType::AUDIO_SETTING); key != m_keys.end()) key->second->setValue(THEME_NAME_KEY, g_variant_new("s", name.c_str())); else AUDIO_ERROR("Not found SettingType::AUDIO_SETTING keys, set theme name failed."); } bool AudioMethod::getCustomThemeStatus() const { if (auto key = m_keys.find(SettingType::AUDIO_SETTING); key != m_keys.end()) { auto status = g_variant_get_boolean(key->second->getValue(CUSTOM_THEME_KEY)); AUDIO_DEBUG("Get custom theme status, status: {}.", status); return status; } AUDIO_ERROR("Not found SettingType::AUDIO_SETTING keys, get custom theme status failed."); return {}; } void AudioMethod::setCustomThemeStatus(bool status) { AUDIO_DEBUG("Set custom theme status to {}.", status); if (auto key = m_keys.find(SettingType::AUDIO_SETTING); key != m_keys.end()) key->second->setValue(CUSTOM_THEME_KEY, g_variant_new("b", status)); else AUDIO_ERROR("Not found SettingType::AUDIO_SETTING keys, set custom theme status failed."); } std::string AudioMethod::getNotifyGeneralName() const { if (auto key = m_keys.find(SettingType::AUDIO_SETTING); key != m_keys.end()) { gsize length; auto name = g_variant_get_string(key->second->getValue(NOTIFY_GENERAL_KEY), &length); AUDIO_DEBUG("Get notify general name, name: {}.", name); return name ? name : ""; } AUDIO_ERROR("Not found SettingType::AUDIO_SETTING keys, get notify general failed."); return {}; } void AudioMethod::setNotifyGeneralName(const std::string& name) { AUDIO_DEBUG("Set notify geberal name to {}.", name); if (auto key = m_keys.find(SettingType::AUDIO_SETTING); key != m_keys.end()) key->second->setValue(NOTIFY_GENERAL_KEY, g_variant_new("s", name.c_str())); else AUDIO_ERROR("Not found SettingType::AUDIO_SETTING keys, set notify general failed."); } std::string AudioMethod::getVolumeChangedName() const { if (auto key = m_keys.find(SettingType::AUDIO_SETTING); key != m_keys.end()) { gsize length; auto name = g_variant_get_string(key->second->getValue(VOLUME_CHANGED_KEY), &length); AUDIO_DEBUG("Get volume changed name, name: {}.", name); return name ? name : ""; } AUDIO_ERROR("Not found SettingType::AUDIO_SETTING keys, get volume changed failed."); return {}; } void AudioMethod::setVolumeChangedName(const std::string& name) { AUDIO_DEBUG("Set volume changed name to {}.", name); if (auto key = m_keys.find(SettingType::AUDIO_SETTING); key != m_keys.end()) key->second->setValue(VOLUME_CHANGED_KEY, g_variant_new("s", name.c_str())); else AUDIO_ERROR("Not found SettingType::AUDIO_SETTING keys, set volume changed failed."); } bool AudioMethod::getAlertStatus() const { if (auto key = m_keys.find(SettingType::AUDIO_SETTING); key != m_keys.end()) { auto status = g_variant_get_boolean(key->second->getValue(EVENT_SOUND_KEY)); AUDIO_DEBUG("Get alert status, status: {}.", status); return status; } AUDIO_ERROR("Not found SettingType::AUDIO_SETTING keys, get alert status failed."); return false; } void AudioMethod::setAlertStatus(bool status) { AUDIO_DEBUG("Set alert status to {}.", status); if (auto key = m_keys.find(SettingType::AUDIO_SETTING); key != m_keys.end()) key->second->setValue(EVENT_SOUND_KEY, g_variant_new("b", status)); else AUDIO_ERROR("Not found SettingType::AUDIO_SETTING keys, set alert status failed."); } bool AudioMethod::getStartupStatus() const { if (auto key = m_keys.find(SettingType::SESSIONMANAGER_SETTING); key != m_keys.end()) { auto status = g_variant_get_boolean(key->second->getValue(STARTUP_MUSIC_KEY)); AUDIO_DEBUG("Get startup status, status: {}.", status); return status; } AUDIO_ERROR("Not found SettingType::SESSIONMANAGER_SETTING keys, get startup status failed."); return false; } void AudioMethod::setStartupStatus(bool status) { AUDIO_DEBUG("Set startup status to {}.", status); if (auto key = m_keys.find(SettingType::SESSIONMANAGER_SETTING); key != m_keys.end()) key->second->setValue(STARTUP_MUSIC_KEY, g_variant_new("b", status)); else AUDIO_ERROR("Not found SettingType::SESSIONMANAGER_SETTING keys, set startup status failed."); } bool AudioMethod::getPoweroffStatus() const { if (auto key = m_keys.find(SettingType::SESSIONMANAGER_SETTING); key != m_keys.end()) { auto status = g_variant_get_boolean(key->second->getValue(POWEROFF_MUSIC_KEY)); AUDIO_DEBUG("Get poweroff status, status: {}.", status); return status; } AUDIO_ERROR("Not found SettingType::SESSIONMANAGER_SETTING keys, get poweroff status failed."); return false; } void AudioMethod::setPoweroffStatus(bool status) { AUDIO_DEBUG("Set poweroff status to {}.", status); if (auto key = m_keys.find(SettingType::SESSIONMANAGER_SETTING); key != m_keys.end()) key->second->setValue(POWEROFF_MUSIC_KEY, g_variant_new("b", status)); else AUDIO_ERROR("Not found SettingType::SESSIONMANAGER_SETTING keys, set poweroff status failed."); } bool AudioMethod::getLogoutStatus() const { if (auto key = m_keys.find(SettingType::SESSIONMANAGER_SETTING); key != m_keys.end()) { auto status = g_variant_get_boolean(key->second->getValue(LOGOUT_MUSIC_KEY)); AUDIO_DEBUG("Get logout status, status: {}.", status); return status; } AUDIO_ERROR("Not found SettingType::SESSIONMANAGER_SETTING keys, get logout status failed."); return false; } void AudioMethod::setLogoutStatus(bool status) { AUDIO_DEBUG("Set logout status to {}.", status); if (auto key = m_keys.find(SettingType::SESSIONMANAGER_SETTING); key != m_keys.end()) key->second->setValue(LOGOUT_MUSIC_KEY, g_variant_new("b", status)); else AUDIO_ERROR("Not found SettingType::SESSIONMANAGER_SETTING keys, set logout status failed."); } bool AudioMethod::getWakeupStatus() const { if (auto key = m_keys.find(SettingType::SESSIONMANAGER_SETTING); key != m_keys.end()) { auto status = g_variant_get_boolean(key->second->getValue(WAKEUP_MUSIC_KEY)); AUDIO_DEBUG("Get wakeup status, status: {}.", status); return status; } AUDIO_ERROR("Not found SettingType::SESSIONMANAGER_SETTING keys, get wakeup status failed."); return false; } void AudioMethod::setWakeupStatus(bool status) { AUDIO_DEBUG("Set wakeup status to {}.", status); if (auto key = m_keys.find(SettingType::SESSIONMANAGER_SETTING); key != m_keys.end()) key->second->setValue(WAKEUP_MUSIC_KEY, g_variant_new("b", status)); else AUDIO_ERROR("Not found SettingType::SESSIONMANAGER_SETTING keys, set wakeup status failed."); } std::string AudioMethod::getGlobalThemeName() const { if (auto key = m_keys.find(SettingType::GLOBALTHEME_SETTING); key != m_keys.end()) { gsize length; auto name = g_variant_get_string(key->second->getValue(GLOBAL_THEME_NAME_KEY), &length); AUDIO_DEBUG("Get global theme name, name: {}.", name); return name; } AUDIO_ERROR("Not found SettingType::GLOBALTHEME_SETTING keys, get global theme name failed."); return {}; } void AudioMethod::setGlobalThemeName(const std::string& name) { AUDIO_DEBUG("Set global theme name to {}.", name); if (auto key = m_keys.find(SettingType::GLOBALTHEME_SETTING); key != m_keys.end()) key->second->setValue(GLOBAL_THEME_NAME_KEY, g_variant_new("s", name.c_str())); else AUDIO_ERROR("Not found SettingType::GLOBALTHEME_SETTING keys, set global theme name failed."); } std::list AudioMethod::getSoundThemeList() { auto list = SoundThemeModule::getInstance().getSoundThemeList(); std::string stl = "["; for (const auto& s : list) { stl += "("; stl += s.toString(); stl += "),"; } stl += "]"; AUDIO_DEBUG("Get sound theme list, theme list: {}.", stl); return list; } std::list AudioMethod::getSoundEffectFileList(const std::string& themeName) { auto list = SoundThemeModule::getInstance().getSoundEffectFileList(themeName); std::string sefl = "["; for (const auto& f : list) { sefl += "("; sefl += f.toString(); sefl += "),"; } sefl += "]"; AUDIO_DEBUG("Get {} theme sound effect file list, list: {}.", themeName, sefl); return list; } void AudioMethod::setEnabled(const std::string& card, const std::string& port, bool enabled) { AUDIO_DEBUG("Set card: {} port: {} enabled to {}.", card, port, enabled); DeviceManagerModule::getInstance().setEnabled(card, port, enabled); } bool AudioMethod::isEnabled(const std::string& card, const std::string& port) const { auto enable = m_pAudioManager->isEnabled(card, port); AUDIO_DEBUG("Get card: {} port: {} enable status, enable: {}.", card, port, enable); return enable; } bool AudioMethod::getAutoPauseStatus() const { auto status = MediaAutoPauseModule::getInstance().getAutoPauseStatus(); AUDIO_DEBUG("Get auto pause status, status: {}", status); return status; } void AudioMethod::setAutoPauseStatus(bool status) { AUDIO_DEBUG("Set auto pause status to {}.", status); MediaAutoPauseModule::getInstance().setAutoPauseStatus(status); } bool AudioMethod::getMultiAudioCombineStatus(const NodeType& type) const { auto status = MultiAudioCombineModule::getInstance().getMultiAudioCombineStatus(type); AUDIO_DEBUG("Get {} multi audio combine status, status: {}.", getNodeTypeName(type), status); return status; } void AudioMethod::setMultiAudioCombine(const NodeType& type, bool status, const std::vector& devList) { std::string deviceList = "["; for (const auto& d : deviceList) { deviceList += d; deviceList += ", "; } deviceList += "]"; AUDIO_DEBUG("Set multi audio combine status to {}, combine devices: {}", status, deviceList); MultiAudioCombineModule::getInstance().setMultiAudioCombine(type, status, devList); } void AudioMethod::volumeChanged(const NodeType& type, int index, int volume) { AUDIO_DEBUG("{} volume changed to {}.", getNodeTypeName(type), volume); m_dbus.sendSignal(AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "volumeChanged", g_variant_new("(iiv)", type, index, g_variant_new("i", volume))); } void AudioMethod::muteChanged(const NodeType& type, int index, bool mute) { AUDIO_DEBUG("{} type mute changed to {}", EnumToInt(type), mute); m_dbus.sendSignal(AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "muteChanged", g_variant_new("(iib)", type, index, mute)); } void AudioMethod::deviceChanged(const NodeType& type, const std::string& portName, const std::string& cardName, bool autoPause) { // 设备更改是可能需要初始化音量 std::thread([type, portName, cardName, this]() { initVolume(type, portName, cardName); }).join(); // 当默认输出设备发生更改时满足以下条件需要暂停播放 if (getAutoPauseStatus() && NodeType::OUTPUT == type && autoPause) MediaControler::getInstance().pauseAllPlayers(); // 默认设备更改是可能存在模块需要加载或卸载的操作 if (NodeType::INPUT == type) { auto status = getEchoCancelStatus(); auto defaultInput = getDefaultDevice(NodeType::INPUT, -1).name; auto isLoaded = m_pAudioManager->isLoaded(ModuleType::MODULE_ECHO_CANCEL); AUDIO_DEBUG("device changed getEchoCancelStatus:{} defaultInput:{} isLoaded:{} .", status, defaultInput, isLoaded); if (status) { if (isLoaded) { if (std::string::npos != defaultInput.find("bluez_input") || std::string::npos != defaultInput.find("bluez_source") || std::string::npos != defaultInput.find("input.usb") || std::string::npos != defaultInput.find(".monitor")) { AUDIO_DEBUG("The default input device is {} unload the module-echo-cancel.", defaultInput); unloadModule("module-echo-cancel"); } } else { if ((std::string::npos != defaultInput.find("alsa_input") && std::string::npos == defaultInput.find("input.usb"))) { auto it = m_settingsMap.find(NOISE_REDUCTION_KEY); if (it != m_settingsMap.end()) { const auto& [module, param] = it->second; loadModule(module, param); } } else { AUDIO_DEBUG("The default input device is {} and there is no need to " "load the module-echo-cancel module.", defaultInput); } } } auto loopbackStatus = getLoopbackStatus(); auto devInfo = getDefaultDevice(NodeType::INPUT, -1); const bool isNullorInternalDev = (std::string::npos != devInfo.name.find("auto_null")) || (std::string::npos != devInfo.activePortName.find("internal")) || (std::string::npos != devInfo.activePortLabel.find("Digital")); AUDIO_DEBUG("device changed loopbackStatus:{} isNullorInternalDev:{}.", loopbackStatus, isNullorInternalDev); if (loopbackStatus && m_pAudioManager->isLoaded(ModuleType::MODULE_LOOPBACK)) { AUDIO_DEBUG("device changed is loaded MODULE_LOOPBACK."); if (isNullorInternalDev) { unloadModule("module-loopback"); } } else if (loopbackStatus && !m_pAudioManager->isLoaded(ModuleType::MODULE_LOOPBACK)) { AUDIO_DEBUG("device changed unloaded MODULE_LOOPBACK."); if (!isNullorInternalDev) { auto it = m_settingsMap.find(LOOPBACK_KEY); if (it != m_settingsMap.end()) { const auto& [module, param] = it->second; loadModule(module, param); } } } } else if (NodeType::OUTPUT == type) { auto status = getMonoStatus(); auto defaultOutput = getDefaultDevice(NodeType::OUTPUT, -1).name; auto isLoaded = m_pAudioManager->isLoaded(ModuleType::MODULE_REMAP_SINK); if (status) { if (isLoaded) { if (std::string::npos != defaultOutput.find("auto_null")) { AUDIO_DEBUG("The default output device is {} unload the module-remap-sink.", defaultOutput); unloadModule("module-remap-sink"); } } else { AUDIO_DEBUG("The default output device is {} load the module-remap-sink.", defaultOutput); if (std::string::npos == defaultOutput.find("auto_null")) { auto it = m_settingsMap.find(MONO_AUDIO_KEY); if (it != m_settingsMap.end()) { const auto& [module, param] = it->second; loadModule(module, param); } } } } } AUDIO_DEBUG("{} device changed to card: {} port: {}.", getNodeTypeName(type), cardName, portName); m_dbus.sendSignal(AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "deviceChanged", g_variant_new("(iss)", type, portName.c_str(), cardName.c_str())); } void AudioMethod::deviceAdjust(const NodeType& type) { AUDIO_DEBUG("Device adjust."); m_dbus.sendSignal(AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "deviceAdjust", g_variant_new("(i)", type)); } void AudioMethod::portChanged(const NodeType& type, const std::string& name) { AUDIO_DEBUG("{} prot change to {}", getNodeTypeName(type), name); m_dbus.sendSignal(AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "portChanged", g_variant_new("(is)", type, name.c_str())); } void AudioMethod::cardRemoved(const std::string& name) { AUDIO_DEBUG("Card: {} removed", name); m_dbus.sendSignal(AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "cardRemoved", g_variant_new("(s)", name.c_str())); } void AudioMethod::settingsChanged(const char* key, GVariant* v) { gsize length; if (const auto& it = m_settingsMap.find(key); it != m_settingsMap.end()) { const auto& [module, param] = it->second; auto status = g_variant_get_boolean(v); if (status) { loadModule(module, param); } else { unloadModule(module); } } AUDIO_DEBUG("Setting key : {} .", key); if (0 == strcmp(key, GLOBAL_THEME_NAME_KEY)) { gsize length; auto name = g_variant_get_string(v, &length); if (strcmp(name, "custom")) { setSoundThemeName(name); } } else if (0 == strcmp(key, THEME_NAME_KEY)) { const std::string theme = g_variant_get_string(v, &length); AUDIO_DEBUG("Setting theme : {} .", theme); // 音效主题更改时需要更新音量更改和通知的音效 if (strcmp(theme.c_str(), "custom")) { setCustomThemeStatus(false); auto list = SoundThemeModule::getInstance().getSoundEffectFileList(theme); AUDIO_DEBUG("Setting theme list: {} .", list.size()); if (auto it = findByName(list, "audio-volume-change"); it != list.end()) { auto avcName = replaceThemeName(it->name, theme); setVolumeChangedName(avcName); } if (auto it = findByName(list, "notification-general"); it != list.end()) { auto ntfName = replaceThemeName(it->name, theme); setNotifyGeneralName(ntfName); } } auto globalName = getGlobalThemeName(); AUDIO_DEBUG("Setting globalName : {} .", globalName); // 切换音效主题时,将系统全局主题切换成自定义 if (g_variant_get_string(v, &length) != globalName) { setGlobalThemeName("custom"); } } else if (0 == strcmp(key, NOTIFY_GENERAL_KEY) || 0 == strcmp(key, VOLUME_CHANGED_KEY)) { gsize lg; const gchar* vName = g_variant_get_string(v, &lg); std::string themeName = getSoundThemeName(); auto it = m_defaultThemeMap.find(themeName); if (it != m_defaultThemeMap.end()) { const auto [avcThemeName, ngThemeName] = it->second; if ((0 == strcmp(key, NOTIFY_GENERAL_KEY) && 0 == strcmp(vName, ngThemeName.c_str())) || (0 == strcmp(key, VOLUME_CHANGED_KEY) && 0 == strcmp(vName, avcThemeName.c_str()))) { setCustomThemeStatus(false); } else { setCustomThemeStatus(true); setSoundThemeName("custom"); } } else { setCustomThemeStatus(true); setSoundThemeName("custom"); } } AUDIO_DEBUG("Setting : {} change to {}.", key, v); m_dbus.sendSignal(AUDIO_OBJECT_PATH, SETTINGS_INTERFACE, "changed", g_variant_new("(sv)", key, v)); } void AudioMethod::addStream(int idx, int value, const std::string& iconName, const std::string& descName) { AUDIO_DEBUG("Stream {} added, index: {}, value: {}, iconName: {}.", descName, idx, value, iconName); m_dbus.sendSignal(AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "addStream", g_variant_new("(iiss)", idx, value, iconName.c_str(), descName.c_str())); } void AudioMethod::removeStream(int idx) { AUDIO_DEBUG("Stream {} removed.", idx); m_dbus.sendSignal(AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "removeStream", g_variant_new("(i)", idx)); } void AudioMethod::addStream(int dir, int idx, int value, bool mute, const std::string& iconName, const std::string& descName) { AUDIO_DEBUG("Stream {} added, index: {}, value: {}, iconName: {}.", descName, idx, value, iconName); m_dbus.sendSignal(AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "addStream", g_variant_new("(iiibss)", dir, idx, value, mute, iconName.c_str(), descName.c_str())); } void AudioMethod::removeStream(int dire, int idx) { AUDIO_DEBUG("Stream {} removed.", idx); m_dbus.sendSignal(AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "removeStream", g_variant_new("(ii)", dire, idx)); } std::string AudioMethod::extractThemeName(const std::string& name) { size_t soundsPos = name.find("/sounds/"); if (soundsPos == std::string::npos) { return ""; } size_t startThemePos = soundsPos + 8; size_t endThemePos = name.find("/", startThemePos); if (endThemePos == std::string::npos) { endThemePos = name.length(); } return name.substr(startThemePos, endThemePos - startThemePos); } std::string AudioMethod::replaceThemeName(const std::string& name, const std::string& themeName) { if (themeName.empty()) { return name; } std::string currentThemeName = extractThemeName(name); if (currentThemeName != themeName) { size_t soundsPos = name.find("/sounds/"); if (soundsPos == std::string::npos) { return name; } size_t startThemePos = soundsPos + 8; size_t endThemePos = name.find("/", startThemePos); if (endThemePos == std::string::npos) { endThemePos = name.length(); } std::string prefix = name.substr(0, startThemePos); std::string suffix = name.substr(endThemePos); return prefix + themeName + suffix; } return name; } bool AudioMethod::isLoaded(const ModuleType& type) { return m_pAudioManager->isLoaded(type); } } ukui-volume-control/backend/ManagerFactory.h0000664000175000017500000000243115171074712020107 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 MANAGERFACTORY_H #define MANAGERFACTORY_H #include #include "AudioManager.h" namespace UkuiAudioFramwork { class ManagerFactory { public: ManagerFactory() = default; virtual ~ManagerFactory() = default; public: virtual std::shared_ptr createManager() = 0; private: ManagerFactory(const ManagerFactory&) = delete; ManagerFactory(ManagerFactory&&) = delete; void operator=(const ManagerFactory&) = delete; void operator=(ManagerFactory&&) = delete; }; } #endif // MANAGERFACTORY_H ukui-volume-control/backend/PipewireNodeListmodel.cpp0000664000175000017500000001702715171074712022016 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "PipewireNodeListmodel.h" #include namespace UkuiAudioFramwork { PipewireNodeListModel::PipewireNodeListModel(/*QObject *parent*/) // : QAbstractListModel(parent) {} PipewireNodeListModel::~PipewireNodeListModel() {} void PipewireNodeListModel::sortList() { // emit layoutAboutToBeChanged(); std::vector old = m_nodes; // std::sort(old.begin(), old.end(), [](QPipewireNode*a, QPipewireNode*b) { // return a->nodeName() > b->nodeName(); // }); m_nodes.clear(); for (PipewireNode* device : old) { if (device->driver() == nullptr) { m_nodes.push_back(device); for(PipewireNode* client : old) { if (device == client->driver()) { m_nodes.push_back(client); } } } } // emit layoutChanged(); } void PipewireNodeListModel::append(PipewireNode* node) { // qWarning() << "Adding new node " << node->name(); // beginInsertRows(QModelIndex(), m_nodes.size(), m_nodes.size()); // node->connect(node, &QPipewireNode::idChanged, this, [this, node]() {this->rowChanged(node, IDRole); }); // node->connect(node, &QPipewireNode::activeChanged, this, [this, node]() {this->rowChanged(node, ActiveRole);}); // node->connect(node, &QPipewireNode::rateChanged, this, [this, node]() {this->rowChanged(node, RateRole); }); // node->connect(node, &QPipewireNode::quantumChanged, this, [this, node]() {this->rowChanged(node, QuantumRole); }); // node->connect(node, &QPipewireNode::waitingChanged, this, [this, node]() {this->rowChanged(node, WaitRole); }); // node->connect(node, &QPipewireNode::busyChanged, this, [this, node]() {this->rowChanged(node, BusyRole); }); // node->connect(node, &QPipewireNode::nameChanged, this, [this, node]() {this->rowChanged(node, NameRole); }); // node->connect(node, &QPipewireNode::driverChanged, this, [this, node]() {this->rowChanged(node, DriverIDRole);}); // node->connect(node, &QPipewireNode::driverChanged, this, &QPipewireNodeListModel::sortList); m_nodes.push_back(node); // endInsertRows(); sortList(); } bool PipewireNodeListModel::removeOne(PipewireNode* node) { // qWarning() << "Removing node" << node->name(); std::cout << "Removing node" << node->name() << std::endl; // int index = m_nodes.indexOf(node); // if (index != -1) { // return removeAt(index); // } else { // return false; // } } bool PipewireNodeListModel::removeAt(int index) { if (index >= 0 && index < m_nodes.size()) { std::cout << "remove at : " << index << std::endl; // beginRemoveRows(QModelIndex(), index, index); // m_nodes.removeAt(index); // endRemoveRows(); return true; } else { return false; } } //int PipewireNodeListModel::rowCount(const QModelIndex &/*parent*/) const //{ // return m_nodes.size(); //} //QVariant QPipewireNodeListModel::data(const QModelIndex &index, int _role) const //{ // int role = NodeRoles(_role); // int i = index.row(); // QPipewireNode *node = m_nodes.at(i); // switch (role) { // case IndexRole: // return i; // case NodeRole: // case Qt::DisplayRole: // { // if (node) { // QVariant vNode; // vNode.setValue(node); // return vNode; // } else { // return QVariant(QVariant::Invalid); // } // } // case IDRole: // return node ? node->id() : QVariant("nullptr"); // case ActiveRole: // return node ? node->active() : false; // case RateRole: // return node ? node->rate() : QVariant("nullptr"); // case QuantumRole: // return node ? node->quantum() : QVariant("nullptr"); // case WaitRole: // return node ? node->waiting() : 0.0; // case BusyRole: // return node ? node->busy() : 0.0; // case NameRole: // return node ? node->name() : "[deleted]"; // case DriverIDRole: // if (node && node->driver()) { // return node->driver()->id(); // } else { // return -1; // } // default: // qWarning() << "UNDEFINED ROLE" << _role; // throw std::runtime_error("Undefined role"); // } //} //QVariant QPipewireNodeListModel::headerData(int section, Qt::Orientation orientation, int role) const //{ // switch (role) { // case IndexRole: // return "Index"; // case NodeRole: // case Qt::DisplayRole: // return "Node"; // case IDRole: // return QVariant("ID"); // case ActiveRole: // return QVariant("Active"); // case RateRole: // return QVariant("Rate"); // case QuantumRole: // return QVariant("Quantum"); // case WaitRole: // return QVariant("Wait"); // case BusyRole: // return QVariant("Busy"); // case NameRole: // return QVariant("Name"); // case DriverIDRole: // return QVariant("Driver"); // default: // qWarning() << "UNDEFINED ROLE" << role; // throw std::runtime_error("Undefined role"); // } //} //bool QPipewireNodeListModel::insertRows(int position, int count, const QModelIndex& parent) //{ // beginInsertRows(parent, position, position+count-1); // for(int i=0; i QPipewireNodeListModel::roleNames() const //{ // return { // { IndexRole, "index" }, // { NodeRole, "node" }, // { Qt::DisplayRole, "display"}, // { IDRole, "id" }, // { ActiveRole, "active" }, // { RateRole, "rate" }, // { QuantumRole, "quantum" }, // { WaitRole, "wait" }, // { BusyRole, "busy" }, // { NameRole, "name" }, // { DriverIDRole, "driverID" }, // }; //} //void QPipewireNodeListModel::rowChanged(QPipewireNode* node, int role) //{ // int index = m_nodes.indexOf(node); // QModelIndex topLeft = createIndex(index, 0); // QModelIndex bottomRight = createIndex(index, 0); // emit dataChanged(topLeft, bottomRight, {role}); //} //void QPipewireNodeListModel::move(int from, int to) //{ // if (from == to) return; // int n=1; // if (from > to) { // // Only move forwards - flip if backwards moving // int tfrom = from; // int tto = to; // from = tto; // to = tto+n; // n = tfrom-tto; // } // beginMoveRows(QModelIndex(), from, from+n-1, QModelIndex(), to+n); // for (int i=n-1; i>=0; i--) // m_nodes.move(from+i, to); // endMoveRows(); //} } ukui-volume-control/backend/MediaAutoPauseModule.h0000664000175000017500000000303215171074712021217 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 MEDIAAUTOPAUSEMODULE_H #define MEDIAAUTOPAUSEMODULE_H #include "RestoreVolumeJson.h" namespace UkuiAudioFramwork { class MediaAutoPauseModule { public: static MediaAutoPauseModule& getInstance(); void setJson(std::shared_ptr); bool getAutoPauseStatus(); void setAutoPauseStatus(bool); private: MediaAutoPauseModule() = default; MediaAutoPauseModule(const MediaAutoPauseModule&) = delete; MediaAutoPauseModule(MediaAutoPauseModule&&) = delete; MediaAutoPauseModule operator=(const MediaAutoPauseModule&) = delete; MediaAutoPauseModule operator=(MediaAutoPauseModule&&) = delete; virtual ~MediaAutoPauseModule() = default; private: std::shared_ptr m_pJson = nullptr; }; } #endif // MEDIAAUTOPAUSEMODULE_H ukui-volume-control/backend/PaSinkInputStream.h0000664000175000017500000001074615171074712020576 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 PASINKINPUTSTREAM_H #define PASINKINPUTSTREAM_H #include "IStream.h" #include "AudioContext.h" namespace UkuiAudioFramwork { class PaSinkInputStream : public IStream { public: PaSinkInputStream(pa_context* ctx, PaDataInfo& info) : IStream(ctx, info, StreamType::STREAM_TYPE_SINKINPUT) {} ~PaSinkInputStream() = default; public: void copy(const pa_sink_input_info&); std::string getMediaRole() const; std::string getAppIconName() const; std::string getProcessBinary() const; std::string getDriver() const; uint32_t getSink() const; virtual std::string toString() const override; virtual NodeType getNodeType() const override; virtual uint32_t getIndex() const override; virtual std::string getName() const override; virtual pa_proplist* getProplist() const override; virtual uint32_t getCard() const override; virtual void setVolume(int) override; virtual const pa_cvolume getVolume() const override; virtual void setBalance(double) override; virtual double getBalance() const override; virtual void setMute(bool) override; virtual bool getMute() const override; virtual DefaultDeviceInfo getDefaultDevice() const override; virtual void setDefaultDevice(const std::string&, const std::string&) override; virtual std::vector getDeviceList() const override; virtual std::list> getAvailablePortList() const override; virtual void volumeChanged(int) override; virtual void muteChanged(bool) override; virtual void portChanged(const std::string&) override; virtual void deviceChanged(bool = false) override; private: uint32_t index {PA_INVALID_INDEX}; /**< Index of the sink input */ std::string name {""}; /**< Name of the sink input */ uint32_t owner_module {PA_INVALID_INDEX}; /**< Index of the module this sink input belongs to, or PA_INVALID_INDEX when it does not belong to any module. */ uint32_t client {PA_INVALID_INDEX}; /**< Index of the client this sink input belongs to, or PA_INVALID_INDEX when it does not belong to any client. */ uint32_t sink {PA_INVALID_INDEX}; /**< Index of the connected sink */ pa_sample_spec sample_spec; /**< The sample specification of the sink input. */ pa_channel_map channel_map; /**< Channel map */ pa_cvolume volume; /**< The volume of this sink input. */ pa_usec_t buffer_usec; /**< Latency due to buffering in sink input, see pa_timing_info for details. */ pa_usec_t sink_usec; /**< Latency of the sink device, see pa_timing_info for details. */ std::string resample_method {""}; /**< The resampling method used by this sink input. */ std::string driver {""}; /**< Driver name */ int mute {0}; /**< Stream muted \since 0.9.7 */ pa_proplist* proplist; /**< Property list \since 0.9.11 */ int corked {0}; /**< Stream corked \since 1.0 */ int has_volume {0}; /**< Stream has volume. If not set, then the meaning of this struct's volume member is unspecified. \since 1.0 */ int volume_writable {0}; /**< The volume can be set. If not set, the volume can still change even though clients can't control the volume. \since 1.0 */ pa_format_info *format {nullptr}; /**< Stream format information. \since 1.0 */ std::string role {""}; std::string m_appIconName {""}; /**< stream icon name */ std::string m_processBinary {""}; /**< stream process binary */ }; } #endif // PASINKINPUTSTREAM_H ukui-volume-control/backend/utils.h0000664000175000017500000000205515171074712016347 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 . */ #pragma once #include #include #include namespace UkuiAudioFramwork { void print_dict(const struct spa_dict* dictionary); uint32_t spa_dict_get_u32(const spa_dict* props, const char* key); std::ostream& operator<< (std::ostream& out, const spa_pod& pod); } ukui-volume-control/backend/RestoreVolumeJson.cpp0000664000175000017500000001051615171074712021210 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "RestoreVolumeJson.h" #include "AudioContext.h" namespace UkuiAudioFramwork { template void updateField(nlohmann::json& jsonObj, const std::string& key, const T& value) { if (jsonObj[key] != value) { jsonObj[key] = value; } } template T getMemberValue(const RestoreVolumeInfo& info, T RestoreVolumeInfo::* memberPtr) { return info.*memberPtr; } bool RestoreVolumeJson::getStatus(const std::string& cardName, const std::string& portName, const std::string& key) const { auto& ports = m_json[cardName]["ports"]; auto portIt = ports.find(portName); if (portIt != ports.end()) { return (*portIt)[key]; } AUDIO_ERROR("Can not get the mute status of sound card: {} port: {}, the device does not exist", cardName, portName); return true; } void RestoreVolumeJson::insert(const RestoreVolumeInfo& info) { auto& ports = m_json[info.cardName]["ports"]; auto portIt = ports.find(info.portName); if (portIt == ports.end()) { ports[info.portName] = { {"name", info.portName}, {"volume", info.volume}, {"priority", info.priority}, {"balance", info.balance}, {"enable", info.enable}, {"mute", info.muted} }; } else { nlohmann::json& portJson = *portIt; for (const auto& [key, memberVar] : m_filedMap) { std::visit([&](auto&& ptr) { using T = std::decay_t; if constexpr (std::is_same_v) { updateField(portJson, key, info.*ptr); } else if constexpr (std::is_same_v || std::is_same_v) { updateField(portJson, key, info.*ptr); } }, memberVar); } } writeToJson(); } RestoreVolumeJson::RestoreVolumeJson() { m_fileName = getenv("HOME"); m_fileName += RESTORE_VOLUME_JSON; loadJson(); initJson(); } bool RestoreVolumeJson::isContain(const RestoreVolumeInfo& info) const { const auto& card = m_json.find(info.cardName); if (card != m_json.end()) { const auto& port = (*card)["ports"].find(info.portName); return port != (*card)["ports"].end(); } return false; } void RestoreVolumeJson::initJson() { auto autoPause = m_json.find("auto-pause"); if (autoPause == m_json.end()) { m_json["auto-pause"] = false; } writeToJson(); } //template //std::optional RestoreVolumeJson::getValue(const std::string& cardName, const std::string& portName, const std::string& key) const { // auto cardIt = m_json.find(cardName); // if (cardIt != m_json.end()) { // auto& cardJson = cardIt.second; // auto portsIt = cardJson.find("ports"); // if (portsIt != cardJson.end()) { // auto& portsJson = portsIt->second; // auto portIt = portsJson.find(portName); // if (portIt != portsJson.end()) { // auto& portJson = portIt->second; // if (portJson.contains(key)) { // return portJson[key].get(); // } // } // } // } // return std::nullopt; //} std::optional RestoreVolumeJson::getValue(const std::string& key) const { auto it = m_json.find(key); if (it != m_json.end()) { return it->get(); } return std::nullopt; } void RestoreVolumeJson::setValue(const std::string& key, bool status) { auto autoPause = m_json.find(key); if (autoPause != m_json.end()) { m_json[key] = status; } writeToJson(); } } ukui-volume-control/backend/SoundThemeModule.h0000664000175000017500000000457015171074712020434 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 SOUNDTHEME_H #define SOUNDTHEME_H #include #include #include #include #include #include #include #include #include #include namespace UkuiAudioFramwork { typedef struct SoundThemeInfo { std::string name; // 音效主题的名称 std::string description; // 音效主题的描述 std::string toString() const; } SoundThemeInfo; typedef struct SoundEffectFileInfo { std::string name; std::string description; std::string toString() const; } SoundEffectFileInfo; class SoundThemeModule { public: static SoundThemeModule& getInstance(); public: std::list getSoundThemeList(); std::list getSoundEffectFileList(const std::string&); private: std::string getIndexThemeName(const std::string&); void soundThemeInDir(GHashTable*, const std::string&); void setupThemeSelector(); std::vector getSoundThemeXmlFiles(const std::vector& soundDirs); std::vector getSoundListFromXml(const std::string& xmlFilePath); bool isExistDirPath(const std::string&); private: SoundThemeModule() = default; SoundThemeModule(const SoundThemeModule&) = delete; SoundThemeModule(SoundThemeModule&&) = delete; SoundThemeModule operator=(const SoundThemeModule&) = delete; SoundThemeModule operator=(SoundThemeModule&&) = delete; virtual ~SoundThemeModule() = default; private: std::list m_soundThemeList {}; }; } #endif // SOUNDTHEME_H ukui-volume-control/backend/SoundThemePlayerSetting.h0000664000175000017500000000334615171074712022001 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 SOUNDTHEMEPLAYERSETTING_H #define SOUNDTHEMEPLAYERSETTING_H #include #include #include "ISetting.h" namespace UkuiAudioFramwork { #define SOUND_THEME_PLAYER_SCHEMA "org.ukui.media.sound" #define SOUND_THEME_PLAYER_PATH "/org/ukui/sound/keybindings/" #define SOUND_FILENAME_KEY "filename" #define SOUND_NAME_KEY "name" class SoundThemePlayerSetting : public ISetting { public: SoundThemePlayerSetting(); ~SoundThemePlayerSetting(); public: void initConnect(); virtual void initSettings() override; virtual GVariant* getValue(const char*) const override; virtual void setValue(const char*, GVariant*) override; virtual bool isContainKeys(const char*) const override; private: static void onSettingsChanged(GSettings*, const gchar*); private: GSettings* m_pSettings = nullptr; GSettingsSchema* m_pSchema = nullptr; GSettingsSchemaSource* m_pSource = nullptr; }; } #endif // SOUNDTHEMEPLAYERSETTING_H ukui-volume-control/backend/PipewireManagerFactory.cpp0000664000175000017500000000224715171074712022154 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "PipewireManagerFactory.h" #include "PipewireManager.h" #include "PipewireManager1.h" namespace UkuiAudioFramwork { std::shared_ptr PipewireManagerFactory::createManager() { // return std::make_shared(5); std::shared_ptr manager = std::make_shared(nullptr, nullptr); std::dynamic_pointer_cast(manager)->round_trip(); return manager; } } ukui-volume-control/backend/PaSourceStream.cpp0000664000175000017500000004515215171074712020444 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "PaSourceStream.h" #include #include "AudioMethod.h" #include "PaSourceOutputStream.h" namespace UkuiAudioFramwork { std::shared_ptr PaSourceStream::getActivePort() const { return active_port; } std::string PaSourceStream::getActivePortName() const { return (this && active_port) ? active_port->name : ""; } std::string PaSourceStream::getActivePortDescription() const { return (this && active_port) ? active_port->description : ""; } std::string PaSourceStream::getCardName() const { return cardName; } std::string PaSourceStream::getDeviceClass() const { return m_deviceClass; } void PaSourceStream::setMasterDevice(const std::string& master) { AUDIO_DEBUG("Set {} master device to {}.", name, master); m_masterDevice = master; } std::string PaSourceStream::getMasterDevice() const { AUDIO_DEBUG("Get {} master device: {}.", name, m_masterDevice); return m_masterDevice; } void PaSourceStream::copy(const pa_source_info& other) { auto it = std::find_if(m_info.cardInfoList.begin(), m_info.cardInfoList.end(), [other](const std::shared_ptr c) { return c->card.index == other.card; }); if (it != m_info.cardInfoList.end()) { cardName = (*it)->card.name; } else { cardName = ""; } if (PA_INVALID_INDEX != index) { if (!pa_cvolume_equal(&volume, &other.volume) && index == other.index) { volumeChanged(paCvolumeToValue(other.volume)); } if (mute != other.mute && index == other.index) { muteChanged(!mute); } if (active_port && other.active_port && g_strcmp0(active_port->name.c_str(), other.active_port->name)) { portChanged(other.active_port->name); } } name = other.name; index = other.index; description = other.description; sample_spec = other.sample_spec; channel_map = other.channel_map; owner_module = other.owner_module; volume = other.volume; mute = other.mute; monitor_of_sink = other.monitor_of_sink; monitor_of_sink_name = other.monitor_of_sink_name ? other.monitor_of_sink_name : ""; latency = other.latency; driver = other.driver; flags = other.flags; proplist = other.proplist; configured_latency = other.configured_latency; base_volume = other.base_volume; state = other.state; n_volume_steps = other.n_volume_steps; card = other.card; n_ports = other.n_ports; n_formats = other.n_formats; formats = other.formats; //start tmpMute = mute; //end AUDIO_DEBUG("copy this.mute: {} other.mute: {}.", this->mute, other.mute); auto deviceClass = pa_proplist_gets(proplist, PA_PROP_DEVICE_CLASS); m_deviceClass = deviceClass ? deviceClass : ""; if (std::string::npos != name.find("noiseReduceSource")) { auto it = std::find_if(m_info.sourceOutputList.begin(), m_info.sourceOutputList.end(), [](const std::shared_ptr& s) { auto driver = std::dynamic_pointer_cast(s)->getDriver(); return driver == "module-echo-cancel.c"; }); if (it != m_info.sourceOutputList.end()) { auto sourceIdx = std::dynamic_pointer_cast(*it)->getSource(); for (const auto& c : m_info.cardInfoList) { auto source = std::find_if(c->sources.begin(), c->sources.end(), [sourceIdx](const std::shared_ptr& s) { return sourceIdx == s->getIndex(); }); if (source != c->sources.end()) { setMasterDevice((*source)->getName()); //start synchronous masterdevice mute status setMute((*source)->getMute()); //end } } } } else { auto masterDevice = pa_proplist_gets(proplist, PA_PROP_DEVICE_MASTER_DEVICE); m_masterDevice = masterDevice ? masterDevice : ""; } ports.clear(); for (int i = 0; i < n_ports; ++i) { ports.push_back(std::make_shared(cardName, *other.ports[i])); } active_port = other.active_port ? std::make_shared(cardName, *other.active_port) : nullptr; } std::vector> PaSourceStream::getPorts() const { return ports; } std::string PaSourceStream::toString() const { std::string vol = "["; for (int i = 0; i < volume.channels; ++i) { vol += std::to_string(volume.values[i]); vol += ", "; } vol += "]"; std::string activePortName = (active_port) ? active_port->name : ""; return FORMAT("name={}, index={}, description={}, card={}, n_ports={}, active_port={}, " "volume={}, mute={}, state={}, monitor_of_sink={}, monitor_of_sink_name={}", name, index, description, card, n_ports, activePortName, vol, mute, state, monitor_of_sink, monitor_of_sink_name); } NodeType PaSourceStream::getNodeType() const { return NodeType::INPUT; } uint32_t PaSourceStream::getIndex() const { return index; } std::string PaSourceStream::getName() const { return name; } pa_proplist* PaSourceStream::getProplist() const { return proplist; } uint32_t PaSourceStream::getCard() const { return card; } void PaSourceStream::setVolume(int value) { auto v = volume; for (int i = 0; i < v.channels; ++i) { v.values[i] = value; } if (pa_cvolume_equal(&v, &volume)) return; pa_context_set_source_volume_by_index(m_pContext, index, &v, nullptr, nullptr); } const pa_cvolume PaSourceStream::getVolume() const { return volume; } void PaSourceStream::setBalance(double v) { if (channel_map.channels == 1) { AUDIO_ERROR("source {} is not support set balance.", name); return; } pa_cvolume_set_balance(&volume, &channel_map, v); pa_context_set_sink_volume_by_index(m_pContext, index, &volume, nullptr, nullptr); } double PaSourceStream::getBalance() const { return pa_cvolume_get_balance(&volume, &channel_map); } void PaSourceStream::setMute(bool mute) { AUDIO_DEBUG("setMute: mute: {} this.mute:{} tmpMute:{} .", mute, this->mute, tmpMute); //start hand asynchronous time issues tmpMute = mute; //end //start synchronous masterdevice mute status if (std::string::npos != name.find("noiseReduceSource")) { auto it = std::find_if(m_info.sourceOutputList.begin(), m_info.sourceOutputList.end(), [](const std::shared_ptr& s) { auto driver = std::dynamic_pointer_cast(s)->getDriver(); return driver == "module-echo-cancel.c"; }); if (it != m_info.sourceOutputList.end()) { auto sourceIdx = std::dynamic_pointer_cast(*it)->getSource(); for (const auto& c : m_info.cardInfoList) { auto source = std::find_if(c->sources.begin(), c->sources.end(), [sourceIdx](const std::shared_ptr& s) { return sourceIdx == s->getIndex(); }); if (source != c->sources.end()) { AUDIO_DEBUG("setMute getName:{} .", (*source)->getName()); (*source)->setMute(mute); } } } } //end pa_context_set_source_mute_by_index(m_pContext, index, mute, nullptr, nullptr); } bool PaSourceStream::getMute() const { //start hand asynchronous time issues return tmpMute; //end // return mute; } DefaultDeviceInfo PaSourceStream::getDefaultDevice() const { DefaultDeviceInfo info; info.idx = m_info.defaultSource->index; info.name = m_info.defaultSource->name; if ("filter" == m_info.defaultSource->getDeviceClass()) { std::string masterDevice = ""; for (const auto& c : m_info.cardInfoList) { auto it = std::find_if(c->sources.begin(), c->sources.end(), [this](const std::shared_ptr& s) { return m_info.defaultSource->getName() == s->getName(); }); if (it != c->sources.end()) { auto source = std::dynamic_pointer_cast(*it); masterDevice = source->getMasterDevice(); AUDIO_DEBUG("Get {} master device: {}.", m_info.defaultSource->getName(), masterDevice); break; } } for (const auto& c : m_info.cardInfoList) { auto it = std::find_if(c->sources.begin(), c->sources.end(), [masterDevice](const std::shared_ptr& s) { return masterDevice == s->getName(); }); if (it != c->sources.end()) { info.cardName = c->card.name; auto sourceStream = std::dynamic_pointer_cast(*it); if (sourceStream) { info.activePortName = sourceStream->getActivePortName(); info.activePortLabel = sourceStream->getActivePortDescription(); } break; } } } else { info.cardName = m_info.defaultSource->cardName; info.activePortName = m_info.defaultSource->active_port ? m_info.defaultSource->active_port->name : ""; info.activePortLabel = m_info.defaultSource->active_port ? m_info.defaultSource->active_port->description : ""; } return info; } void PaSourceStream::setDefaultDevice(const std::string& name, const std::string& dev) { bool found = false; bool setPort = false; bool setProfile = false; std::string profileName = ""; std::string sourceName = ""; uint32_t sourceIdx = PA_INVALID_INDEX, cardIdx = PA_INVALID_INDEX; for (const auto& c : m_info.cardInfoList) { if (0 == strcmp(dev.c_str(), c->card.name.c_str())) { cardIdx = c->card.index; for (const auto& p : c->card.ports) { if (PA_DIRECTION_OUTPUT == p->direction) continue; if (strcmp(name.c_str(), p->name.c_str())) continue; if (m_info.defaultSource->card == cardIdx && contains(m_info, [name](PaDataInfo& data) -> std::vector> { if (data.defaultSource) { return data.defaultSource->ports; } return {}; }, name)) { setPort = true; sourceIdx = m_info.defaultSource->getIndex(); goto success; } else { if (contains(m_info, [&p] (PaDataInfo& data) -> std::vector> { return p->profiles2; }, c->card.active_profile->name)) { for (const auto& s : c->sources) { for (const auto &p : std::dynamic_pointer_cast(s)->ports) { if (p->name == name) { sourceName = s->getName(); goto success; } } } } else { int pri = 0; for (const auto& profile : p->profiles2) { if (0 == profile->available) continue; if (pri < profile->priority) { setProfile = true; pri = profile->priority; profileName = profile->name; } } } } } } } if (!found) AUDIO_DEBUG("Card: {} port: {} not found.", dev, name); success: if (setProfile) { std::thread([cardIdx, profileName, this] () { std::unique_lock lock(m_info.m_mtx); m_info.m_operation = false; pa_context_set_card_profile_by_index(m_pContext, cardIdx, profileName.c_str(), nullptr, this); }).join(); // 设置default source线程,等待主线程cardInfoList更新完毕之后在去设置default source std::thread([cardIdx, name, this] () { std::unique_lock lock(m_info.m_mtx); if (!m_info.m_operation) m_info.m_cond.wait_for(lock, std::chrono::milliseconds(3000)); if (m_info.m_operation) { m_info.m_operation = false; for (const auto& c: m_info.cardInfoList) { if (c->card.index == cardIdx) { for (const auto& s : c->sources) { for (const auto& p : std::dynamic_pointer_cast(s)->ports) { if (p->name == name) { AUDIO_DEBUG("Set deault source to {} port: {}.", s->getName(), name); pa_context_set_source_port_by_name(m_pContext, s->getName().c_str(), name.c_str(), nullptr, nullptr); pa_context_set_default_source(m_pContext, s->getName().c_str(), nullptr, nullptr); return; } } } } } } }).detach(); } if (setPort) pa_context_set_source_port_by_index(m_pContext, sourceIdx, name.c_str(), nullptr, nullptr); if ("" != sourceName) { AUDIO_DEBUG("Set default source to {}.", sourceName); pa_context_set_default_source(m_pContext, sourceName.c_str(), nullptr, nullptr); } } std::vector PaSourceStream::getDeviceList() const { } std::list> PaSourceStream::getAvailablePortList() const { std::list> info; for (const auto& c : m_info.cardInfoList) { for (const auto& d : c->card.deviceList) { if (PA_DIRECTION_INPUT == d->direction && PA_PORT_AVAILABLE_NO != d->available) { info.push_back(d); } } } return info; } void PaSourceStream::volumeChanged(int volume) { AudioMethod::getInstance().volumeChanged(NodeType::INPUT, index, volume); } void PaSourceStream::muteChanged(bool mute) { AudioMethod::getInstance().muteChanged(NodeType::INPUT, index, mute); } void PaSourceStream::portChanged(const std::string& name) { AudioMethod::getInstance().portChanged(NodeType::INPUT, name); } void PaSourceStream::deviceChanged(bool autoPause) { std::string portName, deviceName; if ("filter" == m_deviceClass) { for (const auto& c : m_info.cardInfoList) { auto it = std::find_if(c->sources.begin(), c->sources.end(), [this](const std::shared_ptr& s) { return m_masterDevice == s->getName(); }); if (it != c->sources.end()) { deviceName = c->card.name; auto sourceStream = std::dynamic_pointer_cast(*it); if (sourceStream) { portName = sourceStream->getActivePortName(); } break; } } } else { portName = active_port ? active_port->name : ""; deviceName = cardName; if (AudioMethod::getInstance().isLoaded(ModuleType::MODULE_ECHO_CANCEL)) { auto it = std::find_if(m_info.sourceOutputList.begin(), m_info.sourceOutputList.end(), [](const std::shared_ptr& s) { auto driver = std::dynamic_pointer_cast(s)->getDriver(); return driver == "module-echo-cancel.c"; }); if (it != m_info.sourceOutputList.end() && deviceName.find("alsa_card.usb") == std::string::npos && deviceName.find("bluez_card") == std::string::npos) { AUDIO_DEBUG("prepare set {} default device, card: {}, port: {}.", (*it)->getName(), deviceName, portName); (*it)->setDefaultDevice(portName, deviceName); // 更新noiseReduceSource的master device // std::unique_lock lock(m_info.m_mtx); for (const auto& c : m_info.cardInfoList) { auto source = std::find_if(c->sources.begin(), c->sources.end(), [](const std::shared_ptr& s) { return std::string::npos != s->getName().find("noiseReduceSource"); }); if (source != c->sources.end()) { auto masterDevice = m_info.defaultSource->getName(); // 同步default source的音量和静音状态 AUDIO_DEBUG("noiseReduceSource masterDevice defaultSource: {} getMute: {}, source getMute: {}." , masterDevice, m_info.defaultSource->getMute(), (*source)->getMute()); (*source)->setMute(m_info.defaultSource->getMute()); auto volume = m_info.defaultSource->getVolume(); auto value = paCvolumeToPaValue(volume); AUDIO_DEBUG("noiseReduceSource value:{} .", value); (*source)->setVolume(value); std::dynamic_pointer_cast(*source)->setMasterDevice(masterDevice); break; } } pa_context_set_default_source(m_pContext, "noiseReduceSource", nullptr, nullptr); } } } AudioMethod::getInstance().deviceChanged(NodeType::INPUT, portName, deviceName); } } ukui-volume-control/backend/AudioSetting.cpp0000664000175000017500000000740715171074712020147 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "AudioSetting.h" #include "AudioContext.h" #include "AudioMethod.h" namespace UkuiAudioFramwork { AudioSetting::AudioSetting() { initSettings(); initConnect(); } AudioSetting::~AudioSetting() { if (m_pSchema) g_settings_schema_unref(m_pSchema); if (m_pSource) g_settings_schema_source_unref(m_pSource); if (m_pSettings) g_object_unref(m_pSettings); } void AudioSetting::initSettings() { m_pSource = g_settings_schema_source_get_default(); m_pSchema = g_settings_schema_source_lookup(m_pSource, UKUI_SOUND_SCHEMA, true); if (!m_pSchema) { AUDIO_ERROR("schema error, failed to create {} settings", UKUI_SOUND_SCHEMA); return; } m_pSettings = g_settings_new_with_path(UKUI_SOUND_SCHEMA, UKUI_SOUND_PATH); } void AudioSetting::onSettingsChanged(GSettings* settings, const gchar* key) { AudioMethod::getInstance().settingsChanged(key, g_settings_get_value(settings, key)); } void AudioSetting::initConnect() { g_signal_connect(G_OBJECT(m_pSettings), "changed", G_CALLBACK(onSettingsChanged), nullptr); } GVariant* AudioSetting::getValue(const char* key) const { if (!m_pSettings || !m_pSchema) return g_variant_new(""); if (!isContainKeys(key)) return g_variant_new(""); return g_settings_get_value(m_pSettings, key); } void AudioSetting::setValue(const char* key, GVariant* gvalue) { if (!m_pSettings || !m_pSchema) return; if (!isContainKeys(key)) return; GVariantClass variantClass = g_variant_classify(gvalue); if (G_VARIANT_CLASS_STRING == variantClass) { gsize length; g_settings_set_string(m_pSettings, key, g_variant_get_string(gvalue, &length)); } else if (G_VARIANT_CLASS_INT16 == variantClass) { g_settings_set_int(m_pSettings, key, g_variant_get_int16(gvalue)); } else if (G_VARIANT_CLASS_UINT16 == variantClass) { g_settings_set_int(m_pSettings, key, g_variant_get_uint16(gvalue)); } else if (G_VARIANT_CLASS_INT32 == variantClass) { g_settings_set_int(m_pSettings, key, g_variant_get_int32(gvalue)); } else if (G_VARIANT_CLASS_UINT32 == variantClass) { g_settings_set_int(m_pSettings, key, g_variant_get_uint32(gvalue)); } else if (G_VARIANT_CLASS_INT64 == variantClass) { g_settings_set_int(m_pSettings, key, g_variant_get_int64(gvalue)); } else if (G_VARIANT_CLASS_UINT64 == variantClass) { g_settings_set_int(m_pSettings, key, g_variant_get_uint64(gvalue)); } else if (G_VARIANT_CLASS_BOOLEAN == variantClass) { g_settings_set_boolean(m_pSettings, key, g_variant_get_boolean(gvalue)); } else { AUDIO_DEBUG("Unknown variantClass"); } } bool AudioSetting::isContainKeys(const char* key) const { bool ret = false; if (!m_pSettings || !m_pSchema) return ret; gchar** keys = g_settings_schema_list_keys(m_pSchema); if (g_strv_contains(keys, key)) ret = true; g_strfreev(keys); return ret; } } ukui-volume-control/backend/AudioMethod.h0000664000175000017500000004460715171074712017422 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 AUDIOMETHOD_H #define AUDIOMETHOD_H #include #include #include #include "BaseType.h" #include "ISetting.h" #include "AudioSetting.h" #include "GlobalThemeSettings.h" #include "MediaAutoPauseModule.h" #include "SoundThemeModule.h" #include "SoundThemePlayerSetting.h" #include "SessionManagerSetting.h" #include "AudioManager.h" #include "DbusServer.h" #include "MultiAudioCombineJson.h" namespace UkuiAudioFramwork { class AudioDBusServer; class AudioMethod { public: static AudioMethod& getInstance(); void setManager(std::shared_ptr); void init(); void run(); /** * @brief 获取当前桌面环境版本信息 * * @return 返回当前桌面环境版本信息 */ DesktopEnvironmentVersion getDesktopEnvironmentVersion(); /** * @brief 获取音频流的描述 * * @param name 对应的desktop名称 * @return 音频流的描述 */ std::string getStreamDescription(const std::string&); /** * @brief 初始化音量 * * @param type 设备类型 * portName 端口名称 * cardName 声卡名称 * @return 无 */ void initVolume(NodeType, const std::string&, const std::string&); // Interface /** * @brief 加载模块 * * @param name 模块名称,例module-echo-cancel * param 模块参数 * @return 无 */ void loadModule(const std::string&, const std::string&); /** * @brief 卸载模块 * * @param name 模块名称,例module-echo-cancel * @return 无 */ void unloadModule(const std::string&); // Method /** * @brief 获取音频后端名称 * * @return 返回后端名 */ std::string getBackend() const; /** * @brief 设置音频后端 * * @param type 后端类型 * @return 无 */ void setBackend(const BackendType&); /** * @brief 设置音量 * * @param type 设置音量的类型,类型包括INPUT, OUTPUT, SINK_INPUT, SOURCE_OUTPUT * name 对应的名称(如果是设置INPUT和OUTPUT音量,该参数为需要获取端口的名称,例如analog-output-speaker; * 如果是设置SINK_INPUT和SOURCE_OUTPUT的音量,则将该参数设置成当前应用的名称) * dev 设备名称(如果是设置INPUT和OUTPUT音量,该参数为对应端口的声卡名称; * 如果是设置SINK_INPUT和SOURCE_OUTPUT的音量,则该值可设置为任意值) * volume 设置的音量值 * @return 无 */ void setDetailVolume(const NodeType&, const std::string&, const std::string&, int); /** * @brief 获取音量 * * @param type 获取音量的类型,类型包括INPUT, OUTPUT, SINK_INPUT, SOURCE_OUTPUT * name 对应的名称(如果是获取INPUT和OUTPUT音量,该参数为需要获取端口的名称,例如analog-output-speaker; * 如果是获取SINK_INPUT和SOURCE_OUTPUT的音量,则将该参数设置成当前应用的名称) * dev 设备名称(如果是获取INPUT和OUTPUT音量,该参数为对应端口的声卡名称; * 如果是获取SINK_INPUT和SOURCE_OUTPUT的音量,则该值可设置为任意值) * volume 设置的音量值 * @return 无 */ int getDetailVolume(const NodeType&, const std::string&, const std::string&) const; /** * @brief 设置音量 * * @param type 设置音量的类型,类型包括INPUT, OUTPUT, SINK_INPUT, SOURCE_OUTPUT * index 音频流的索引(如果是设置INPUT和OUTPUT音量,则该值可设置为任意值; * 如果是设置SINK_INPUT和SOURCE_OUTPUT的音量,则该值可设置为应用的索引) * volume 设置的音量值 * @return 无 */ void setVolume(const NodeType&, int, int); /** * @brief 获取音量 * * @param type 获取音量的类型,类型包括INPUT, OUTPUT, SINK_INPUT, SOURCE_OUTPUT * index 音频流的索引(如果是设置INPUT和OUTPUT音量,则该值可设置为任意值; * 如果是获取SINK_INPUT和SOURCE_OUTPUT的音量,则将该参数设置成应用的索引) * @return 返回获取的音量 */ int getVolume(const NodeType&, int = -1) const; /** * @brief 设置平衡音量 * * @param type 设置音量的类型,类型包括INPUT, OUTPUT, SINK_INPUT, SOURCE_OUTPUT * volume 设置的音量值 * @return 无 */ void setBalance(const NodeType&, double); /** * @brief 获取平衡音量 * * @param type 获取音量的类型,类型包括INPUT, OUTPUT, SINK_INPUT, SOURCE_OUTPUT * @return 返回平衡音量 */ double getBalance(const NodeType&) const; /** * @brief 设置静音状态 * * @param type 设置静音状态的类型,类型包括INPUT, OUTPUT, SINK_INPUT, SOURCE_OUTPUT * name 对应的名称(如果是设置INPUT和OUTPUT的静音状态,该参数可为任意值; * 如果是设置SINK_INPUT和SOURCE_OUTPUT的静音状态,则将该参数设置成当前应用的名称) * dev 设备名称(如果是设置INPUT和OUTPUT的静音状态,该参数为对应端口的声卡名称; * 如果是设置SINK_INPUT和SOURCE_OUTPUT的静音状态,则该值可设置为任意值) * mute 设置的静音状态 * @return 无 */ void setDetailMute(const NodeType&, const std::string&, const std::string&, bool); /** * @brief 获取静音状态 * * @param type 获取静音的类型,类型包括INPUT, OUTPUT, SINK_INPUT, SOURCE_OUTPUT * name 对应的名称(如果是获取INPUT和OUTPUT的静音状态,该参数为需要获取端口的名称,例如analog-output-speaker; * 如果是获取SINK_INPUT和SOURCE_OUTPUT的静音状态,则将该参数设置成当前应用的名称) * dev 设备名称(如果是获取INPUT和OUTPUT的静音状态,该参数为对应端口的声卡名称; * 如果是获取SINK_INPUT和SOURCE_OUTPUT的静音状态,则该值可设置为任意值) * @return 返回对应的静音状态 */ bool getDetailMute(const NodeType&, const std::string&, const std::string&) const; /** * @brief 设置默认设备或应用的静音状态 * * @param type 设置静音状态的类型,类型包括INPUT, OUTPUT, SINK_INPUT, SOURCE_OUTPUT * name 音频流的索引(如果是设置INPUT和OUTPUT音量,则该值可设置为任意值; * 如果是设置SINK_INPUT和SOURCE_OUTPUT的静音状态,则将该参数设置成当前应用的索引) * mute 设置的静音状态 * @return 无 */ void setMute(const NodeType&, int, bool); /** * @brief 获取静音状态 * * @param type 获取静音的类型,类型包括INPUT, OUTPUT, SINK_INPUT, SOURCE_OUTPUT * index 音频流的索引(如果是设置INPUT和OUTPUT音量,则该值可设置为任意值; * 如果是获取SINK_INPUT和SOURCE_OUTPUT的静音状态,则将该参数设置成当前应用的索引) * @return 返回对应的静音状态 */ bool getMute(const NodeType&, int = -1) const; /** * @brief 获取默认的设备 * * @param type 获取设备的类型,类型包括INPUT, OUTPUT, SINK_INPUT, SOURCE_OUTPUT * index 音频流的索引(如果是设置INPUT和OUTPUT的默认设备,则该值可设置成-1 * 如果是设置SINK_INPUT和SOURCE_OUTPUT的默认设备,则该值可设置为应用的索引) * @return 返回获取的默认设备名称 */ DefaultDeviceInfo getDefaultDevice(const NodeType&, int) const; /** * @brief 设置默认的设备 * * @param type 设置默认设备的类型,类型包括INPUT, OUTPUT, SINK_INPUT, SOURCE_OUTPUT * idx 音频流的索引(如果是设置INPUT和OUTPUT的默认设备,则该值可设置成-1 * 如果是设置SINK_INPUT和SOURCE_OUTPUT的默认设备,则该值可设置为应用的索引) * name 端口名称(该参数为需要设置端口的名称,例如analog-output-speaker) * dev 声卡名称(该参数为对应端口的声卡名称) * @return 无 */ void setDefaultDevice(const NodeType&, int idx, const std::string&, const std::string&); std::vector getDeviceList(const NodeType&) const; std::list getSinkList() const; std::list getSourceList() const; std::list> getAvailablePortList(const NodeType&) const; std::list getSinkInputList() const; std::list getSourceOutputList() const; std::list getStreamMediaList() const; bool isValidDevice(const NodeType&) const; // settings method /** * @brief 获取音量增强状态 * * @return 返回音量增强的开关状态 */ bool getVolumeBoostStatus() const; /** * @brief 设置音量增强的状态 * * @param status 设置的状态 * * @return 无 */ void setVolumeBoostStatus(bool); /** * @brief 获取开启音量增强状态下的最大音量值 * * @return 返回开启音量增强状态的音量值 */ int getVolumeBoostValue() const; /** * @brief 设置音量增强状态下的最大音量值 * * @param value 设置的最大音量 * * @return 无 */ void setVolumeBoostValue(int); /** * @brief 获取单声道音频功能的开启状态 * * @return 返回单声道音频的开启状态 */ bool getMonoStatus() const; /** * @brief 设置单声道音频功能的开启状态 * * @param status 设置的状态 * * @return 无 */ void setMonoStatus(bool); /** * @brief 获取智能降噪功能的开启状态 * * @return 返回智能降噪功能的开启状态 */ bool getEchoCancelStatus() const; /** * @brief 设置智能降噪功能的开启状态 * * @param status 设置的状态 * * @return 无 */ void setEchoCancelStatus(bool); /** * @brief 获取侦听功能的开启状态 * * @return 返回侦听功能的开启状态 */ bool getLoopbackStatus() const; /** * @brief 设置侦听功能的开启状态 * * @param status 设置的状态 * * @return 无 */ void setLoopbackStatus(bool); /** * @brief 获取当前音效主题名称 * * @return 返回当前音效主题名称 */ std::string getSoundThemeName() const; /** * @brief 设置当前音效主题名称 * * @param 当前音效主题名称 * * @return 无 */ void setSoundThemeName(const std::string&); /** * @brief 获取自定义音效主题的状态 * * @return 返回自定义音效主题的状态 */ bool getCustomThemeStatus() const; /** * @brief 设置自定义音效主题的状态 * * @param 自定义音效主题的状态 * * @return 无 */ void setCustomThemeStatus(bool); /** * @brief 获取通知音效的名称 * * @return 返回通知音效的名称 */ std::string getNotifyGeneralName() const; /** * @brief 设置通知音效的名称 * * @param 通知音效的名称 * * @return 无 */ void setNotifyGeneralName(const std::string&); /** * @brief 获取音量调节的名称 * * @return 返回音量调节的名称 */ std::string getVolumeChangedName() const; /** * @brief 设置音量调节的名称 * * @param 音量调节的名称 * * @return 无 */ void setVolumeChangedName(const std::string&); /** * @brief 获取提示音功能的开启状态 * * @return 返回提示音功能的开启状态 */ bool getAlertStatus() const; /** * @brief 设置提示音功能的开启状态 * * @param status 设置的状态 * * @return 无 */ void setAlertStatus(bool); bool getStartupStatus() const; void setStartupStatus(bool); bool getPoweroffStatus() const; void setPoweroffStatus(bool); bool getLogoutStatus() const; void setLogoutStatus(bool); bool getWakeupStatus() const; void setWakeupStatus(bool); std::string getGlobalThemeName() const; void setGlobalThemeName(const std::string&); // sound theme method /** * @brief 获取系统音效主题列表 * * @return 返回音效主题列表 */ std::list getSoundThemeList(); /** * @brief 获取指定音效主题的音效文件列表 * * @return 返回音效文件列表 */ std::list getSoundEffectFileList(const std::string&); void setEnabled(const std::string&, const std::string&, bool); bool isEnabled(const std::string&, const std::string&) const; bool getAutoPauseStatus() const; void setAutoPauseStatus(bool); bool getMultiAudioCombineStatus(const NodeType&) const; void setMultiAudioCombine(const NodeType&, bool, const std::vector&); bool isLoaded(const ModuleType&); // signal void volumeChanged(const NodeType&, int, int); void muteChanged(const NodeType&, int, bool); void deviceChanged(const NodeType&, const std::string&, const std::string&, bool = false); void deviceAdjust(const NodeType&); void portChanged(const NodeType&, const std::string&); void cardRemoved(const std::string&); void settingsChanged(const char*, GVariant*); void addStream(int, int, const std::string&, const std::string&); void removeStream(int); void addStream(int, int, int, bool, const std::string&, const std::string&); void removeStream(int, int); private: void initJson(); SystemVersionInfo getSystemVersionInfo(); std::optional getFieldValue(const std::string&, const std::string_view&); DesktopEnvironmentVersion getTypeFromSystemVersionInfo(const SystemVersionInfo&) const; std::string extractThemeName(const std::string&); std::string replaceThemeName(const std::string&, const std::string&); template auto findByName(const Container& container, const std::string& name) { return std::find_if(container.begin(), container.end(), [&name](const SoundEffectFileInfo& info) { return info.name.find(name) != std::string::npos; }); } private: AudioMethod() = default; AudioMethod(const AudioMethod&) = delete; AudioMethod(AudioMethod&&) = delete; AudioMethod operator=(const AudioMethod&) = delete; AudioMethod operator=(AudioMethod&&) = delete; virtual ~AudioMethod() = default; private: AudioDBusServer m_dbus; std::shared_ptr m_pAudioManager {}; std::shared_ptr m_pRestoreVolumeJson {}; std::shared_ptr m_pMultiAudioJson {}; std::unordered_map> m_settingsMap = { {MONO_AUDIO_KEY, {"module-remap-sink", "sink_name=mono channels=1 channel_map=mono"}}, {NOISE_REDUCTION_KEY, {"module-echo-cancel", "use_master_format=1 aec_method=webrtc aec_args=analog_gain_control=0 source_name=noiseReduceSource"}}, {LOOPBACK_KEY, {"module-loopback", ""}} }; std::unordered_map> m_statusFunctions = { {MONO_AUDIO_KEY, [this]() { return getMonoStatus(); }}, {NOISE_REDUCTION_KEY, [this]() { return getEchoCancelStatus(); }}, {LOOPBACK_KEY, [this]() { return getLoopbackStatus(); }} }; std::unordered_map> m_keys = { {SettingType::AUDIO_SETTING, std::shared_ptr()}, {SettingType::GLOBALTHEME_SETTING, std::shared_ptr()}, {SettingType::SESSIONMANAGER_SETTING, std::shared_ptr()}, {SettingType::SOUDNTHEMEPLAYER_SETTING, std::shared_ptr()} }; std::unordered_map m_outputVolumeMap = { {DevicePortType::PORT_TYPE_BLUETOOTH, 17}, {DevicePortType::PORT_TYPE_USB, 67}, {DevicePortType::PORT_TYPE_HEADSET, 17}, {DevicePortType::PORT_TYPE_BUILTIN, 67}, {DevicePortType::PORT_TYPE_LINEIO, 17}, {DevicePortType::PORT_TYPE_HDMI, 100}, {DevicePortType::PORT_TYPE_MULTICHANNEL, 67}, {DevicePortType::PORT_TYPE_UNKNOWN, 67} }; std::unordered_map> m_defaultThemeMap = { {"HeYin", {"/usr/share/sounds/HeYin/stereo/audio-volume-change.ogg", "/usr/share/sounds/HeYin/stereo/notification-general.ogg"}}, {"Light-Seeking", {"/usr/share/sounds/Light-Seeking/stereo/audio-volume-change.ogg", "/usr/share/sounds/Light-Seeking/stereo/notification-general.ogg"}}, {"Classic", {"/usr/share/sounds/Classic/stereo/audio-volume-change.ogg", "/usr/share/sounds/Classic/stereo/notification-general.ogg"}}, {"DawnLight", {"/usr/share/sounds/DawnLight/stereo/audio-volume-change.ogg", "/usr/share/sounds/DawnLight/stereo/notification-general.ogg"}} }; std::unordered_map m_adapterStreamNameMap = { {"qaxbrowser", "qaxbrowser-safe"} }; }; } #endif // AUDIOMETHOD_H ukui-volume-control/backend/DeviceManagerModule.cpp0000664000175000017500000000624115171074712021403 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "DeviceManagerModule.h" #include "RestoreVolumeJson.h" namespace UkuiAudioFramwork { DeviceManagerModule& DeviceManagerModule::getInstance() { static DeviceManagerModule instance; return instance; } void DeviceManagerModule::setManager(std::shared_ptr manager) { m_pAudioManager = manager; } void DeviceManagerModule::setJson(std::shared_ptr json) { m_pJson = json; } /** * 禁用分为以下两种情况 * 1.该声卡中除了禁用的端口外还存在其他可用的同类型的端口 * · 可用的端口在同一个sink/sources上 --> 需要将当前设备静音并记录之前的静音状态,并将该端口切换到其他端口上,将禁用的端口的状态同步到数据库并发出信号,通知ui程序去更新端口列表(典型事例:扬声器和模拟耳机、前后置耳机和线缆输出,内部话筒和话筒) * · 可用的端口在不同的sink/source上 --> 如果另一个端口和当前端口是所在相同的配置文件下,则将当前设备静音并记录之前的静音状态,判断禁用的设备是不是默认的设备,是的话则需要切换到一个可用的优先级最高的设备上 * --> 如果另一个端口和当前端口是不在相同的配置文件下,只需要切换到另一个设备的最高优先级的配置文件即可,相当于切换设备;将禁用的端口的状态同步到数据库并发出信号,通知ui程序去更新端口列表 * 2.该声卡中除了禁用的端口外不存在其他可用的同类型的端口 * · 将当前设备静音并记录之前的静音状态,判断禁用的设备是不是默认的设备,是的话则需要切换到一个可用的优先级最高的设备上,如果不是则不需要进行切换操作; * · 将禁用的端口的状态同步到数据库并发出信号,通知ui程序去更新端口列表 */ void DeviceManagerModule::setEnabled(const std::string& card, const std::string& port, bool enable) { if (!m_pAudioManager) { std::cerr << "Setting enable failed, please check the status of the audio server!" << std::endl; return; } // 1. 首先更新端口的状态 m_pAudioManager->setEnabled(m_pJson, card, port, enable); } bool DeviceManagerModule::isEnabled(const std::string& card, const std::string& port) const { return m_pJson->getStatus(card, port, "enable"); } } ukui-volume-control/backend/PaSourceOutputStream.cpp0000664000175000017500000001530715171074712021664 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "PaSourceOutputStream.h" #include "PaSourceStream.h" #include "AudioMethod.h" namespace UkuiAudioFramwork { void PaSourceOutputStream::copy(const pa_source_output_info& other) { if (PA_INVALID_INDEX != index) { if (!pa_cvolume_equal(&volume, &other.volume) && index == other.index) { volumeChanged(paCvolumeToValue(other.volume)); } if (mute != other.mute && index == other.index) { muteChanged(!mute); } } index = other.index; name = other.name; owner_module = other.owner_module; client = other.client; source = other.source; sample_spec = other.sample_spec; channel_map = other.channel_map; volume = other.volume; buffer_usec = other.buffer_usec; source_usec = other.source_usec; resample_method = other.resample_method ? other.resample_method : ""; driver = other.driver; proplist = other.proplist; corked = other.corked; mute = other.mute; has_volume = other.has_volume; volume_writable = other.volume_writable; format = other.format; auto mediaRole = pa_proplist_gets(proplist, PA_PROP_MEDIA_ROLE); role = mediaRole ? mediaRole : "Record Stream"; auto appIconName = pa_proplist_gets(proplist, PA_PROP_APPLICATION_ICON_NAME); m_appIconName = appIconName ? appIconName : "application-x-desktop"; auto binary = pa_proplist_gets(proplist, PA_PROP_APPLICATION_PROCESS_BINARY); m_processBinary = binary ? binary : name; } std::string PaSourceOutputStream::getMediaRole() const { // 在这里使用pa_proplist_gets获取role会出错,暂时先使用这种方式获取media.role return role; } std::string PaSourceOutputStream::getAppIconName() const { return m_appIconName; } std::string PaSourceOutputStream::getProcessBinary() const { return m_processBinary; } std::string PaSourceOutputStream::getDriver() const { return driver; } uint32_t PaSourceOutputStream::getSource() const { return source; } std::string PaSourceOutputStream::toString() const { std::string vol = "["; for (int i = 0; i < volume.channels; ++i) { vol += std::to_string(volume.values[i]); vol += ", "; } vol += "]"; return FORMAT("name={}, index={}, owner_module={}, source={}, driver={}, volume={}, mute={}", name, index, owner_module, source, driver, vol, mute); } NodeType PaSourceOutputStream::getNodeType() const { return NodeType::SOURCE_OUTPUT; } uint32_t PaSourceOutputStream::getIndex() const { return index; } std::string PaSourceOutputStream::getName() const { return name; } pa_proplist* PaSourceOutputStream::getProplist() const { return proplist; } uint32_t PaSourceOutputStream::getCard() const { return {}; } void PaSourceOutputStream::setVolume(int vol) { auto v = volume; for (int i = 0; i < v.channels; ++i) { v.values[i] = vol; } if (pa_cvolume_equal(&v, &volume)) return; pa_context_set_source_output_volume(m_pContext, index, &v, nullptr, nullptr); } const pa_cvolume PaSourceOutputStream::getVolume() const { return volume; } void PaSourceOutputStream::setBalance(double v) { // TODO } double PaSourceOutputStream::getBalance() const { // TODO return {}; } void PaSourceOutputStream::setMute(bool mute) { pa_context_set_source_mute_by_index(m_pContext, index, mute, nullptr, nullptr); } bool PaSourceOutputStream::getMute() const { return mute; } DefaultDeviceInfo PaSourceOutputStream::getDefaultDevice() const { DefaultDeviceInfo info; std::shared_ptr s = nullptr; for (const auto& c : m_info.cardInfoList) { auto it = std::find_if(c->sources.begin(), c->sources.end(), [this](const std::shared_ptr& stream) { return source == stream->getIndex(); }); if (it != c->sources.end()) { s = *it; break; } } if (s) { info.idx = s->getIndex(); info.name = s->getName(); info.cardName = std::dynamic_pointer_cast(s)->getCardName(); info.activePortName = std::dynamic_pointer_cast(s)->getActivePortName(); info.activePortLabel = std::dynamic_pointer_cast(s)->getActivePortDescription(); } return info; } void PaSourceOutputStream::setDefaultDevice(const std::string& port, const std::string& card) { std::shared_ptr source = nullptr; for (const auto& c : m_info.cardInfoList) { if (strcmp(card.c_str(), c->card.name.c_str())) continue; for(const auto& s : c->sources) { auto ports = std::dynamic_pointer_cast(s)->getPorts(); auto it = std::find_if(ports.begin(), ports.end(), [s, port](const std::shared_ptr p) { return p->name == port; }); if (it != ports.end()) { source = s; break; } } } if (!source) { AUDIO_ERROR("Set {} device to card: {}, port: {} error, not found such device.", name, card, port); return; } this->source = source->getIndex(); AUDIO_DEBUG("Set {} default device to {}", name, source->getName()); pa_context_move_source_output_by_index(m_pContext, index, source->getIndex(), nullptr, nullptr); } std::vector PaSourceOutputStream::getDeviceList() const { return {}; } std::list> PaSourceOutputStream::getAvailablePortList() const { return {}; } void PaSourceOutputStream::volumeChanged(int volume) { AudioMethod::getInstance().volumeChanged(NodeType::SOURCE_OUTPUT, index, volume); } void PaSourceOutputStream::muteChanged(bool mute) { AudioMethod::getInstance().muteChanged(NodeType::SOURCE_OUTPUT, index, mute); } void PaSourceOutputStream::portChanged(const std::string& name) { AudioMethod::getInstance().portChanged(NodeType::SOURCE_OUTPUT, name); } void PaSourceOutputStream::deviceChanged(bool autoPause) { } } ukui-volume-control/backend/VolumeControl.h0000664000175000017500000000517015171074712020020 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 VOLUMECONTROL_H #define VOLUMECONTROL_H #include "BackendFactory.h" #include "BaseType.h" #include "PulseaudioBackendFactory.h" #include "PipewireBackendFactory.h" #include "ManagerFactory.h" #include "PulseaudioManagerFactory.h" #include "PipewireManagerFactory.h" #include "AudioManager.h" #include "AudioContext.h" #include "AudioMethod.h" namespace UkuiAudioFramwork { class VolumeControl { public: static VolumeControl& getInstance(); public: void init(); private: VolumeControl() = default; VolumeControl(const VolumeControl&) = delete; VolumeControl(VolumeControl&&) = delete; VolumeControl operator=(const VolumeControl&) = delete; VolumeControl operator=(VolumeControl&&) = delete; virtual ~VolumeControl() = default; private: BackendType m_type = BackendType::PULSEAUDIO; std::shared_ptr m_pBackendFactory; std::shared_ptr m_pBackend; std::shared_ptr m_pManagerFactory; std::shared_ptr m_pAudioManager; std::unordered_map> m_settingsMap = { {ModuleType::MODULE_REMAP_SINK, {MONO_AUDIO_KEY, "module-remap-sink", "sink_name=mono channels=1 channel_map=mono"}}, {ModuleType::MODULE_ECHO_CANCEL, {NOISE_REDUCTION_KEY, "module-echo-cancel", "use_master_format=1 aec_method=webrtc aec_args=analog_gain_control=0 source_name=noiseReduceSource"}}, {ModuleType::MODULE_LOOPBACK ,{LOOPBACK_KEY, "module-loopback", ""}} }; std::unordered_map> m_statusFunctions = { {MONO_AUDIO_KEY, [this]() { return AudioMethod::getInstance().getMonoStatus(); }}, {NOISE_REDUCTION_KEY, [this]() { return AudioMethod::getInstance().getEchoCancelStatus(); }}, {LOOPBACK_KEY, [this]() { return AudioMethod::getInstance().getLoopbackStatus(); }} }; }; } #endif // VOLUMECONTROL_H ukui-volume-control/backend/PulseaudioManagerFactory.h0000664000175000017500000000213615171074712022144 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 PULSEAUDIOMANAGERFACTORY_H #define PULSEAUDIOMANAGERFACTORY_H #include "ManagerFactory.h" namespace UkuiAudioFramwork { class PulseaudioManagerFactory : public ManagerFactory { public: PulseaudioManagerFactory() = default; virtual auto createManager() -> std::shared_ptr override; }; } #endif // PULSEAUDIOMANAGERFACTORY_H ukui-volume-control/backend/SoundThemePlayerSetting.cpp0000664000175000017500000000763715171074712022343 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "SoundThemePlayerSetting.h" #include "AudioContext.h" #include "AudioMethod.h" namespace UkuiAudioFramwork { SoundThemePlayerSetting::SoundThemePlayerSetting() { initSettings(); initConnect(); } SoundThemePlayerSetting::~SoundThemePlayerSetting() { if (m_pSchema) g_settings_schema_unref(m_pSchema); if (m_pSource) g_settings_schema_source_unref(m_pSource); if (m_pSettings) g_object_unref(m_pSettings); } void SoundThemePlayerSetting::initSettings() { m_pSource = g_settings_schema_source_get_default(); m_pSchema = g_settings_schema_source_lookup(m_pSource, SOUND_THEME_PLAYER_SCHEMA, true); if (!m_pSchema) { AUDIO_ERROR("schema error, failed to create {} settings", SOUND_THEME_PLAYER_SCHEMA); return; } m_pSettings = g_settings_new_with_path(SOUND_THEME_PLAYER_SCHEMA, SOUND_THEME_PLAYER_PATH); } void SoundThemePlayerSetting::onSettingsChanged(GSettings* settings, const gchar* key) { AudioMethod::getInstance().settingsChanged(key, g_settings_get_value(settings, key)); } void SoundThemePlayerSetting::initConnect() { g_signal_connect(G_OBJECT(m_pSettings), "changed", G_CALLBACK(onSettingsChanged), nullptr); } GVariant* SoundThemePlayerSetting::getValue(const char* key) const { if (!m_pSettings || !m_pSchema) return g_variant_new(""); if (!isContainKeys(key)) return g_variant_new(""); return g_settings_get_value(m_pSettings, key); } void SoundThemePlayerSetting::setValue(const char* key, GVariant* gvalue) { if (!m_pSettings || !m_pSchema) return; if (!isContainKeys(key)) return; GVariantClass variantClass = g_variant_classify(gvalue); if (G_VARIANT_CLASS_STRING == variantClass) { gsize length; g_settings_set_string(m_pSettings, key, g_variant_get_string(gvalue, &length)); } else if (G_VARIANT_CLASS_INT16 == variantClass) { g_settings_set_int(m_pSettings, key, g_variant_get_int16(gvalue)); } else if (G_VARIANT_CLASS_UINT16 == variantClass) { g_settings_set_int(m_pSettings, key, g_variant_get_uint16(gvalue)); } else if (G_VARIANT_CLASS_INT32 == variantClass) { g_settings_set_int(m_pSettings, key, g_variant_get_int32(gvalue)); } else if (G_VARIANT_CLASS_UINT32 == variantClass) { g_settings_set_int(m_pSettings, key, g_variant_get_uint32(gvalue)); } else if (G_VARIANT_CLASS_INT64 == variantClass) { g_settings_set_int(m_pSettings, key, g_variant_get_int64(gvalue)); } else if (G_VARIANT_CLASS_UINT64 == variantClass) { g_settings_set_int(m_pSettings, key, g_variant_get_uint64(gvalue)); } else if (G_VARIANT_CLASS_BOOLEAN == variantClass) { g_settings_set_boolean(m_pSettings, key, g_variant_get_boolean(gvalue)); } else { AUDIO_DEBUG("Unknown variantClass"); } } bool SoundThemePlayerSetting::isContainKeys(const char* key) const { bool ret = false; if (!m_pSettings || !m_pSchema) return ret; gchar** keys = g_settings_schema_list_keys(m_pSchema); if (g_strv_contains(keys, key)) ret = true; g_strfreev(keys); return ret; } } ukui-volume-control/backend/JackDetect.cpp0000664000175000017500000001522715171074712017550 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "JackDetect.h" #include #include #include #include #include #include #include #include #include "AudioContext.h" namespace UkuiAudioFramwork { std::vector JackDetect::listSoundCards() { std::vector cards; DIR* d = opendir("/dev/snd"); if (!d) return cards; dirent* ent; while ((ent = readdir(d))) { if (strncmp(ent->d_name, "controlC", 8) == 0) { cards.emplace_back(ent->d_name + 8); } } closedir(d); return cards; } bool JackDetect::jackIsPlugged(snd_ctl_elem_id_t* id, const char* card) { snd_ctl_t* handle; std::string name = std::string("hw:") + card; if (snd_ctl_open(&handle, name.c_str(), 0) < 0) return false; snd_ctl_elem_value_t* val; snd_ctl_elem_value_alloca(&val); snd_ctl_elem_value_set_id(val, id); bool plugged = false; if (snd_ctl_elem_read(handle, val) == 0) { plugged = snd_ctl_elem_value_get_boolean(val, 0); } snd_ctl_close(handle); return plugged; } void JackDetect::enumJacks(const std::string& card, JackDetect::EventCallback cb) { snd_ctl_t* handle; std::string name = "hw:" + card; if (snd_ctl_open(&handle, name.c_str(), 0) < 0) return; snd_ctl_elem_list_t* list; snd_ctl_elem_list_alloca(&list); if (snd_ctl_elem_list(handle, list) < 0) { snd_ctl_close(handle); return; } unsigned int count = snd_ctl_elem_list_get_count(list); snd_ctl_elem_list_alloc_space(list, count); if (snd_ctl_elem_list(handle, list) < 0) { snd_ctl_close(handle); return; } AlsaCard c; c.name = card; for (unsigned int idx = 0; idx < count; ++idx) { snd_ctl_elem_id_t* id; snd_ctl_elem_id_alloca(&id); snd_ctl_elem_list_get_id(list, idx, id); const char* ename = snd_ctl_elem_id_get_name(id); if (!ename || !strstr(ename, "Jack")) continue; bool plugged = jackIsPlugged(id, card.c_str()); AlsaJack jack; jack.name = ename; jack.numid = snd_ctl_elem_id_get_index(id); jack.plugin = plugged; jack.type = getJackTypeByName(ename); c.jacks.emplace_back(jack); if (cb) cb(card, ename, jack.type, plugged); } m_cards.emplace_back(c); snd_ctl_close(handle); } AlsaJackType JackDetect::getJackTypeByName(const std::string& name) const { for (const auto& [k, v] : m_jackMap) { if (k.compare(name) == 0) return v; } return AlsaJackType::ALSA_JACK_UNKNOEW; } JackDetect::JackDetect(EventCallback cb) : m_callback(std::move(cb)) {} JackDetect::~JackDetect() { stop(); } void JackDetect::start() { if (m_bRunning) return; m_bRunning = true; m_inotifyFd = inotify_init1(IN_NONBLOCK); scanCards(); m_worker = std::thread(&JackDetect::loop, this); } void JackDetect::stop() { if (!m_bRunning) return; m_bRunning = false; if (m_worker.joinable()) m_worker.join(); for (int wd : m_watchDescriptors) ::inotify_rm_watch(m_inotifyFd, wd); m_watchDescriptors.clear(); if (m_inotifyFd >= 0) ::close(m_inotifyFd); } void JackDetect::scanCards() { for (const auto& card : listSoundCards()) { std::string path = "/dev/snd/controlC" + card; int wd = inotify_add_watch(m_inotifyFd, path.c_str(), IN_ATTRIB); if (wd >= 0) m_watchDescriptors.push_back(wd); enumJacks(card, m_callback); } } void JackDetect::loop() { std::vector> ctlList; for (const auto& card : listSoundCards()) { snd_ctl_t* handle; std::string hw = "hw:" + card; if (snd_ctl_open(&handle, hw.c_str(), 0) < 0) continue; if (snd_ctl_subscribe_events(handle, 1) == 0){ ctlList.emplace_back(handle, card); } else { snd_ctl_close(handle); } } while (m_bRunning) { for (auto& [handle, card] : ctlList) { pollfd pfd{.fd = snd_ctl_poll_descriptors_count(handle), .events = POLLIN, .revents = 0}; int count = snd_ctl_poll_descriptors(handle, &pfd, 1); if (count <= 0 || poll(&pfd, 1, 200) <= 0) continue; snd_ctl_event_t* ev; snd_ctl_event_alloca(&ev); if (snd_ctl_read(handle, ev) < 0) continue; if (snd_ctl_event_get_type(ev) != SND_CTL_EVENT_ELEM) continue; snd_ctl_elem_id_t* id; snd_ctl_elem_id_alloca(&id); snd_ctl_event_elem_get_id(ev, id); const char* ename = snd_ctl_elem_id_get_name(id); if (!ename || !strstr(ename, "Jack")) continue; bool plugged = jackIsPlugged(id, card.c_str()); auto it = std::find_if(m_cards.begin(), m_cards.end(), [card, plugged](const AlsaCard& c) { return c.name == card; }); if (it != m_cards.end()) { auto jacks = (*it).jacks; auto jack = std::find_if(jacks.begin(), jacks.end(), [ename, plugged](const AlsaJack& jack) { return ename == jack.name; }); if (jack != jacks.end()) { (*jack).plugin = plugged; } else { AUDIO_WARN("Jacks not found {} jack.", ename); } } else { AUDIO_WARN("Cards not found {} card.", card); } if (m_callback) m_callback(card, ename, getJackTypeByName(ename), plugged); } } for (auto& [handle, _] : ctlList) { snd_ctl_subscribe_events(handle, 0); snd_ctl_close(handle); } } } ukui-volume-control/backend/PipewirePort.h0000664000175000017500000000260615171074712017642 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 . */ #pragma once //#include #include "PipewireManager1.h" namespace UkuiAudioFramwork { /** * @todo write docs */ class PipewirePort { public: PipewirePort(Pipewire *parent, uint32_t id, const spa_dict *props); virtual ~PipewirePort(); uint32_t id() const; void _port_info(const struct pw_port_info *info); void _port_param(int seq, uint32_t id, uint32_t index, uint32_t next, const struct spa_pod *param); void set_id(uint32_t id); void idChanged(uint32_t id); private: Pipewire* pipewire; uint32_t m_id = 0; pw_port *port = nullptr; spa_hook port_listener; }; } ukui-volume-control/backend/PipewireClient.h0000664000175000017500000000614615171074712020137 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 . */ #pragma once #include #include #include #include #include #include namespace UkuiAudioFramwork { class Pipewire; class PipewireClient { void propertiesChanged(); void idChanged(); void propertyChanged(const std::string& key, const std::string& value); private: Pipewire *pipewire = nullptr; uint32_t m_id = 0; struct pw_client *client = nullptr; struct spa_hook client_listener; std::unordered_map m_properties; public: explicit PipewireClient(Pipewire *parent, uint32_t id, const spa_dict* props); virtual ~PipewireClient(); [[nodiscard]] int id() const { return m_id; } std::string property(const std::string &key) { return m_properties[key]; } bool has_property(const std::string &key) { return m_properties.contains(key); } // QVariantMap properties() // { // QVariantMap rval; // QMap::iterator i = m_properties.begin(); // while (i != m_properties.end()) { // rval[i.key()] = QVariant::fromValue(i.value()); // ++i; // } // return rval; // } // QList propertiesList() // { // QList rval; // QMap::iterator i = m_properties.begin(); // while (i != m_properties.end()) { // QVariantMap value; // value["name"] = i.key(); // value["value"] = i.value(); // rval.append(value); // ++i; // } // return rval; // } std::unordered_map properties() { std::unordered_map rval; for (const auto& pair : m_properties) { rval[pair.first] = pair.second; } return rval; } std::vector> propertiesList() { std::vector> rval; for (const auto& pair : m_properties) { std::unordered_map value; value["name"] = pair.first; value["value"] = pair.second; rval.push_back(value); } return rval; } void _client_info(const struct pw_client_info *info); }; } ukui-volume-control/backend/PulseaudioBackendFactory.cpp0000664000175000017500000000173315171074712022456 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "PulseaudioBackendFactory.h" #include "PulseaudioBackend.h" namespace UkuiAudioFramwork { std::shared_ptr PulseaudioBackendFactory::createBackend() { return std::make_shared(); } } ukui-volume-control/backend/PulseaudioManager.cpp0000664000175000017500000030753715171074712021164 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "PulseaudioManager.h" #include #include #include "PaSinkStream.h" #include "PaSourceStream.h" namespace UkuiAudioFramwork { pa_mainloop_api* PulseaudioManager::m_pApi = nullptr; pa_context* PulseaudioManager::m_pContext = nullptr; template static std::shared_ptr findStream(const Range& rng, const std::string& name) { auto it = std::find_if(rng.begin(), rng.end(), [name](const std::shared_ptr& s) { return s->getName() == name; }); return it != rng.end() ? *it : nullptr; } PulseaudioManager::PulseaudioManager(int times) : m_reconnectTime(times) { } // Interface void PulseaudioManager::loadModule(const std::string& name, const std::string& param) { bool isLoadModule = true; auto it = std::find_if(m_info.moduleInfoList.begin(), m_info.moduleInfoList.end(), [name](const PaModuleInfo& info) { return info.name == name; }); if (it != m_info.moduleInfoList.end()) { AUDIO_WARN("{} has been loaded and does not need to be loaded again.", name); return; } std::thread([name, param, &isLoadModule, this] () { { std::unique_lock lock(m_contextMtx); if (!m_bContexReady) m_contextCond.wait_for(lock, std::chrono::milliseconds(3000)); if (!m_bContexReady) { AUDIO_WARN("Can not load {}, wrong context state.", name); return; } } // 如果是降噪模块,需要先判断输入设备是否存在usb或蓝牙,如果存在usb或蓝牙则不加载 if (std::string::npos != name.find("module-echo-cancel")) { if (std::string::npos == m_info.defaultSource->getName().find("alsa_input") || std::string::npos != m_info.defaultSource->getName().find("input.usb")) { AUDIO_WARN("Can not load module-echo-cancel, default_source: {}.", m_info.defaultSource->getName()); isLoadModule = false; return; } } else if (std::string::npos != name.find("module-remap-sink")) { if (std::string::npos != m_info.defaultSource->getName().find("auto_null") ) { AUDIO_WARN("module-remap-sink cannot be loaded with {} sink.", m_info.defaultSink->getName()); isLoadModule = false; return; } } AUDIO_DEBUG("ctf load module {} with param {}.", name, param); auto o = pa_context_load_module(m_pContext, name.c_str(), param.c_str(), nullptr, nullptr); o = pa_context_get_module_info_list(m_pContext, moduleInfoCb, this); }).join(); // 设置default sink线程,等待主线程cardInfoList更新完毕之后在去设置default sink std::thread([name, param, isLoadModule, this] () { { std::unique_lock lock(m_moduleMtx); m_bModuleReady = false; if (!m_bModuleReady) { m_moduleCond.wait_for(lock, std::chrono::milliseconds(3000)); } /** If the module does not need to be loaded or the module loading timeout occurs, * no operation is required. */ if (!isLoadModule || !m_bModuleReady) { AUDIO_DEBUG("{} module was not loaded or the loading timed out, no need to configure the default sink/source.", name); m_bModuleReady = false; return; } m_bModuleReady = false; } if ("module-echo-cancel" == name) { AUDIO_DEBUG("Loaded module-echo-cancel, set default source to noiseReduceSource."); pa_context_set_default_source(m_pContext, "noiseReduceSource", nullptr, nullptr); } else if ("module-remap-sink" == name) { AUDIO_DEBUG("Loaded module-remap-sink, set default sink to mono."); pa_cvolume volume; pa_cvolume_set(&volume, 1, PA_VOLUME_NORM); pa_context_set_sink_volume_by_name(m_pContext, "mono", &volume, nullptr, nullptr); pa_context_set_default_sink(m_pContext, "mono", nullptr, nullptr); } else if ("module-combine-sink" == name) { auto devList = parseSlaves(param); for (const auto& dev : devList) { for (const auto& c : m_info.cardInfoList) { auto it = std::find_if(c->sinks.begin(), c->sinks.end(), [dev](const std::shared_ptr& s){ return dev == s->getName(); }); if (it != c->sinks.end()) { std::dynamic_pointer_cast(*it)->setCombineStatus(true); } } } deviceAdjust(NodeType::OUTPUT); AUDIO_DEBUG("Loaded module-combine-sink, set default sink to combined."); pa_context_set_default_sink(m_pContext, "combined", nullptr, nullptr); } }).detach(); } void PulseaudioManager::unloadModule(const std::string& name) { uint32_t idx = PA_INVALID_INDEX; auto it = std::find_if(m_info.moduleInfoList.begin(), m_info.moduleInfoList.end(), [name](const PaModuleInfo& info) { return info.name == name; }); if (it != m_info.moduleInfoList.end()) { idx = (*it).index; m_info.moduleInfoList.erase(it); } if (PA_INVALID_INDEX == idx) { AUDIO_ERROR("Unload {} module failed, not found such module.", name); return; } /** If the mono module is uninstalled, the default output device needs * to be set to the master device of mono. */ if (name == "module-remap-sink") { auto master = std::dynamic_pointer_cast(m_info.defaultSink)->getMasterDevice(); pa_context_set_default_sink(m_pContext, master.c_str(), nullptr, nullptr); } AUDIO_DEBUG("Unload module {}.", name); pa_context_unload_module(m_pContext, idx, nullptr, nullptr); } // Method void PulseaudioManager::setDetailVolume(const NodeType& type, const std::string& port, const std::string& card, int volume) { std::shared_ptr s = foundInfoByName(type, port, card); if (nullptr == s) { AUDIO_ERROR("Set {} detail volume failed, not found such port: {} on card: {}.", getNodeTypeName(type), port, card); return; } s->setVolume(valueToPaVolume(volume)); } int PulseaudioManager::getDetailVolume(const NodeType& type, const std::string& name, const std::string& dev) const { pa_cvolume v {}; std::shared_ptr s = foundInfoByName(type, name, dev); if (nullptr == s) { AUDIO_ERROR("Get {} volume failed, not found such port: {} on device: {}.", getNodeTypeName(type), name, dev); return {}; } v = s->getVolume(); return paVolumeToValue(paCvolumeToPaValue(v)); } void PulseaudioManager::setVolume(const NodeType& type, int idx, int volume) { std::shared_ptr s = foundInfoByName(type, idx, true); if (nullptr == s) { AUDIO_ERROR("set {} volume failed, not found such device.", idx); return; } std::string deviceClass = ""; if (NodeType::OUTPUT == type) deviceClass = std::dynamic_pointer_cast(s)->getDeviceClass(); else if (NodeType::INPUT == type) { deviceClass = std::dynamic_pointer_cast(s)->getDeviceClass(); } if (deviceClass == "filter") { auto master = getMasterStream(s.get(), type); if (master) { AudioMethod::getInstance().volumeChanged(type, s->getIndex(), volume); s = master; } else AUDIO_ERROR("Filter stream {} has no valid master device, volume not changed.", s->getName()); } s->setVolume(valueToPaVolume(volume)); } int PulseaudioManager::getVolume(const NodeType& type, int idx) const { int ret = 0; std::shared_ptr s = foundInfoByName(type, idx, true); if (nullptr == s) { AUDIO_ERROR("Get {} volume failed, not found such device.", idx); return ret; } std::string deviceClass = ""; if (NodeType::OUTPUT == type) deviceClass = std::dynamic_pointer_cast(s)->getDeviceClass(); else if (NodeType::INPUT == type) { deviceClass = std::dynamic_pointer_cast(s)->getDeviceClass(); } if (deviceClass == "filter") { auto master = getMasterStream(s.get(), type); if (master) { s = master; } else { AUDIO_ERROR("Filter stream {} has no valid master device .", s->getName()); } } auto v = s->getVolume(); return paVolumeToValue(paCvolumeToPaValue(v)); } void PulseaudioManager::setDetailMute(const NodeType& type, const std::string& name, const std::string& dev, bool mute) { auto s = foundInfoByName(type, name, dev); if (nullptr == s) { AUDIO_ERROR("Set {} detail mute failed, not found such port:{} on device: {}.", getNodeTypeName(type), name, dev); return; } s->setMute(mute); } bool PulseaudioManager::getDetailMute(const NodeType& type, const std::string& name, const std::string& dev) const { auto s = foundInfoByName(type, name, dev); if (nullptr == s) { AUDIO_ERROR("get {} mute failed, not found such device on {}.", name, dev); return false; } AUDIO_DEBUG("Get {} {} detail mute.", getNodeTypeName(type), name); return s->getMute(); } void PulseaudioManager::setBalance(const NodeType& type, double v) { auto s = foundInfoByName(type, "", "", true); if (nullptr == s) { AUDIO_ERROR("Set balance failed, not found such {} type.", getNodeTypeName(type)); return; } return s->setBalance(v); } double PulseaudioManager::getBalance(const NodeType& type) const { auto s = foundInfoByName(type, "", "", true); if (nullptr == s) { AUDIO_ERROR("Get balance failed, not found such {} type.", getNodeTypeName(type)); return {}; } return s->getBalance(); } void PulseaudioManager::setMute(const NodeType& type, int idx, bool mute) { auto s = foundInfoByName(type, idx, true); if (nullptr == s) { AUDIO_ERROR("Set {} mute failed, not found such device.", idx); return; } s->setMute(mute); } bool PulseaudioManager::getMute(const NodeType& type, int idx) const { auto s = foundInfoByName(type, idx, true); if (nullptr == s) { AUDIO_ERROR("Get {} mute failed, not found such device.", idx); return false; } return s->getMute(); } DefaultDeviceInfo PulseaudioManager::getDefaultDevice(const NodeType& type, int idx) const { { std::unique_lock lock(m_contextMtx); if (!m_bContexReady) m_contextCond.wait_for(lock, std::chrono::milliseconds(3000)); if (!m_bContexReady) { AUDIO_WARN("Can not get {} default device, wrong context state.", getNodeTypeName(type)); return {}; } } auto s = foundInfoByName(type, idx, true); if (nullptr == s) { AUDIO_ERROR("Get {} default device failed, not found such device.", getNodeTypeName(type)); return {}; } return s->getDefaultDevice(); } void PulseaudioManager::setDefaultDevice(const NodeType& type, int idx, const std::string& name, const std::string& dev) { auto s = foundInfoByName(type, idx, true); if (nullptr == s) { AUDIO_ERROR("set {} default device failed, not found such card: {}, port: {}.", getNodeTypeName(type), dev, name); return; } s->setDefaultDevice(name, dev); } std::vector PulseaudioManager::getDeviceList(const NodeType& type) const { auto s = foundInfoByName(type, "", "", true); if (nullptr == s) { AUDIO_ERROR("Get {} device list failed, not found such device."); return {}; } return s->getDeviceList(); } std::list PulseaudioManager::getSinkList() const { std::list list; std::lock_guard lock(m_info.m_mtx); for (const auto& c : m_info.cardInfoList) { for (const auto& s : c->sinks) { DefaultDeviceInfo info; info.idx = s->getIndex(); info.name = s->getName(); // info.cardDesc = c->card.deviceDesc; auto v = s->getVolume(); info.volume = paCvolumeToPaValue(v); if (std::string::npos != info.name.find("combined")) { info.activePortName = "output-combine"; info.activePortLabel = "Mulit Audio Output"; info.cardName = "combine-device"; } else { info.cardName = c->card.name; info.activePortName = std::dynamic_pointer_cast(s)->getActivePortName(); info.activePortLabel = std::dynamic_pointer_cast(s)->getActivePortDescription(); } list.push_back(info); } } std::string sl = "["; for (const auto& s : list) { sl += "("; sl += s.toString(); sl += "),"; } sl += "]"; AUDIO_DEBUG("Get sink list, list: {}", sl); return list; } std::list PulseaudioManager::getSourceList() const { std::list list; for (const auto& c : m_info.cardInfoList) { for (const auto& s : c->sources) { DefaultDeviceInfo info; info.idx = s->getIndex(); info.name = s->getName(); info.cardName = c->card.name; info.activePortName = std::dynamic_pointer_cast(s)->getActivePortName(); info.activePortLabel = std::dynamic_pointer_cast(s)->getActivePortDescription(); list.push_back(info); } } return list; } std::list> PulseaudioManager::getAvailablePortList(const NodeType& type) const { auto s = foundInfoByName(type, "", "", true); if (nullptr == s) { AUDIO_ERROR("get {} device list failed, not found such device.", type); return {}; } return s->getAvailablePortList(); } std::list PulseaudioManager::getSinkInputList() const { std::list infoList; for (const auto& s : m_info.sinkInputList) { StreamMediaInfo info; info.index = s->getIndex(); info.volume = paCvolumeToValue(s->getVolume()); info.binary = std::dynamic_pointer_cast(s)->getProcessBinary(); info.name = AudioMethod::getInstance().getStreamDescription(info.binary); info.iconName = std::dynamic_pointer_cast(s)->getAppIconName(); //start adapter application auto it = m_adapterStreamNameMap.find(info.iconName); if (it != m_adapterStreamNameMap.end()) { info.iconName = it->second; } //end info.mediaRole = std::dynamic_pointer_cast(s)->getMediaRole(); infoList.push_back(info); } return infoList; } std::list PulseaudioManager::getSourceOutputList() const { std::list infoList; for (const auto& s : m_info.sourceOutputList) { if (s->getName().compare("Peak detect") == 0) continue; StreamMediaInfo info; info.index = s->getIndex(); info.volume = paCvolumeToValue(s->getVolume()); info.binary = std::dynamic_pointer_cast(s)->getProcessBinary(); info.name = AudioMethod::getInstance().getStreamDescription(info.binary); info.iconName = std::dynamic_pointer_cast(s)->getAppIconName(); info.mediaRole = std::dynamic_pointer_cast(s)->getMediaRole(); infoList.push_back(info); } return infoList; } std::list PulseaudioManager::getStreamMediaList() const { return m_streamMediaList; } std::list PulseaudioManager::getModuleList() { std::list list; { std::unique_lock lock(m_contextMtx); if (!m_bContexReady) m_contextCond.wait_for(lock, std::chrono::milliseconds(3000)); if (!m_bContexReady) { AUDIO_ERROR("Unable to get module list, wrong context state."); return {}; } } auto o = pa_context_get_module_info_list(m_pContext, moduleInfoCb, this); pa_threaded_mainloop* loop = pa_threaded_mainloop_new(); while(pa_operation_get_state(o) == PA_OPERATION_RUNNING) { pa_threaded_mainloop_wait(loop); } pa_threaded_mainloop_free(loop); for (const auto& l : m_info.moduleInfoList) { list.emplace_back(l.name); } return list; } /** * 禁用分为以下两种情况 * 1.该声卡中除了禁用的端口外还存在其他可用的同类型的端口 * · 可用的端口在同一个sink/sources上 --> 需要将当前设备静音并记录之前的静音状态,并将该端口切换到其他端口上,将禁用的端口的状态同步到数据库并发出信号,通知ui程序去更新端口列表(典型事例:扬声器和模拟耳机、前后置耳机和线缆输出,内部话筒和话筒) * · 可用的端口在不同的sink/source上 --> 如果另一个端口和当前端口是所在相同的配置文件下,则将当前设备静音并记录之前的静音状态,判断禁用的设备是不是默认的设备,是的话则需要切换到一个可用的优先级最高的设备上 * --> 如果另一个端口和当前端口是不在相同的配置文件下,只需要切换到另一个设备的最高优先级的配置文件即可,相当于切换设备;将禁用的端口的状态同步到数据库并发出信号,通知ui程序去更新端口列表 * 2.该声卡中除了禁用的端口外不存在其他可用的同类型的端口 * · 将当前设备静音并记录之前的静音状态,判断禁用的设备是不是默认的设备,是的话则需要切换到一个可用的优先级最高的设备上,如果不是则不需要进行切换操作; * · 将禁用的端口的状态同步到数据库并发出信号,通知ui程序去更新端口列表 */ //void PulseaudioManager::setEnabled(const std::string& port, const std::string& card, bool enabled) //{ // auto cardInfo = findCardInfo(card); // if (!cardInfo) { // std::cout << "Setting " << card << ":" << port << " status to " << enabled << " Failed, no such card!" << std::endl; // return; // } // auto deviceInfo = findDeviceInfoInCard(*cardInfo, port); // if (!deviceInfo) { // std::cout << "Setting " << card << ":" << port << " status to " << enabled << " Failed, no such device!" << std::endl; // return; // } // bool found = false; // std::shared_ptr tmp = nullptr; // for (const auto& d : (*cardInfo)->card.ports) { // if ((*deviceInfo)->direction == d->direction && (*deviceInfo)->name != d->name) { // found = true; // if (!tmp || tmp->priority < d->priority) { // tmp = d; // } // } // } // // 除了禁用的端口外还存在其他同类型的可用的端口 // if (found && tmp) { // // 判断端口是否在相同的sink/source上 // if (PA_DIRECTION_OUTPUT == tmp->direction) { // for(const auto& s : (*cardInfo)->sinks) { // auto ports = std::dynamic_pointer_cast(s)->getPorts(); // auto it = std::find_if(ports.begin(), ports.end(), [port, tmp](const std::shared_ptr& info){ // return port == info->name; // }); // auto at = std::find_if(ports.begin(), ports.end(), [port, tmp](const std::shared_ptr& info){ // return tmp->name == info->name; // }); // // 都能找到的话说明在同一个sink上 // if (it != ports.end() && at != ports.end()) { // // 保存当前设备静音状态 // // 设置当前设备静音 // s->setMute(true); // std::cout << " set sink port:" << tmp->name << std::endl; // // 设置优先级较高的端口 // pa_context_set_sink_port_by_index(m_pContext, s->getIndex(), tmp->name.c_str(), nullptr, nullptr); // } // else { // // 判断两个端口是否存在相同的配置文件 // bool found = false; // auto it = std::find_if((*cardInfo)->card.ports.begin(), (*cardInfo)->card.ports.end(), [port](const std::shared_ptr& info){ // return port == info->name; // }); // auto at = std::find_if((*cardInfo)->card.ports.begin(), (*cardInfo)->card.ports.end(), [tmp](const std::shared_ptr& info){ // return tmp->name == info->name; // }); // for (const auto& p1 : (*it)->profiles2) { // for (const auto& p2 : (*at)->profiles2) { // if (p1->name == p2->name) { // found = true; // break; // } // } // } // if (found) { // // 判断禁用的设备是不是默认的设备,是的话则需要切换到一个可用的优先级最高的设备上 // // 设置当前设备静音 // s->setMute(true); // } // else { // 不在相同的配置文件下,只需要切换到另一个设备的最高优先级的配置文件即可, // int pri = 0; // std::string profileName = ""; // for (const auto& profile : tmp->profiles2) { // if (0 == profile->available) // continue; // if (pri < profile->priority) { // pri = profile->priority; // profileName = profile->name; // } // } // std::cout << "std cardprofile:" << profileName << "sinkName:" << s->getName() << std::endl; // if (profileName != "") // pa_context_set_card_profile_by_name(m_pContext, (*cardInfo)->card.name.c_str(), profileName.c_str(), nullptr, nullptr); // } // } // } // } // else { // for(const auto& s : (*cardInfo)->sources) { // } // } // } // else { // } // (*deviceInfo)->enabled = enabled; // std::cout << "Setting " << card << ":" << port << " status to " << enabled << " Successed." << std::endl; //} bool PulseaudioManager::isEnabled(const std::string& card, const std::string& port) const { auto cardInfo = findCardByName(card); if (!cardInfo) { AUDIO_ERROR("Get card: {} port: {} enable failed, no such card.", card, port); return true; } auto deviceInfo = findPortByName(*cardInfo, port); if (!deviceInfo) { AUDIO_ERROR("Get card: {} port: {} enable failed, no such port.", card, port); return true; } AUDIO_DEBUG("Get card: {} port: {} enabled successed.", card, port); return (*deviceInfo)->enabled; } bool PulseaudioManager::isLoaded(const ModuleType& type) const { std::string moduleName = "unknown"; switch (type) { case ModuleType::MODULE_ECHO_CANCEL: moduleName = "module-echo-cancel"; break; case ModuleType::MODULE_LOOPBACK: moduleName = "module-loopback"; break; case ModuleType::MODULE_REMAP_SINK: moduleName = "module-remap-sink"; break; default: return false; } auto it = std::find_if(m_info.moduleInfoList.begin(), m_info.moduleInfoList.end(), [moduleName](const PaModuleInfo& info){ return moduleName == info.name; }); return it != m_info.moduleInfoList.end(); } bool PulseaudioManager::isValidDevice(const NodeType& type) const { bool ret = false; auto portList = getAvailablePortList(type); for (const auto& p : portList) { if (p->enabled) { ret = true; break; } } return ret; } std::optional> PulseaudioManager::findPortByName(const std::shared_ptr& cardInfo, const std::string& portName) const { for (const auto& d : cardInfo->card.ports) { if (d->name == portName) { return d; } } return std::nullopt; } std::optional> PulseaudioManager::findCardByName(const std::string& cardName) const { for (const auto& c : m_info.cardInfoList) { if (c->card.name == cardName) { return c; } } return std::nullopt; } bool PulseaudioManager::streamIsExitPort(const std::shared_ptr& stream, const std::string& portName) const { auto type = stream->getNodeType(); if (NodeType::INPUT == type) { auto ports = std::dynamic_pointer_cast(stream)->getPorts(); for (const auto& d : ports) { if (d->name == portName) { return true; } } } else if (NodeType::OUTPUT == type) { auto ports = std::dynamic_pointer_cast(stream)->getPorts(); for (const auto& d : ports) { if (d->name == portName) { return true; } } } else { AUDIO_ERROR("{} type does not exit ports.", EnumToInt(type)); return false; } return false; } std::optional> PulseaudioManager::findCommonProfile(const std::shared_ptr& port1, const std::shared_ptr& port2) const { for (const auto& p1 : port1->profiles2) { for (const auto& p2 : port2->profiles2) { if (p1->name == p2->name) { return p1; } } } return std::nullopt; } std::shared_ptr PulseaudioManager::findHighestPriorityAvailableProfile(const std::vector>& profiles) const { std::shared_ptr highestPriorityProfile = nullptr; int highestPriority = -1; for (const auto& profile : profiles) { if (profile->available == 0) continue; if (!highestPriorityProfile || profile->priority > highestPriority) { highestPriorityProfile = profile; highestPriority = profile->priority; } } return highestPriorityProfile; } /** * 禁用分以下情况: * 1.该声卡中除了禁用的端口外还存在其他可用的同类型的端口: * 如果端口在同一个sink/source上则只需要切换端口接口; * 如果不在同一个sink/source上则需要判断是否在统一配置文件下,在同一配置文件下则需要找一个优先级最高的设备, * 不在同一配置文件下时则需要切换配置文件; * 2.该声卡中除了禁用的端口外不存在其他可用的同类型的端口: * 禁用设备之后需要切换到另一个可用的优先级高的设备上 * * 启用: * 1. 如果启用的设备默认设备则不需要处理 * 2. 启用的设备不是默认的设备且当前没有可用的设备时需要将默认设备切换为启用的设备 */ void PulseaudioManager::setEnabled(std::shared_ptr json, const std::string& card, const std::string& port, bool enabled) { RestoreVolumeInfo info; double balance = 0.0; // 记录上一次的平衡音量 bool mute = false; // 记录上一次的静音状态 int value = 0; // 记录上一次的音量值 auto cardInfo = findCardByName(card); if (!cardInfo) { AUDIO_ERROR("Setting card: {} port: {} status to {} failed, no such card!", card, port, enabled); return; } auto deviceInfo = findPortByName(*cardInfo, port); if (!deviceInfo) { AUDIO_ERROR("Setting card: {} port: {} status to {} failed, no such device!", card, port, enabled); return; } auto s = findStreamByPort(cardInfo, (*deviceInfo)->name, (*deviceInfo)->direction); // 禁用时,需先判断sink/source是否存在,如USB声卡当前配置为模拟输出,此时禁用数字输出,无需处理静音及设备切换流程 if (!enabled) { if (s) { // 1. 记录当前待禁用设备的音量信息 balance = s->getBalance(); mute = s->getMute(); auto volume = s->getVolume(); value = paCvolumeToPaValue(volume); // 2. 禁用设备为默认sink/source下的端口 if (isDefaultDevice(s)) { AUDIO_DEBUG("Disable device case 1: device is default sink/source."); // 2.1 禁用该设备端口时,还存在其他可用类型端口,且为同一sink/source,仅需要切换端口,不处理设置禁音 auto availablePort = findHighestPriorityAvailablePort(cardInfo, (*deviceInfo)->direction, (*deviceInfo)->name); if (availablePort && streamIsExitPort(s, (*availablePort)->name)) { AUDIO_DEBUG("Card: {} has other available port: {}.", card, (*availablePort)->name); AUDIO_DEBUG("Set sink/source port: {}.", (*availablePort)->name); if ((*deviceInfo)->direction == PA_DIRECTION_OUTPUT) { pa_context_set_sink_port_by_index(m_pContext, s->getIndex(), (*availablePort)->name.c_str(), nullptr, nullptr); } else { pa_context_set_source_port_by_index(m_pContext, s->getIndex(), (*availablePort)->name.c_str(), nullptr, nullptr); } } // 2.2 禁用该设备端口时,存在其他可用端口,但需要切换声卡配置文件产生新的sink(如: USB声卡的模拟输出与数字输出),需要处理静音 else if (availablePort && !streamIsExitPort(s, (*availablePort)->name)) { s->setMute(true); auto highestPriorityProfile = findHighestPriorityAvailableProfile((*availablePort)->profiles2); if (highestPriorityProfile) { AUDIO_DEBUG("Set card: {} profile to {}.", (*cardInfo)->card.name, highestPriorityProfile->name); pa_context_set_card_profile_by_name(m_pContext, (*cardInfo)->card.name.c_str(), highestPriorityProfile->name.c_str(), nullptr, nullptr); // 由于设置配置文件可能会导致默认设备发生变化,因此需要重新设置默认输入/输出设备 if (NodeType::OUTPUT == s->getNodeType()) pa_context_set_default_sink(m_pContext, m_info.defaultSink->getName().c_str(), nullptr, nullptr); else pa_context_set_default_source(m_pContext, m_info.defaultSource->getName().c_str(), nullptr, nullptr); } else { AUDIO_ERROR("Can not found available profile for: {} in {}.", (*availablePort)->name, (*cardInfo)->card.name); } } // 2.3 禁用该设备端口时,sink/source不存在其他可用端口,需要将默认设备切换至别的stream,并将原来的stream静音 else { s->setMute(true); // 切换到优先级最高的可用输出设备上 auto stream = findHighestPriorityStream(s); AUDIO_DEBUG("Disable sink/source: {} Switch to new default sink/source: {}", s->getName(), stream->getName()); if (NodeType::OUTPUT == stream->getNodeType()) { pa_context_set_default_sink(m_pContext, stream->getName().c_str(), nullptr, nullptr); } else if (NodeType::INPUT == stream->getNodeType()) { pa_context_set_default_source(m_pContext, stream->getName().c_str(), nullptr, nullptr); } } } // 3. 禁用的设备为非默认sink/source下的端口 else { AUDIO_DEBUG("Disable device case 2: device is not default sink/source."); // 3.1 禁用该设备端口时,还存在其他可用端口,且为同一sink/source,仅需要切换端口,不处理设置禁音 auto availablePort = findHighestPriorityAvailablePort(cardInfo, (*deviceInfo)->direction, (*deviceInfo)->name); if (availablePort && streamIsExitPort(s, (*availablePort)->name)) { AUDIO_DEBUG("Card: {} has other available port: {}.", card, (*availablePort)->name); AUDIO_DEBUG("Set sink/source port: {}.", (*availablePort)->name); if ((*deviceInfo)->direction == PA_DIRECTION_OUTPUT) { pa_context_set_sink_port_by_index(m_pContext, s->getIndex(), (*availablePort)->name.c_str(), nullptr, nullptr); } else { pa_context_set_source_port_by_index(m_pContext, s->getIndex(), (*availablePort)->name.c_str(), nullptr, nullptr); } } // 3.2 禁用该设备端口时,sink/source不存在其他可用端口,由于该设备非默认sink/source,所以仅设置静音即可 else { s->setMute(true); } } } else { AUDIO_DEBUG("待禁用的设备: card:{} port: {} 不在sink/source list中,无需处理", card, port); } } // 启用设备 else { // 启用的是默认设备则不做处理,启用的不是默认设备/启用设备的声卡不存在启用的端口则将默认设备切换到启用的设备 NodeType type = (*deviceInfo)->direction == PA_DIRECTION_INPUT ? NodeType::INPUT : NodeType::OUTPUT; if (!isDefaultDevice(s)) { auto it = std::find_if((*cardInfo)->card.ports.begin(), (*cardInfo)->card.ports.end(), [deviceInfo](const std::shared_ptr& info){ AUDIO_DEBUG("ctf ... port: {}, enabled: {}", info->name, info->enabled); return (*deviceInfo)->direction == info->direction && info->enabled; }); AUDIO_DEBUG("The enabled device is not the default device, set card: {} port: {} to default.", card, port); if (!isExitEnableDevice(type) || it == (*cardInfo)->card.ports.end()) { setDefaultDevice(type, -1, port, card); } } if (s) { s->setMute(json->getStatus(card, port, "mute")); } } // 更新设备状态 (*deviceInfo)->enabled = enabled; auto portIt = std::find_if((*cardInfo)->card.deviceList.begin(), (*cardInfo)->card.deviceList.end(), [port](const std::shared_ptr& info){ return port == info->portName; }); if (portIt != (*cardInfo)->card.deviceList.end()) { (*portIt)->enabled = enabled; } AUDIO_DEBUG("{} card: {} port: {} successed, mute: {}.", enabled ? "Enable" : "Disable", card, port, json->getStatus(card, port, "mute")); // 写入到数据库文件并发出信号 info.cardName = card; info.portName = port; info.volume = value; info.priority = (*deviceInfo)->priority; info.balance = balance; info.enable = enabled; info.muted = mute; json->insert(info); deviceAdjust(NodeType::OUTPUT); } std::optional> PulseaudioManager::findHighestPriorityAvailablePort(const std::optional>& cardInfo, int direction, const std::string& excludedPortName) const { std::optional> highestPriorityPort = std::nullopt; for (const auto& port : (*cardInfo)->card.ports) { if (PA_PORT_AVAILABLE_NO == port->available) continue; if (!port->enabled) continue; if (port->direction == direction && port->name != excludedPortName) { if (!highestPriorityPort || port->priority > (*highestPriorityPort)->priority) { highestPriorityPort = port; } } } return highestPriorityPort; } std::shared_ptr PulseaudioManager::findStreamByPort(const std::optional>& cardInfo, const std::string& portName, int direction) const { if (direction == PA_DIRECTION_OUTPUT) { for (const auto& sink : (*cardInfo)->sinks) { auto ports = std::dynamic_pointer_cast(sink)->getPorts(); for (const auto& port : ports) { if (port->name == portName) { return sink; } } } } else { // 对于source的处理 for (const auto& source : (*cardInfo)->sources) { auto ports = std::dynamic_pointer_cast(source)->getPorts(); for (const auto& port : ports) { if (port->name == portName) { return source; } } } } return nullptr; } std::shared_ptr PulseaudioManager::findHighestPriorityStream(const std::shared_ptr& stream) { std::shared_ptr ret = stream; int priority = 0; auto findHighestPriority = [this, stream, &ret, &priority](const std::list>& streams, auto getActivePortName) { for (const auto& s : streams) { if (s->getName() == stream->getName()) continue; std::string activePortName = getActivePortName(s); auto card = s->getCard(); auto it = std::find_if(m_info.cardInfoList.begin(), m_info.cardInfoList.end(), [card, activePortName](const std::shared_ptr& c) { return card == c->card.index && std::any_of(c->card.ports.begin(), c->card.ports.end(), [activePortName](const std::shared_ptr& info) { return info->enabled && activePortName == info->name && info->available != PA_PORT_AVAILABLE_NO; }); }); if (it == m_info.cardInfoList.end()) continue; int pri = calaPriorityByName(s->getName()); if (!ret || pri > priority) { ret = s; priority = pri; } } }; for (const auto& c : m_info.cardInfoList) { if (NodeType::INPUT == stream->getNodeType()) { findHighestPriority(c->sources, [](const std::shared_ptr& s) { return std::dynamic_pointer_cast(s)->getActivePortName(); }); } else if (NodeType::OUTPUT == stream->getNodeType()) { findHighestPriority(c->sinks, [](const std::shared_ptr& s) { return std::dynamic_pointer_cast(s)->getActivePortName(); }); } } return ret; } bool PulseaudioManager::isDefaultDevice(const std::shared_ptr& s) { if (!s) return false; auto type = s->getNodeType(); if (NodeType::INPUT == type) { return s->getName() == m_info.defaultSource->getName(); } else if (NodeType::OUTPUT == type) { return s->getName() == m_info.defaultSink->getName(); } AUDIO_ERROR("Invaild {} type, unable to determine if device: {} is the default device.", getNodeTypeName(type), s->getName()); return false; } // signal void PulseaudioManager::deviceChanged(const NodeType& type, const std::string& portName, const std::string& cardName) { auto s = foundInfoByName(type, "", "", true); if (nullptr == s) { AUDIO_ERROR("send {} deviceChanged failed, not found such device.", type); return; } s->deviceChanged(); } void PulseaudioManager::portChanged(const NodeType& type, const std::string& name) { } void PulseaudioManager::cardRemoved(const std::string& name) { } void PulseaudioManager::deviceAdjust(const NodeType& type) { AudioMethod::getInstance().deviceAdjust(type); } void PulseaudioManager::addStream(int idx, int value, const std::string& iconName, const std::string& descName) { AudioMethod::getInstance().addStream(idx, value, iconName, descName); } void PulseaudioManager::removeStream(int idx) { AudioMethod::getInstance().removeStream(idx); } void PulseaudioManager::addStream(int dire, int idx, int value, bool mute, const std::string& iconName, const std::string& descName) { AudioMethod::getInstance().addStream(dire, idx, value, mute, iconName, descName); } void PulseaudioManager::removeStream(int dire, int idx) { AudioMethod::getInstance().removeStream(dire, idx); } std::shared_ptr PulseaudioManager::foundStreamByPortName(const std::string& card, const std::string& port) const { } std::shared_ptr PulseaudioManager::foundInfoByName(const NodeType& type, const std::string& name, const std::string& dev, bool general) const { switch (type) { case NodeType::INPUT: { if (general) return m_info.defaultSource; for (const auto& c : m_info.cardInfoList) { if (!general && strcmp(dev.c_str(), c->card.name.c_str())) continue; for(const auto& s : c->sources) { auto it = std::find_if(std::dynamic_pointer_cast(s)->getPorts().begin(), std::dynamic_pointer_cast(s)->getPorts().end(), [name](const std::shared_ptr s) { return s->name == name; }); if (it != std::dynamic_pointer_cast(s)->getPorts().end()) { return s; } } } return m_info.defaultSource; } case NodeType::OUTPUT: { if (general) return m_info.defaultSink; for (const auto& c : m_info.cardInfoList) { if (!general && strcmp(dev.c_str(), c->card.name.c_str())) continue; for(const auto& s : c->sinks) { auto it = std::find_if(std::dynamic_pointer_cast(s)->getPorts().begin(), std::dynamic_pointer_cast(s)->getPorts().end(), [name](const std::shared_ptr s) { return s->name == name; }); if (general || it != std::dynamic_pointer_cast(s)->getPorts().end()) { return s; } } } return m_info.defaultSink; } case NodeType::SINK_INPUT: { for(const auto& si : m_info.sinkInputList) { if (general || 0 == strcmp(si->getName().c_str(), name.c_str())) { return si; } } break; } case NodeType::SOURCE_OUTPUT: { for(const auto& so : m_info.sourceOutputList) { if (general || 0 == strcmp(so->getName().c_str(), name.c_str())) { return so; } } break; } default: break; } return nullptr; } std::shared_ptr PulseaudioManager::foundInfoByName(const NodeType& type, int idx, bool general) const { switch (type) { case NodeType::INPUT: { if (general) return m_info.defaultSource; for (const auto& c : m_info.cardInfoList) { auto it = std::find_if(c->sources.begin(), c->sources.end(), [idx](const std::shared_ptr& s) { return idx == s->getIndex(); }); if (it != c->sources.end()) return *it; } return m_info.defaultSource; } case NodeType::OUTPUT: { if (general) return m_info.defaultSink; for (const auto& c : m_info.cardInfoList) { auto it = std::find_if(c->sinks.begin(), c->sinks.end(), [idx](const std::shared_ptr& s) { return idx == s->getIndex(); }); if (it != c->sources.end()) return *it; } return m_info.defaultSink; } case NodeType::SINK_INPUT: { for(const auto& si : m_info.sinkInputList) { if (idx == si->getIndex()) { return si; } } break; } case NodeType::SOURCE_OUTPUT: { for(const auto& so : m_info.sourceOutputList) { if (idx == so->getIndex()) { return so; } } break; } default: break; } return nullptr; } bool PulseaudioManager::deviceInfosIsEqual(const std::list>& list1, const std::list>& list2) { if (list1.size() != list2.size()) { return false; } auto it1 = list1.begin(); auto it2 = list2.begin(); for (; it1 != list1.end(); ++it1, ++it2) { if (!(*it1) || !(*it2)) { return false; } if (**it1 != **it2) { return false; } } return true; } void PulseaudioManager::init() { if (m_pContext) { AUDIO_DEBUG("pulseAudio is connected"); return; } pa_glib_mainloop* m = pa_glib_mainloop_new(g_main_context_default()); m_pApi = pa_glib_mainloop_get_api(m); pa_proplist* proplist = pa_proplist_new(); pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, "Ukui Media Volume Control"); pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, "org.ukui.media.vucontrol"); pa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, "audio-card"); pa_proplist_sets(proplist, PA_PROP_APPLICATION_VERSION, "PACKAGE_VERSION"); m_pContext = pa_context_new_with_proplist(m_pApi, nullptr, proplist); g_assert(m_pContext); pa_proplist_free(proplist); } bool PulseaudioManager::connect() { pa_context_set_state_callback(m_pContext, contextStateCallback, this); if (pa_context_connect(m_pContext, nullptr, PA_CONTEXT_NOFAIL, nullptr) < 0) { if (pa_context_errno(m_pContext) == PA_ERR_INVALID) { /*w->setConnectingMessage(QObject::tr("Connection to PulseAudio failed. Automatic retry in 5s\n\n" "In this case this is likely because PULSE_SERVER in the Environment/X11 Root Window Properties\n" "or default-server in client.conf is misconfigured.\n" "This situation can also arrise when PulseAudio crashed and left stale details in the X11 Root Window.\n" "If this is the case, then PulseAudio should autospawn again, or if this is not configured you should\n" "run start-pulseaudio-x11 manually.").toUtf8().constData());*/ AUDIO_DEBUG("connect pulseaudio failed"); } else { m_reconnectTime--; if (m_reconnectTime <= 0) { AUDIO_ERROR("reconnect pulseaudio Three times failed"); return false; } // g_timeout_add_seconds(5, connect(), nullptr); } } return false; } void PulseaudioManager::cardCb(pa_context* ctx, const pa_card_info* i, int eol, void* userdata) { PulseaudioManager* manager = static_cast(userdata); if (eol < 0) { if (pa_context_errno(ctx) == PA_ERR_NOENTITY) return; AUDIO_ERROR("Card callback failure"); return; } if (eol > 0) { // decOutstanding(w); return; } auto info = std::make_shared(); info->card = *i; for (const auto& c : manager->m_info.cardInfoList) { if (c->card.index != i->index) continue; if (!manager->deviceInfosIsEqual(info->card.deviceList, c->card.deviceList)) { manager->deviceAdjust(NodeType::OUTPUT); } } auto it = std::find_if(manager->m_info.cardInfoList.begin(), manager->m_info.cardInfoList.end(), [info](const std::shared_ptr i) { return info->card.index == i->card.index; }); if (it == manager->m_info.cardInfoList.end()) { manager->deviceAdjust(NodeType::OUTPUT); } updateData(manager->m_info, info, [](PaDataInfo& data) -> std::optional>>> { return data.cardInfoList; }, [](const std::shared_ptr existing, const std::shared_ptr newCard) { return existing->card.index == newCard->card.index; } ); AUDIO_DEBUG("card cb, card->name={}, card->index={}, info={}", info->card.name, info->card.index, manager->m_info.toString()); } #if HAVE_EXT_DEVICE_RESTORE_API static void ext_device_restore_subscribeCb(pa_context* ctx, pa_device_type_t type, uint32_t idx, void* userdata); #endif void PulseaudioManager::sinkCb(pa_context* ctx, const pa_sink_info* i, int eol, void* userdata) { auto manager = static_cast(userdata); if (eol < 0) { if (pa_context_errno(ctx) == PA_ERR_NOENTITY) { return; } AUDIO_ERROR("Sink callback failure"); return; } if (eol > 0) { return; } std::shared_ptr info = std::make_shared(m_pContext, manager->m_info); std::dynamic_pointer_cast(info)->copy(*i); for (const auto& c : manager->m_info.cardInfoList) { // if (c->card.index != i->card) // continue; auto it = std::find_if(c->sinks.begin(), c->sinks.end(), [info](const std::shared_ptr& s){ return info->getName() == s->getName(); }); if (it != c->sinks.end()) { auto sink = std::dynamic_pointer_cast(*it); auto status = sink->getCombineStatus(); #if 0 /* 移除同步单声道的master音量的设置逻辑 */ auto master = std::dynamic_pointer_cast(info)->getMasterDevice(); // 重设单声道的master device if (std::string::npos != info->getName().find("mono") && (manager->m_info.serverInfo.default_sink_name == "mono" || manager->m_info.serverInfo.default_sink_name == master)) { // 同步设置maser音量 int volume = 0; bool mute = false; for (const auto& c : manager->m_info.cardInfoList) { auto it = std::find_if(c->sinks.begin(), c->sinks.end(), [master](const std::shared_ptr& s){ return master == s->getName(); }); if (it != c->sinks.end()) { volume = paCvolumeToPaValue(i->volume); auto v = paCvolumeToPaValue((*it)->getVolume()); AUDIO_DEBUG("Device: {} master device is: {}, update mute: {} and volume from {} change to {}.", sink->getName(), master, mute, v, volume); (*it)->setMute(i->mute); (*it)->setBalance(0); (*it)->setVolume(volume); break; } } } #endif std::dynamic_pointer_cast(info)->setCombineStatus(status); break; } } { bool isModuleSink = false; auto getSinkist = [&info](PaDataInfo& data) -> std::optional>>> { std::shared_ptr cardInfo = nullptr; // 如果cardInfoList为空,返回空值 if (data.cardInfoList.empty()) { AUDIO_DEBUG("Card info list is empty."); return std::nullopt; } for (auto& c : data.cardInfoList) { if (PA_INVALID_INDEX == info->getCard()) { cardInfo = c; } auto prop = pa_proplist_gets(info->getProplist(), PA_PROP_DEVICE_CLASS); // 如果是filer,则返回当前所在的声卡 auto found = false; if (!prop || 0 == strcmp(prop, "filter")) { for (const auto& s : c->sinks) { if (!cardInfo || 0 == strcmp(data.serverInfo.default_sink_name.c_str(), s->getName().c_str())) { cardInfo = c; } if (0 == strcmp(info->getName().c_str(), s->getName().c_str())) { found = true; break; } } } if (c->card.index == info->getCard() || found) { AUDIO_DEBUG("Found sinks, info->name: {}, info->card: {}, c->card: {}.", info->getName(), info->getCard(), c->card.index); return c->sinks; } } AUDIO_DEBUG("Can not found sink, return default sinks, info->name: {}, info->card: {}.", info->getName(), info->getCard()); return cardInfo->sinks; }; auto isSameSink = [manager, &isModuleSink](std::shared_ptr existing, std::shared_ptr newSink) { if (std::string::npos != newSink->getName().find("mono") || std::string::npos != newSink->getName().find("combined")) { isModuleSink = true; } return existing->getIndex() == newSink->getIndex(); }; std::unique_lock lock(manager->m_info.m_mtx); auto isNew = updateData(manager->m_info, info, getSinkist, isSameSink); if (isNew) { manager->m_info.m_operation = true; manager->m_info.m_cond.notify_one(); std::unique_lock lock(manager->m_moduleMtx); if (isModuleSink) { AUDIO_DEBUG("The module is loaded successfully, which notifies other threads to " "set the default output device to {}.", i->name); manager->m_bModuleReady = true; manager->m_moduleCond.notify_one(); } } } if (0 == g_strcmp0(i->name, manager->m_info.serverInfo.default_sink_name.c_str())) { if (nullptr == manager->m_info.defaultSink) { manager->m_info.defaultSink = std::make_shared(m_pContext, manager->m_info); manager->m_info.defaultSink->copy(*i); } if (manager->m_info.defaultSink && (i->name != manager->m_info.defaultSink->getName() || (i->active_port && i->active_port->name != manager->m_info.defaultSink->getActivePortName()))) { bool found = false; for (const auto& c : manager->m_info.cardInfoList) { auto it = std::find_if(c->sinks.begin(), c->sinks.end(), [manager](const std::shared_ptr& s) { return manager->m_info.defaultSink->getName() == s->getName(); }); if (it != c->sinks.end()) { found = true; auto ports = std::dynamic_pointer_cast(*it)->getPorts(); AUDIO_DEBUG("Found sink {} on database.", manager->m_info.defaultSink->getName()); auto at = std::find_if(ports.begin(), ports.end(), [manager](const std::shared_ptr& p){ return manager->m_info.defaultSink->getActivePort()->name == p->name; }); if (at != ports.end()) { if (PA_PORT_AVAILABLE_NO == (*at)->available /*&& (*at)->priority < i->active_port->priority*/) { manager->m_autoPause = true; AUDIO_DEBUG("The default output device port is changed to {}, and multimedia applications need " "to be paused to play.", (*at)->name); } } else { AUDIO_DEBUG("The {} port cannot be found in the default output device {}. The multimedia application being " "played does not need to be paused.", manager->m_info.defaultSink->getActivePort()->name, (*it)->getName()); manager->m_autoPause = false; } break; } } /** To find the corresponding default sink, you need to determine whether the port has been removed. * If it is not found, it means that the device has been removed. */ if (!found) { AUDIO_DEBUG("The output device {} has been removed, and the multimedia application that is already" " playing needs to be paused.", manager->m_info.defaultSink->getName()); manager->m_autoPause = true; } manager->m_info.defaultSink->copy(*i); manager->m_info.defaultSink->deviceChanged(manager->m_autoPause); if (manager->m_info.defaultSink->getName().starts_with("bluez") && manager->isBluetoothDeviceReady(manager->m_info.defaultSink->getCardName())) { manager->setBluezReadyStatus(true); } if (manager->m_autoPause) manager->m_autoPause = false; } else { manager->m_info.defaultSink->copy(*i); } } AUDIO_DEBUG("sink cb, i: {}", std::dynamic_pointer_cast(info)->toString()); } void PulseaudioManager::sourceCb(pa_context* ctx, const pa_source_info* i, int eol, void* userdata) { auto manager = static_cast(userdata); if (eol < 0) { if (pa_context_errno(ctx) == PA_ERR_NOENTITY) { return; } AUDIO_ERROR("Source callback failure."); return; } if (eol > 0) { // decOutstanding(w); return; } std::shared_ptr masterSource = nullptr; if (manager->m_info.defaultSource) masterSource = manager->getMasterStream(manager->m_info.defaultSource.get(), NodeType::INPUT); if (0 == g_strcmp0(i->name, manager->m_info.serverInfo.default_source_name.c_str()) || (masterSource && 0 == g_strcmp0(i->name, masterSource->getName().c_str()))) { if (nullptr == manager->m_info.defaultSource) { manager->m_info.defaultSource = std::make_shared(m_pContext, manager->m_info); manager->m_info.defaultSource->copy(*i); std::unique_lock lock(manager->m_contextMtx); manager->m_bContexReady = true; manager->m_contextCond.notify_all(); } const auto& defaultSource = manager->m_info.defaultSource; const auto& expectedName = manager->m_info.serverInfo.default_source_name; const bool matchedName = (i->name == expectedName || (masterSource && i->name == masterSource->getName())); const bool nameOutOfSync = matchedName && (i->name != defaultSource->getName()); const bool portOutOfSync = matchedName && i->active_port && (i->active_port->name != defaultSource->getActivePortName() || (masterSource && i->active_port->name != std::dynamic_pointer_cast(masterSource)->getActivePortName())); if (defaultSource && (nameOutOfSync || portOutOfSync)) { if (i->name == manager->m_info.serverInfo.default_source_name || (masterSource && i->name == masterSource->getName())) manager->m_info.defaultSource->copy(*i); else { AUDIO_DEBUG("source cb, input device changed to {}.", i->name); manager->m_info.defaultSource->deviceChanged(); } } else { manager->m_info.defaultSource->copy(*i); } } // 暂时不处理.monitor source if (strstr(i->name, ".monitor")) { return; } std::shared_ptr info = std::make_shared(m_pContext, manager->m_info); std::dynamic_pointer_cast(info)->copy(*i); { bool isModuleSource = false; auto getSourceList = [&info](PaDataInfo& data) -> std::optional>>> { std::shared_ptr cardInfo = nullptr; for (auto& c : data.cardInfoList) { auto prop = pa_proplist_gets(info->getProplist(), PA_PROP_DEVICE_CLASS); // if filter,则找到对应maser_device auto found = false; if (!prop || 0 == strcmp(prop, "filter") || 0 == strcmp(prop, "monitor")) { auto masterDevice = pa_proplist_gets(info->getProplist(), PA_PROP_DEVICE_MASTER_DEVICE); for (const auto& s : c->sources) { if (!cardInfo || 0 == strcmp(data.serverInfo.default_source_name.c_str(), s->getName().c_str())) { cardInfo = c; } if (masterDevice && 0 == strcmp(masterDevice, s->getName().c_str())) { found = true; break; } } } if (c->card.index == info->getCard() || found) { return c->sources; } } if (!cardInfo) { cardInfo = std::make_shared(); } return cardInfo->sources; }; auto isSameSource = [manager, &isModuleSource](std::shared_ptr existing, std::shared_ptr newSource) { if (std::string::npos != newSource->getName().find("noiseReduceSource")) { isModuleSource = true; } return existing->getIndex() == newSource->getIndex(); }; std::unique_lock lock(manager->m_info.m_mtx); auto isNew = updateData(manager->m_info, info, getSourceList, isSameSource); if (isNew) { manager->m_info.m_operation = true; manager->m_info.m_cond.notify_one(); std::unique_lock lock(manager->m_moduleMtx); if (isModuleSource) { AUDIO_DEBUG("The module is loaded successfully, which notifies other threads to " "set the default input device to {}.", i->name); manager->m_bModuleReady = true; manager->m_moduleCond.notify_one(); } } } /** If the default input device is noise reduction, and the active port status of the noise * reduction master device is unavailable, you need to switch to a high-priority input device. */ manager->resetDefaultSource("noiseReduceSource", info); AUDIO_DEBUG("Source cb, i: {}.", std::dynamic_pointer_cast(info)->toString()); } void PulseaudioManager::sinkInputCb(pa_context* ctx, const pa_sink_input_info* i, int eol, void* userdata) { auto manager = static_cast(userdata); if (eol < 0) { if (pa_context_errno(ctx) == PA_ERR_NOENTITY) return; AUDIO_ERROR("Sink input callback failure"); return; } if (eol > 0) { return; } std::shared_ptr info = std::make_shared(m_pContext, manager->m_info); std::dynamic_pointer_cast(info)->copy(*i); auto it = std::find_if(manager->m_info.sinkInputList.begin(), manager->m_info.sinkInputList.end(), [i](const std::shared_ptr s) { return s->getIndex() == i->index; }); // 没找到,说明是新产生的sinkinput if (it == manager->m_info.sinkInputList.end()) { StreamMediaIntegrationInfo streamInfo; MediaInfo mInfo; streamInfo.mediaRole = std::dynamic_pointer_cast(info)->getMediaRole(); streamInfo.iconName = std::dynamic_pointer_cast(info)->getAppIconName(); streamInfo.binary = std::dynamic_pointer_cast(info)->getProcessBinary(); auto description = AudioMethod::getInstance().getStreamDescription(streamInfo.binary); mInfo.direction = 1; mInfo.index = i->index; mInfo.mute = i->mute; mInfo.volume = paCvolumeToValue(info->getVolume()); mInfo.name = description != "" ? description : info->getName(); auto it = std::find_if(manager->m_streamMediaList.begin(), manager->m_streamMediaList.end(), [streamInfo](const StreamMediaIntegrationInfo& i) { return streamInfo.binary == i.binary; }); /** If the same binary audio stream is found, it means that they are generated * by the same binary and need to be stored in mediaInfoList */ if (it != manager->m_streamMediaList.end()) { (*it).mediaInfoList.emplace_back(mInfo); } else { streamInfo.mediaInfoList.emplace_back(mInfo); manager->m_streamMediaList.emplace_back(streamInfo); } manager->addStream(info->getIndex(), mInfo.volume, streamInfo.iconName, mInfo.name); manager->addStream(mInfo.direction, mInfo.index, mInfo.volume, mInfo.mute, streamInfo.iconName, mInfo.name); } else { auto v = (*it)->getVolume(); if (!pa_cvolume_equal(&i->volume, &v)) { (*it)->volumeChanged(paCvolumeToValue(i->volume)); } auto mute = (*it)->getMute(); if (mute != i->mute) { (*it)->muteChanged(i->mute); } } updateData(manager->m_info, info, [](PaDataInfo& data) -> std::optional>>> { return data.sinkInputList; }, [](std::shared_ptr oldSi, std::shared_ptr newSi) { return oldSi->getIndex() == newSi->getIndex(); } ); AUDIO_DEBUG("Sink input cb, i: {}.", std::dynamic_pointer_cast(info)->toString()); } void PulseaudioManager::sourceOutputCb(pa_context* ctx, const pa_source_output_info* i, int eol, void* userdata) { auto manager = static_cast(userdata); if (eol < 0) { if (pa_context_errno(ctx) == PA_ERR_NOENTITY) return; AUDIO_ERROR("Source output callback failure"); return; } if (eol > 0) { return; } if (i->source == PA_ID_INVALID) return; std::shared_ptr info = std::make_shared(m_pContext, manager->m_info); std::dynamic_pointer_cast(info)->copy(*i); auto it = std::find_if(manager->m_info.sourceOutputList.begin(), manager->m_info.sourceOutputList.end(), [i](const std::shared_ptr s) { return s->getIndex() == i->index; }); // 没找到,说明是新产生的sinkinput if (it == manager->m_info.sourceOutputList.end()) { StreamMediaIntegrationInfo streamInfo; MediaInfo mInfo; streamInfo.mediaRole = std::dynamic_pointer_cast(info)->getMediaRole(); streamInfo.iconName = std::dynamic_pointer_cast(info)->getAppIconName(); streamInfo.binary = std::dynamic_pointer_cast(info)->getProcessBinary(); auto description = AudioMethod::getInstance().getStreamDescription(streamInfo.binary); mInfo.direction = 0; mInfo.index = i->index; mInfo.volume = paCvolumeToValue(info->getVolume()); mInfo.mute = i->mute; mInfo.name = description != "" ? description : info->getName(); auto it = std::find_if(manager->m_streamMediaList.begin(), manager->m_streamMediaList.end(), [streamInfo](const StreamMediaIntegrationInfo& i) { return streamInfo.binary == i.binary; }); /** If the same binary audio stream is found, it means that they are generated * by the same binary and need to be stored in mediaInfoList */ if (it != manager->m_streamMediaList.end()) { (*it).mediaInfoList.emplace_back(mInfo); } else { streamInfo.mediaInfoList.emplace_back(mInfo); manager->m_streamMediaList.emplace_back(streamInfo); } manager->addStream(mInfo.direction, mInfo.index, mInfo.volume, mInfo.mute, streamInfo.iconName, mInfo.name); } else { auto v = (*it)->getVolume(); if (!pa_cvolume_equal(&i->volume, &v)) { (*it)->volumeChanged(paCvolumeToValue(i->volume)); } auto mute = (*it)->getMute(); if (mute != i->mute) { (*it)->muteChanged(i->mute); } } updateData(manager->m_info, info, [](PaDataInfo& data) -> std::optional>>> { return data.sourceOutputList; }, [](std::shared_ptr oldValue, std::shared_ptr newVaule) { return oldValue->getIndex() == newVaule->getIndex(); } ); AUDIO_DEBUG("Source output cb, i: {}.", std::dynamic_pointer_cast(info)->toString()); } void PulseaudioManager::clientCb(pa_context* ctx, const pa_client_info* i, int eol, void* userdata) { auto manager = static_cast(userdata); if (eol < 0) { if (pa_context_errno(ctx) == PA_ERR_NOENTITY) return; AUDIO_ERROR("Client callback failure"); return; } if (eol > 0) { // decOutstanding(w); return; } AUDIO_DEBUG("clientCb, client->name: {}", i->name); // manager->updateClient(*i); } void PulseaudioManager::serverInfoCb(pa_context* ctx, const pa_server_info* i, void* userdata) { auto manager = static_cast(userdata); if (!i) { AUDIO_ERROR("Server info callback failure"); return; } // 如果只是做sink切换,是不会触发sink回调的,此时我们需要更新default sink以及发出设备更改的信号 if (manager->m_info.serverInfo.default_sink_name != i->default_sink_name) { bool found = false; for (const auto&c : manager->m_info.cardInfoList) { auto at = std::find_if(c->sinks.begin(), c->sinks.end(), [manager](const std::shared_ptr s) { return manager->m_info.defaultSink->getName() == s->getName(); }); /** * If it is not found, it means that the default output device has been removed * and the multimedia application needs to be paused. If it is found, it means * that the default device has been manually switched and no pause is needed. */ if (at != c->sinks.end()) { AUDIO_DEBUG("Find the last default output device without pausing multimedia applications."); manager->m_autoPause = false; break; } else { AUDIO_DEBUG("The last default output device was removed, and the multimedia application needs to be paused."); manager->m_autoPause = true; } } for (const auto& c : manager->m_info.cardInfoList) { auto it = std::find_if(c->sinks.begin(), c->sinks.end(), [i](const std::shared_ptr s) { return s->getName() == i->default_sink_name; }); // 更新default sink if (it != c->sinks.end()) { found = true; manager->m_info.defaultSink = std::dynamic_pointer_cast(*it); break; } } if (manager->m_info.defaultSink && "mono" != manager->m_info.defaultSink->getName()) { for (const auto& c : manager->m_info.cardInfoList) { auto mute = manager->m_info.defaultSink->getMute(); auto volume = manager->m_info.defaultSink->getVolume(); // 当设备发生变更且存在单声道设备时,同步当前设备音量到单声道音频 auto at = std::find_if(c->sinks.begin(), c->sinks.end(), [i](const std::shared_ptr s) { return s->getName() == "mono"; }); if (at != c->sinks.end()) { int value = paCvolumeToPaValue(volume); AUDIO_DEBUG("Default device changed to: {}, set {} volume to {}.", manager->m_info.defaultSink->getName(), (*at)->getName(), value); (*at)->setMute(mute); (*at)->setVolume(value); break; } } } if (found && strcmp(i->default_sink_name, "mono")) { AUDIO_DEBUG("serverinfo cb, mono to deviceChanged"); manager->m_info.defaultSink->deviceChanged(manager->m_autoPause); } if (manager->m_autoPause) manager->m_autoPause = false; AUDIO_DEBUG("Default sink from {} change to {}.", manager->m_info.serverInfo.default_sink_name, i->default_sink_name); } if (manager->m_info.serverInfo.default_source_name != i->default_source_name) { for (const auto& c : manager->m_info.cardInfoList) { auto it = std::find_if(c->sources.begin(), c->sources.end(), [i](const std::shared_ptr s) { return s->getName() == i->default_source_name; }); // 更新default source if (it != c->sources.end()) { manager->m_info.defaultSource = std::dynamic_pointer_cast(*it); AUDIO_DEBUG("serverinfo cb, defaultSource to deviceChanged"); manager->m_info.defaultSource->deviceChanged(); break; } } AUDIO_DEBUG("Default source from {} change to {}.", manager->m_info.serverInfo.default_source_name, i->default_source_name); } manager->m_info.serverInfo = *i; AUDIO_DEBUG("serverInfoCb, default sink name: {}, default source name: {}", i->default_sink_name, i->default_source_name); } //void PulseaudioManager::getBatteryLevel(std::string dev) //{ // pa_operation *o; // if (!(o = pa_context_get_card_info_by_name(getContext(), dev.toLatin1().data(), batteryLevelCb, this))) { // std::cout << "pa_context_get_card_info_by_index() failed"; // return ; // } // pa_operation_unref(o); //} void PulseaudioManager::extStreamRestoreReadCb( pa_context* ctx, const pa_ext_stream_restore_info* i, int eol, void* userdata) { auto manager = static_cast(userdata); if (eol < 0) { // decOutstanding(w); AUDIO_ERROR("Failed to initialize stream_restore extension ={}", pa_strerror(pa_context_errno(ctx))); return; } if (eol > 0) { // decOutstanding(w); return; } } void PulseaudioManager::extStreamRestoreSubscribeCb(pa_context* ctx, void* userdata) { auto manager = static_cast(userdata); pa_operation* o; if (!(o = pa_ext_stream_restore_read(ctx, extStreamRestoreReadCb, manager))) { AUDIO_ERROR("pa_ext_stream_restore_read() failed"); return; } AUDIO_DEBUG("extStreamRestoreSubscribeCb"); pa_operation_unref(o); } #if HAVE_EXT_DEVICE_RESTORE_API void ext_device_restore_read_cb( pa_context *ctx, const pa_ext_device_restore_info *i, int eol, void *userdata) { PulseaudioManager* manager = static_cast(userdata); if (eol < 0) { manager->decOutstanding(manager); AUDIO_ERROR("Failed to initialize device restore extension: ={}", pa_strerror(pa_context_errno(ctx)); return; } if (eol > 0) { return; } /* Do something with a widget when this part is written */ manager->updateDeviceInfo(*i); } static void ext_device_restore_subscribeCb(pa_context* ctx, pa_device_type_t type, uint32_t idx, void* userdata) { PulseaudioManager* manager = static_cast(userdata); pa_operation* o; if (type != PA_DEVICE_TYPE_SINK) return; if (!(o = pa_ext_device_restore_read_formats(ctx, type, idx, ext_device_restore_read_cb, manager))) { AUDIO_ERROR("pa_ext_device_restore_read_sink_formats() failed")); return; } pa_operation_unref(o); } #endif void PulseaudioManager::extDeviceManagerReadCb( pa_context* ctx, const pa_ext_device_manager_info*, int eol, void* userdata) { auto manager = static_cast(userdata); if (eol < 0) { // decOutstanding(w); AUDIO_ERROR("Failed to initialize device manager extension: ={}", pa_strerror(pa_context_errno(ctx))); return; } // w->canRenameDevices = true; if (eol > 0) { // decOutstanding(w); return; } AUDIO_DEBUG("extDeviceManagerReadCb"); /* Do something with a widget when this part is written */ } void PulseaudioManager::extDeviceManagerSubscribeCb(pa_context* ctx, void *userdata) { auto manager = static_cast(userdata); pa_operation* o; if (!(o = pa_ext_device_manager_read(ctx, extDeviceManagerReadCb, manager))) { AUDIO_ERROR("pa_ext_device_manager_read() failed"); return; } AUDIO_DEBUG("extDeviceManagerSubscribeCb"); pa_operation_unref(o); } void PulseaudioManager::subscribeCb(pa_context* ctx, pa_subscription_event_type_t t, uint32_t index, void *userdata) { auto manager = static_cast(userdata); switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { case PA_SUBSCRIPTION_EVENT_SINK: { if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE){ AUDIO_DEBUG("Sink removed index: {}.", index); for (const auto& c : manager->m_info.cardInfoList) { auto it = std::find_if(c->sinks.begin(), c->sinks.end(), [index](const std::shared_ptr& s) { return s->getIndex() == index; }); if (it != c->sinks.end()) { // 如果是组合设备被移除,则需要卸载模块 AUDIO_DEBUG("Remove {} sink, name : {}.", index, (*it)->getName()); if (std::dynamic_pointer_cast(*it)->getCombineStatus()) { AudioMethod::getInstance().unloadModule("module-combine-sink"); manager->deviceAdjust(NodeType::OUTPUT); } if ((*it)->getName().compare("mono") == 0) { manager->removeModuleInfoByName("module-remap-sink"); } c->sinks.erase(it); } else { AUDIO_ERROR("Card {} not found {} index sink.", c->card.index, index); } } } else { pa_operation* o; if (!(o = pa_context_get_sink_info_by_index(ctx, index, sinkCb, manager))) { AUDIO_ERROR("pa_context_get_sink_info_by_index() failed"); return; } pa_operation_unref(o); } break; } case PA_SUBSCRIPTION_EVENT_SOURCE: { if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { for (const auto& c : manager->m_info.cardInfoList) { auto it = std::find_if(c->sources.begin(), c->sources.end(), [index](const std::shared_ptr s) { return s->getIndex() == index; }); if (it != c->sources.end()) { AUDIO_DEBUG("Remove {} source, name : {}.", index, (*it)->getName()); c->sources.erase(it); // break; } else { AUDIO_ERROR("Card {} not found {} index source.", c->card.index, index); } } } else { pa_operation* o; if (!(o = pa_context_get_source_info_by_index(ctx, index, sourceCb, manager))) { AUDIO_ERROR("pa_context_get_source_info_by_index() failed"); return; } pa_operation_unref(o); } break; } case PA_SUBSCRIPTION_EVENT_SINK_INPUT: { if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { auto it = std::find_if(manager->m_info.sinkInputList.begin(), manager->m_info.sinkInputList.end(), [index](const std::shared_ptr s) { return s->getIndex() == index; }); if (it != manager->m_info.sinkInputList.end()) { auto binary = std::dynamic_pointer_cast(*it)->getProcessBinary(); manager->removeStreamMedia(binary, 1, index); manager->m_info.sinkInputList.erase(it); AUDIO_DEBUG("Sink input {} removed.", index); } else { AUDIO_ERROR("Can not found {} index sink input.", index); } manager->removeStream(index); manager->removeStream(1, index); } else { pa_operation* o; if (!(o = pa_context_get_sink_input_info(ctx, index, sinkInputCb, manager))) { AUDIO_ERROR("pa_context_get_sink_input_info() failed."); return; } pa_operation_unref(o); } break; } case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT: { if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { auto it = std::find_if(manager->m_info.sourceOutputList.begin(), manager->m_info.sourceOutputList.end(), [index](const std::shared_ptr s) { return s->getIndex() == index; }); if (it != manager->m_info.sourceOutputList.end()) { auto binary = std::dynamic_pointer_cast(*it)->getProcessBinary(); manager->removeStreamMedia(binary, 0, index); manager->m_info.sourceOutputList.erase(it); AUDIO_DEBUG("Source output {} removed.", index); } else { AUDIO_ERROR("Can not found {} index source output.", index); } manager->removeStream(0, index); } else { pa_operation* o; if (!(o = pa_context_get_source_output_info(ctx, index, sourceOutputCb, manager))) { AUDIO_ERROR("pa_context_get_sink_input_info() failed"); return; } pa_operation_unref(o); } break; } case PA_SUBSCRIPTION_EVENT_CLIENT: { if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { // w->removeClient(index); } else { pa_operation* o; if (!(o = pa_context_get_client_info(ctx, index, clientCb, manager))) { AUDIO_ERROR("pa_context_get_client_info() failed"); return; } pa_operation_unref(o); } break; } case PA_SUBSCRIPTION_EVENT_SERVER: { pa_operation* o; if (!(o = pa_context_get_server_info(ctx, serverInfoCb, manager))) { AUDIO_ERROR("pa_context_get_server_info() failed"); return; } pa_operation_unref(o); break; } case PA_SUBSCRIPTION_EVENT_CARD: { if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { auto it = std::find_if(manager->m_info.cardInfoList.begin(), manager->m_info.cardInfoList.end(), [index](const std::shared_ptr c) { return c->card.index == index; }); if (it != manager->m_info.cardInfoList.end()) { manager->m_info.cardInfoList.erase(it); } else { AUDIO_ERROR("Can not found {} index card.", index); } manager->deviceAdjust(NodeType::OUTPUT); } else { pa_operation* o; if (!(o = pa_context_get_card_info_by_index(ctx, index, cardCb, manager))) { AUDIO_ERROR("pa_context_get_card_info_by_index() failed"); return; } pa_operation_unref(o); } break; } default: break; } } void PulseaudioManager::contextStateCallback(pa_context* ctx, void* userdata) { auto manager = static_cast(userdata); g_assert(ctx); switch (pa_context_get_state(ctx)) { case PA_CONTEXT_UNCONNECTED: case PA_CONTEXT_CONNECTING: case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_SETTING_NAME: break; case PA_CONTEXT_READY: { pa_operation* o; AUDIO_DEBUG("pa context state change to ={}", pa_context_get_state(ctx)); manager->m_reconnectTime = 3; pa_context_set_subscribe_callback(ctx, subscribeCb, manager); if (!(o = pa_context_subscribe(ctx, (pa_subscription_mask_t) (PA_SUBSCRIPTION_MASK_SINK| PA_SUBSCRIPTION_MASK_SOURCE| PA_SUBSCRIPTION_MASK_SINK_INPUT| PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT| PA_SUBSCRIPTION_MASK_CLIENT| PA_SUBSCRIPTION_MASK_SERVER| PA_SUBSCRIPTION_MASK_CARD), nullptr, nullptr))) { AUDIO_ERROR("pa_context_subscribe() failed."); return; } pa_operation_unref(o); if (!(o = pa_context_get_server_info(ctx, serverInfoCb, manager))) { AUDIO_ERROR("pa_context_get_server_info() failed."); return; } pa_operation_unref(o); if (!(o = pa_context_get_client_info_list(ctx, clientCb, manager))) { AUDIO_ERROR("pa_context_client_info_list() failed."); return; } pa_operation_unref(o); if (!(o = pa_context_get_card_info_list(ctx, cardCb, manager))) { AUDIO_ERROR("pa_context_get_card_info_list() failed."); return; } pa_operation_unref(o); if (!(o = pa_context_get_sink_info_list(ctx, sinkCb, manager))) { AUDIO_ERROR("pa_context_get_sink_info_list() failed."); return; } pa_operation_unref(o); if (!(o = pa_context_get_source_info_list(ctx, sourceCb, manager))) { AUDIO_ERROR("pa_context_get_source_info_list() failed."); return; } pa_operation_unref(o); if (!(o = pa_context_get_sink_input_info_list(ctx, sinkInputCb, manager))) { AUDIO_ERROR("pa_context_get_sink_input_info_list() failed."); return; } pa_operation_unref(o); if (!(o = pa_context_get_source_output_info_list(ctx, sourceOutputCb, manager))) { AUDIO_ERROR("pa_context_get_source_output_info_list() failed."); return; } pa_operation_unref(o); if (!(o = pa_context_get_module_info_list(ctx, moduleInfoCb, manager))) { AUDIO_ERROR("pa_context_get_module_info_list() failed."); return; } pa_operation_unref(o); break; } case PA_CONTEXT_FAILED: pa_context_unref(manager->m_pContext); manager->m_pContext = nullptr; AUDIO_ERROR("Connection failed, attempting reconnect."); manager->m_reconnectTime--; if (manager->m_reconnectTime <= 0) { AUDIO_DEBUG("reconnect pulseaudio Three times failed."); return; } // g_timeout_add_seconds(5, connectToPulse, w); return; case PA_CONTEXT_TERMINATED: default: return; } } void PulseaudioManager::moduleInfoCb(pa_context* ctx, const pa_module_info* i, int eol, void *userdata) { if (!i) return; if (eol < 0) { if (pa_context_errno(ctx) == PA_ERR_NOENTITY) return; AUDIO_ERROR("moduleInfoCb callback failure."); return; } auto manager = static_cast(userdata); PaModuleInfo info; memset(&info, 0, sizeof(PaModuleInfo)); info = *i; updateData(manager->m_info, info, [](PaDataInfo& data) -> std::optional>> { return data.moduleInfoList; }, [](const PaModuleInfo& existing, const PaModuleInfo& newCard) { return existing.index == newCard.index; } ); AUDIO_DEBUG("Module info cb, info: {}.", info.toString()); } std::vector PulseaudioManager::parseSlaves(const std::string& config) { std::vector tokens; std::string token; std::istringstream tokenStream(config); while (std::getline(tokenStream, token, '=')) { if (token == "slaves") { std::getline(tokenStream, token, ','); // 读取第一个sink token.erase(0, token.find_first_not_of(" \t\n\r\f\v")); // 去除前导空格 token.erase(token.find_last_not_of(" \t\n\r\f\v") + 1); // 去除尾随空格 if (!token.empty()) { tokens.push_back(token); } // 继续读取剩余的sink,直到没有更多的分隔符 while (std::getline(tokenStream, token, ',')) { token.erase(0, token.find_first_not_of(" \t\n\r\f\v")); // 去除前导空格 token.erase(token.find_last_not_of(" \t\n\r\f\v") + 1); // 去除尾随空格 if (!token.empty()) { tokens.push_back(token); } } // 如果最后一个sink后面没有逗号,也会被正确处理 if (tokenStream) { std::getline(tokenStream, token); token.erase(0, token.find_first_not_of(" \t\n\r\f\v")); // 去除前导空格 token.erase(token.find_last_not_of(" \t\n\r\f\v") + 1); // 去除尾随空格 if (!token.empty()) { tokens.push_back(token); } } } } return tokens; } bool PulseaudioManager::isExitEnableDevice(const NodeType& type) { auto ports = getAvailablePortList(type); auto it = std::find_if(ports.begin(), ports.end(), [](const std::shared_ptr& info){ return info->enabled; }); return it != ports.end(); } void PulseaudioManager::resetDefaultSource(const std::string& name, const std::shared_ptr& info) { if (m_info.serverInfo.default_source_name == name) { std::shared_ptr source = nullptr; for (const auto& c : m_info.cardInfoList) { auto s = std::find_if(c->sources.begin(), c->sources.end(), [this](const std::shared_ptr& s) { return s->getName() == m_info.serverInfo.default_source_name; }); if (s != c->sources.end()) { auto masterDevice = std::dynamic_pointer_cast(*s)->getMasterDevice(); if (masterDevice != info->getName()) break; auto it = std::find_if(c->sources.begin(), c->sources.end(), [masterDevice](const std::shared_ptr& s) { return masterDevice == s->getName(); }); if (it != c->sources.end()) { source = *it; } break; } } if (source) { auto port = std::dynamic_pointer_cast(source)->getActivePort(); if (port && port->available == PA_PORT_AVAILABLE_NO) { auto defaultSouce = findHighestPriorityStream(source); AUDIO_DEBUG("noiseReduceSource master device {} active port become not available, set default source to {}.", info->getName(), defaultSouce->getName()); pa_context_set_default_source(m_pContext, defaultSouce->getName().c_str(), nullptr, nullptr); } } } } void PulseaudioManager::removeStreamMedia(const std::string& binary, int direction, int idx) { auto it = std::find_if(m_streamMediaList.begin(), m_streamMediaList.end(), [binary](const StreamMediaIntegrationInfo& i) { return binary == i.binary; }); if (it != m_streamMediaList.end()) { if ((*it).mediaInfoList.size() > 1) { std::erase_if((*it).mediaInfoList, [direction, idx](const MediaInfo& i) { return direction == i.direction && idx == i.index; }); } else { m_streamMediaList.erase(it); } } } void PulseaudioManager::removeModuleInfoByName(const std::string& name) { auto it = std::find_if(m_info.moduleInfoList.begin(), m_info.moduleInfoList.end(), [name](const PaModuleInfo& info) { return name == info.name; }); if (it != m_info.moduleInfoList.end()) { AUDIO_DEBUG("Module {} has removed.", name); m_info.moduleInfoList.erase(it); } else { AUDIO_ERROR("Module {} not found in module list.", name); } } std::shared_ptr PulseaudioManager::getMasterStream(const IStream* filter, NodeType type) const { const std::string masterName = (type == NodeType::INPUT) ? static_cast(filter)->getMasterDevice() : static_cast(filter)->getMasterDevice(); if (masterName.empty()) return nullptr; for (const auto& card : m_info.cardInfoList) { const auto& candidates = (type == NodeType::INPUT) ? card->sources : card->sinks; if (auto m = findStream(candidates, masterName)) return m; } return nullptr; } std::string PulseaudioManager::getBlueMediaTransportPath(GDBusConnection* connection , const std::string& deviceMac) { std::string transportPath = ""; GError* error = nullptr; GVariant* result = nullptr; // 1. 调用ObjectManager的GetManagedObjects方法 result = g_dbus_connection_call_sync(connection, "org.bluez", "/", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects", nullptr, G_VARIANT_TYPE("(a{oa{sa{sv}}})"), G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error); if (!result) { AUDIO_WARN("GetManagedObjects failed in bluez bus, error: {}.", error->message); g_error_free(error); return ""; } // 2. 解析返回的对象数组 GVariant* objectsDict; g_variant_get(result, "(@a{oa{sa{sv}}})", &objectsDict); GVariantIter iter; g_variant_iter_init(&iter, objectsDict); char* objectPath; GVariant* interfacesDict; // 3. 遍历所有对象路径和接口 while (g_variant_iter_next(&iter, "{&o@a{sa{sv}}}", &objectPath, &interfacesDict)) { GVariantIter interfacesIter; g_variant_iter_init(&interfacesIter, interfacesDict); char* interfaceName; bool found = false; // 4. 检查每个对象的接口 while (g_variant_iter_next(&interfacesIter, "{&s@a{sv}}", &interfaceName, nullptr)) { // 5. 寻找org.bluez.MediaTransport1接口 if (std::string(interfaceName) == "org.bluez.MediaTransport1") { // 6. 检查对象路径是否包含目标设备MAC地址 if (std::string(objectPath).find(deviceMac) != std::string::npos) { transportPath = objectPath; found = true; break; } } } if (found) break; } g_variant_unref(objectsDict); g_variant_unref(interfacesDict); g_variant_unref(result); return transportPath; } bool PulseaudioManager::isBluetoothDeviceReady(const std::string& cardName) { if (!cardName.starts_with("bluez")) { AUDIO_ERROR("Device '{}' is not a Bluetooth device.", cardName); return false; } size_t dotPos = cardName.find ('.'); if (dotPos == std::string::npos || dotPos + 1 >= cardName.size ()) { AUDIO_ERROR ("Invalid bluetooth cardName format: {}.", cardName); return false; } std::string targetMac = cardName.substr (dotPos + 1); bool isReady = false; GError* error = nullptr; GVariant* result = nullptr; GDBusConnection* connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, &error); if (!connection) { AUDIO_ERROR("Can't not connect to D-Bus, Error: {}.", error->message); g_error_free(error); return false; } std::string transportPath = getBlueMediaTransportPath(connection, targetMac); if (transportPath.empty()) { AUDIO_ERROR("Please check device {} is support AVRCP.", targetMac); return false; } else { result = g_dbus_connection_call_sync(connection, "org.bluez", transportPath.c_str(), "org.freedesktop.DBus.Properties", "Get", g_variant_new("(ss)", "org.bluez.MediaTransport1", "Volume"), G_VARIANT_TYPE("(v)"), G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error); if (!result) { // 获取属性失败,可能是该属性不存在(例如设备未连接或不支持) AUDIO_ERROR("Get device volume property failed, Error: {}.", error->message); g_error_free(error); return false; } else { isReady = true; } GVariant* volumeVariant; g_variant_get(result, "(v)", &volumeVariant); if (g_variant_is_of_type(volumeVariant, G_VARIANT_TYPE_UINT16)) { guint16 volume = g_variant_get_uint16(volumeVariant); AUDIO_DEBUG("Device {} a2dp volume is {}.", cardName, volume); } else { gchar* valueStr = g_variant_print(volumeVariant, TRUE); AUDIO_DEBUG("Device {} a2dp volume is {}.", cardName, valueStr); g_free(valueStr); } g_variant_unref(volumeVariant); g_variant_unref(result); } return isReady; } } ukui-volume-control/backend/BaseType.h0000664000175000017500000000271315171074712016724 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 BASETYPE_H #define BASETYPE_H #include #include #include #include #include "simple_logger/Formatter.h" namespace UkuiAudioFramwork { enum class Module { Service = 0, Tray, ControlCenter }; // enum class BackendType { PULSEAUDIO = 0, PIPEWIRE, ALSA, JACK, UNKNOWN }; enum class VolumeType { BASE_VOLUME = 0, BALANCE_VOLUME }; enum class NodeType { INPUT = 0, OUTPUT, SINK_INPUT, SOURCE_OUTPUT }; enum class PaDataType { CARD = 0, SINK, SOURCE, SINK_INPUT, SOURCE_OUTPUT, MODULE }; typedef struct NodeInfo { uint32_t index; uint8_t channels; } NodeInfo; } #endif // BASETYPE_H ukui-volume-control/backend/CustomAudioVolumeJson.h0000664000175000017500000000257515171074712021474 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 CUSTOMAUDIOVOLUMEJSON_H #define CUSTOMAUDIOVOLUMEJSON_H #include #include #include "IJson.h" namespace UkuiAudioFramwork { #define CUSTOM_AUDIO_VOLUME_JSON "/.config/ukui-volume-control/custom-audio-volume.json" typedef struct AudioVolumeInfo { std::string portName; std::string cardName; uint32_t volume; }AudioVolumeInfo; class CustomAudioVolumeJson : public IJson { public: CustomAudioVolumeJson(); public: virtual bool isContain(const AudioVolumeInfo&) const override; virtual void insert(const AudioVolumeInfo&) override; }; } #endif // CUSTOMAUDIOVOLUMEJSON_H ukui-volume-control/backend/CustomAudioVolumeJson.cpp0000664000175000017500000000316115171074712022017 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "CustomAudioVolumeJson.h" #include #include #include #include namespace UkuiAudioFramwork { CustomAudioVolumeJson::CustomAudioVolumeJson() { m_fileName = getenv("HOME"); m_fileName += CUSTOM_AUDIO_VOLUME_JSON; loadJson(); } bool CustomAudioVolumeJson::isContain(const AudioVolumeInfo& info) const { const auto& card = m_json.find(info.cardName); if (card != m_json.end()) { const auto& port = (*card).find(info.portName); return port != (*card).end(); } return false; } void CustomAudioVolumeJson::insert(const AudioVolumeInfo& info) { nlohmann::json& ports = m_json[info.cardName]; if (ports.find(info.portName) == ports.end()) { ports[info.portName] = { {"name", info.portName}, {"volume", info.volume} }; } writeToJson(); } } ukui-volume-control/backend/PulseaudioManager.h0000664000175000017500000002200215171074712020606 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 PULSEAUDIOMANAGER_H #define PULSEAUDIOMANAGER_H #include #include #include #include #include #include #include #include #include #include "Util.h" #include "AudioMethod.h" #include "AudioContext.h" #include "AudioManager.h" #include "PaSinkInputStream.h" #include "PaSourceOutputStream.h" namespace UkuiAudioFramwork { #define PA_ID_INVALID ((uint32_t)0xffffffff) class PulseaudioManager : public AudioManager { public: PulseaudioManager(int); ~PulseaudioManager() = default; public: virtual void init() override; virtual bool connect() override; // Interface virtual void loadModule(const std::string&, const std::string&) override; virtual void unloadModule(const std::string&) override; // Method virtual void setDetailVolume(const NodeType&, const std::string&, const std::string&, int) override; virtual int getDetailVolume(const NodeType&, const std::string&, const std::string&) const override; virtual void setVolume(const NodeType&, int, int) override; virtual int getVolume(const NodeType&, int) const override; virtual void setDetailMute(const NodeType&, const std::string&, const std::string&, bool) override; virtual bool getDetailMute(const NodeType&, const std::string&, const std::string&) const override; virtual void setBalance(const NodeType&, double) override; virtual double getBalance(const NodeType&) const override; virtual void setMute(const NodeType&, int, bool) override; virtual bool getMute(const NodeType&, int) const override; virtual DefaultDeviceInfo getDefaultDevice(const NodeType&, int) const override; virtual void setDefaultDevice(const NodeType&, int, const std::string&, const std::string&) override; virtual std::vector getDeviceList(const NodeType&) const override; virtual std::list getSinkList() const override; virtual std::list getSourceList() const override; virtual std::list> getAvailablePortList(const NodeType&) const override; virtual std::list getSinkInputList() const override; virtual std::list getSourceOutputList() const override; virtual std::list getStreamMediaList() const override; virtual std::list getModuleList() override; virtual void setEnabled(std::shared_ptr, const std::string&, const std::string&, bool) override; virtual bool isEnabled(const std::string&, const std::string&) const override; virtual bool isLoaded(const ModuleType&) const override; virtual bool isValidDevice(const NodeType&) const override; // signal 不需要声明成虚函数只需要内部调用,需要修改 virtual void deviceChanged(const NodeType&, const std::string&, const std::string&) override; virtual void deviceAdjust(const NodeType&) override; virtual void portChanged(const NodeType&, const std::string&) override; virtual void cardRemoved(const std::string&) override; virtual void addStream(int, int, const std::string&, const std::string&) override; virtual void removeStream(int) override; virtual void addStream(int, int, int, bool, const std::string&, const std::string&) override; virtual void removeStream(int, int) override; std::shared_ptr foundStreamByPortName(const std::string&, const std::string&) const; private: std::shared_ptr foundInfoByName(const NodeType&, const std::string&, const std::string& = "", bool = false) const; std::shared_ptr foundInfoByName(const NodeType&, int = -1, bool = false) const; bool deviceInfosIsEqual(const std::list>&, const std::list>&); std::optional> findCardByName(const std::string&) const; std::optional> findPortByName(const std::shared_ptr&, const std::string&) const; bool streamIsExitPort(const std::shared_ptr&, const std::string&) const; std::optional> findCommonProfile(const std::shared_ptr&, const std::shared_ptr&) const; std::shared_ptr findHighestPriorityAvailableProfile(const std::vector>&) const; std::optional> findHighestPriorityAvailablePort(const std::optional>&, int, const std::string&) const; std::shared_ptr findStreamByPort(const std::optional>&, const std::string&, int) const; std::shared_ptr findHighestPriorityStream(const std::shared_ptr&); bool isDefaultDevice(const std::shared_ptr&); std::vector parseSlaves(const std::string&); bool isExitEnableDevice(const NodeType&); void resetDefaultSource(const std::string&, const std::shared_ptr&); void removeStreamMedia(const std::string&, int, int); void removeModuleInfoByName(const std::string&); std::shared_ptr getMasterStream(const IStream* filter, NodeType type) const; std::string getBlueMediaTransportPath(GDBusConnection*, const std::string&); bool isBluetoothDeviceReady(const std::string&); private: static void contextStateCallback(pa_context*, void*); static void cardCb(pa_context*, const pa_card_info*, int eol, void*); static void simple_callback(pa_context*, int, void*); static void sinkCb(pa_context*, const pa_sink_info*, int, void*); static void sourceCb(pa_context*, const pa_source_info*, int, void*); static void sinkInputCb(pa_context*, const pa_sink_input_info*, int, void*); static void sourceOutputCb(pa_context*, const pa_source_output_info*, int, void*); static void clientCb(pa_context*, const pa_client_info*, int, void*); static void serverInfoCb(pa_context*, const pa_server_info*, void*); static void extStreamRestoreReadCb(pa_context*,const pa_ext_stream_restore_info*,int,void*); static void extStreamRestoreSubscribeCb(pa_context*, void*); static void extDeviceManagerReadCb(pa_context*,const pa_ext_device_manager_info*,int, void*); static void extDeviceManagerSubscribeCb(pa_context*, void*); static void subscribeCb(pa_context*, pa_subscription_event_type_t, uint32_t, void*); static void moduleInfoCb(pa_context*, const pa_module_info*, int, void*); private: int m_reconnectTime; bool m_autoPause = false; bool m_bContexReady = false; bool m_bModuleReady = false; mutable std::mutex m_contextMtx; mutable std::mutex m_moduleMtx; mutable std::condition_variable m_contextCond; mutable std::condition_variable m_moduleCond; std::list m_streamMediaList {}; static pa_context* m_pContext; static pa_mainloop_api* m_pApi; PaDataInfo m_info; std::unordered_map m_adapterStreamNameMap = { {"qaxbrowser", "qaxbrowser-safe"} }; }; template bool updateData( PaDataInfo& info, const DataType& data, GetDataListFunc getDataList, IsSameDataFunc isSameData) { bool isNew = false; auto listOpt = getDataList(info); // 检查返回值是否有效 if (!listOpt.has_value()) { AUDIO_ERROR("List is empty or invalid."); return isNew; } auto& list = listOpt.value().get(); auto it = std::find_if(list.begin(), list.end(), [&data, &isSameData](const DataType& existingData) { return isSameData(existingData, data); }); // 如果存在相同的数据则替换,不存在则添加 if (it != list.end()) { // 只更新CardInfo中的card成员变量 if constexpr (std::is_same_v, std::shared_ptr>) { (*it)->card = data->card; } else { *it = data; } } else { isNew = true; list.push_back(data); } return isNew; } } #endif // PULSEAUDIOMANAGER_H ukui-volume-control/backend/PipewireManagerFactory.h0000664000175000017500000000212415171074712021613 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 PIPEWIREMANAGERFACTORY_H #define PIPEWIREMANAGERFACTORY_H #include "ManagerFactory.h" namespace UkuiAudioFramwork { class PipewireManagerFactory : public ManagerFactory { public: PipewireManagerFactory() = default; virtual auto createManager() -> std::shared_ptr override; }; } #endif // PIPEWIREMANAGERFACTORY_H ukui-volume-control/backend/PipewireProfiler.cpp0000664000175000017500000001604315171074712021033 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "PipewireProfiler.h" #include "PipewireManager1.h" #include "PipewireNode.h" #include #include namespace UkuiAudioFramwork { #define PIPEWIRE_CAST(x) PipewireProfiler* _this = static_cast(x); void profiler_profile(void *data, const struct spa_pod *pod) { PIPEWIRE_CAST(data); _this->_profiler_profile(pod); } void PipewireProfiler::_profiler_profile(const struct spa_pod *pod) { struct spa_pod *o; struct spa_pod_prop *p; point point; uint32_t pod_size = SPA_POD_BODY_SIZE(pod); for (o = static_cast(SPA_POD_BODY(pod)); spa_pod_is_inside(pod, pod_size, o); o = (struct spa_pod *)spa_pod_next(o)) { int res = 0; if (!spa_pod_is_object_type(o, SPA_TYPE_OBJECT_Profiler)) continue; spa_zero(point); SPA_POD_OBJECT_FOREACH((struct spa_pod_object*)o, p) { switch(p->key) { case SPA_PROFILER_info: res = process_info(&p->value, &point.info); break; case SPA_PROFILER_clock: res = process_clock(&p->value, &point.info); break; case SPA_PROFILER_driverBlock: res = process_driver_block(&p->value, &point); break; case SPA_PROFILER_followerBlock: process_follower_block(&p->value, &point); break; default: break; } if (res < 0) break; } if (res < 0) continue; } } static const struct pw_profiler_events profiler_events = { .version = PW_VERSION_PROFILER_EVENTS, .profile = profiler_profile, }; // ---------------------------------------------------------------------------- int PipewireProfiler::process_info(const struct spa_pod *pod, struct PipewireNode::driver *info) { return spa_pod_parse_struct(pod, SPA_POD_Long(&info->count), SPA_POD_Float(&info->cpu_load[0]), SPA_POD_Float(&info->cpu_load[1]), SPA_POD_Float(&info->cpu_load[2]), SPA_POD_Int(&info->xrun_count)); } int PipewireProfiler::process_clock(const struct spa_pod *pod, struct PipewireNode::driver *info) { return spa_pod_parse_struct(pod, SPA_POD_Int(&info->clock.flags), SPA_POD_Int(&info->clock.id), SPA_POD_Stringn(info->clock.name, sizeof(info->clock.name)), SPA_POD_Long(&info->clock.nsec), SPA_POD_Fraction(&info->clock.rate), SPA_POD_Long(&info->clock.position), SPA_POD_Long(&info->clock.duration), SPA_POD_Long(&info->clock.delay), SPA_POD_Double(&info->clock.rate_diff), SPA_POD_Long(&info->clock.next_nsec)); } int PipewireProfiler::process_driver_block(const struct spa_pod *pod, struct point *point) { char *name = NULL; uint32_t id = 0; struct PipewireNode::measurement measure; PipewireNode *node = nullptr; spa_zero(measure); int res = spa_pod_parse_struct(pod, SPA_POD_Int(&id), SPA_POD_String(&name), SPA_POD_Long(&measure.prev_signal), SPA_POD_Long(&measure.signal), SPA_POD_Long(&measure.awake), SPA_POD_Long(&measure.finish), SPA_POD_Int(&measure.status), SPA_POD_Fraction(&measure.latency)); if (res < 0) return res; { // PipewireNode *i; for(const auto& it : pipewire->m_nodes) { if (it->id() == id) { node = it; break; } } } if (node == nullptr) return -ENOENT; node->setDriver(node); node->setMeasurement(measure); node->setInfo(point->info); point->driver = node; if (measure.status != 3) { node->errors++; // emit node->errorChanged(); if (node->last_error_status == -1) node->last_error_status = measure.status; } return 0; } int PipewireProfiler::process_follower_block(const struct spa_pod *pod, struct point *point) { uint32_t id = 0; const char *name = nullptr; struct PipewireNode::measurement measure; PipewireNode *node = nullptr; spa_zero(measure); int res = spa_pod_parse_struct(pod, SPA_POD_Int(&id), SPA_POD_String(&name), SPA_POD_Long(&measure.prev_signal), SPA_POD_Long(&measure.signal), SPA_POD_Long(&measure.awake), SPA_POD_Long(&measure.finish), SPA_POD_Int(&measure.status), SPA_POD_Fraction(&measure.latency)); if (res < 0) return res; { // PipewireNode *i; for(const auto& it : pipewire->m_nodes) { if (it->id() == id) { node = it; break; } } } if (node == nullptr) return -ENOENT; node->setMeasurement(measure); node->setDriver(point->driver); if (measure.status != 3) { node->errors++; // emit node->errorChanged(); if (node->last_error_status == -1) node->last_error_status = measure.status; } return 0; } // ---------------------------------------------------------------------------- PipewireProfiler::PipewireProfiler(Pipewire *parent, uint32_t id, const spa_dict* props) : /*QObject(parent) ,*/ pipewire(parent) { profiler = static_cast( pw_registry_bind(pipewire->registry, id, PW_TYPE_INTERFACE_Profiler, PW_VERSION_PROFILER, 0)); if (profiler == nullptr) { throw std::runtime_error("Error creating profiler proxy"); } pw_proxy_add_object_listener(profiler, &profiler_listener, &profiler_events, this); pipewire->resync(); } PipewireProfiler::~PipewireProfiler() { spa_hook_remove(&profiler_listener); pw_proxy_destroy(profiler); } } ukui-volume-control/backend/PipewireProfiler.h0000664000175000017500000000332215171074712020474 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 . */ #pragma once #include #include #include #include "PipewireNode.h" namespace UkuiAudioFramwork { class Pipewire; class PipewireProfiler { private: Pipewire *pipewire = nullptr; pw_proxy *profiler = nullptr; spa_hook profiler_listener; int check_profiler; struct point { struct PipewireNode* driver; struct PipewireNode::driver info; }; public: explicit PipewireProfiler(Pipewire *parent, uint32_t id, const spa_dict* props); virtual ~PipewireProfiler(); int process_info(const struct spa_pod *pod, struct PipewireNode::driver *info); int process_clock(const struct spa_pod *pod, struct PipewireNode::driver *info); int process_driver_block(const struct spa_pod *pod, struct point *point); int process_follower_block(const struct spa_pod *pod, struct point *point); // private void _profiler_profile(const struct spa_pod *pod); }; } ukui-volume-control/backend/PipewireLink.cpp0000664000175000017500000000343015171074712020142 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "PipewireLink.h" #include "PipewireManager1.h" #include "utils.h" namespace UkuiAudioFramwork { PipewireLink::PipewireLink(Pipewire* parent, uint32_t id, const spa_dict* props) : pipewire(parent) , m_id(id) , m_input_port(spa_dict_get_u32(props, PW_KEY_LINK_INPUT_PORT)) , m_output_port(spa_dict_get_u32(props, PW_KEY_LINK_OUTPUT_PORT)) , m_input_node(spa_dict_get_u32(props, PW_KEY_LINK_INPUT_NODE)) , m_output_node(spa_dict_get_u32(props, PW_KEY_LINK_INPUT_NODE)) { link = static_cast( pw_registry_bind(pipewire->registry, id, PW_TYPE_INTERFACE_Link, PW_VERSION_CLIENT, 0)); if (link == nullptr) { throw std::runtime_error("Error creating link proxy"); } std::cout << "Adding link (" << id << ") with props:" << std::endl; const struct spa_dict_item *item; spa_dict_for_each(item, props) { std::cout << '\t' << item->key << ":" << item->value << std::endl; } } PipewireLink::~PipewireLink() { if (link != nullptr) { pw_proxy_destroy((struct pw_proxy*) link); } } } ukui-volume-control/backend/PipewireSettings.cpp0000664000175000017500000000754115171074712021054 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "PipewireSettings.h" //#include #include namespace UkuiAudioFramwork { #define LOG_LEVEL_KEY "log.level" #define MIN_BUFFER_KEY "clock.min-quantum" #define MAX_BUFFER_KEY "clock.max-quantum" #define FORCE_SAMPLERATE_KEY "clock.force-rate" #define FORCE_BUFFER_KEY "clock.force-quantum" #define RATE_KEY "clock.rate" #define ALLOWED_RATES_KEY "clock.allowed-rates" #define QUANTUM_KEY "clock.quantum" //----------------------------------------------------------------------------- PipewireSettings::PipewireSettings(Pipewire *parent, uint32_t id, const spa_dict* props) :PipewireMetadata(parent, id, props) { // connect(this, &QPipewireMetadata::onKeyUpdated, this, &QPipewireSettings::keyUpdated); } //----------------------------------------------------------------------------- void PipewireSettings::keyUpdated(uint32_t id, const char *key, const char *type, const char *value) { // if (strcmp(key, LOG_LEVEL_KEY)==0){ // m_logLevel = static_cast(std::stoi(value)); // emit logLevelChanged(m_logLevel); // } else if (strcmp(key, FORCE_SAMPLERATE_KEY)==0){ // m_force_sampleRate = std::stoi(value); // emit force_sampleRateChanged(m_force_sampleRate); // } else if (strcmp(key, FORCE_BUFFER_KEY)==0) { // m_force_buffer = std::stoi(value); // emit force_bufferChanged(m_force_buffer); // } else if (strcmp(key, MIN_BUFFER_KEY)==0) { // m_minBuffer = std::stoi(value); // emit minBufferChanged(m_minBuffer); // } else if (strcmp(key, MAX_BUFFER_KEY)==0) { // m_maxBuffer = std::stoi(value); // emit maxBufferChanged(m_maxBuffer); // // } else if (strcmp(key, RATE_KEY)==0) { // // //TODO // // } else if (strcmp(key, ALLOWED_RATES_KEY)==0) { // // //TODO // // } else if (strcmp(key, QUANTUM_KEY)==0) { // // //TODO // } else { // qWarning() << "Unrecognized pipewire setting \"" << key << "\" with value \"" << value << '"'; // } } //----------------------------------------------------------------------------- void PipewireSettings::setLogLevel(int newLogLevel) { if (newLogLevel == m_logLevel) return; std::string value = std::to_string(newLogLevel); this->setProperty(LOG_LEVEL_KEY, value.c_str()); } void PipewireSettings::setMinBuffer(int newMinBuffer) { if (newMinBuffer == m_minBuffer) return; std::string value = std::to_string(newMinBuffer); this->setProperty(MIN_BUFFER_KEY, value.c_str()); } void PipewireSettings::setMaxBuffer(int newMaxBuffer) { if (newMaxBuffer == m_maxBuffer) return; std::string value = std::to_string(newMaxBuffer); this->setProperty(MAX_BUFFER_KEY, value.c_str()); } void PipewireSettings::setForce_sampleRate(int newSampleRate) { if (newSampleRate == m_force_sampleRate) return; std::string value = std::to_string(newSampleRate); this->setProperty(FORCE_SAMPLERATE_KEY, value.c_str()); } void PipewireSettings::setForce_buffer(int newBuffer) { if (newBuffer == m_force_buffer) return; std::string value = std::to_string(newBuffer); this->setProperty(FORCE_BUFFER_KEY, value.c_str()); } } ukui-volume-control/backend/PulseaudioBackend.h0000664000175000017500000000224515171074712020572 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 PULSEAUDIOBACKEND_H #define PULSEAUDIOBACKEND_H #include "IBackend.h" #include "PulseaudioManager.h" namespace UkuiAudioFramwork { class PulseaudioBackend : public IBackend { public: PulseaudioBackend(); ~PulseaudioBackend() = default; public: virtual void init(); virtual bool detect(); // virtual auto getManager() -> std::unique_ptr; private: }; } #endif // PULSEAUDIOBACKEND_H ukui-volume-control/backend/SoundThemeModule.cpp0000664000175000017500000002072415171074712020766 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "SoundThemeModule.h" #include #include #include #include #include "AudioContext.h" namespace UkuiAudioFramwork { std::string SoundThemeInfo::toString() const { return FORMAT("name: {}, description: {}", name, description); } std::string SoundEffectFileInfo::toString() const { return FORMAT("name: {}, description: {}", name, description); } SoundThemeModule& SoundThemeModule::getInstance() { static SoundThemeModule instance; return instance; } std::list SoundThemeModule::getSoundThemeList() { setupThemeSelector(); return m_soundThemeList; } std::list SoundThemeModule::getSoundEffectFileList(const std::string& themeName) { std::list list; // 定义主要搜索路径和备用路径 std::vector soundDirs = { "/usr/share/sounds", "/opt/system/resource/sounds" }; // 获取所有音效主题的 XML 文件 auto xmlFiles = getSoundThemeXmlFiles(soundDirs); if (xmlFiles.empty()) { AUDIO_ERROR("No sound theme XML files found in any search directories."); return {}; } // 遍历每个 XML 文件并提取音效列表 for (const auto& xmlFile : xmlFiles) { AUDIO_DEBUG("getSoundEffectFileList xmlFile: {} .", xmlFile); //start parse all sound // if ((xmlFile.find(themeName) == std::string::npos) // && (themeName.compare("custom"))) { // continue; // } //end auto soundList = getSoundListFromXml(xmlFile); if (soundList.empty()) { AUDIO_ERROR("No sounds found in {}.", xmlFile); continue; } // 输出音效列表 for (const auto& sound : soundList) { list.push_back({sound.name, sound.description}); } } return list; } void SoundThemeModule::setupThemeSelector() { GHashTable* hash; const char* const* dataDirs; std::string dataDir, dir; uint i; m_soundThemeList.clear(); /* Add the theme names and their display name to a hash table, * makes it easy to avoid duplicate themes */ hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); dataDirs = g_get_system_data_dirs(); for (i = 0; dataDirs[i] != nullptr; ++i) { dir = g_build_filename(dataDirs[i], "sounds", nullptr); soundThemeInDir(hash, dir); } dataDir = g_get_user_data_dir(); dir = g_build_filename(dataDir.c_str(), "sounds", nullptr); soundThemeInDir(hash, dir); /* If there isn't at least one theme, make everything * insensitive, LAME! */ if (g_hash_table_size(hash) == 0) { std::cerr << "Bad setup, install the freedesktop sound theme" << std::endl; g_hash_table_destroy(hash); return; } g_hash_table_destroy(hash); } void SoundThemeModule::soundThemeInDir(GHashTable* hash, const std::string& dir) { GDir* d; std::string name; d = g_dir_open(dir.c_str(), 0, nullptr); if (nullptr == d) { return; } while(auto dName = g_dir_read_name(d)) { name = dName; std::string dirName, index, indexName; /* Look for directories */ dirName = g_build_filename(dir.c_str(), name.c_str(), nullptr); if (g_file_test(dirName.c_str(), G_FILE_TEST_IS_DIR) == false) { continue; } /* Look for index files */ index = std::string(g_build_filename(dirName.c_str(), "index.theme", nullptr)); /* Check the name of the theme in the index.theme file */ indexName = getIndexThemeName(index); if ("" == indexName || "Default" == indexName) { continue; } SoundThemeInfo info; info.name = name; info.description = indexName; AUDIO_DEBUG("Get soudtheme theme: {}, desc: {}", info.name, info.description); m_soundThemeList.push_back(info); } g_dir_close (d); } /* * 获取下标的主题名 */ std::string SoundThemeModule::getIndexThemeName(const std::string& index) { GKeyFile* file; std::string indexname = ""; bool hidden; file = g_key_file_new(); if (g_key_file_load_from_file(file, index.c_str(), G_KEY_FILE_KEEP_TRANSLATIONS, nullptr) == false) { g_key_file_free(file); return indexname; } /* Don't add hidden themes to the list */ hidden = g_key_file_get_boolean(file, "Sound Theme", "Hidden", nullptr); if (!hidden) { indexname = g_key_file_get_locale_string(file, "Sound Theme", "Name", nullptr, nullptr); } g_key_file_free(file); return indexname; } std::vector SoundThemeModule::getSoundThemeXmlFiles(const std::vector& soundDirs) { std::vector xmlFiles; for (const auto& soundDir : soundDirs) { try { for (const auto& entry : std::filesystem::recursive_directory_iterator(soundDir)) { if (entry.is_regular_file() && entry.path().extension() == ".xml") { //start std::string xmlFilePath = entry.path().string(); size_t last_slash = xmlFilePath.find_last_of('/'); std::string themeDirs = xmlFilePath.substr(0, last_slash+1); if (isExistDirPath(themeDirs.append("stereo"))) { AUDIO_DEBUG("getSoundThemeXmlFiles push_back: {} .", entry.path().string()); xmlFiles.push_back(entry.path().string()); } else { AUDIO_DEBUG("getSoundThemeXmlFiles stereoDir no found ."); } //end } } } catch (const std::exception& e) { std::cerr << "Error accessing directory " << soundDir << ": " << e.what() << std::endl; } } return xmlFiles; } std::vector SoundThemeModule::getSoundListFromXml(const std::string& xmlFilePath) { std::vector soundList; pugi::xml_document doc; if (!doc.load_file(xmlFilePath.c_str())) { std::cerr << "Failed to load XML file: " << xmlFilePath << std::endl; return {}; } auto sounds = doc.child("sounds"); if (!sounds) { std::cerr << "Invalid sound theme XML file: " << xmlFilePath << std::endl; return {}; } std::string currentLanguage = getenv("LANGUAGE"); AUDIO_DEBUG("getSoundListFromXml currentLanguage: {}", currentLanguage); for (const auto& sound : sounds.children("sound")) { SoundEffectFileInfo soundInfo {}; auto defaultNameNode = sound.child("filename"); if (defaultNameNode) { soundInfo.name = defaultNameNode.child_value(); } else { continue; } if (!currentLanguage.empty()) { auto descriptionNode = sound.find_child_by_attribute("name", "xml:lang", currentLanguage.substr(0, currentLanguage.find(':')).c_str()); if (descriptionNode) { soundInfo.description = descriptionNode.child_value(); } else { //start adapter en language auto nameNode = sound.child("name"); if (nameNode) { soundInfo.description = nameNode.child_value(); } //end } } soundList.push_back(soundInfo); } return soundList; } bool SoundThemeModule::isExistDirPath(const std::string& path) { bool ret = false; std::filesystem::path dir(path); if (std::filesystem::exists(dir) && std::filesystem::is_directory(dir)) { AUDIO_DEBUG("Dir: {} found .", dir); ret = true; } else { AUDIO_DEBUG("Dir: {} no found .", dir); } return ret; } } ukui-volume-control/backend/PaDataType.h0000664000175000017500000004153015171074712017204 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 PADATATYPE_H #define PADATATYPE_H #include #include #include #include #include #include #include #include #include #include #include extern "C" { #include #include #include #include #include #include #include #include #include #include } #include "IStream.h" #include "Util.h" #include "DeviceManagerModule.h" namespace UkuiAudioFramwork { #define PA_VOLUME_NORMAL 65536.0 #define UKMEDIA_VOLUME_NORMAL 100.0 struct PaDataInfo; struct PaSinkPortInfo; struct PaCardPortInfo; class IStream; class PaSinkStream; class PaSourceStream; class DeviceManagerModule; template bool contains(PaDataInfo& data, GetDeviceListFunc getDeviceList, const std::string& name) { auto list = getDeviceList(data); for (const auto& p : list) { if (p && p->name == name) return true; } return false; } typedef struct PaSinkPortInfo { std::string name; /**< Name of this port */ std::string description; /**< Description of this port */ uint32_t priority; /**< The higher this value is, the more useful this port is as a default. */ int available; /**< A flags (see #pa_port_available), indicating availability status of this port. \since 2.0 */ DevicePortType type = DevicePortType::PORT_TYPE_UNKNOWN; PaSinkPortInfo(const std::string& cardName, const pa_sink_port_info& other) { name = other.name ? other.name : ""; description = other.description ? other.description : ""; priority = other.priority; available = other.available; type = getDevicePortType(cardName, other.name ? other.name : ""); } } PaSinkPortInfo; typedef struct PaSourcePortInfo { std::string name; /**< Name of this port */ std::string description; /**< Description of this port */ uint32_t priority; /**< The higher this value is, the more useful this port is as a default. */ int available; /**< A flags (see #pa_port_available), indicating availability status of this port. \since 2.0 */ DevicePortType type; PaSourcePortInfo(const std::string& cardName, const pa_source_port_info& other) { name = other.name; description = other.description; priority = other.priority; available = other.available; type = getDevicePortType(cardName, other.name); } } PaSourcePortInfo; typedef struct PaModuleInfo { uint32_t index; /**< Index of the module */ std::string name; /**< Name of the module */ const char* argument; /**< Argument string of the module */ uint32_t n_used; /**< Usage counter or PA_INVALID_INDEX */ /** \cond fulldocs */ int auto_unload; /**< \deprecated Non-zero if this is an autoloaded module. */ /** \endcond */ pa_proplist *proplist; /**< Property list \since 0.9.15 */ void operator =(const pa_module_info& other) { index = other.index; name = other.name ? other.name : ""; argument = other.argument ? other.argument : ""; n_used = other.n_used; auto_unload = other.auto_unload; proplist = other.proplist; } std::string toString() const; } PaModuleInfo; typedef struct PaCardProfileInfo { std::string name; /**< Name of this profile */ std::string description; /**< Description of this profile */ uint32_t n_sinks; /**< Number of sinks this profile would create */ uint32_t n_sources; /**< Number of sources this profile would create */ uint32_t priority; /**< The higher this value is, the more useful this profile is as a default. */ PaCardProfileInfo(const pa_card_profile_info& other) { name = other.name; description = other.description; n_sinks = other.n_sinks; n_sources = other.n_sources; priority = other.priority; } } PaCardProfileInfo; typedef struct PaCardProfileInfo2 { std::string name; /**< Name of this profile */ std::string description; /**< Description of this profile */ uint32_t n_sinks; /**< Number of sinks this profile would create */ uint32_t n_sources; /**< Number of sources this profile would create */ uint32_t priority; /**< The higher this value is, the more useful this profile is as a default. */ int available; PaCardProfileInfo2(const pa_card_profile_info2& other) { name = other.name; description = other.description; n_sinks = other.n_sinks; n_sources = other.n_sources; priority = other.priority; available = other.available; } void operator =(const pa_card_profile_info2& other) { name = other.name; description = other.description; n_sinks = other.n_sinks; n_sources = other.n_sources; priority = other.priority; available = other.available; } } PaCardProfileInfo2; typedef struct PaCardPortInfo { std::string name; /**< Name of this port */ std::string description; /**< Description of this port */ uint32_t priority; /**< The higher this value is, the more useful this port is as a default. */ int available; /**< A #pa_port_available enum, indicating availability status of this port. */ int direction; /**< A #pa_direction enum, indicating the direction of this port. */ uint32_t n_profiles; /**< Number of entries in profile array */ std::vector> profiles; /**< \deprecated Superseded by profiles2 */ pa_proplist *proplist; /**< Property list */ int64_t latency_offset; /**< Latency offset of the port that gets added to the sink/source latency when the port is active. \since 3.0 */ std::vector> profiles2; /**< Array of pointers to available profiles, or NULL. Array is terminated by an entry set to NULL. \since 5.0 */ bool enabled; DevicePortType type; PaCardPortInfo(const std::string& cardName, const pa_card_port_info& other, bool enable) { name = other.name; description = other.description; priority = other.priority; available = other.available; direction = other.direction; n_profiles = other.n_profiles; proplist = other.proplist; latency_offset = other.latency_offset; enabled = enable; type = getDevicePortType(cardName, other.name); profiles.clear(); for (int i = 0; i < n_profiles; ++i) { profiles.push_back(std::make_shared(*other.profiles[i])); } profiles2.clear(); for (int i = 0; i < n_profiles; ++i) { profiles2.push_back(std::make_shared(*other.profiles2[i])); } } } PaCardPortInfo; typedef struct PaCardInfo { uint32_t index; /**< Index of this card */ std::string name; /**< Name of this card */ std::string deviceDesc; /**< Decription of this card */ uint32_t owner_module; /**< Index of the owning module, or PA_INVALID_INDEX. */ std::string driver; /**< Driver name */ uint32_t n_profiles; /**< Number of entries in profile array */ std::shared_ptr profiles; /**< \deprecated Superseded by profiles2 */ std::shared_ptr active_profile; /**< \deprecated Superseded by active_profile2 */ pa_proplist *proplist; /**< Property list */ uint32_t n_ports; /**< Number of entries in port array */ std::vector> ports; /**< Array of pointers to ports, or NULL. Array is terminated by an entry set to NULL. */ std::vector> profiles2; /**< Array of pointers to available profiles, or NULL. Array is terminated by an entry set to NULL. \since 5.0 */ std::shared_ptr active_profile2; /**< Pointer to active profile in the array, or NULL. \since 5.0 */ std::list> deviceList {}; void operator =(const pa_card_info& other); } PaCardInfo; typedef struct PaServerInfo { std::string user_name; /**< User name of the daemon process */ std::string host_name; /**< Host name the daemon is running on */ std::string server_version; /**< Version string of the daemon */ std::string server_name; /**< Server package name (usually "pulseaudio") */ pa_sample_spec sample_spec; /**< Default sample specification */ std::string default_sink_name; /**< Name of default sink. */ std::string default_source_name; /**< Name of default source. */ uint32_t cookie; /**< A random cookie for identifying this instance of PulseAudio. */ pa_channel_map channel_map; /**< Default channel map. \since 0.9.15 */ void operator =(const pa_server_info& other) { user_name = other.user_name ? other.user_name : ""; host_name = other.host_name ? other.host_name : ""; server_version = other.server_version ? other.server_version : ""; server_name = other.server_name ? other.server_name : ""; sample_spec = other.sample_spec; channel_map = other.channel_map; default_sink_name = other.default_sink_name ? other.default_sink_name : ""; default_source_name = other.default_source_name ? other.default_source_name : ""; cookie = other.cookie; channel_map = other.channel_map; } } PaServerInfo; typedef struct PaSinkInputInfo { uint32_t index; /**< Index of the sink input */ std::string name; /**< Name of the sink input */ uint32_t owner_module; /**< Index of the module this sink input belongs to, or PA_INVALID_INDEX when it does not belong to any module. */ uint32_t client; /**< Index of the client this sink input belongs to, or PA_INVALID_INDEX when it does not belong to any client. */ uint32_t sink; /**< Index of the connected sink */ pa_sample_spec sample_spec; /**< The sample specification of the sink input. */ pa_channel_map channel_map; /**< Channel map */ pa_cvolume volume; /**< The volume of this sink input. */ pa_usec_t buffer_usec; /**< Latency due to buffering in sink input, see pa_timing_info for details. */ pa_usec_t sink_usec; /**< Latency of the sink device, see pa_timing_info for details. */ std::string resample_method; /**< The resampling method used by this sink input. */ std::string driver; /**< Driver name */ int mute; /**< Stream muted \since 0.9.7 */ pa_proplist *proplist; /**< Property list \since 0.9.11 */ int corked; /**< Stream corked \since 1.0 */ int has_volume; /**< Stream has volume. If not set, then the meaning of this struct's volume member is unspecified. \since 1.0 */ int volume_writable; /**< The volume can be set. If not set, the volume can still change even though clients can't control the volume. \since 1.0 */ pa_format_info *format; /**< Stream format information. \since 1.0 */ void operator =(const pa_sink_input_info& other) { index = other.index; name = other.name; owner_module = other.owner_module; client = other.client; sink = other.sink; sample_spec = other.sample_spec; channel_map = other.channel_map; volume = other.volume; buffer_usec = other.buffer_usec; sink_usec = other.sink_usec; resample_method = other.resample_method; driver = other.driver; mute = other.mute; proplist = other.proplist; corked = other.corked; has_volume = other.has_volume; volume_writable = other.volume_writable; format = other.format; } } PaSinkInputInfo; typedef struct PaSourceOutputInfo { uint32_t index; /**< Index of the source output */ std::string name; /**< Name of the source output */ uint32_t owner_module; /**< Index of the module this source output belongs to, or PA_INVALID_INDEX when it does not belong to any module. */ uint32_t client; /**< Index of the client this source output belongs to, or PA_INVALID_INDEX when it does not belong to any client. */ uint32_t source; /**< Index of the connected source */ pa_sample_spec sample_spec; /**< The sample specification of the source output */ pa_channel_map channel_map; /**< Channel map */ pa_usec_t buffer_usec; /**< Latency due to buffering in the source output, see pa_timing_info for details. */ pa_usec_t source_usec; /**< Latency of the source device, see pa_timing_info for details. */ std::string resample_method; /**< The resampling method used by this source output. */ std::string driver; /**< Driver name */ pa_proplist *proplist; /**< Property list \since 0.9.11 */ int corked; /**< Stream corked \since 1.0 */ pa_cvolume volume; /**< The volume of this source output \since 1.0 */ int mute; /**< Stream muted \since 1.0 */ int has_volume; /**< Stream has volume. If not set, then the meaning of this struct's volume member is unspecified. \since 1.0 */ int volume_writable; /**< The volume can be set. If not set, the volume can still change even though clients can't control the volume. \since 1.0 */ pa_format_info *format; /**< Stream format information. \since 1.0 */ void operator =(const pa_source_output_info& other) { index = other.index; name = other.name; owner_module = other.owner_module; client = other.client; source = other.source; sample_spec = other.sample_spec; channel_map = other.channel_map; volume = other.volume; buffer_usec = other.buffer_usec; source_usec = other.source_usec; resample_method = other.resample_method; driver = other.driver; proplist = other.proplist; corked = other.corked; mute = other.mute; has_volume = other.has_volume; volume_writable = other.volume_writable; format = other.format; } } PaSourceOutputInfo; typedef struct CardInfo { PaCardInfo card; std::list> sinks; std::list> sources; std::string toString() const; } CardInfo; typedef struct PaDataInfo { std::list> cardInfoList; std::list> sinkInputList; std::list> sourceOutputList; std::list moduleInfoList; PaServerInfo serverInfo; std::shared_ptr defaultSink; std::shared_ptr defaultSource; mutable std::mutex m_mtx; std::condition_variable m_cond; bool m_operation = false; std::string toString() const; } PaDataInfo; uint32_t valueToPaVolume(int); //滑动条值转换成音量 uint32_t paVolumeToValue(int); //音量值转换成滑动条值 uint32_t paCvolumeToValue(const pa_cvolume&); uint32_t paCvolumeToPaValue(const pa_cvolume&); } #endif // PADATATYPE_H ukui-volume-control/backend/PaDataType.cpp0000664000175000017500000001137715171074712017545 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "PaDataType.h" namespace UkuiAudioFramwork { std::string PaModuleInfo::toString() const { return FORMAT("index: {}, name: {}, argument: {}, n_used: {}, auto_unload:{}", index, name, argument, n_used, auto_unload); } std::string CardInfo::toString() const { std::string pcil = "["; pcil += FORMAT("card.name={}, card.index={}, dirver={}, active_profile={}", card.name, card.index, card.driver, card.active_profile->name); pcil += "]"; std::string psil = "["; for (const auto& s : sinks) { psil += "("; psil += s->toString(); psil += "),"; } psil += "]"; std::string psol = "["; for (const auto& s : sources) { psol += "("; psol += s->toString(); psol += "),"; } psol += "]"; return FORMAT("card={}, sinks={}, sources={}", pcil, psil, psol); } std::string PaDataInfo::toString() const { std::string cil = "["; for (const auto& c : cardInfoList) { cil += "("; cil += c->toString(); cil += "),"; } cil += "]"; std::string psil = "["; for (const auto& si : sinkInputList) { psil += "("; psil += si->toString(); psil += "),"; } psil += "]"; std::string psol = "["; for (const auto& so : sourceOutputList) { psol += "("; psol += so->toString(); psol += "),"; } std::string pmil = "["; for (const auto& m : moduleInfoList) { pmil += "("; pmil += FORMAT("index={}, name={}, argument={}, auto_unload={}", m.index, m.name, m.argument, m.auto_unload); pmil += "),"; } pmil += "]"; return FORMAT("cardInfoList={}, sinkInputList={}, sourceOutputList={}, moduleInfoList={}", cil, psil, psol, pmil); } void PaCardInfo::operator =(const pa_card_info& other) { index = other.index; name = other.name; owner_module = other.owner_module; driver = other.driver; n_profiles = other.n_profiles; profiles = other.profiles ? std::make_shared(*other.profiles) : nullptr; active_profile = other.active_profile ? std::make_shared(*other.active_profile) : nullptr; proplist = other.proplist; n_ports = other.n_ports; deviceDesc = pa_proplist_gets(proplist, PA_PROP_DEVICE_DESCRIPTION) ? pa_proplist_gets(proplist, PA_PROP_DEVICE_DESCRIPTION) : ""; ports.clear(); for (int i = 0; i < n_ports; ++i) { bool enable = DeviceManagerModule::getInstance().isEnabled(other.name, other.ports[i]->name); if (other.ports[i]->available != PA_PORT_AVAILABLE_NO) { deviceList.push_back(std::make_shared(other.name, deviceDesc, other.ports[i]->name, other.ports[i]->description, other.ports[i]->priority, other.ports[i]->direction, other.ports[i]->available, enable, getDevicePortType(other.name, other.ports[i]->name))); } ports.push_back(std::make_shared(name, *other.ports[i], enable)); } profiles2.clear(); for (int i = 0; i < n_profiles; ++i) { profiles2.push_back(std::make_shared(*other.profiles2[i])); } active_profile2 = other.active_profile2 ? std::make_shared(*other.active_profile2) : nullptr; } uint32_t valueToPaVolume(int value) { return value / UKMEDIA_VOLUME_NORMAL * PA_VOLUME_NORMAL; } uint32_t paVolumeToValue(int value) { return (value / PA_VOLUME_NORMAL * UKMEDIA_VOLUME_NORMAL) + 0.5; } uint32_t paCvolumeToValue(const pa_cvolume& v) { uint32_t value = 0; for (int i = 0; i < v.channels; ++i) { if (value < v.values[i]) value = v.values[i]; } return (value / PA_VOLUME_NORMAL * UKMEDIA_VOLUME_NORMAL) + 0.5; } uint32_t paCvolumeToPaValue(const pa_cvolume& v) { uint32_t value = 0; for (int i = 0; i < v.channels; ++i) { if (value < v.values[i]) value = v.values[i]; } return value; } } ukui-volume-control/backend/SystemdService.cpp0000664000175000017500000003523715171074712020523 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "SystemdService.h" //#include //#include #include #include namespace UkuiAudioFramwork { //static bool argToString(const QDBusArgument &arg, QString &out); //static bool variantToString(const QVariant &arg, QString &out); SystemdService::SystemdService(const std::string& serviceName, bool userService) : serviceName(serviceName) // , bus(userService ? QDBusConnection::sessionBus() : QDBusConnection::systemBus()) { if (!serviceName.ends_with(".service")) { this->serviceName.append(".service"); } // systemd = new QDBusInterface("org.freedesktop.systemd1", "/org/freedesktop/systemd1", // "org.freedesktop.systemd1.Manager", bus); checkIsRunning(); } SystemdService::~SystemdService() { // delete systemd; } void SystemdService::setRunning(bool running) { if (isRunning == running) return; // if (running) { // this->start(); // } else { // this->stop(); // } isRunning = running; // emit runningChanged(isRunning); } void SystemdService::_setRunning(bool running) { if (isRunning == running) return; isRunning = running; // emit runningChanged(isRunning); } bool SystemdService::checkIsRunning() { bool service_found = false; // QDBusMessage r = systemd->call("ListUnits"); // if (systemd->lastError().isValid()) // qWarning() << "Call failed: " << systemd->lastError().message(); // else { // assert(r.signature() == "a(ssssssouso)"); // QVariantList arguments = r.arguments(); // const QDBusArgument busArgument = qvariant_cast(arguments.at(0)); // QString busSig = busArgument.currentSignature(); // assert(busSig == r.signature()); // QDBusArgument::ElementType elementType = busArgument.currentType(); // assert(elementType == QDBusArgument::ArrayType); // busArgument.beginArray(); // while (!busArgument.atEnd()) // { // busArgument.beginStructure(); // // The primary unit name as string // QString name = busArgument.asVariant().toString(); // if (name != this->serviceName) { // // We are only interested in one service // busArgument.endStructure(); // continue; // } // // Service found! // // The human readable description string // QString desc = busArgument.asVariant().toString(); // // The load state (i.e. whether the unit file has been loaded successfully) // QString loaded = busArgument.asVariant().toString(); // // The active state (i.e. whether the unit is currently started or not) // QString active = busArgument.asVariant().toString(); // // The sub state (a more fine-grained version of the active state that is specific to the unit type, which the active state is not) // QString substate = busArgument.asVariant().toString(); // // A unit that is being followed in its state by this unit, if there is any, otherwise the empty string. // QString following = busArgument.asVariant().toString(); // // The unit object path // QString path = busArgument.asVariant().toString(); // // If there is a job queued for the job unit the numeric job id, 0 otherwise // uint jobqueued = busArgument.asVariant().toUInt(); // // The job type as string // QString jobtype = busArgument.asVariant().toString(); // // The job object path // QString jobobject = busArgument.asVariant().toString(); //// std::cout << "Unit: " << std::endl //// << "\tname: " << name.toStdString() << std::endl //// << "\tdec: " << desc.toStdString() << std::endl //// << "\tloaded: " << loaded.toStdString() << std::endl //// << "\tactive: " << active.toStdString() << std::endl //// << "\tsubstate: " << substate.toStdString() << std::endl //// << "\tfollowing: " << following.toStdString() << std::endl //// << "\tpath: " << path.toStdString() << std::endl //// << "\tjobqueued: " << jobqueued << std::endl //// << "\tjobtype: " << jobtype.toStdString() << std::endl //// << "\tjobobject: " << jobobject.toStdString() << std::endl //// ; // this->_setRunning(active == "active" && substate == "running"); // busArgument.endStructure(); // service_found = true; // break; // } // busArgument.endArray(); // } // if (!service_found) { // qWarning() << "Service"<< serviceName <<"not found!"; // _setRunning(false); // } return isRunning; } void delay(uint32_t msecs, bool processQtEvents = false) { if (processQtEvents) { } else { std::this_thread::sleep_for(std::chrono::duration(msecs)); } } #define SYSTEMD_TIMEOUT 30 void SystemdService::start() { checkIsRunning(); // qWarning() << "STARTING SERVICE: " << serviceName; if (isRunning) { // qWarning() << "WARNING! Service " << serviceName << " was already running."; return; } // systemd->call("StartUnit", serviceName, "fail"); //TODO check return of above call for(int c=0; this->isRunning; c++) { if (c > SYSTEMD_TIMEOUT*2) { // 30 seconds std::cout << "Failed to start service " << serviceName << "after " << SYSTEMD_TIMEOUT << "seconds." << std::endl; return; } std::cout << "Starting " << serviceName << " ..." << std::endl; delay(500); checkIsRunning(); } std::cout << "Service " << serviceName << " successfully started" << std::endl; } void SystemdService::stop() { checkIsRunning(); std::cout << "STOPPING SERVICE: " << serviceName << std::endl; if (!isRunning) { std::cout << "WARNING! Service " << serviceName << " was already stopped." << std::endl; return; } // systemd->call("StopUnit", serviceName, "fail"); //TODO check return of above call for(int c=0; !this->isRunning; c++) { if (c > SYSTEMD_TIMEOUT*2) { // 30 seconds std::cout << "Failed to stop service " << serviceName << "after " << SYSTEMD_TIMEOUT << "seconds." << std::endl; return; } std::cout << "Stopping " << serviceName << " ..." << std::endl; delay(500); checkIsRunning(); } std::cout << "Service " << serviceName << " successfully stopped" << std::endl; } void SystemdService::restart() { checkIsRunning(); std::cout << "RESTARTING SERVICE: " << serviceName << std::endl; // systemd->call("RestartUnit", serviceName, "fail"); //TODO check return of above call _setRunning(false); for(int c=0; !this->isRunning; c++) { if (c > SYSTEMD_TIMEOUT*2) { // 30 seconds std::cout << "Failed to restart service " << serviceName << "after " << SYSTEMD_TIMEOUT << "seconds." << std::endl; return; } std::cout << "Restarting " << serviceName << " ..." << std::endl; delay(500); checkIsRunning(); } std::cout << "Service " << serviceName << " successfully restarted" << std::endl; } //static bool variantToString(const QVariant &arg, QString &out) //{ //// int argType = arg.userType(); //// if (argType == QVariant::StringList) { //// out += QLatin1Char('{'); //// const QStringList list = arg.toStringList(); //// for (const QString &item : list) //// out += QLatin1Char('\"') + item + QLatin1String("\", "); //// if (!list.isEmpty()) //// out.chop(2); //// out += QLatin1Char('}'); //// } else if (argType == QVariant::ByteArray) { //// out += QLatin1Char('{'); //// QByteArray list = arg.toByteArray(); //// for (int i = 0; i < list.count(); ++i) { //// out += QString::number(list.at(i)); //// out += QLatin1String(", "); //// } //// if (!list.isEmpty()) //// out.chop(2); //// out += QLatin1Char('}'); //// } else if (argType == QVariant::List) { //// out += QLatin1Char('{'); //// const QList list = arg.toList(); //// for (const QVariant &item : list) { //// if (!variantToString(item, out)) //// return false; //// out += QLatin1String(", "); //// } //// if (!list.isEmpty()) //// out.chop(2); //// out += QLatin1Char('}'); //// } else if (argType == QMetaType::Char || argType == QMetaType::Short || argType == QMetaType::Int //// || argType == QMetaType::Long || argType == QMetaType::LongLong) { //// out += QString::number(arg.toLongLong()); //// } else if (argType == QMetaType::UChar || argType == QMetaType::UShort || argType == QMetaType::UInt //// || argType == QMetaType::ULong || argType == QMetaType::ULongLong) { //// out += QString::number(arg.toULongLong()); //// } else if (argType == QMetaType::Double) { //// out += QString::number(arg.toDouble()); //// } else if (argType == QMetaType::Bool) { //// out += QLatin1String(arg.toBool() ? "true" : "false"); //// } else if (argType == qMetaTypeId()) { //// argToString(qvariant_cast(arg), out); //// } else if (argType == qMetaTypeId()) { //// const QString path = qvariant_cast(arg).path(); //// out += QLatin1String("[ObjectPath: "); //// out += path; //// out += QLatin1Char(']'); //// } else if (argType == qMetaTypeId()) { //// out += QLatin1String("[Signature: ") + qvariant_cast(arg).signature(); //// out += QLatin1Char(']'); //// } else if (argType == qMetaTypeId()) { //// out += QLatin1String("[Unix FD: "); //// out += QLatin1String(qvariant_cast(arg).isValid() ? "valid" : "not valid"); //// out += QLatin1Char(']'); //// } else if (argType == qMetaTypeId()) { //// const QVariant v = qvariant_cast(arg).variant(); //// out += QLatin1String("[Variant"); //// int vUserType = v.userType(); //// if (vUserType != qMetaTypeId() //// && vUserType != qMetaTypeId() //// && vUserType != qMetaTypeId() //// && vUserType != qMetaTypeId()) //// out += QLatin1Char('(') + QLatin1String(v.typeName()) + QLatin1Char(')'); //// out += QLatin1String(": "); //// if (!variantToString(v, out)) //// return false; //// out += QLatin1Char(']'); //// } else if (arg.canConvert(QVariant::String)) { //// out += QLatin1Char('\"') + arg.toString() + QLatin1Char('\"'); //// } else { //// out += QLatin1Char('['); //// out += QLatin1String(arg.typeName()); //// out += QLatin1Char(']'); //// } // return true; //} //bool argToString(const QDBusArgument &busArg, QString &out) //{ // QString busSig = busArg.currentSignature(); // bool doIterate = false; // QDBusArgument::ElementType elementType = busArg.currentType(); // if (elementType != QDBusArgument::BasicType && elementType != QDBusArgument::VariantType // && elementType != QDBusArgument::MapEntryType) // out += QLatin1String("[Argument: ") + busSig + QLatin1Char(' '); // switch (elementType) { // case QDBusArgument::BasicType: // case QDBusArgument::VariantType: // if (!variantToString(busArg.asVariant(), out)) // return false; // break; // case QDBusArgument::StructureType: // busArg.beginStructure(); // out += QLatin1Char('%'); // doIterate = true; // break; // case QDBusArgument::ArrayType: // busArg.beginArray(); // out += QLatin1Char('{'); // doIterate = true; // break; // case QDBusArgument::MapType: // busArg.beginMap(); // out += QLatin1Char('{'); // doIterate = true; // break; // case QDBusArgument::MapEntryType: // busArg.beginMapEntry(); // if (!variantToString(busArg.asVariant(), out)) // return false; // out += QLatin1String(" = "); // if (!argToString(busArg, out)) // return false; // busArg.endMapEntry(); // break; // case QDBusArgument::UnknownType: // default: // out += QLatin1String(""); // return false; // } // if (doIterate && !busArg.atEnd()) { // while (!busArg.atEnd()) { // if (!argToString(busArg, out)) // return false; // out += QLatin1String(", "); // } // out.chop(2); // } // switch (elementType) { // case QDBusArgument::BasicType: // case QDBusArgument::VariantType: // case QDBusArgument::UnknownType: // case QDBusArgument::MapEntryType: // // nothing to do // break; // case QDBusArgument::StructureType: // busArg.endStructure(); // out += QLatin1Char('%'); // break; // case QDBusArgument::ArrayType: // out += QLatin1Char('}'); // busArg.endArray(); // break; // case QDBusArgument::MapType: // out += QLatin1Char('}'); // busArg.endMap(); // break; // } // if (elementType != QDBusArgument::BasicType && elementType != QDBusArgument::VariantType // && elementType != QDBusArgument::MapEntryType) // out += QLatin1Char(']'); // return true; //} } ukui-volume-control/backend/PaSourceStream.h0000664000175000017500000001207515171074712020107 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 PASOURCESTREAM_H #define PASOURCESTREAM_H #include "IStream.h" #include "AudioContext.h" namespace UkuiAudioFramwork { class PaSourceStream : public IStream { public: PaSourceStream(pa_context* ctx, PaDataInfo& info) : IStream(ctx, info, StreamType::STREAM_TYPE_SOURCE) {} ~PaSourceStream() = default; public: std::shared_ptr getActivePort() const; std::string getActivePortName() const; std::string getActivePortDescription() const; std::string getCardName() const; std::string getDeviceClass() const; void setMasterDevice(const std::string&); std::string getMasterDevice() const; void copy(const pa_source_info&); std::vector> getPorts() const; virtual std::string toString() const override; virtual NodeType getNodeType() const override; virtual uint32_t getIndex() const override; virtual std::string getName() const override; virtual pa_proplist* getProplist() const override; virtual uint32_t getCard() const override; virtual void setVolume(int) override; virtual const pa_cvolume getVolume() const override; virtual void setBalance(double) override; virtual double getBalance() const override; virtual void setMute(bool) override; virtual bool getMute() const override; virtual DefaultDeviceInfo getDefaultDevice() const override; virtual void setDefaultDevice(const std::string&, const std::string&) override; virtual std::vector getDeviceList() const override; virtual std::list> getAvailablePortList() const override; virtual void volumeChanged(int) override; virtual void muteChanged(bool) override; virtual void portChanged(const std::string&) override; virtual void deviceChanged(bool = false) override; private: std::string cardName; /**< Name of the card */ std::string name; /**< Name of the source */ uint32_t index; /**< Index of the source */ std::string description; /**< Description of this source */ pa_sample_spec sample_spec; /**< Sample spec of this source */ pa_channel_map channel_map; /**< Channel map */ uint32_t owner_module; /**< Owning module index, or PA_INVALID_INDEX. */ pa_cvolume volume; /**< Volume of the source */ int mute; /**< Mute switch of the sink */ uint32_t monitor_of_sink; /**< If this is a monitor source, the index of the owning sink, otherwise PA_INVALID_INDEX. */ std::string monitor_of_sink_name; /**< Name of the owning sink, or NULL. */ pa_usec_t latency; /**< Length of filled record buffer of this source. */ std::string driver; /**< Driver name */ pa_source_flags_t flags; /**< Flags */ pa_proplist *proplist = nullptr; /**< Property list \since 0.9.11 */ pa_usec_t configured_latency; /**< The latency this device has been configured to. \since 0.9.11 */ pa_volume_t base_volume; /**< Some kind of "base" volume that refers to unamplified/unattenuated volume in the context of the input device. \since 0.9.15 */ pa_source_state_t state; /**< State \since 0.9.15 */ uint32_t n_volume_steps; /**< Number of volume steps for sources which do not support arbitrary volumes. \since 0.9.15 */ uint32_t card; /**< Card index, or PA_INVALID_INDEX. \since 0.9.15 */ uint32_t n_ports; /**< Number of entries in port array \since 0.9.16 */ std::vector> ports; /**< Array of available ports, or NULL. Array is terminated by an entry set to NULL. The number of entries is stored in n_ports. \since 0.9.16 */ std::shared_ptr active_port {}; /**< Pointer to active port in the array, or NULL. \since 0.9.16 */ uint8_t n_formats; /**< Number of formats supported by the source. \since 1.0 */ pa_format_info **formats; /**< Array of formats supported by the source. \since 1.0 */ uint32_t priority; std::string m_deviceClass = ""; std::string m_masterDevice = ""; //start int tmpMute; //end }; } #endif // PASOURCESTREAM_H ukui-volume-control/backend/PipewireDevice.h0000664000175000017500000000324215171074712020112 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 PIPEWIREDEVICE_H #define PIPEWIREDEVICE_H #include #include namespace UkuiAudioFramwork { class Pipewire; /** * @todo write docs */ class PipewireDevice { void volumeChanged(float); public: /** * Default constructor */ PipewireDevice(Pipewire *parent, uint32_t id, const struct spa_dict *props); /** * Destructor */ virtual ~PipewireDevice(); void _device_info (const struct pw_device_info *info); void _device_param (int seq, uint32_t id, uint32_t index, uint32_t next, const struct spa_pod *param); // setters getters float volume() const { return m_volume; } void setVolume(float v) { m_volume = v; /*TODO*/ } private: const Pipewire* pipewire; const uint32_t m_id; pw_device* m_device = nullptr; spa_hook m_listener; float m_volume = 0; }; } #endif // PIPEWIREDEVICE_H ukui-volume-control/backend/PipewireManager.h0000664000175000017500000000743715171074712020277 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 PIPEWIREMANAGER_H #define PIPEWIREMANAGER_H #include "AudioManager.h" namespace UkuiAudioFramwork { class PipewireManager : public AudioManager { public: PipewireManager(int); ~PipewireManager() = default; public: virtual void init() override; virtual bool connect() override; // Interface virtual void loadModule(const std::string&, const std::string&) override; virtual void unloadModule(const std::string&) override; // Method virtual void setDetailVolume(const NodeType&, const std::string&, const std::string&, int) override; virtual int getDetailVolume(const NodeType&, const std::string&, const std::string&) const override; virtual void setVolume(const NodeType&, int, int) override; virtual int getVolume(const NodeType&, int) const override; virtual void setDetailMute(const NodeType&, const std::string&, const std::string&, bool) override; virtual bool getDetailMute(const NodeType&, const std::string&, const std::string&) const override; virtual void setBalance(const NodeType&, double) override; virtual double getBalance(const NodeType&) const override; virtual void setMute(const NodeType&, int, bool) override; virtual bool getMute(const NodeType&, int) const override; virtual DefaultDeviceInfo getDefaultDevice(const NodeType&, int) const override; virtual void setDefaultDevice(const NodeType&, int, const std::string&, const std::string&) override; virtual std::vector getDeviceList(const NodeType&) const override; virtual std::list getSinkList() const override; virtual std::list getSourceList() const override; virtual std::list> getAvailablePortList(const NodeType&) const override; virtual std::list getSinkInputList() const override; virtual std::list getSourceOutputList() const override; virtual std::list getStreamMediaList() const override; virtual std::list getModuleList() override; virtual void setEnabled(std::shared_ptr, const std::string&, const std::string&, bool) override; virtual bool isEnabled(const std::string&, const std::string&) const override; virtual bool isLoaded(const ModuleType&) const override; virtual bool isValidDevice(const NodeType&) const override; // signal virtual void deviceChanged(const NodeType&, const std::string&, const std::string&) override; virtual void deviceAdjust(const NodeType&) override; virtual void portChanged(const NodeType&, const std::string&) override; virtual void cardRemoved(const std::string&) override; virtual void addStream(int, int, const std::string&, const std::string&) override; virtual void removeStream(int) override; virtual void addStream(int, int, int, bool, const std::string&, const std::string&) override; virtual void removeStream(int, int) override; private: int m_reconnectTime; }; } #endif // PIPEWIREMANAGER_H ukui-volume-control/backend/PaSinkInputStream.cpp0000664000175000017500000001512615171074712021126 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "PaSinkInputStream.h" #include "PaSinkStream.h" #include "AudioMethod.h" namespace UkuiAudioFramwork { void PaSinkInputStream::copy(const pa_sink_input_info& other) { if (PA_INVALID_INDEX != index) { if (!pa_cvolume_equal(&volume, &other.volume) && index == other.index) { volumeChanged(paCvolumeToValue(other.volume)); } if (mute != other.mute && index == other.index) { muteChanged(!mute); } } index = other.index; name = other.name; owner_module = other.owner_module; client = other.client; sink = other.sink; sample_spec = other.sample_spec; channel_map = other.channel_map; volume = other.volume; buffer_usec = other.buffer_usec; sink_usec = other.sink_usec; resample_method = other.resample_method ? other.resample_method : ""; driver = other.driver; mute = other.mute; proplist = other.proplist; corked = other.corked; has_volume = other.has_volume; volume_writable = other.volume_writable; format = other.format; auto mediaRole = pa_proplist_gets(proplist, PA_PROP_MEDIA_ROLE); role = mediaRole ? mediaRole : "Playback Stream"; auto iconName = pa_proplist_gets(proplist, PA_PROP_APPLICATION_ICON_NAME); m_appIconName = iconName ? iconName : "application-x-desktop"; auto binary = pa_proplist_gets(proplist, PA_PROP_APPLICATION_PROCESS_BINARY); m_processBinary = binary ? binary : name; } std::string PaSinkInputStream::getMediaRole() const { return role; } std::string PaSinkInputStream::getAppIconName() const { return m_appIconName; } std::string PaSinkInputStream::getProcessBinary() const { return m_processBinary; } std::string PaSinkInputStream::getDriver() const { return driver; } uint32_t PaSinkInputStream::getSink() const { return sink; } std::string PaSinkInputStream::toString() const { std::string vol = "["; for (int i = 0; i < volume.channels; ++i) { vol += std::to_string(volume.values[i]); vol += ", "; } vol += "]"; return FORMAT("name={}, index={}, owner_module={}, sink={}, driver={}, volume={}, mute={}", name, index, owner_module, sink, driver, vol, mute); } NodeType PaSinkInputStream::getNodeType() const { return NodeType::SINK_INPUT; } uint32_t PaSinkInputStream::getIndex() const { return index; } std::string PaSinkInputStream::getName() const { return name; } pa_proplist* PaSinkInputStream::getProplist() const { return proplist; } uint32_t PaSinkInputStream::getCard() const { // TODO return {}; } void PaSinkInputStream::setVolume(int vol) { auto v = volume; for (int i = 0; i < v.channels; ++i) { v.values[i] = vol; } if (pa_cvolume_equal(&v, &volume)) return; pa_context_set_sink_input_volume(m_pContext, index, &v, nullptr, nullptr); } const pa_cvolume PaSinkInputStream::getVolume() const { return volume; } void PaSinkInputStream::setBalance(double v) { // TODO } double PaSinkInputStream::getBalance() const { // TODO return {}; } void PaSinkInputStream::setMute(bool mute) { pa_context_set_sink_input_mute(m_pContext, index, mute, nullptr, nullptr); } bool PaSinkInputStream::getMute() const { return mute; } DefaultDeviceInfo PaSinkInputStream::getDefaultDevice() const { DefaultDeviceInfo info; std::shared_ptr s = nullptr; for (const auto& c : m_info.cardInfoList) { auto it = std::find_if(c->sinks.begin(), c->sinks.end(), [this](const std::shared_ptr& stream) { return sink == stream->getIndex(); }); if (it != c->sinks.end()) { s = *it; break; } } if (s) { info.idx = s->getIndex(); info.name = s->getName(); info.cardName = std::dynamic_pointer_cast(s)->getCardName(); info.activePortName = std::dynamic_pointer_cast(s)->getActivePortName(); info.activePortLabel = std::dynamic_pointer_cast(s)->getActivePortDescription(); } return info; } void PaSinkInputStream::setDefaultDevice(const std::string& port, const std::string& card) { std::shared_ptr sink = nullptr; for (const auto& c : m_info.cardInfoList) { if (strcmp(card.c_str(), c->card.name.c_str())) continue; for(const auto& s : c->sinks) { auto ports = std::dynamic_pointer_cast(s)->getPorts(); auto it = std::find_if(ports.begin(), ports.end(), [port](const std::shared_ptr& info) { return info->name == port; }); if (it != std::dynamic_pointer_cast(s)->getPorts().end()) { sink = s; break; } } } if (!sink) { AUDIO_ERROR("Set {} default device to card: {}, port: {} failed, not found such device.", name, card, port); return; } this->sink = sink->getIndex(); AUDIO_DEBUG("Set {} default device to {}.", name, sink->getName()); pa_context_move_sink_input_by_index(m_pContext, index, sink->getIndex(), nullptr, nullptr); } std::vector PaSinkInputStream::getDeviceList() const { return {}; } std::list> PaSinkInputStream::getAvailablePortList() const { return {}; } void PaSinkInputStream::volumeChanged(int volume) { AudioMethod::getInstance().volumeChanged(NodeType::SINK_INPUT, index, volume); } void PaSinkInputStream::muteChanged(bool mute) { AudioMethod::getInstance().muteChanged(NodeType::SINK_INPUT, index, mute); } void PaSinkInputStream::portChanged(const std::string& name) { AudioMethod::getInstance().portChanged(NodeType::SINK_INPUT, name); } void PaSinkInputStream::deviceChanged(bool autoPause) { // AudioMethod::getInstance().deviceChanged(NodeType::SINK_INPUT, name); } } ukui-volume-control/backend/InitVolumeModule.cpp0000664000175000017500000000237715171074712021012 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "InitVolumeModule.h" namespace UkuiAudioFramwork { InitVolumeModule& InitVolumeModule::getInstance() { static InitVolumeModule instance; return instance; } bool InitVolumeModule::isNeedInitVolume(const std::string& portName, const std::string& cardName) { return !m_json.isContain(AudioVolumeInfo{portName, cardName, 0}); } void InitVolumeModule::insert(const std::string& portName, const std::string& cardName, uint32_t vol) { m_json.insert(AudioVolumeInfo{portName, cardName, vol}); } } ukui-volume-control/backend/AudioContext.cpp0000664000175000017500000000332615171074712020152 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "AudioContext.h" namespace UkuiAudioFramwork { AudioContext::AudioContext() : m_log(getLogPath(), "audio-service.log", EnumToInt(simple_logger::OutputType::Console) | EnumToInt(simple_logger::OutputType::LogFile)) { // 开启日志 std::string name = getModuleName(getModule()); m_log.AddModule(EnumToInt(getModule()), name); m_log.SetLogSwitchOn(simple_logger::LogLevel::Debug); m_log.SetOutputTypeOn(simple_logger::OutputType::LogFile); m_log.SetDetailMode(true); } AudioContext& AudioContext::getInstance() { static AudioContext context; return context; } std::string AudioContext::getLogPath() { const char* homeDir = getenv("HOME"); if (homeDir == nullptr) { return std::string("ukui-volume-control/log/"); } return std::string(homeDir) + "/.log/ukui-volume-control/log/"; } simple_logger::Log& AudioContext::getLogger() { return m_log; } Module AudioContext::getModule() const { return m_module; } } ukui-volume-control/backend/MultiAudioCombineJson.cpp0000664000175000017500000000761515171074712021754 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "MultiAudioCombineJson.h" #include "AudioContext.h" namespace UkuiAudioFramwork { MultiAudioCombineJson::MultiAudioCombineJson() { m_fileName = getenv("HOME"); m_fileName += MULTI_AUDIO_COMBINE_JSON; loadJson(); initJson(); } bool MultiAudioCombineJson::isContain(const MultiAudioCombineInfo&) const { // TODO: implement this function return false; } void MultiAudioCombineJson::insert(const MultiAudioCombineInfo& info) { auto str = info.type ? "output-combine" : "input-combine"; if (m_json[str]["status"] == true) { m_json[str]["devices"] = info.deviceList; // 直接赋值设备列表 } else { m_json[str]["devices"].clear(); // 当status为false时,清空devices数组 } } std::optional MultiAudioCombineJson::getValue(const std::string& key1, const std::string& key2) const { try { if (m_json.contains(key1)) { const nlohmann::json& combine = m_json[key1]; if (combine.contains(key2) && combine[key2].is_boolean()) { return combine[key2].get(); } else { AUDIO_ERROR("The key value {} does not exist in {}.", key2, key1); } } else { AUDIO_ERROR("{} does not exist.", key1); } } catch (nlohmann::json::parse_error& e) { AUDIO_ERROR("JSON parsing error, error: {}.", e.what()); } catch (nlohmann::json::out_of_range& e) { AUDIO_ERROR("JSON key out of range, error: {}.", e.what()); } catch (nlohmann::json::type_error& e) { AUDIO_ERROR("JSON type error, error: {}.", e.what()); } catch (std::exception& e) { AUDIO_ERROR("Other abnormalities, error: {}.", e.what()); } return std::nullopt; } void MultiAudioCombineJson::setValue(const std::string& key1, const std::string& key2, bool status) { try { if (m_json.contains(key1)) { nlohmann::json& combine = m_json[key1]; if (combine.contains(key2) && combine[key2].is_boolean()) { combine[key2] = status; } else { AUDIO_ERROR("The key value {} does not exist in {}.", key2, key1); } } else { AUDIO_ERROR("{} does not exist.", key1); } } catch (nlohmann::json::parse_error& e) { AUDIO_ERROR("JSON parsing error, error: {}.", e.what()); } catch (nlohmann::json::out_of_range& e) { AUDIO_ERROR("JSON key out of range, error: {}.", e.what()); } catch (nlohmann::json::type_error& e) { AUDIO_ERROR("JSON type error, error: {}.", e.what()); } catch (std::exception& e) { AUDIO_ERROR("Other abnormalities, error: {}.", e.what()); } writeToJson(); } void MultiAudioCombineJson::initJson() { auto input = m_json.find("input-combine"); if (input == m_json.end()) { m_json["input-combine"]["status"] = false; m_json["input-combine"]["devices"] = nlohmann::json::array(); } auto output = m_json.find("output-combine"); if (output == m_json.end()) { m_json["output-combine"]["status"] = false; m_json["output-combine"]["devices"] = nlohmann::json::array(); } writeToJson(); } } ukui-volume-control/backend/PipewireManager.cpp0000664000175000017500000001115115171074712020616 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "PipewireManager.h" #include namespace UkuiAudioFramwork { PipewireManager::PipewireManager(int times) : m_reconnectTime(times) { } void PipewireManager::init() { // TODO } bool PipewireManager::connect() { // TODO return true; } // Interface void PipewireManager::loadModule(const std::string& name, const std::string& param) { // TODO } void PipewireManager::unloadModule(const std::string& name) { // TODO } // Method void PipewireManager::setDetailVolume(const NodeType& type, const std::string& name, const std::string& dev, int volume) { // TODO } int PipewireManager::getDetailVolume(const NodeType& type, const std::string& name, const std::string& dev) const { // TODO return 100; } void PipewireManager::setVolume(const NodeType& type, int idx, int volume) { // TODO } int PipewireManager::getVolume(const NodeType& type, int idx) const { // TODO return 100; } void PipewireManager::setDetailMute(const NodeType& type, const std::string& name, const std::string& dev, bool mute) { // TODO } bool PipewireManager::getDetailMute(const NodeType& type, const std::string& name, const std::string& dev) const { // TODO return false; } void PipewireManager::setBalance(const NodeType& type, double v) { // TODO } double PipewireManager::getBalance(const NodeType& type) const { // TODO return 0.0; } void PipewireManager::setMute(const NodeType& type, int idx, bool mute) { // TODO } bool PipewireManager::getMute(const NodeType& type, int idx) const { // TODO return false; } DefaultDeviceInfo PipewireManager::getDefaultDevice(const NodeType& type, int idx) const { // TODO return {}; } void PipewireManager::setDefaultDevice(const NodeType& type, int idx, const std::string& name, const std::string& dev) { // TODO } std::vector PipewireManager::getDeviceList(const NodeType& type) const { // TODO return {}; } std::list PipewireManager::getSinkList() const { // TODO return {}; } std::list PipewireManager::getSourceList() const { // TODO return {}; } std::list> PipewireManager::getAvailablePortList(const NodeType& type) const { // TODO return {}; } std::list PipewireManager::getSinkInputList() const { // TODO return {}; } std::list PipewireManager::getSourceOutputList() const { // TODO return {}; } std::list PipewireManager::getStreamMediaList() const { // TODO return {}; } std::list PipewireManager::getModuleList() { // TODO return {}; } void PipewireManager::setEnabled(std::shared_ptr, const std::string&, const std::string&, bool) { // TODO } bool PipewireManager::isEnabled(const std::string&, const std::string&) const { // TODO return true; } bool PipewireManager::isLoaded(const ModuleType&) const { // TODO return true; } bool PipewireManager::isValidDevice(const NodeType&) const { // TODO return true; } // signal void PipewireManager::deviceChanged(const NodeType& type, const std::string& portName, const std::string& cardName) { // TODO } void PipewireManager::deviceAdjust(const NodeType& type) { // TODO } void PipewireManager::portChanged(const NodeType& type, const std::string& name) { // TODO } void PipewireManager::cardRemoved(const std::string& name) { // TODO } void PipewireManager::addStream(int idx, int value, const std::string& iconName, const std::string& descName) { // TODO } void PipewireManager::removeStream(int idx) { // TODO } void PipewireManager::addStream(int dire, int idx, int value, bool mute, const std::string& iconName, const std::string& descName) { // TODO } void PipewireManager::removeStream(int dire, int idx) { // TODO } } ukui-volume-control/backend/BackendFactory.h0000664000175000017500000000237615171074712020074 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 BACKENDFACTORY_H #define BACKENDFACTORY_H #include "IBackend.h" namespace UkuiAudioFramwork { class BackendFactory { public: BackendFactory() = default; virtual ~BackendFactory() = default; public: virtual std::shared_ptr createBackend() = 0; private: BackendFactory(const BackendFactory&) = delete; BackendFactory(BackendFactory&&) = delete; void operator=(const BackendFactory&) = delete; void operator=(BackendFactory&&) = delete; }; } #endif // BACKENDFACTORY_H ukui-volume-control/backend/utils.cpp0000664000175000017500000000723015171074712016702 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "utils.h" //#include #include namespace UkuiAudioFramwork { void print_dict(const struct spa_dict *dictionary) { const struct spa_dict_item *item; spa_dict_for_each(item, dictionary) { std::cout << '\t' << item->key << ":" << item->value << std::endl; } } uint32_t spa_dict_get_u32(const spa_dict *props, const char *key) { const char* str = spa_dict_lookup(props, key); if (str) { try { return std::stoul(str); } catch (const std::invalid_argument&) {} catch (const std::out_of_range&) {} } return -1; } std::ostream& operator<< (std::ostream& out, const spa_pod& _pod) { const spa_pod *pod = &_pod; switch (pod->type) { case SPA_TYPE_None: out << "[None]"; break; case SPA_TYPE_Bool: out << SPA_POD_VALUE(spa_pod_bool, pod); break; case SPA_TYPE_Id: out << SPA_POD_VALUE(spa_pod_id, pod); break; case SPA_TYPE_Int: out << SPA_POD_VALUE(spa_pod_int, pod); break; case SPA_TYPE_Long: out << SPA_POD_VALUE(spa_pod_long, pod); break; case SPA_TYPE_Float: out << SPA_POD_VALUE(spa_pod_float, pod); break; case SPA_TYPE_Double: out << SPA_POD_VALUE(spa_pod_double, pod); break; case SPA_TYPE_String: out << "[string]";//SPA_POD_VALUE(spa_pod_string, pod); break; case SPA_TYPE_Bytes: out << "[bytes]";//SPA_POD_VALUE(spa_type_, pod); break; case SPA_TYPE_Rectangle: out << "[rectangle]";//SPA_POD_VALUE(spa_type_rec, pod); break; case SPA_TYPE_Fraction: out << "[fraction]";//SPA_POD_VALUE(spa_type_fraction, pod); break; case SPA_TYPE_Bitmap: out << "[bitmap]";//SPA_POD_VALUE(spa_type_bitmap, pod); break; case SPA_TYPE_Array: out << "[array]";//SPA_POD_VALUE(spa_type_array, pod); break; case SPA_TYPE_Struct: out << "[struct]";//SPA_POD_VALUE(SPA_TYPE_, pod); break; case SPA_TYPE_Object: out << "[object]";//SPA_POD_VALUE(SPA_TYPE_, pod); break; case SPA_TYPE_Sequence: out << "[sequence]";//SPA_POD_VALUE(SPA_TYPE_, pod); break; case SPA_TYPE_Pointer: out << "[pointer]";//SPA_POD_VALUE(SPA_TYPE_, pod); break; case SPA_TYPE_Fd: out << "[fd]";//SPA_POD_VALUE(SPA_TYPE_, pod); break; case SPA_TYPE_Choice: out << "[choice]";//SPA_POD_VALUE(SPA_TYPE_, pod); break; case SPA_TYPE_Pod: out << "[pod]";//SPA_POD_VALUE(SPA_TYPE_, pod); break; default: out << "{unknown}"; break; } return out; } } ukui-volume-control/backend/PipewireManager1.cpp0000664000175000017500000003666615171074712020721 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "PipewireManager1.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "PipewireSettings.h" #include "PipewireClient.h" #include "PipewireNode.h" #include "PipewireLink.h" #include "PipewireAlsaNode.h" #include "PipewirePort.h" #include "PipewireDevice.h" //#include namespace UkuiAudioFramwork { #define PIPEWIRE_CAST(x) Pipewire* _this = static_cast(x); static inline bool streq(const char *s1, const char *s2) { return SPA_LIKELY(s1 && s2) ? strcmp(s1, s2) == 0 : s1 == s2; } void Pipewire::round_trip() { resync(); // The event is collected by callback _on_core_done and // when proper, it sets round_trip_done to true while(!round_trip_done) { pw_main_loop_run(loop); } } std::string Pipewire::formatTime(double val) { char buffer[64]; val *= 1000000000.0f; if (val < 1000000llu) { val /= 1000.0f; std::sprintf(buffer, "%5.1fµs", val); } else if (val < 1000000000llu) { val /= 1000000.0f; std::sprintf(buffer, "%5.1fms", val); } else { val /= 1000000000.0f; std::sprintf(buffer, "%5.1fs", val); } return std::string(buffer); } void Pipewire::resync() { round_trip_done = false; // This is async, will trigger a DONE event // with `seq` number the number just returned by this function sync = pw_core_sync(core, PW_ID_CORE, sync); } //----------------------------------------------------------------------------- static void do_quit(void *userdata, int /*signal_number*/) { PIPEWIRE_CAST(userdata); _this->_quit(); } void Pipewire::_quit() { pw_main_loop_quit(loop); // emit quit(); } //----------------------------------------------------------------------------- static void on_core_done(void *data, uint32_t id, int seq) { PIPEWIRE_CAST(data); _this->_on_core_done(id, seq); } void Pipewire::_on_core_done(uint32_t id, int seq) { // std::cout << "_on_core_done(" << id << ',' << seq << ')' << std::endl; if (id == PW_ID_CORE && sync == seq) { round_trip_done = true; pw_main_loop_quit(loop); } } static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message) { PIPEWIRE_CAST(data); _this->_on_core_error(id, seq, res, message); } void Pipewire::_on_core_error(u_int32_t id, int seq, int res, const char *message) { pw_log_error("error id:%u seq:%d res:%d (%s): %s", id, seq, res, spa_strerror(res), message); if (id == PW_ID_CORE && res == -EPIPE) pw_main_loop_quit(loop); } static const pw_core_events core_events = { .version = PW_VERSION_CORE_EVENTS, .info = nullptr, .done = on_core_done, .ping = nullptr, .error = on_core_error, .remove_id = nullptr, .bound_id = nullptr, .add_mem = nullptr, .remove_mem = nullptr }; //----------------------------------------------------------------------------- static void registry_event(void *data, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const struct spa_dict *props) { PIPEWIRE_CAST(data); _this->_registry_event(id, permissions, type, version, props); } static void registry_event_remove(void *data, uint32_t id) { PIPEWIRE_CAST(data); _this->_registry_event_remove(id); } void Pipewire::_registry_event(uint32_t id, uint32_t permissions, const char *type, uint32_t version, const struct spa_dict *props) { std::cout << "object: id(" << id << ") type(" << type << '/' << version << ')' << std::endl; // emit registryObject(id, permissions, type, version, props); if (strcmp(type, PW_TYPE_INTERFACE_Client) == 0) { PipewireClient *pw_client = new PipewireClient(this, id, props); // emit clientChanged(); // Shot only once when data has arrived // QMetaObject::Connection *const connection = new QMetaObject::Connection; // *connection = connect(pw_client, &QPipewireClient::propertiesChanged, [this, pw_client, connection]() { // if (this->isPipewireMediaSession()) { // // creating alsa properties when media-session is not installed could crash the application // alsa_properties = new AlsaProperties(pw_client, this); // emit alsaPropertiesChanged(); // } // // delete so the connection is not shot twice // delete connection; // }); pw_clients.push_back(pw_client); } else if (strcmp(type, PW_TYPE_INTERFACE_Metadata) == 0) { const char *metadata_name; metadata_name = spa_dict_lookup(props, PW_KEY_METADATA_NAME); if (metadata_name != nullptr) { if (this->pw_settings == nullptr && streq(metadata_name, "settings")) { pw_settings = new PipewireSettings(this, id, props); // emit settingsChanged(); } else { // qWarning() << "Ignoring metadata \"" << metadata_name << '"'; } } } else if (streq(type, PW_TYPE_INTERFACE_Profiler)) { if (pw_profiler != nullptr) { // qWarning() << "Ignoring profiler " << id << ": already attached"; return; } pw_profiler = new PipewireProfiler(this, id, props); // emit profilerChanged(); } else if (streq(type, PW_TYPE_INTERFACE_Node)) { PipewireNode *node; const std::string str = spa_dict_lookup(props, PW_KEY_NODE_NAME) ? spa_dict_lookup(props, PW_KEY_NODE_NAME) : ""; if (str.starts_with("alsa_input.") || str.starts_with("alsa_output.")) { node = new PipewireAlsaNode(this, id, props); } else { node = new PipewireNode(this, id, props); } m_nodes.push_back(node); std::cout << "Adding node id(" << id << "): " << node->name() << " nodetype (" << node->nodeType() << ")" << std::endl; // emit nodesChanged(); } else if (streq(type, PW_TYPE_INTERFACE_Link)) { PipewireLink* link = new PipewireLink(this, id, props); m_links.push_back(link); std::cout << "Adding link id(" << id << "): " << std::endl; // emit linksChanged(); } else if (streq(type, PW_TYPE_INTERFACE_Port)) { PipewirePort *port = new PipewirePort(this, id, props); m_ports.push_back(port); std::cout << "Adding port id(" << id << "): " << std::endl; // emit portsChanged(); } else if (streq(type, PW_TYPE_INTERFACE_Device)) { PipewireDevice *device = new PipewireDevice(this, id, props); m_devices.push_back(device); std::cout << "Adding device id(" << id << "): " << std::endl; } } void Pipewire::_registry_event_remove(uint32_t id) { std::cout << "Attempting to remove id(" << id << ")" << std::endl; for(int i=0; iid_u32() == id) { PipewireNode *node = candidate; // ALL interfaces that are not NODES are ignored here. std::cout << "Removing id(" << id << ":" << node->id() << "): " << node->name() << std::endl; m_nodes.erase(m_nodes.begin()+ i); // emit nodesChanged(); // node->deleteLater(); return; } } for(int i=0; iid_u32() == id) { PipewireLink* link = candidate; m_links.erase(m_links.begin() + i); // emit linksChanged(); // link->deleteLater(); return; } } } static const pw_registry_events registry_events = { .version = PW_VERSION_REGISTRY_EVENTS, .global = registry_event, .global_remove = registry_event_remove, }; //----------------------------------------------------------------------------- Pipewire::Pipewire(int *argc, char **argv[]) { spa_zero(core_listener); spa_zero(registry_listener); pw_init(argc, argv); std::cout << "Compiled with libpipewire " << pw_get_headers_version() << std::endl; std::cout << "Linked with libpipewire " << pw_get_library_version() << std::endl; loop = pw_main_loop_new(nullptr); if (loop == nullptr) { throw std::runtime_error("Error initializing Pipewire mainloop"); } pw_loop_add_signal(pw_main_loop_get_loop(loop), SIGINT, do_quit, this); pw_loop_add_signal(pw_main_loop_get_loop(loop), SIGTERM, do_quit, this); context = pw_context_new(pw_main_loop_get_loop(loop), nullptr, 0); if (context == nullptr) { throw std::runtime_error("Error creating Pipewire context"); } // Need this line or the profiler will not load. pw_context_load_module(context, PW_EXTENSION_MODULE_PROFILER, nullptr, nullptr); //TODO remote should be? (empty string seems to work correctly) const char *remote = ""; core = pw_context_connect(context, nullptr,//pw_properties_new(PW_KEY_REMOTE_NAME, remote, nullptr), 0); if (core == nullptr) { throw std::runtime_error("Can't connect to pipewire"); } pw_core_add_listener(core, &core_listener, &core_events, this); registry = pw_core_get_registry(core, PW_VERSION_REGISTRY, 0); pw_registry_add_listener(registry, ®istry_listener, ®istry_events, this); pipewire_media_session = new SystemdService("pipewire-media-session", true); wireplumber_service = new SystemdService("wireplumber", true); // QT stuff // connect(m_nodes, &QPipewireNodeListModel::layoutChanged, this, [this]() { // emit nodesChanged(); // }); } Pipewire::~Pipewire() { delete pipewire_media_session; if (pw_settings != nullptr) { delete pw_settings; } for (PipewireClient* pw_client: pw_clients) { // delete pw_client; } if (alsa_properties != nullptr) { delete alsa_properties; } if (pw_profiler != nullptr) { delete pw_profiler; } // PipewireNode *node; for(const auto& node : m_nodes) { delete node; } // delete m_nodes; pw_proxy_destroy((struct pw_proxy*) registry); pw_core_disconnect(core); pw_context_destroy(context); pw_main_loop_destroy(loop); pw_deinit(); } void Pipewire::init() { std::atomic stop(false); std::thread worker([this, &stop]() { while (!stop) { round_trip(); std::this_thread::sleep_for(std::chrono::milliseconds(100)); } }); worker.join(); } bool Pipewire::connect() { // TODO return true; } // Interface void Pipewire::loadModule(const std::string& name, const std::string& param) { pw_context_load_module(context, name.c_str(), param.c_str(), nullptr); } void Pipewire::unloadModule(const std::string& name) { // pw_impl_module_destroy() } // Method void Pipewire::setDetailVolume(const NodeType& type, const std::string& name, const std::string& dev, int volume) { // TODO } int Pipewire::getDetailVolume(const NodeType& type, const std::string& name, const std::string& dev) const { // TODO return 100; } void Pipewire::setVolume(const NodeType& type, int idx, int volume) { // TODO } int Pipewire::getVolume(const NodeType& type, int idx) const { // TODO return 100; } void Pipewire::setDetailMute(const NodeType& type, const std::string& name, const std::string& dev, bool mute) { // TODO } bool Pipewire::getDetailMute(const NodeType& type, const std::string& name, const std::string& dev) const { // TODO return false; } void Pipewire::setBalance(const NodeType& type, double v) { // TODO } double Pipewire::getBalance(const NodeType& type) const { // TODO return 0.0; } void Pipewire::setMute(const NodeType& type, int idx, bool mute) { // TODO } bool Pipewire::getMute(const NodeType& type, int idx) const { // TODO return false; } DefaultDeviceInfo Pipewire::getDefaultDevice(const NodeType& type, int idx) const { // TODO return {}; } void Pipewire::setDefaultDevice(const NodeType& type, int idx, const std::string& name, const std::string& dev) { // TODO } std::vector Pipewire::getDeviceList(const NodeType& type) const { // TODO return {}; } std::list Pipewire::getSinkList() const { // TODO return {}; } std::list Pipewire::getSourceList() const { // TODO return {}; } std::list> Pipewire::getAvailablePortList(const NodeType& type) const { // TODO return {}; } std::list Pipewire::getSinkInputList() const { // TODO return {}; } std::list Pipewire::getSourceOutputList() const { // TODO return {}; } std::list Pipewire::getStreamMediaList() const { // TODO return {}; } std::list Pipewire::getModuleList() { // TODO return {}; } void Pipewire::setEnabled(std::shared_ptr, const std::string&, const std::string&, bool) { // TODO } bool Pipewire::isEnabled(const std::string&, const std::string&) const { // TODO return true; } bool Pipewire::isLoaded(const ModuleType&) const { // TODO return true; } bool Pipewire::isValidDevice(const NodeType&) const { // TODO return true; } // signal void Pipewire::deviceChanged(const NodeType& type, const std::string& portName, const std::string& cardName) { // TODO } void Pipewire::deviceAdjust(const NodeType& type) { // TODO } void Pipewire::portChanged(const NodeType& type, const std::string& name) { // TODO } void Pipewire::cardRemoved(const std::string& name) { // TODO } void Pipewire::addStream(int idx, int value, const std::string& iconName, const std::string& descName) { // TODO } void Pipewire::removeStream(int idx) { // TODO } void Pipewire::addStream(int dire, int idx, int value, bool mute, const std::string& iconName, const std::string& descName) { // TODO } void Pipewire::removeStream(int dire, int idx) { // TODO } } ukui-volume-control/backend/PipewireDevice.cpp0000664000175000017500000000645515171074712020456 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "PipewireDevice.h" #include #include #include "PipewireManager1.h" #include "AudioContext.h" namespace UkuiAudioFramwork { #define PIPEWIRE_CAST(x) PipewireDevice* _this = static_cast(x); static void s_device_info (void* data, const struct pw_device_info* info) { PIPEWIRE_CAST(data); _this->_device_info(info); } void PipewireDevice::_device_info(const struct pw_device_info* info) { // spa_dict_lookup(info->props, PW_KEY_NODE_NICK); if (info->change_mask & PW_DEVICE_CHANGE_MASK_PROPS) { } if (info->change_mask & PW_DEVICE_CHANGE_MASK_PARAMS) { // TODO pw_node_enum_params(m_device, 0, SPA_PARAM_Route, 0, 0, nullptr); } } static void s_device_param (void *data, int seq, uint32_t id, uint32_t index, uint32_t next, const struct spa_pod *param) { PIPEWIRE_CAST(data); _this->_device_param(seq, id, index, next, param); } void PipewireDevice::_device_param(int seq, uint32_t id, uint32_t index, uint32_t next, const struct spa_pod* param) { // spa_debug_pod(2, nullptr, param); const spa_pod_prop* prop; prop = spa_pod_find_prop(param, nullptr, SPA_PARAM_ROUTE_props); if (prop == nullptr) return; const spa_pod *route_props = &prop->value; prop = spa_pod_find_prop(route_props, nullptr, SPA_PROP_channelVolumes); if (prop == nullptr) return; assert(spa_pod_is_array(&prop->value)); const spa_pod_array *volumes = (const spa_pod_array*) &prop->value; uint32_t n_channels = 0; uint32_t type = SPA_POD_ARRAY_VALUE_TYPE(volumes); assert(SPA_TYPE_Float == type); float *data = (float*) spa_pod_get_array((const spa_pod*) volumes, &n_channels); m_volume = data[0]; // emit volumeChanged(m_volume); AUDIO_DEBUG("New volume for device: {} volume: {}", m_id, m_volume); } static const pw_device_events device_events = { .version = PW_VERSION_CORE_EVENTS, .info = &s_device_info, .param = &s_device_param }; PipewireDevice::PipewireDevice(Pipewire* parent, uint32_t id, const spa_dict* props) : pipewire(parent) , m_id(id) { m_device = static_cast( pw_registry_bind(pipewire->registry, id, PW_TYPE_INTERFACE_Device, PW_VERSION_CLIENT, 0)); if (m_device == nullptr) { throw std::runtime_error("Error creating device proxy"); } pw_device_add_listener(m_device, &m_listener, &device_events, this); } PipewireDevice::~PipewireDevice() { if (m_device != nullptr) { pw_proxy_destroy((pw_proxy*) m_device); } } } ukui-volume-control/backend/Util.cpp0000664000175000017500000001002115171074712016447 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "Util.h" #include namespace UkuiAudioFramwork { std::string getModuleName(const Module& module) { switch (module) { CASE(Module::ControlCenter); CASE(Module::Service); CASE(Module::Tray); DEFAULT(module); } } std::string getBackendName(const BackendType& type) { switch (type) { CASE(BackendType::PULSEAUDIO); CASE(BackendType::PIPEWIRE); CASE(BackendType::ALSA); CASE(BackendType::JACK); DEFAULT(type); } } std::string getNodeTypeName(const NodeType& type) { switch (type) { CASE(NodeType::INPUT); CASE(NodeType::OUTPUT); CASE(NodeType::SINK_INPUT); CASE(NodeType::SOURCE_OUTPUT); DEFAULT(type); } } bool hasKeyword(const std::vector& stringList, const std::string& keyword) { for (const auto& str : stringList) { if (str.find(keyword) != std::string::npos) { return true; } } return false; } DevicePortType getDevicePortType(const std::string& cardName, const std::string& portName) { std::vector stringList = {cardName, portName}; if (hasKeyword(stringList, "multichannel")) { return DevicePortType::PORT_TYPE_MULTICHANNEL; } if (hasKeyword(stringList, "bluez") || hasKeyword(stringList, "bluetooth")) { return DevicePortType::PORT_TYPE_BLUETOOTH; } if (hasKeyword(stringList, "linein") || hasKeyword(stringList, "lineout")) { return DevicePortType::PORT_TYPE_LINEIO; } if (hasKeyword(stringList, "rear-mic") || hasKeyword(stringList, "front-mic") || hasKeyword(stringList, "[In] Mic2") || hasKeyword(stringList, "headphone") || hasKeyword(stringList, "Headphone") || hasKeyword(stringList, "headset")) { return DevicePortType::PORT_TYPE_HEADSET; } if (hasKeyword(stringList, "usb")) { return DevicePortType::PORT_TYPE_USB; } if (hasKeyword(stringList, "hdmi") || hasKeyword(stringList, "HDMI")) { return DevicePortType::PORT_TYPE_HDMI; } if (hasKeyword(stringList, "speaker") || hasKeyword(stringList, "input-mic") || hasKeyword(stringList, "[In] Mic1")) { return DevicePortType::PORT_TYPE_BUILTIN; } return DevicePortType::PORT_TYPE_UNKNOWN; } int calaPriorityByName(const std::string& name) { if (!std::string::npos != name.find("bluez")) { return 2; } else if (!std::string::npos != name.find("usb")) { return 1; } return 0; } std::string DeviceInfo::toString() const { return FORMAT("cardName: {}, cardDesc: {}, portName: {}, portLabel: {}, priority: {}, direction: {}, available: {}, enabled: {}, type: {}", cardName, cardDesc, portName, portLabel, priority, direction, available, enabled, EnumToInt(type)); } std::string DefaultDeviceInfo::toString() const { return FORMAT("idx: {}, name: {}, cardName: {}, activePortName: {}, activePortLabel: {}", idx, name, cardName, activePortName, activePortLabel); } std::string StreamMediaInfo::toString() const { return FORMAT("name: {}, index: {}, volume: {}, mediaRole: {}, iconName: {}, binary: {}", name, index, volume, mediaRole, iconName, binary); } } ukui-volume-control/backend/JackDetect.h0000664000175000017500000001266415171074712017217 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 . */ #pragma once #include #include #include #include #include #include #include namespace UkuiAudioFramwork { enum class AlsaJackType { ALSA_JACK_FRONTMIC, ALSA_JACK_HEADPHONEMIC, ALSA_JACK_HEADSETMIC, ALSA_JACK_INTERNALMIC, ALSA_JACK_LINEIN, ALSA_JACK_INPUTMIC, ALSA_JACK_REARMIC, ALSA_JACK_HEADPHONE2, ALSA_JACK_HEADPHONE, ALSA_JACK_LINEOUT, ALSA_JACK_SPEAKER, ALSA_JACK_HDMI, ALSA_JACK_UNKNOEW }; struct AlsaJack { // uint32_t card; uint32_t numid; std::string name; // jack name AlsaJackType type; bool plugin; }; struct AlsaCard { std::string name; std::vector jacks; }; class JackDetect { public: using EventCallback = std::function; explicit JackDetect(EventCallback cb); virtual ~JackDetect(); public: void start(); void stop(); private: void loop(); void scanCards(); std::vector listSoundCards(); bool jackIsPlugged(snd_ctl_elem_id_t*, const char*); void enumJacks(const std::string&, JackDetect::EventCallback); AlsaJackType getJackTypeByName(const std::string&) const; private: EventCallback m_callback; std::thread m_worker; std::atomic m_bRunning {false}; int m_inotifyFd {-1}; std::vector m_watchDescriptors; std::vector m_cards; std::unordered_map m_jackMap { {"Front Mic Jack", AlsaJackType::ALSA_JACK_FRONTMIC}, {"Front Mic Phantom Jack", AlsaJackType::ALSA_JACK_FRONTMIC}, {"Headphone Mic Jack", AlsaJackType::ALSA_JACK_HEADPHONEMIC}, {"Headset Mic Jack", AlsaJackType::ALSA_JACK_HEADSETMIC}, {"Headset Mic Phantom Jack", AlsaJackType::ALSA_JACK_HEADSETMIC}, {"Internal Mic Phantom Jack", AlsaJackType::ALSA_JACK_INTERNALMIC}, {"Line Phantom Jack", AlsaJackType::ALSA_JACK_LINEIN}, {"Line - Input Jack", AlsaJackType::ALSA_JACK_LINEIN}, {"Mic Jack", AlsaJackType::ALSA_JACK_INPUTMIC}, {"Mic Phantom Jack", AlsaJackType::ALSA_JACK_INPUTMIC}, {"Mic - Input Jack", AlsaJackType::ALSA_JACK_INPUTMIC}, {"Rear Mic Jack", AlsaJackType::ALSA_JACK_REARMIC}, {"Rear Mic - Input Jack", AlsaJackType::ALSA_JACK_REARMIC}, {"Rear Mic Phantom Jack", AlsaJackType::ALSA_JACK_REARMIC}, {"Front Headphone,1 Jack", AlsaJackType::ALSA_JACK_HEADPHONE2}, {"Front Headphone Surround Jack", AlsaJackType::ALSA_JACK_HEADPHONE2}, {"Dock Headphone Jack", AlsaJackType::ALSA_JACK_HEADPHONE}, {"Dock Headphone Phantom Jack", AlsaJackType::ALSA_JACK_HEADPHONE}, {"Front Headphone Jack", AlsaJackType::ALSA_JACK_HEADPHONE}, {"Front Headphone Front Jack", AlsaJackType::ALSA_JACK_HEADPHONE}, {"Front Headphone Phantom Jack", AlsaJackType::ALSA_JACK_HEADPHONE}, {"Headphone Jack", AlsaJackType::ALSA_JACK_HEADPHONE}, {"Headphone Phantom Jack", AlsaJackType::ALSA_JACK_HEADPHONE}, {"Headphone Mic Jack", AlsaJackType::ALSA_JACK_HEADPHONE}, {"Headphone - Output Jack", AlsaJackType::ALSA_JACK_HEADPHONE}, {"Line Out Jack", AlsaJackType::ALSA_JACK_LINEOUT}, {"Line Out Phantom Jack", AlsaJackType::ALSA_JACK_LINEOUT}, {"Front Line Out Jack", AlsaJackType::ALSA_JACK_LINEOUT}, {"Front Line Out Phantom Jack", AlsaJackType::ALSA_JACK_LINEOUT}, {"Rear Line Out Jack", AlsaJackType::ALSA_JACK_LINEOUT}, {"Line Out Front Jack", AlsaJackType::ALSA_JACK_LINEOUT}, {"Line Out Front Phantom Jack", AlsaJackType::ALSA_JACK_LINEOUT}, {"Line Out CLFE Jack", AlsaJackType::ALSA_JACK_LINEOUT}, {"Line Out CLFE Phantom Jack", AlsaJackType::ALSA_JACK_LINEOUT}, {"Line Out Surround Jack", AlsaJackType::ALSA_JACK_LINEOUT}, {"Line Out Surround Phantom Jack", AlsaJackType::ALSA_JACK_LINEOUT}, {"Line Out Side Jack", AlsaJackType::ALSA_JACK_LINEOUT}, {"Line Out Side Phantom Jack", AlsaJackType::ALSA_JACK_LINEOUT}, {"Dock Line Out Jack", AlsaJackType::ALSA_JACK_LINEOUT}, {"Dock Line Out Phantom Jack", AlsaJackType::ALSA_JACK_LINEOUT}, {"Speaker Jack", AlsaJackType::ALSA_JACK_SPEAKER}, {"Speaker Phantom Jack", AlsaJackType::ALSA_JACK_SPEAKER}, {"Speaker Front Phantom Jack", AlsaJackType::ALSA_JACK_SPEAKER}, {"Speaker - Output Jack", AlsaJackType::ALSA_JACK_SPEAKER}, {"HDMI/DP", AlsaJackType::ALSA_JACK_HDMI} }; }; } ukui-volume-control/backend/PipewireSettings.h0000664000175000017500000000367715171074712020527 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 . */ #pragma once #include "PipewireMetadata.h" namespace UkuiAudioFramwork { class PipewireMetadata; class Pipewire; class PipewireSettings : public PipewireMetadata { void logLevelChanged(int); void force_sampleRateChanged(int); void force_bufferChanged(int); void minBufferChanged(int); void maxBufferChanged(int); public: explicit PipewireSettings(Pipewire *parent, uint32_t id, const spa_dict* props); virtual ~PipewireSettings() = default; int logLevel() { return m_logLevel; } int minBuffer() { return m_minBuffer; } int maxBuffer() { return m_maxBuffer; } int force_sampleRate() { return m_force_sampleRate; } int force_buffer() { return m_force_buffer; } void setLogLevel(int newLogLevel); void setMinBuffer(int newMinBuffer); void setMaxBuffer(int newMaxBuffer); void setForce_sampleRate(int newSampleRate); void setForce_buffer(int newBuffer); private: void keyUpdated(uint32_t id, const char* key, const char* type, const char* value); private: int m_logLevel = static_cast(spa_log_level::SPA_LOG_LEVEL_WARN); int m_minBuffer = 0; int m_maxBuffer = 0; int m_force_sampleRate = 0; int m_force_buffer = 0; }; } ukui-volume-control/backend/MediaControler.cpp0000664000175000017500000001232015171074712020445 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "MediaControler.h" #include #include "AudioContext.h" namespace UkuiAudioFramwork { const char* mprisPlayerDestPrefix = "org.mpris.MediaPlayer2"; MediaControler& MediaControler::getInstance() { static MediaControler instance; return instance; } void MediaControler::pauseAllPlayers() { GError* error = nullptr; auto connection = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, &error); if (!connection) { AUDIO_ERROR("Failed to get session bus connection: {}.", error->message); g_error_free(error); return; } auto playerNames = getMprisPlayers(connection); if (playerNames.empty()) { AUDIO_ERROR("No MPRIS players found."); g_bus_unown_name(g_bus_own_name(G_BUS_TYPE_SESSION, nullptr, G_BUS_NAME_OWNER_FLAGS_NONE, nullptr, nullptr, nullptr, nullptr, nullptr)); if (error) { AUDIO_ERROR("Failed to unown name: {}.", error->message); g_error_free(error); } return; } AUDIO_DEBUG("Pausing all players."); for (const auto& playerName : playerNames) { auto playerProxy = g_dbus_proxy_new_sync(connection, G_DBUS_PROXY_FLAGS_NONE, nullptr, playerName.c_str(), "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2.Player", nullptr, &error); if (!playerProxy) { AUDIO_ERROR("Failed to create proxy for player: {}, error: {}.", playerName, error->message); g_error_free(error); continue; } auto pauseResult = g_dbus_proxy_call_sync(playerProxy, "Pause", nullptr, G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error); if (!pauseResult) { AUDIO_ERROR("Failed to pause player:{}, error: {}.", playerName, error->message); g_error_free(error); } else { g_variant_unref(pauseResult); } g_object_unref(playerProxy); } g_bus_unown_name(g_bus_own_name(G_BUS_TYPE_SESSION, nullptr, G_BUS_NAME_OWNER_FLAGS_NONE, nullptr, nullptr, nullptr, nullptr, nullptr)); if (error) { AUDIO_ERROR("Failed to unown name: {}.", error->message); g_error_free(error); } } std::vector MediaControler::getMprisPlayers(GDBusConnection* connection) const { std::vector playerNames; GError* error = nullptr; GDBusProxy* proxy = g_dbus_proxy_new_sync(connection, G_DBUS_PROXY_FLAGS_NONE, nullptr, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", nullptr, &error); if (!proxy) { AUDIO_ERROR("Failed to create proxy: {}.", error->message); g_error_free(error); return playerNames; } GVariant* result = g_dbus_proxy_call_sync(proxy, "ListNames", nullptr, G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error); if (!result) { AUDIO_ERROR("Failed to call ListNames: {}.", error->message); g_error_free(error); } else { GVariantIter* iter; const gchar* name; g_variant_get(result, "(as)", &iter); while (g_variant_iter_loop(iter, "&s", &name)) { if (g_str_has_prefix(name, mprisPlayerDestPrefix)) { playerNames.push_back(name); } } g_variant_iter_free(iter); g_variant_unref(result); } g_object_unref(proxy); return playerNames; } } ukui-volume-control/backend/IStream.h0000664000175000017500000000507415171074712016557 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 ISTREAM_H #define ISTREAM_H #include #include #include #include #include #include #include #include "PaDataType.h" #include "AudioManager.h" namespace UkuiAudioFramwork { struct PaCardPortInfo; struct PaDataInfo; struct DeviceInfo; class AudioDBusServer; enum class StreamType { STREAM_TYPE_SINK, STREAM_TYPE_SOURCE, STREAM_TYPE_SINKINPUT, STREAM_TYPE_SOURCEOUTPUT }; class IStream { public: IStream(pa_context* ctx, PaDataInfo& info, StreamType type) : m_pContext(ctx), m_info(info), m_type(type) {} virtual ~IStream() = default; public: virtual std::string toString() const = 0; virtual NodeType getNodeType() const = 0; virtual uint32_t getIndex() const = 0; virtual std::string getName() const = 0; virtual pa_proplist* getProplist() const = 0; virtual uint32_t getCard() const = 0; virtual void setVolume(int) = 0; virtual const pa_cvolume getVolume() const = 0; virtual void setBalance(double) = 0; virtual double getBalance() const = 0; virtual void setMute(bool) = 0; virtual bool getMute() const = 0; virtual DefaultDeviceInfo getDefaultDevice() const = 0; virtual void setDefaultDevice(const std::string&, const std::string&) = 0; virtual std::vector getDeviceList() const = 0; virtual std::list> getAvailablePortList() const = 0; virtual void volumeChanged(int) = 0; virtual void muteChanged(bool) = 0; virtual void portChanged(const std::string&) = 0; virtual void deviceChanged(bool = false) = 0; protected: pa_context* m_pContext; PaDataInfo& m_info; StreamType m_type; }; } #endif // ISTREAM_H ukui-volume-control/backend/PipewireNode.cpp0000664000175000017500000002531415171074712020137 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "PipewireNode.h" #include #include #include #include #include #include #include #include #include #include "PipewireManager1.h" #include "utils.h" namespace UkuiAudioFramwork { template T clamp(const T v, const T low, const T high) { if (v < low) return low; if (v > high) return high; return v; } #define PIPEWIRE_CAST(x) PipewireNode* _this = static_cast(x); void node_event_info(void *data, const pw_node_info *info) { PIPEWIRE_CAST(data); _this->_node_event_info(info); } void PipewireNode::_node_event_info(const pw_node_info* info) { // message when it's updated // qDebug() << "node_event_info: "; const struct spa_dict_item *item; if (info->change_mask == PW_NODE_CHANGE_MASK_ALL) { //TODO // qDebug() << "CHANGE ALL"; } if (info->change_mask & PW_NODE_CHANGE_MASK_INPUT_PORTS ) { //TODO // qDebug() << "CHANGE INPUT PORTS"; } if (info->change_mask & PW_NODE_CHANGE_MASK_OUTPUT_PORTS) { //TODO // qDebug() << "CHANGE OUTPUT PORTS"; } if (info->change_mask & PW_NODE_CHANGE_MASK_STATE) { m_state = info->state; // emit stateChanged(); // qDebug() << "CHANGE STATE"; } if (info->change_mask & PW_NODE_CHANGE_MASK_PROPS) { spa_dict_for_each(item, info->props) { //volume is not here.... const std::string key = item->key; const std::string value = item->value; m_properties[key] = value; std::cout << "node_" << this->m_name << ":[" << key << "]=" << value << std::endl; //emit propertyChanged(key, value); } // qDebug() << "CHANGE PROPS"; } if (info->change_mask & PW_NODE_CHANGE_MASK_PARAMS) { enumParams(); // qDebug() << "CHANGE PARAMS"; } } void event_param(void *data, int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param) { PIPEWIRE_CAST(data); _this->_event_param(seq, id, index, next, param); //volume is here! } void PipewireNode::_event_param(int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod* param) { const spa_pod_prop *prop; const spa_pod_object *obj = (const struct spa_pod_object*)param; prop = spa_pod_find_prop(param, nullptr, SPA_PROP_channelVolumes); if (prop != nullptr) { assert(spa_pod_is_array(&prop->value)); const spa_pod_array *volumes = (const spa_pod_array*) &prop->value; uint32_t n_channels = 0; uint32_t type = SPA_POD_ARRAY_VALUE_TYPE(volumes); assert(SPA_TYPE_Float == type); float *data = (float*) spa_pod_get_array((const spa_pod*) volumes, &n_channels); m_volume = data[0]; std::cout << "m_node_name:" << m_node_name << " volume changed, volume:" << m_volume << " m_node_description:" << m_node_description << std::endl; // emit volumeChanged(m_volume); } } static const struct pw_node_events node_events = { .version = PW_VERSION_NODE_EVENTS, .info = &node_event_info, .param = &event_param }; PipewireNode::PipewireNode(Pipewire *parent, uint32_t id, const struct spa_dict *props) : /*QObject(parent) ,*/ pipewire(parent) , m_id(id) , m_node_type(NodeTypeNone) , m_media_type(MediaTypeNone) , m_driver(this) { m_node_name = spa_dict_lookup(props, PW_KEY_NODE_NAME); m_node_description = spa_dict_lookup(props, PW_KEY_NODE_DESCRIPTION) ? spa_dict_lookup(props, PW_KEY_NODE_DESCRIPTION) : ""; std::cout << "Pipewire Node.name: " << m_node_name << " node desc: " << m_node_description << std::endl; // Find name const char* str; if ((str = spa_dict_lookup(props, PW_KEY_NODE_NICK)) == nullptr && (str = spa_dict_lookup(props, PW_KEY_NODE_DESCRIPTION)) == nullptr && (str = spa_dict_lookup(props, PW_KEY_NODE_NAME)) == nullptr) { str = spa_dict_lookup(props, PW_KEY_APP_NAME); } if (str == nullptr) { m_name = std::to_string(id); } else { m_name = str; } // Find node type if ((str = spa_dict_lookup(props, PW_KEY_MEDIA_CATEGORY)) != nullptr) { m_category = str; } else { m_category = ""; } // Find node type if ((str = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS)) != nullptr) { m_media_class = str; if (std::string::npos != m_media_class.find("Audio")) m_media_type = MediaTypeAudio; else if (std::string::npos != m_media_class.find("Video")) m_media_type = MediaTypeVideo; else if (std::string::npos != m_media_class.find("Midi")) m_media_type = MediaTypeMidi; else m_media_type = MediaTypeNone; } else { m_media_class = ""; } if ((!m_category.empty()) && std::string::npos != m_category.find("Duplex")) { m_node_type = NodeTypeNone; } else if (!m_media_class.empty()) { if (std::string::npos != m_media_class.find("Sink")) { m_node_type = NodeTypeSink; } else if (std::string::npos != m_media_class.find("Input")) { m_node_type = NodeTypeInput; } else if (std::string::npos != m_media_class.find("Source")) { m_node_type = NodeTypeSource; } else if (std::string::npos != m_media_class.find("Output")) { m_node_type = NodeTypeOutput; } } else { m_node_type = NodeTypeNone; } m_node = static_cast( pw_registry_bind(pipewire->registry, m_id, PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, 0)); m_spa_node = nullptr; // Not used, kept for potential future use const struct spa_dict_item *item; spa_dict_for_each(item, props) { std::cout << "Property[" << item->key << "]=" << item->value << std::endl; m_properties[item->key] = item->value; } // struct spa_pod_object *obj = (struct spa_pod_object *) m_spa_node; // struct spa_pod_prop *prop; // SPA_POD_OBJECT_FOREACH(obj, prop) { // switch (prop->key) { // case SPA_PROP_volume: // float volume; // if (spa_pod_get_float(&prop->value, &volume) < 0) // continue; // if (volume == m_volume) // continue; // m_volume = volume; //// emit volumeChanged(m_volume); // break; // } // const spa_pod *pod = &prop->value; // std::cout << "KEY: " << prop->key << "=" << prop->value << std::endl; // } pw_node_add_listener(m_node, &object_listener, &node_events, this); // enumParams(); } PipewireNode::~PipewireNode() { if (m_node != nullptr) { pw_proxy_destroy((pw_proxy *) m_node); } } std::string PipewireNode::formatPercentage(float val, float quantum) const { char buffer[64]; std::sprintf(buffer, "%5.2f", quantum == 0.0f ? 0.0f : val/quantum); return std::string(buffer); } //QIcon PipewireNode::activeIcon(bool active) const //{ // if(active) { // return QIcon::fromTheme("media-playback-start"); // } else { // return QIcon::fromTheme("media-playback-pause"); // } //} void PipewireNode::setDriver(PipewireNode* newDriver) { if (newDriver == m_driver) return; m_driver = newDriver; // emit driverChanged(); } void PipewireNode::setMeasurement(const struct PipewireNode::measurement &measure) { const struct measurement old = this->measurement; this->measurement = measure; // if (old.status != measure.status) emit activeChanged(); // if (old.signal != measure.signal || old.awake != measure.awake) // emit waitingChanged(); // if (old.finish != measure.finish || old.awake != measure.awake) // emit busyChanged(); // if (m_driver != this && old.latency.num != measure.latency.num) // emit quantumChanged(); // if (m_driver != this && old.latency.denom != measure.latency.denom) // emit rateChanged(); } void PipewireNode::setInfo(const struct PipewireNode::driver &info) { const struct driver old = this->info; this->info = info; // if (m_driver == this && // (old.clock.duration != info.clock.duration || old.clock.rate.num != info.clock.rate.num)) // emit quantumChanged(); // if (m_driver == this && old.clock.rate.denom != info.clock.rate.denom) // emit rateChanged(); // if (old.xrun_count != info.xrun_count) // emit xrunChanged(); } GVariant* PipewireNode::property(const char *key) { return g_variant_new("s", m_properties[key].c_str()); } void PipewireNode::setProperty(const char *key, GVariant* value) { throw std::runtime_error("not implemented"); } void PipewireNode::setProperties(struct spa_pod *properties) { spa_debug_pod(0, nullptr, properties); int res = pw_node_set_param(m_node, SPA_PARAM_Props, 0, properties); if (res < 0) { std::ostringstream err_msg; err_msg << "Got set_property error \"" << res << '"'; throw std::runtime_error(err_msg.str()); } } void PipewireNode::setVolume(float volume) { volume = clamp(volume, 0.0f, 1.0f); struct spa_pod_builder builder = {}; unsigned char buffer[512]; spa_pod_builder_init(&builder, buffer, sizeof(buffer)); struct spa_pod *props = static_cast( spa_pod_builder_add_object(&builder, SPA_TYPE_OBJECT_Props, 0, SPA_PROP_volume, SPA_POD_Float(volume))); this->setProperties(props); m_volume = volume; // emit volumeChanged(m_volume); } void PipewireNode::enumParams() // static bool do_enum_params(struct data *data, const char *cmd, char *args, char **error) { spa_pod *filter = nullptr; pw_node_enum_params(m_node, _props_seq++, SPA_PARAM_Props, 0, 0, filter); // pw_node_enum_params(m_spa_node, _props_seq++, SPA_PARAM_Route, 0, 0, filter); // pw_node_set_param(); } } ukui-volume-control/backend/PulseaudioManagerFactory.cpp0000664000175000017500000000174015171074712022477 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "PulseaudioManagerFactory.h" #include "PulseaudioManager.h" namespace UkuiAudioFramwork { std::shared_ptr PulseaudioManagerFactory::createManager() { return std::make_shared(5); } } ukui-volume-control/backend/AudioManager.cpp0000664000175000017500000000427215171074712020101 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "AudioManager.h" namespace UkuiAudioFramwork { AudioManager::AudioManager() { if (checkAudioService("pipewire")) m_type = BackendType::PIPEWIRE; else if (checkAudioService("pulseaudio")) m_type = BackendType::PULSEAUDIO; else m_type = BackendType::UNKNOWN; } std::string AudioManager::getBackend() const { return getBackendName(m_type); } void AudioManager::setBackend(BackendType type) { m_type = type; } void AudioManager::waitBluezReady(int timeout) { std::unique_lock lock(m_blueMtx); if (!m_bBlueReady) m_blueCond.wait_for(lock, std::chrono::milliseconds(timeout)); } void AudioManager::setBluezReadyStatus(bool status) { { std::unique_lock lock(m_blueMtx); m_bBlueReady = status; } if (status) m_blueCond.notify_all(); } bool AudioManager::getBluezReady() const { std::lock_guard lock(m_blueMtx); return m_bBlueReady; } bool AudioManager::checkAudioService(const std::string& name) { // std::ifstream psOutput("/bin/ps"); std::ostringstream cmd; cmd << "/bin/ps -e | grep " << name; std::string command = cmd.str(); FILE* pipe = popen(command.c_str(), "r"); if (!pipe) return false; char buffer[128]; std::string result = ""; while (!feof(pipe)) { if (fgets(buffer, 128, pipe) != nullptr) result += buffer; } pclose(pipe); return !result.empty(); } } ukui-volume-control/backend/PipewireBackendFactory.h0000664000175000017500000000210615171074712021570 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 PIPEWIREBACKENDFACTORY_H #define PIPEWIREBACKENDFACTORY_H #include "BackendFactory.h" namespace UkuiAudioFramwork { class PipewireBackendFactory : public BackendFactory { public: PipewireBackendFactory(); virtual auto createBackend() -> std::shared_ptr override; }; } #endif // PIPEWIREBACKENDFACTORY_H ukui-volume-control/backend/PaSourceOutputStream.h0000664000175000017500000001054115171074712021324 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 PASOURCEOUTPUTSTREAM_H #define PASOURCEOUTPUTSTREAM_H #include "AudioContext.h" #include "IStream.h" namespace UkuiAudioFramwork { class PaSourceOutputStream : public IStream { public: PaSourceOutputStream(pa_context* ctx, PaDataInfo& info) : IStream(ctx, info, StreamType::STREAM_TYPE_SOURCEOUTPUT) {} ~PaSourceOutputStream() = default; public: void copy(const pa_source_output_info&); std::string getMediaRole() const; std::string getAppIconName() const; std::string getProcessBinary() const; std::string getDriver() const; uint32_t getSource() const; virtual std::string toString() const override; virtual NodeType getNodeType() const override; virtual uint32_t getIndex() const override; virtual std::string getName() const override; virtual pa_proplist* getProplist() const override; virtual uint32_t getCard() const override; virtual void setVolume(int) override; virtual const pa_cvolume getVolume() const override; virtual void setBalance(double) override; virtual double getBalance() const override; virtual void setMute(bool) override; virtual bool getMute() const override; virtual DefaultDeviceInfo getDefaultDevice() const override; virtual void setDefaultDevice(const std::string&, const std::string&) override; virtual std::vector getDeviceList() const override; virtual std::list> getAvailablePortList() const override; virtual void volumeChanged(int) override; virtual void muteChanged(bool) override; virtual void portChanged(const std::string&) override; virtual void deviceChanged(bool = false) override; private: uint32_t index; /**< Index of the source output */ std::string name; /**< Name of the source output */ uint32_t owner_module; /**< Index of the module this source output belongs to, or PA_INVALID_INDEX when it does not belong to any module. */ uint32_t client; /**< Index of the client this source output belongs to, or PA_INVALID_INDEX when it does not belong to any client. */ uint32_t source; /**< Index of the connected source */ pa_sample_spec sample_spec; /**< The sample specification of the source output */ pa_channel_map channel_map; /**< Channel map */ pa_usec_t buffer_usec; /**< Latency due to buffering in the source output, see pa_timing_info for details. */ pa_usec_t source_usec; /**< Latency of the source device, see pa_timing_info for details. */ std::string resample_method; /**< The resampling method used by this source output. */ std::string driver; /**< Driver name */ pa_proplist *proplist; /**< Property list \since 0.9.11 */ int corked; /**< Stream corked \since 1.0 */ pa_cvolume volume; /**< The volume of this source output \since 1.0 */ int mute; /**< Stream muted \since 1.0 */ int has_volume; /**< Stream has volume. If not set, then the meaning of this struct's volume member is unspecified. \since 1.0 */ int volume_writable; /**< The volume can be set. If not set, the volume can still change even though clients can't control the volume. \since 1.0 */ pa_format_info *format; /**< Stream format information. \since 1.0 */ std::string role; std::string m_appIconName; std::string m_processBinary; }; } #endif // PASOURCEOUTPUTSTREAM_H ukui-volume-control/backend/SingleApplication.h0000664000175000017500000000216215171074712020613 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 SINGLEAPPLICATION_H #define SINGLEAPPLICATION_H #include #include #include #include class SingleApplication { public: SingleApplication(const std::string& name); virtual ~SingleApplication(); bool isAlreadyRuning(); private: std::string m_name; int m_lockFile = -1; }; #endif // SINGLEAPPLICATION_H ukui-volume-control/backend/PaSinkStream.h0000664000175000017500000001245215171074712017552 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 PASINKSTREAM_H #define PASINKSTREAM_H #include #include #include #include #include #include #include "IStream.h" #include "AudioContext.h" namespace UkuiAudioFramwork { class PaSinkStream : public IStream { public: PaSinkStream(pa_context* ctx, PaDataInfo& info) : IStream(ctx, info, StreamType::STREAM_TYPE_SINK) {} ~PaSinkStream() = default; public: std::shared_ptr getActivePort() const; std::string getActivePortName() const; std::string getActivePortDescription() const; std::string getCardName() const; std::string getDeviceClass() const; void setMasterDevice(const std::string&); std::string getMasterDevice() const; void copy(const pa_sink_info&); std::vector> getPorts() const; bool getCombineStatus() const; void setCombineStatus(bool); virtual std::string toString() const override; virtual NodeType getNodeType() const override; virtual uint32_t getIndex() const override; virtual std::string getName() const override; virtual pa_proplist* getProplist() const override; virtual uint32_t getCard() const override; virtual void setVolume(int) override; virtual const pa_cvolume getVolume() const override; virtual void setBalance(double) override; virtual double getBalance() const override; virtual void setMute(bool) override; virtual bool getMute() const override; virtual DefaultDeviceInfo getDefaultDevice() const override; virtual void setDefaultDevice(const std::string&, const std::string&) override; virtual std::vector getDeviceList() const override; virtual std::list> getAvailablePortList() const override; virtual void volumeChanged(int) override; virtual void muteChanged(bool) override; virtual void portChanged(const std::string&) override; virtual void deviceChanged(bool = false) override; private: static void setCardProfileCb(pa_context*, int, void*); private: std::string cardName; /**< Name of the card */ std::string name; /**< Name of the sink */ bool m_bIsCombine = false; /**< Is Combine */ uint32_t index = PA_INVALID_INDEX; /**< Index of the sink */ std::string description; /**< Description of this sink */ pa_sample_spec sample_spec; /**< Sample spec of this sink */ pa_channel_map channel_map; /**< Channel map */ uint32_t owner_module; /**< Index of the owning module of this sink, or PA_INVALID_INDEX. */ pa_cvolume volume; /**< Volume of the sink */ int mute; /**< Mute switch of the sink */ uint32_t monitor_source; /**< Index of the monitor source connected to this sink. */ std::string monitor_source_name; /**< The name of the monitor source. */ pa_usec_t latency; /**< Length of queued audio in the output buffer. */ std::string driver; /**< Driver name */ pa_sink_flags_t flags; /**< Flags */ pa_proplist *proplist; /**< Property list \since 0.9.11 */ pa_usec_t configured_latency; /**< The latency this device has been configured to. \since 0.9.11 */ pa_volume_t base_volume; /**< Some kind of "base" volume that refers to unamplified/unattenuated volume in the context of the output device. \since 0.9.15 */ pa_sink_state_t state; /**< State \since 0.9.15 */ uint32_t n_volume_steps; /**< Number of volume steps for sinks which do not support arbitrary volumes. \since 0.9.15 */ uint32_t card; /**< Card index, or PA_INVALID_INDEX. \since 0.9.15 */ uint32_t n_ports; /**< Number of entries in port array \since 0.9.16 */ std::vector> ports; /**< Array of available ports, or NULL. Array is terminated by an entry set to NULL. The number of entries is stored in n_ports. \since 0.9.16 */ std::shared_ptr active_port; /**< Pointer to active port in the array, or NULL. \since 0.9.16 */ uint8_t n_formats; /**< Number of formats supported by the sink. \since 1.0 */ pa_format_info **formats; /**< Array of formats supported by the sink. \since 1.0 */ uint32_t priority; mutable std::mutex m_mutex; std::string m_deviceClass = ""; std::string m_masterDevice = ""; }; } #endif // PASINKSTREAM_H ukui-volume-control/backend/MultiAudioCombineModule.h0000664000175000017500000000331315171074712021724 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 MULTIAUDIOCOMBINEMODULE_H #define MULTIAUDIOCOMBINEMODULE_H #include "Util.h" #include "MultiAudioCombineJson.h" namespace UkuiAudioFramwork { class MultiAudioCombineModule { public: static MultiAudioCombineModule& getInstance(); void setJson(std::shared_ptr); bool getMultiAudioCombineStatus(const NodeType&) const; void setMultiAudioCombine(const NodeType&, bool, const std::vector&); private: MultiAudioCombineModule() = default; MultiAudioCombineModule(const MultiAudioCombineModule&) = delete; MultiAudioCombineModule(MultiAudioCombineModule&&) = delete; MultiAudioCombineModule operator=(const MultiAudioCombineModule&) = delete; MultiAudioCombineModule operator=(MultiAudioCombineModule&&) = delete; virtual ~MultiAudioCombineModule() = default; private: uint32_t m_retryTimes = 0; std::shared_ptr m_pJson; }; } #endif // MULTIAUDIOCOMBINEMODULE_H ukui-volume-control/backend/PulseaudioBackend.cpp0000664000175000017500000000166115171074712021126 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "PulseaudioBackend.h" namespace UkuiAudioFramwork { PulseaudioBackend::PulseaudioBackend() { } void PulseaudioBackend::init() { } bool PulseaudioBackend::detect() { } } ukui-volume-control/backend/SessionManagerSetting.cpp0000664000175000017500000000757615171074712022033 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "SessionManagerSetting.h" #include "AudioContext.h" #include "AudioMethod.h" namespace UkuiAudioFramwork { SessionManagerSetting::SessionManagerSetting() { initSettings(); initConnect(); } SessionManagerSetting::~SessionManagerSetting() { if (m_pSchema) g_settings_schema_unref(m_pSchema); if (m_pSource) g_settings_schema_source_unref(m_pSource); if (m_pSettings) g_object_unref(m_pSettings); } void SessionManagerSetting::initSettings() { m_pSource = g_settings_schema_source_get_default(); m_pSchema = g_settings_schema_source_lookup(m_pSource, SESSION_MANAGER_SCHEMA, true); if (!m_pSchema) { AUDIO_ERROR("schema error, failed to create {} settings", SESSION_MANAGER_SCHEMA); return; } m_pSettings = g_settings_new_with_path(SESSION_MANAGER_SCHEMA, SESSION_MANAGER_PATH); } void SessionManagerSetting::initConnect() { g_signal_connect(G_OBJECT(m_pSettings), "changed", G_CALLBACK(onSettingsChanged), nullptr); } GVariant* SessionManagerSetting::getValue(const char* key) const { if (!m_pSettings || !m_pSchema) return g_variant_new(""); if (!isContainKeys(key)) return g_variant_new(""); return g_settings_get_value(m_pSettings, key); } void SessionManagerSetting::setValue(const char* key, GVariant* gvalue) { if (!m_pSettings || !m_pSchema) return; if (!isContainKeys(key)) return; GVariantClass variantClass = g_variant_classify(gvalue); if (G_VARIANT_CLASS_STRING == variantClass) { gsize length; g_settings_set_string(m_pSettings, key, g_variant_get_string(gvalue, &length)); } else if (G_VARIANT_CLASS_INT16 == variantClass) { g_settings_set_int(m_pSettings, key, g_variant_get_int16(gvalue)); } else if (G_VARIANT_CLASS_UINT16 == variantClass) { g_settings_set_int(m_pSettings, key, g_variant_get_uint16(gvalue)); } else if (G_VARIANT_CLASS_INT32 == variantClass) { g_settings_set_int(m_pSettings, key, g_variant_get_int32(gvalue)); } else if (G_VARIANT_CLASS_UINT32 == variantClass) { g_settings_set_int(m_pSettings, key, g_variant_get_uint32(gvalue)); } else if (G_VARIANT_CLASS_INT64 == variantClass) { g_settings_set_int(m_pSettings, key, g_variant_get_int64(gvalue)); } else if (G_VARIANT_CLASS_UINT64 == variantClass) { g_settings_set_int(m_pSettings, key, g_variant_get_uint64(gvalue)); } else if (G_VARIANT_CLASS_BOOLEAN == variantClass) { g_settings_set_boolean(m_pSettings, key, g_variant_get_boolean(gvalue)); } else { AUDIO_DEBUG("Unknown variantClass"); } } bool SessionManagerSetting::isContainKeys(const char* key) const { bool ret = false; if (!m_pSettings || !m_pSchema) return ret; gchar** keys = g_settings_schema_list_keys(m_pSchema); if (g_strv_contains(keys, key)) ret = true; g_strfreev(keys); return ret; } void SessionManagerSetting::onSettingsChanged(GSettings* settings, const gchar* key) { AudioMethod::getInstance().settingsChanged(key, g_settings_get_value(settings, key)); } } ukui-volume-control/backend/Util.h0000664000175000017500000001742515171074712016133 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 UTIL_H #define UTIL_H #include #include #include #include "BaseType.h" namespace UkuiAudioFramwork { // DBus info #define AUDIO_OBJECT_SERVICE "org.ukui.volume.control" #define AUDIO_OBJECT_PATH "/org/ukui/volume/control" #define AUDIO_INTERFACE "org.ukui.volume.control" #define SETTINGS_INTERFACE "org.ukui.audio.settings" #define SOUNDTHEME_INTERFACE "org.ukui.soundTheme" // settings info #define UKUI_SOUND_SCHEMA "org.ukui.sound" #define UKUI_SOUND_PATH "/org/ukui/sound/" #define EVENT_SOUND_KEY "event-sounds" #define THEME_NAME_KEY "theme-name" #define CUSTOM_THEME_KEY "custom-theme" #define MONO_AUDIO_KEY "mono-audio" #define NOISE_REDUCTION_KEY "dns-noise-reduction" #define LOOPBACK_KEY "loopback" #define VOLUME_BOOST_KEY "volume-increase" #define VOLUME_BOOST_VOLUME_KEY "volume-increase-value" #define STARTUP_MUSIC_KEY "startup-music" #define NOTIFY_GENERAL_KEY "notification-general" #define VOLUME_CHANGED_KEY "audio-volume-change" #define ALERT_VOLUME_KEY "alert-volume" // UKUI Session #define SESSION_MANAGER_SCHEMA "org.ukui.session" #define SESSION_MANAGER_PATH "/org/ukui/desktop/session/" #define STARTUP_MUSIC_KEY "startup-music" #define POWEROFF_MUSIC_KEY "poweroff-music" #define LOGOUT_MUSIC_KEY "logout-music" #define WAKEUP_MUSIC_KEY "weakup-music" #define DEFAULT_VOLUME_BOOST_VALUE 125 /** * @brief 设备端口的类型 */ enum class DevicePortType { PORT_TYPE_BLUETOOTH, // 蓝牙音频 声卡名包含bluez PORT_TYPE_USB, // USB 声卡名称包含usb PORT_TYPE_HEADSET, // 3.5mm 耳麦 端口名包含rear-mic, front-mic,headset,headphone PORT_TYPE_BUILTIN, // 内置扬声器和话筒 端口名包含speaker, input-mic PORT_TYPE_LINEIO, // 线缆输入输出 端口名称包含linein或lineout PORT_TYPE_HDMI, // HDMI 端口名称包含hdmi PORT_TYPE_MULTICHANNEL, // 多声道 端口名称包含multichannel PORT_TYPE_UNKNOWN // 其他类型 }; enum class ModuleType { MODULE_ECHO_CANCEL, // 智能降噪模块 MODULE_REMAP_SINK, // 单声道模块 MODULE_LOOPBACK // 侦听模块 }; /** * @brief 设备端口信息 */ typedef struct DeviceInfo { DeviceInfo(std::string name, std::string desc, std::string pName, std::string pLabel, int prio, int dire, int ava, bool enable, DevicePortType type) : cardName(name), cardDesc(desc), portName(pName), portLabel(pLabel), priority(prio), direction(dire), available(ava), enabled(enable), type(type){} DeviceInfo() {} bool operator==(const DeviceInfo& other) const { return cardName == other.cardName && cardDesc == other.cardDesc && portName == other.portName && portLabel == other.portLabel && priority == other.priority && direction == other.direction && available == other.available && enabled == other.enabled && type == other.type; } bool operator!=(const DeviceInfo& other) const { return !(*this == other); } std::string toString() const; std::string cardName; // 声卡名称 std::string cardDesc; // 声卡描述 std::string portName; // 设备端口名称 std::string portLabel; // 设备端口描述 uint32_t priority; // 端口优先级 uint32_t direction; // 端口方向 input or output uint32_t available; // 端口可用状态 bool enabled = true; // 端口是否启用 DevicePortType type; // 端口的类型 } DeviceInfo; /** * @brief 默认设备信息 */ typedef struct DefaultDeviceInfo { uint32_t idx; // 默认设备的索引 uint32_t volume; // 默认设备的音量 std::string name; // 默认设备的名称 std::string cardName; // 默认设备所在的声卡名称 // std::string cardDesc; // 默认设备所在的声卡描述 std::string activePortName; // 默认设备的活跃端口名称,可能为空 std::string activePortLabel; // 默认设备的活跃端口标签 std::string toString() const; } DefaultDeviceInfo; /** * @brief 流媒体信息 */ typedef struct StreamMediaInfo { std::string name; // 流媒体名称 uint32_t direction; // 流媒体的direction, input or output uint32_t index; // 流媒体索引 uint32_t volume; // 流媒体音量 std::string mediaRole; // 流媒体角色 std::string iconName; // 流媒体应用图标名称 std::string binary; // 流媒体对应的二进制名称 std::string toString() const; } StreamMediaInfo; typedef struct MediaInfo { uint32_t direction; // 流媒体的direction, input or output uint32_t index; // 流媒体索引 uint32_t volume; // 流媒体音量 bool mute; // 流媒体静音状态 std::string name; // 流媒体名称 } MediaInfo; typedef struct StreamMediaIntegrationInfo { std::string mediaRole; // 流媒体角色 std::string iconName; // 流媒体应用图标名称 std::string binary; // 流媒体对应的二进制名称 std::list mediaInfoList; // 流媒体信息 std::string toString() const; } StreamMediaIntegrationInfo; /** * @brief 系统版本信息 */ typedef struct SystemVersionInfo { std::optional prettyName; // 系统版本名称 std::optional releaseId; // 发布版本Id } SystemVersionInfo; /** * @brief 系统版本类型 */ enum class DesktopEnvironmentVersion { DESKTOP_ENVIRONMENT_VERSION_UKUI2, // ukui2 对应商业版本2107, DESKTOP_ENVIRONMENT_VERSION_UKUI3, // ukui3 对应商业版本2203、2303、 2403、2503? 社区版本yangtze nile DESKTOP_ENVIRONMENT_VERSION_UKUI4, // ukui4 对应商业版本2603?, 社区版本openkylin huanghe DESKTOP_ENVIRONMENT_VERSION_UKUI5, // 后续的版本 DESKTOP_ENVIRONMENT_VERSION_UNKNOWN }; template::value>::type> inline constexpr auto EnumToInt(EnumType t) { return static_cast(t); } template ::value>::type> inline EnumType IntToEnum(int value) { return static_cast(value); } #define CASE(_code) \ case _code: { \ std::string name(#_code); \ return name.substr(name.find_last_of(':') + 1);\ } #define DEFAULT(_value) \ default: \ return FORMAT("Unknown({})", _value); std::string getModuleName(const Module&); std::string getBackendName(const BackendType&); std::string getNodeTypeName(const NodeType&); DevicePortType getDevicePortType(const std::string&, const std::string&); int calaPriorityByName(const std::string&); } #endif // UTIL_H ukui-volume-control/backend/MultiAudioCombineJson.h0000664000175000017500000000337415171074712021417 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 MULTIAUDIOCOMBINEJSON_H #define MULTIAUDIOCOMBINEJSON_H #include #include "IJson.h" namespace UkuiAudioFramwork { #define MULTI_AUDIO_COMBINE_JSON "/.config/ukui-volume-control/multi_audio_combine.json" typedef struct MultiAudioCombineInfo { int type; // 0 输入 1 输出 bool status; std::vector deviceList; } MultiAudioCombineInfo; class MultiAudioCombineJson : public IJson { public: MultiAudioCombineJson(); ~MultiAudioCombineJson() = default; public: virtual bool isContain(const MultiAudioCombineInfo&) const override; virtual void insert(const MultiAudioCombineInfo&) override; // template // std::optional getValue(const std::string&, const std::string&, const std::string&) const; std::optional getValue(const std::string&, const std::string&) const; void setValue(const std::string&, const std::string&, bool); private: void initJson(); }; } #endif // MULTIAUDIOCOMBINEJSON_H ukui-volume-control/backend/MediaAutoPauseModule.cpp0000664000175000017500000000255515171074712021563 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "MediaAutoPauseModule.h" namespace UkuiAudioFramwork { MediaAutoPauseModule& MediaAutoPauseModule::getInstance() { static MediaAutoPauseModule instance; return instance; } void MediaAutoPauseModule::setJson(std::shared_ptr json) { m_pJson = json; } bool MediaAutoPauseModule::getAutoPauseStatus() { auto status = m_pJson->getValue("auto-pause"); if (status) return *status; std::cerr << "getAutoPauseStatus failed!" << std::endl; return false; } void MediaAutoPauseModule::setAutoPauseStatus(bool status) { m_pJson->setValue("auto-pause", status); } } ukui-volume-control/backend/PipewireNodeListmodel.h0000664000175000017500000000514115171074712021455 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 . */ #pragma once #include #include #include "PipewireNode.h" namespace UkuiAudioFramwork { class PipewireNodeListModel { enum NodeRoles { // IndexRole = Qt::UserRole + 1, NodeRole, IDRole, ActiveRole, RateRole, QuantumRole, WaitRole, BusyRole, NameRole, DriverIDRole, }; const unsigned int N_ROLES = 10; private: std::vector m_nodes; public: explicit PipewireNodeListModel(/*QObject *parent = nullptr*/); virtual ~PipewireNodeListModel(); inline std::vector &list() { return m_nodes; } inline const std::vector &constList() const { return m_nodes; } // Q_INVOKABLE void sortList(); public: // Delegate methods from m_nodes void append(PipewireNode *node); bool removeOne(PipewireNode *node); bool removeAt(int index); inline int size() const { return m_nodes.size(); } inline PipewireNode*& operator[](int index) { return m_nodes[index]; } inline PipewireNode* const& operator[](int index) const { return m_nodes[index]; } public: // QAbstractItemModel interface // virtual int rowCount(const QModelIndex& index = QModelIndex()) const override; // //virtual int columnCount(const QModelIndex& index = QModelIndex()) const override; // virtual QVariant data(const QModelIndex& index, int role) const override; // virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override; // virtual bool insertRows(int row, int count, const QModelIndex& parent) override; // virtual bool removeRows(int row, int count, const QModelIndex& parent) override; // virtual QHash roleNames() const override; void rowChanged(PipewireNode* node, int role); // Q_INVOKABLE void move(int oldIndex, int newIndex); }; } ukui-volume-control/backend/GlobalThemeSettings.cpp0000664000175000017500000000753515171074712021456 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "GlobalThemeSettings.h" #include "AudioContext.h" #include "AudioMethod.h" namespace UkuiAudioFramwork { GlobalThemeSettings::GlobalThemeSettings() { initSettings(); initConnect(); } GlobalThemeSettings::~GlobalThemeSettings() { if (m_pSchema) g_settings_schema_unref(m_pSchema); if (m_pSource) g_settings_schema_source_unref(m_pSource); if (m_pSettings) g_object_unref(m_pSettings); } void GlobalThemeSettings::initSettings() { m_pSource = g_settings_schema_source_get_default(); m_pSchema = g_settings_schema_source_lookup(m_pSource, GLOBAL_THEME_SCHEMA, true); if (!m_pSchema) { AUDIO_ERROR("schema error, failed to create {} settings", GLOBAL_THEME_SCHEMA); return; } m_pSettings = g_settings_new_with_path(GLOBAL_THEME_SCHEMA, GLOBAL_THEME_PATH); } void GlobalThemeSettings::initConnect() { g_signal_connect(G_OBJECT(m_pSettings), "changed", G_CALLBACK(onSettingsChanged), nullptr); } GVariant* GlobalThemeSettings::getValue(const char* key) const { if (!m_pSettings || !m_pSchema) return g_variant_new(""); if (!isContainKeys(key)) return g_variant_new(""); return g_settings_get_value(m_pSettings, key); } void GlobalThemeSettings::setValue(const char* key, GVariant* gvalue) { if (!m_pSettings || !m_pSchema) return; if (!isContainKeys(key)) return; GVariantClass variantClass = g_variant_classify(gvalue); if (G_VARIANT_CLASS_STRING == variantClass) { gsize length; g_settings_set_string(m_pSettings, key, g_variant_get_string(gvalue, &length)); } else if (G_VARIANT_CLASS_INT16 == variantClass) { g_settings_set_int(m_pSettings, key, g_variant_get_int16(gvalue)); } else if (G_VARIANT_CLASS_UINT16 == variantClass) { g_settings_set_int(m_pSettings, key, g_variant_get_uint16(gvalue)); } else if (G_VARIANT_CLASS_INT32 == variantClass) { g_settings_set_int(m_pSettings, key, g_variant_get_int32(gvalue)); } else if (G_VARIANT_CLASS_UINT32 == variantClass) { g_settings_set_int(m_pSettings, key, g_variant_get_uint32(gvalue)); } else if (G_VARIANT_CLASS_INT64 == variantClass) { g_settings_set_int(m_pSettings, key, g_variant_get_int64(gvalue)); } else if (G_VARIANT_CLASS_UINT64 == variantClass) { g_settings_set_int(m_pSettings, key, g_variant_get_uint64(gvalue)); } else if (G_VARIANT_CLASS_BOOLEAN == variantClass) { g_settings_set_boolean(m_pSettings, key, g_variant_get_boolean(gvalue)); } else { AUDIO_DEBUG("Unknown variantClass"); } } void GlobalThemeSettings::onSettingsChanged(GSettings* settings, const gchar* key) { AudioMethod::getInstance().settingsChanged(key, g_settings_get_value(settings, key)); } bool GlobalThemeSettings::isContainKeys(const char* key) const { bool ret = false; if (!m_pSettings || !m_pSchema) return ret; gchar** keys = g_settings_schema_list_keys(m_pSchema); if (g_strv_contains(keys, key)) ret = true; g_strfreev(keys); return ret; } } ukui-volume-control/backend/AudioContext.h0000664000175000017500000000432715171074712017621 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 AUDIOCONTEXT_H #define AUDIOCONTEXT_H #include "simple_logger/Logger.h" #include "BaseType.h" #include "Util.h" namespace UkuiAudioFramwork { #define AUDIO_DEBUG(fmt, ...) DBG_DEBUG(AudioContext::getInstance().getLogger(), EnumToInt(AudioContext::getInstance().getModule()), fmt, ##__VA_ARGS__) #define AUDIO_INFO(fmt, ...) DBG_INFO(AudioContext::getInstance().getLogger(), EnumToInt(AudioContext::getInstance().getModule()), fmt, ##__VA_ARGS__) #define AUDIO_WARN(fmt, ...) DBG_WARN(AudioContext::getInstance().getLogger(), EnumToInt(AudioContext::getInstance().getModule()), fmt, ##__VA_ARGS__) #define AUDIO_ERROR(fmt, ...) DBG_ERROR(AudioContext::getInstance().getLogger(), EnumToInt(AudioContext::getInstance().getModule()), fmt, ##__VA_ARGS__) #define AUDIO_FATAL(fmt, ...) DBG_FATAL(AudioContext::getInstance().getLogger(), EnumToInt(AudioContext::getInstance().getModule()), fmt, ##__VA_ARGS__) class AudioContext { public: static AudioContext& getInstance(); public: Module getModule() const; simple_logger::Log& getLogger(); private: std::string getLogPath(); private: AudioContext(); AudioContext(const AudioContext&) = delete; AudioContext(AudioContext&&) = delete; AudioContext operator=(const AudioContext&) = delete; AudioContext operator=(AudioContext&&) = delete; virtual ~AudioContext() { m_log.Close(); } private: simple_logger::Log m_log; Module m_module = Module::Service; }; } #endif // AUDIOCONTEXT_H ukui-volume-control/backend/PipewireManager1.h0000664000175000017500000002142515171074712020351 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 . */ #pragma once #include #include #include #include #include "PipewireMetadata.h" #include "PipewireSettings.h" #include "PipewireNode.h" #include "PipewireProfiler.h" //#include "PipewireNodeListmodel.h" #include "media-session/alsaproperties.h" #include "SystemdService.h" #include "AudioManager.h" namespace UkuiAudioFramwork { class PipewireMetadata; class PipewireSettings; class PipewireLink; class PipewirePort; class PipewireDevice; class PipewireClient; class AlsaProperties; class Pipewire : public AudioManager { public: virtual void init() override; virtual bool connect() override; // Interface virtual void loadModule(const std::string&, const std::string&) override; virtual void unloadModule(const std::string&) override; // Method virtual void setDetailVolume(const NodeType&, const std::string&, const std::string&, int) override; virtual int getDetailVolume(const NodeType&, const std::string&, const std::string&) const override; virtual void setVolume(const NodeType&, int, int) override; virtual int getVolume(const NodeType&, int) const override; virtual void setDetailMute(const NodeType&, const std::string&, const std::string&, bool) override; virtual bool getDetailMute(const NodeType&, const std::string&, const std::string&) const override; virtual void setBalance(const NodeType&, double) override; virtual double getBalance(const NodeType&) const override; virtual void setMute(const NodeType&, int, bool) override; virtual bool getMute(const NodeType&, int) const override; virtual DefaultDeviceInfo getDefaultDevice(const NodeType&, int) const override; virtual void setDefaultDevice(const NodeType&, int, const std::string&, const std::string&) override; virtual std::vector getDeviceList(const NodeType&) const override; virtual std::list getSinkList() const override; virtual std::list getSourceList() const override; virtual std::list> getAvailablePortList(const NodeType&) const override; virtual std::list getSinkInputList() const override; virtual std::list getSourceOutputList() const override; virtual std::list getStreamMediaList() const override; virtual std::list getModuleList() override; virtual void setEnabled(std::shared_ptr, const std::string&, const std::string&, bool) override; virtual bool isEnabled(const std::string&, const std::string&) const override; virtual bool isLoaded(const ModuleType&) const override; virtual bool isValidDevice(const NodeType&) const override; // signal virtual void deviceChanged(const NodeType&, const std::string&, const std::string&) override; virtual void deviceAdjust(const NodeType&) override; virtual void portChanged(const NodeType&, const std::string&) override; virtual void cardRemoved(const std::string&) override; virtual void addStream(int, int, const std::string&, const std::string&) override; virtual void removeStream(int) override; virtual void addStream(int, int, int, bool, const std::string&, const std::string&) override; virtual void removeStream(int, int) override; //signals: void quit(); void registryObject(uint32_t id, uint32_t permissions, const char *type, uint32_t version, const struct spa_dict *props); void appVersionChanged(); //bogus, never emitted void settingsChanged(); // void clientChanged(); void nodesChanged(); void linksChanged(); void portsChanged(); void profilerChanged(); void pipewireMediaSessionChanged(); void wirePlumberServiceChanged(); void alsaPropertiesChanged(); public: struct pw_registry *registry = nullptr; // PipewireNodeListModel *m_nodes = nullptr; std::vector m_nodes; private: struct pw_main_loop *loop = nullptr; struct pw_context *context = nullptr; struct pw_core *core = nullptr; struct spa_hook core_listener; // std::vector m_nodes; // struct pw_registry *registry = nullptr; struct spa_hook registry_listener; int sync = 0; bool round_trip_done = false; std::vector pw_clients = {}; PipewireSettings *pw_settings = nullptr; // PipewireNodeListModel *m_nodes = nullptr; std::vector m_links; std::list m_ports; std::list m_devices; PipewireProfiler *pw_profiler = nullptr; SystemdService *pipewire_media_session = nullptr; SystemdService *wireplumber_service = nullptr; AlsaProperties *alsa_properties = nullptr; public: explicit Pipewire(int *argc, char **argv[]); virtual ~Pipewire(); /** * Executes pipewire main loop, calling callbacks and other stuff. * Runs in main thread and is blocking until all operations are done. */ // Q_INVOKABLE void round_trip(); void round_trip(); // QString appVersion() { return QString(PROJECT_VERSION); } // Q_INVOKABLE QString pipewireCompiledVersion() { return pw_get_headers_version(); } // Q_INVOKABLE QString pipewireLinkedVersion() { return pw_get_library_version(); } // Q_INVOKABLE QString platformName() { return QGuiApplication::platformName(); } // Q_INVOKABLE QString qtCompiledVersion() { return QStringLiteral(QT_VERSION_STR); } // Q_INVOKABLE QString qtLinkedVersion() { return QString::fromLocal8Bit(qVersion()); } // Q_INVOKABLE QString kframeworksCompiledVersion() { return QStringLiteral(KF5_COMPILED_VERSION); } // Q_INVOKABLE static QString formatTime(double val); // Q_INVOKABLE bool isPipewireMediaSession() { // //Wireplumber may mask itself as pipewire-media-session, maybe not but let's be double sure // return pipewire_media_session->running() && !wireplumber_service->running(); // } // Q_INVOKABLE bool isWireplumber() { // return wireplumber_service->running(); // } static std::string formatTime(double val); bool isPipewireMediaSession() { //Wireplumber may mask itself as pipewire-media-session, maybe not but let's be double sure return pipewire_media_session->running() && !wireplumber_service->running(); } bool isWireplumber() { return wireplumber_service->running(); } // QPipewireClient* client() { return pw_client; } PipewireSettings* settings() { return pw_settings; } PipewireProfiler* profiler() { return pw_profiler; } // PipewireNodeListModel* nodes() { return m_nodes; } // std::vector nodeList() { return m_nodes->list(); } std::vector linkList() { return m_links; } std::list portList() { return m_ports; } SystemdService* pipewireMediaSession() { return pipewire_media_session; } SystemdService* wirePlumberService() { return wireplumber_service; } AlsaProperties* alsaProperties() { return alsa_properties; } // QObjectList nodeObjectList() { // auto list = QObjectList(); // for(int i=0; isize(); i++) { // list.append(m_nodes->list()[i]); // } // return list; // } public: // actually, private // PIPEWIRES CALLBACKS (private) void _quit(); void _on_core_done(uint32_t id, int seq); void _on_core_error(uint32_t id, int seq, int res, const char *message); void _registry_event(uint32_t id, uint32_t permissions, const char *type, uint32_t version, const struct spa_dict *props); void _registry_event_remove(uint32_t id); //private: public: void resync(); friend class QPipewireClient; friend class QPipewireMetadata; friend class QPipewireProfiler; friend class QPipewireNode; friend class QPipewireLink; friend class QPipewirePort; friend class QPipewireDevice; }; } ukui-volume-control/backend/simple_logger/0000775000175000017500000000000015171074712017664 5ustar fengfengukui-volume-control/backend/simple_logger/LICENSE0000664000175000017500000002613515171074677020712 0ustar fengfeng Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ukui-volume-control/backend/simple_logger/README.md0000664000175000017500000001115615171074677021161 0ustar fengfengSimple logger lib writen with C++ ========================= - author:Dreams chen - first release: 2023-03-23 ## Sumary # This is a simple logger lib writen by C++, it is small, very easy to used, and fast logger, and can be used in multiple threads environment. ## Features # - Five log levels: debug, info, warn, error, fatal. Each level can be turned on or off by api. The debug level do nothing by default, other levels will output log content to the log terminal. - Three output types: console, file, user defined writer. Each output type can be turned on or off by api. - In detail mode, the file name, line number, and function name can be show in log content, it is easy to identify where the log happen. - Support multiple thread. - Support C++20's formatted function, it is modern formatter, convenient to print multiple parameters. If not support C++20's formatted function in user complile environment, the alternative formatted function is available, can be used in similary usage(but fewer functions, only the simplest function is support). - Writen with standard C++, supply multiple os platforms, include linux, windows, etc. ## Examples # - Use simple logger ``` int main() { string dir{ filesystem::current_path().generic_string() }; string fileName{ "test.log" }; Log log(dir, fileName, static_cast(OutputType::Console), true); log.SetLogSwitchOn(LogLevel::Debug); DBG_TRACE(log, "hello logger, intVal={}", 1); DBG_INFO(log, "hello logger, doubleVal={}", 3.14); DBG_WARN(log, "hello logger, stringVal={}", "simple logger"); DBG_ERROR(log, "hello logger, stringVal={}", string("std::string")); DBG_FATAL(log, "hello logger"); while (!log.IsLogQueEmpty()) { std::this_thread::sleep_for(std::chrono::seconds(1)); } } ``` - Use simple logger with user defined writer ``` class MyWriter : public UserDefinedWriter { public: virtual ~MyWriter() { cout << "Destructor MyWriter." << endl; } public: virtual void Write(const char* str) override { cout << "UserDefinedWriter: " << str; } }; int main() { string dir{ filesystem::current_path().generic_string()}; string fileName{ "test.log" }; Log log(dir, fileName, static_cast(OutputType::UserDefined), true); MyWriter* writer = new MyWriter(); log.SetUserWriter(writer); DBG_TRACE(log, "hello logger, intVal={}", 1); DBG_INFO(log, "hello logger, doubleVal={}", 3.14); DBG_WARN(log, "hello logger, stringVal={}", "simple logger"); DBG_ERROR(log, "hello logger, stringVal={}", string("std::string")); DBG_FATAL(log, "hello logger"); while (!log.IsLogQueEmpty()) { std::this_thread::sleep_for(std::chrono::seconds(1)); } } ``` - Output to multiple log terminals ``` int main() { string dir{ filesystem::current_path().generic_string() }; string fileName{ "test.log" }; Log log(dir, fileName, static_cast(OutputType::Console) | static_cast(OutputType::LogFile), true); log.SetLogSwitchOn(LogLevel::Debug); DBG_TRACE(log, "hello logger, intVal={}", 1); DBG_INFO(log, "hello logger, doubleVal={}", 3.14); DBG_WARN(log, "hello logger, stringVal={}", "simple logger"); DBG_ERROR(log, "hello logger, stringVal={}", string("std::string")); DBG_FATAL(log, "hello logger"); while (!log.IsLogQueEmpty()) { std::this_thread::sleep_for(std::chrono::seconds(1)); } } ``` - Use simple logger in multiple threads environment ``` int main() { string dir{ filesystem::current_path().generic_string()}; string fileName{ "test.log" }; Log log(dir, fileName, static_cast(OutputType::LogFile), true); int loopCount = 100; thread t1([&log, loopCount]() { for (int i = 0; i < loopCount; ++i) { DBG_TRACE(log, "@@@@@@T1: hello logger, i={}", i); DBG_INFO(log, "@@@@@@T1: hello logger, i={}", i); DBG_WARN(log, "@@@@@@T1: hello logger, i={}", i); DBG_ERROR(log, "@@@@@@T1: hello logger, i={}", i); DBG_FATAL(log, "@@@@@@T1: hello logger, i={}", i); } }); thread t2([&log, loopCount]() { for (int i = 0; i < loopCount; ++i) { DBG_TRACE(log, "******T2: hello logger, i={}", i); DBG_INFO(log, "******T2: hello logger, i={}", i); DBG_WARN(log, "******T2: hello logger, i={}", i); DBG_ERROR(log, "******T2: hello logger, i={}", i); DBG_FATAL(log, "******T2: hello logger, i={}", i); } }); t1.join(); t2.join(); while (!log.IsLogQueEmpty()) { std::this_thread::sleep_for(std::chrono::seconds(1)); } } ```ukui-volume-control/backend/simple_logger/example/0000775000175000017500000000000015171074677021331 5ustar fengfengukui-volume-control/backend/simple_logger/example/Example.cpp0000664000175000017500000001620515171074677023434 0ustar fengfeng// Copyright(c) 2023-present, Dreams Chen. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include //#include #include "Logger.h" #include "DateTime.h" enum class Module { Example = 0, App, MaxNum = 0xff, }; class ExampleContext { public: static ExampleContext& GetInstance() { static ExampleContext context; // static auto callOnce = [&]() -> int { // // once call code here // context.m_log.AddModule(context.GetModuleValue(), context.GetModuleName()); // return 0; // }(); return context; } inline simple_logger::Log& GetLogger() { return m_log; } inline Module GetModuleType() const { return Module::Example; } inline std::string GetModuleName() const { return "Module::Example"; } inline int GetModuleValue() const { return static_cast(Module::Example); } private: ExampleContext() : m_log("./log", "test.log") {}; ExampleContext(const ExampleContext&) = delete; ExampleContext(ExampleContext&&) = delete; ExampleContext& operator=(const ExampleContext&) = delete; ExampleContext& operator=(ExampleContext&&) = delete; ~ExampleContext() { m_log.Close(); } private: simple_logger::Log m_log; }; // define print micro of module level to simplify usage #define EXAMPLE_DEBUG(fmt, ...) DBG_DEBUG(ExampleContext::GetInstance().GetLogger(), ExampleContext::GetInstance().GetModuleValue(), fmt, ##__VA_ARGS__) #define EXAMPLE_INFO(fmt, ...) DBG_INFO(ExampleContext::GetInstance().GetLogger(), ExampleContext::GetInstance().GetModuleValue(), fmt, ##__VA_ARGS__) #define EXAMPLE_WARN(fmt, ...) DBG_WARN(ExampleContext::GetInstance().GetLogger(), ExampleContext::GetInstance().GetModuleValue(), fmt, ##__VA_ARGS__) #define EXAMPLE_ERROR(fmt, ...) DBG_ERROR(ExampleContext::GetInstance().GetLogger(), ExampleContext::GetInstance().GetModuleValue(), fmt, ##__VA_ARGS__) #define EXAMPLE_FATAL(fmt, ...) DBG_FATAL(ExampleContext::GetInstance().GetLogger(), ExampleContext::GetInstance().GetModuleValue(), fmt, ##__VA_ARGS__) // user defined writer example: // define a textbox writer in QT GUI application. // //class TextBoxWriter : public QObject, public simple_logger::UserDefinedWriter //{ // Q_OBJECT; // //public: // TextBoxWriter(MainWindow* window) : m_window(window) // { // // } // virtual ~TextBoxWriter() { m_window = nullptr; } // //public: // virtual void Write(const std::string& str) override // { // if (m_window != nullptr) { // emit ReceiveNewLog(QString(str.c_str())); // } // } // // virtual void Close() override // { // m_window = nullptr; // } // //signals: // void ReceiveNewLog(const QString& logStr); // //private: // MainWindow* m_window; //}; // // use textbox writer on mainwindow class // //class TextBoxWriter; //class MainWindow : public QMainWindow //{ // Q_OBJECT // //public: // MainWindow(QWidget* parent = nullptr); // ~MainWindow(); // //private slots: // void OnReceiveNewLog(const QString str); // //private: // Ui::MainWindow* ui; // std::shared_ptr m_textboxWriter; // simple_logger::Log m_log; //}; // //MainWindow::MainWindow(QWidget *parent) //: QMainWindow(parent) //, ui(new Ui::MainWindow) //, m_textboxWriter(std::make_shared(this)) //, m_log(std::filesystem::current_path().string(), "app.log") //{ // ui->setupUi(this); // connect(m_textboxWriter.get(), &TextBoxWriter::ReceiveNewLog, this, &MainWindow::OnReceiveNewLog, Qt::ConnectionType::QueuedConnection); // m_log.SetUserWriter(m_textboxWriter) //} // //void MainWindow::OnReceiveNewLog(const QString str) //{ // ui->outputWindow->append(str); //} int main() { class MyWriter : public simple_logger::UserDefinedWriter { public: virtual ~MyWriter() { std::cout << "Destructor MyWriter." << std::endl; } public: virtual void Write(const std::string& str) override { std::cout << "UserDefinedWriter: " << str; } }; simple_logger::Log& log = ExampleContext::GetInstance().GetLogger(); log.SetOutputTypeOn(simple_logger::OutputType::Console); // log.SetOutputTypeOff(simple_logger::OutputType::Console); log.SetLogSwitchOn(simple_logger::LogLevel::Debug); log.SetDetailMode(true); //std::shared_ptr writer = std::make_shared(); //log.SetUserWriter(writer); int loopCount = 2; START_TIME(); std::thread t1([&log, loopCount]() { for (int i = 0; i < loopCount; ++i) { EXAMPLE_DEBUG("######T1: hello logger, i={}, Module::App={}", i, Module::App); EXAMPLE_INFO("######T1: hello logger, i={}", i); EXAMPLE_WARN("######T1: hello logger, i={}", i); EXAMPLE_ERROR("######T1: hello logger, string={}", std::string("test")); EXAMPLE_FATAL("######T1: hello logger, pi={}", 3.1415); } }); // std::thread t2([&log, loopCount]() { // for (int i = 0; i < loopCount; ++i) { // EXAMPLE_DEBUG("******T2: hello logger, i={}", i); // EXAMPLE_INFO("******T2: hello logger, i={}", i); // EXAMPLE_WARN("******T2: hello logger, i={}", i); // EXAMPLE_ERROR("******T2: hello logger, i={}", std::string("sss")); // EXAMPLE_FATAL("******T2: hello logger, i={}", 3.1415); // } // }); // std::thread t3([&log, loopCount]() { // for (int i = 0; i < loopCount; ++i) { // EXAMPLE_DEBUG("&&&&&&T3: hello logger, i={}", i); // EXAMPLE_INFO("&&&&&&T3: hello logger, i={}", i); // EXAMPLE_WARN("&&&&&&T3: hello logger, i={}", i); // EXAMPLE_ERROR("&&&&&&T3: hello logger, i={}", std::string("sss")); // EXAMPLE_FATAL("&&&&&&T3: hello logger, i={}", 3.1415); // } // }); // std::thread t4([&log, loopCount]() { // for (int i = 0; i < loopCount; ++i) { // EXAMPLE_DEBUG("$$$$$$T4: hello logger, i={}", i); // EXAMPLE_INFO("$$$$$$T4: hello logger, i={}", i); // EXAMPLE_WARN("$$$$$$&T4: hello logger, i={}", i); // EXAMPLE_ERROR("$$$$$$&T42: hello logger, i={}", std::string("sss")); // EXAMPLE_FATAL("$$$$$$T4: hello logger, i={}", 3.1415); // } // }); t1.join(); // t2.join(); // t3.join(); // t4.join(); while (!log.IsLogQueEmpty()) { //std::cout<< "Log que not empty after join." <= 1200) # pragma once #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) #include #include namespace simple_logger { #ifdef linux using errno_t = error_t; #endif using Now = std::chrono::time_point; time_t GetTime(); Now GetCurrentTime(); errno_t GetUtcTm(tm* utcTm); errno_t GetLocalTm(tm* localTm); std::string GetUtcDateTime(); std::string GetUtcDate(); std::string GetUtcTime(); std::string GetUtcDateTimeWithMilliSecond(); std::string GetUtcDateTimeWithMilliSecond(const Now& now); std::string ToUtcDateTime(const time_t* time); std::string ToUtcDate(const time_t* time); std::string ToUtcTime(const time_t* time); std::string GetLocalDateTime(); std::string GetLocalDate(); std::string GetLocalTime(); std::string GetLocalDateTimeWithMilliSecond(); std::string GetLocalDateTimeWithMilliSecond(const Now& now); std::string GetLocalDateFromUnixTimeStamp(long long timeStamp); std::string GetLocalTimeFromUnixTimeStamp(long long timeStamp); std::string ToLocalDateTime(const time_t* time); std::string ToLocalDate(const time_t* time); std::string ToLocalTime(const time_t* time); time_t GetTimeFromString(std::string dateTime, std::string fmt = "%04d%02d%02d-%02d:%02d:%02d"); } #endif // !DATA_TIME_H ukui-volume-control/backend/simple_logger/DateTime.cpp0000664000175000017500000002111715171074677022100 0ustar fengfeng// Copyright(c) 2023-present, Dreams Chen. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "DateTime.h" #include #include #include #include namespace simple_logger { errno_t GetTime(const time_t* timer, struct tm* buf) { #ifdef linux gmtime_r(timer, buf); return 0; #else return gmtime_s(buf, timer); #endif } errno_t GetLocalTime(const time_t* timer, struct tm* buf) { #ifdef linux localtime_r(timer, buf); return 0; #else return localtime_s(buf, timer); #endif } int Sprintf(char* const buffer, size_t const bufferCount, char const* const fmt, ...) { int ret; va_list argList; va_start(argList, fmt); #ifdef linux ret = vsprintf(buffer, fmt, argList); #else ret = _vsprintf_s_l(buffer, bufferCount, fmt, nullptr, argList); #endif va_end(argList); return ret; } int StrScanf(char const* const buffer, char const* const fmt, ...) { int ret; va_list argList; va_start(argList, fmt); #if _MSC_VER ret = vsscanf_s(buffer, fmt, argList); #else // defined(__linux__) or defined(__MINGW32__) or defined(__MINGW64__) ret = vsscanf(buffer, fmt, argList); #endif va_end(argList); return ret; } time_t GetTime() { std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); return std::chrono::system_clock::to_time_t(now); } errno_t GetUtcTm(tm* t) { time_t time = GetTime(); return GetTime(&time, t); } errno_t GetLocalTm(tm* localTm) { time_t t = GetTime(); return GetLocalTime(&t, localTm); } std::string GetUtcDateTime() { auto time = GetTime(); tm localTm = { 0 }; auto ret = GetTime(&time, &localTm); if (ret != 0) { return ""; } char buff[32] = { 0 }; strftime(buff, sizeof(buff), "%Y-%m-%d %H:%M:%S", &localTm); return std::string(buff); } std::string GetUtcDate() { auto t = GetTime(); tm localTm = { 0 }; auto ret = GetTime(&t, &localTm); if (ret != 0) { return ""; } char buff[32] = { 0 }; strftime(buff, sizeof(buff), "%Y-%m-%d", &localTm); return std::string(buff); } std::string GetUtcTime() { auto t = GetTime(); tm localTm = { 0 }; auto ret = GetTime(&t, &localTm); if (ret != 0) { return ""; } char buff[32] = { 0 }; strftime(buff, sizeof(buff), "%H:%M:%S", &localTm); return std::string(buff); } std::string GetUtcDateTimeWithMilliSecond() { return GetUtcDateTimeWithMilliSecond(GetCurrentTime()); } std::string GetUtcDateTimeWithMilliSecond(const Now& now) { time_t t = std::chrono::system_clock::to_time_t(now); int milliSecond = now.time_since_epoch().count() % 1000; tm localTm = { 0 }; auto ret = GetTime(&t, &localTm); if (ret != 0) { return ""; } char buff[32] = { 0 }; int len = (int)strftime(buff, sizeof(buff), "%Y-%m-%d %H:%M:%S", &localTm); Sprintf(buff + len, sizeof(buff) - len, ".%03u", milliSecond); return std::string(buff); } std::string ToUtcDateTime(const time_t* time) { char buff[32] = { 0 }; tm localTm = { 0 }; auto ret = GetTime(time, &localTm); if (ret != 0) { return ""; } strftime(buff, sizeof(buff), "%Y-%m-%d %H:%M:%S", &localTm); return std::string(buff); } std::string ToUtcDate(const time_t* time) { char buff[32] = { 0 }; tm localTm = { 0 }; auto ret = GetTime(time, &localTm); if (ret != 0) { return ""; } strftime(buff, sizeof(buff), "%Y-%m-%d", &localTm); return std::string(buff); } std::string ToUtcTime(const time_t* time) { char buff[32] = { 0 }; tm localTm = { 0 }; auto ret = GetTime(time, &localTm); if (ret != 0) { return ""; } strftime(buff, sizeof(buff), "%H:%M:%S", &localTm); return std::string(buff); } std::string GetLocalDateTime() { auto t = GetTime(); tm localTm = { 0 }; auto ret = GetLocalTime(&t, &localTm); if (ret != 0) { return ""; } char buff[32] = { 0 }; strftime(buff, sizeof(buff), "%Y-%m-%d %H:%M:%S", &localTm); return std::string(buff); } std::string GetLocalDate() { auto t = GetTime(); tm localTm = { 0 }; auto ret = GetLocalTime(&t, &localTm); if (ret != 0) { return ""; } char buff[32] = { 0 }; strftime(buff, sizeof(buff), "%Y-%m-%d", &localTm); return std::string(buff); } std::string GetLocalTime() { auto t = GetTime(); tm localTm = { 0 }; auto ret = GetLocalTime(&t, &localTm); if (ret != 0) { return ""; } char buff[32] = { 0 }; strftime(buff, sizeof(buff), "%H:%M:%S", &localTm); return std::string(buff); } Now GetCurrentTime() { return std::chrono::time_point_cast(std::chrono::system_clock::now()); } std::string GetLocalDateTimeWithMilliSecond() { return GetLocalDateTimeWithMilliSecond(GetCurrentTime()); } std::string GetLocalDateTimeWithMilliSecond(const Now& now) { time_t t = std::chrono::system_clock::to_time_t(now); int milliSecond = now.time_since_epoch().count() % 1000; tm localTm = { 0 }; auto ret = GetLocalTime(&t, &localTm); if (ret != 0) { return ""; } char buff[32] = { 0 }; int len = (int)strftime(buff, sizeof(buff), "%Y-%m-%d %H:%M:%S", &localTm); Sprintf(buff + len, sizeof(buff) - len, ".%03u", milliSecond); return std::string(buff); } std::string GetLocalDateFromUnixTimeStamp(long long timeStamp) { time_t time = timeStamp / 1000000000LL; static char buff[16] = { 0 }; tm localTm = { 0 }; auto ret = GetLocalTime(&time, &localTm); if (ret != 0) { return ""; } strftime(buff, sizeof(buff), "%Y-%m-%d", &localTm); return std::string(buff); } std::string GetLocalTimeFromUnixTimeStamp(long long timeStamp) { time_t time = timeStamp / 1000000000LL; static char buff[16] = { 0 }; tm localTm = { 0 }; auto ret = GetLocalTime(&time, &localTm); if (ret != 0) { return ""; } strftime(buff, sizeof(buff), "%H:%M:%S", &localTm); return std::string(buff); } std::string ToLocalDateTime(const time_t* time) { char buff[32] = { 0 }; tm localTm = { 0 }; auto ret = GetLocalTime(time, &localTm); if (ret != 0) { return ""; } strftime(buff, sizeof(buff), "%Y-%m-%d %H:%M:%S", &localTm); return std::string(buff); } std::string ToLocalDate(const time_t* time) { char buff[32] = { 0 }; tm localTm = { 0 }; auto ret = GetLocalTime(time, &localTm); if (ret != 0) { return ""; } strftime(buff, sizeof(buff), "%Y-%m-%d", &localTm); return std::string(buff); } std::string ToLocalTime(const time_t* time) { char buff[32] = { 0 }; tm localTm = { 0 }; auto ret = GetLocalTime(time, &localTm); if (ret != 0) { return ""; } strftime(buff, sizeof(buff), "%H:%M:%S", &localTm); return std::string(buff); } time_t GetTimeFromString(std::string dateTime, std::string format) { tm t; StrScanf(dateTime.c_str(), format.c_str(), &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &t.tm_sec); t.tm_year -= 1900; t.tm_mon -= 1; return mktime(&t); } } ukui-volume-control/backend/simple_logger/Formatter.h0000664000175000017500000000512515171074677022015 0ustar fengfeng// Copyright(c) 2023-present, Dreams Chen. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef FORMATTER_H #define FORMATTER_H #if defined(_MSC_VER) && (_MSC_VER >= 1200) # pragma once #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) #include #ifdef HAS_STD_FORMAT #include #include #else #include #include #endif namespace simple_logger { #ifdef HAS_STD_FORMAT #define FORMAT std::format template concept EnumType = std::is_enum_v; template struct std::formatter : std::formatter { template typename FormatContext::iterator format(const EnumValue& v, FormatContext& formatContext) { typename FormatContext::iterator itr = std::formatter().format(static_cast(v), formatContext); return itr; } }; #else #define FORMAT simple_logger::format void FormatExpand(std::stringstream& s, const char* fmt); template>::type> inline std::stringstream& operator<<(std::stringstream& ss, T& value) { ss << static_cast(value); return ss; } template void FormatExpand(std::stringstream& ss, const char* fmt, T& value, Args... args) { while (*fmt) { // 处理参数打印 if (*fmt == '{' && *(++fmt) == '}') { ss << value; FormatExpand(ss, ++fmt, args...); return; } ss << *fmt++; } #ifndef NDEBUG // 调试版本抛出异常,Release版本不抛异常 throw std::logic_error("Invalid formatting: too much arguments are provided to format"); #endif } template std::string format(const char* fmt, Args ... args) { std::stringstream ss; FormatExpand(ss, fmt, args...); return ss.str(); } #endif } #endif // !FORMATTER_H ukui-volume-control/backend/simple_logger/CMakeLists.txt0000664000175000017500000000311415171074677022435 0ustar fengfengcmake_minimum_required(VERSION 3.0.0) project(simple_logger) include(CheckIncludeFileCXX) set(CMAKE_CXX_STANDARD 20) if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") MESSAGE("#### Use Clang compiler ####") elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") MESSAGE("#### Use GNU compiler ####") elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel") MESSAGE("#### Use Intel compiler ####") elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") MESSAGE("#### Use MSVC compiler ####") endif() CHECK_INCLUDE_FILE_CXX(format HAS_FORMAT) message(HAS_FORMAT=${HAS_FORMAT}) if(${HAS_FORMAT}) message("HAS_FORMAT=${HAS_FORMAT}, add HAS_STD_FORMAT definition") add_definitions(-DHAS_STD_FORMAT) endif() set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../lib) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../lib) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../lib) set(SRC ${PROJECT_SOURCE_DIR}/src/DateTime.cpp ${PROJECT_SOURCE_DIR}/src/Formatter.cpp ${PROJECT_SOURCE_DIR}/src/Logger.cpp ) include_directories( ${PROJECT_SOURCE_DIR}/include ) link_directories(${PROJECT_SOURCE_DIR}) #add_library(simple_logger STATIC ${SRC}) add_library(simple_logger SHARED ${SRC}) if(WIN32) MESSAGE(STATUS "Current OS is windows system") target_link_libraries(simple_logger) elseif(APPLE) MESSAGE(STATUS "Current OS is Apple system.") target_link_libraries(simple_logger -lstdc++ -lpthread) elseif(UNIX) MESSAGE(STATUS "Current OS is UNIX-like(including linux) system.") target_link_libraries(simple_logger -lstdc++ -lpthread) endif() ukui-volume-control/backend/simple_logger/Logger.cpp0000664000175000017500000006657615171074712021633 0ustar fengfeng// Copyright(c) 2023-present, Dreams Chen. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "Logger.h" #include #include #include #include #include #include #include #include #include #ifdef WIN32 #include #include #ifndef F_OK #define F_OK 0 #endif #else #include #include #endif #include "DateTime.h" #ifdef _MSC_VER #define PATH_SEPERATOR "\\" #else #define PATH_SEPERATOR "/" #endif namespace simple_logger { const char* FONT_STYLE_RED = "\033[31m"; const char* FONT_STYLE_GREEN = "\033[32m"; const char* FONT_STYLE_YELLOW = "\033[33m"; const char* FONT_STYLE_PURPLE = "\033[35m"; const char* FONT_STYLE_CYAN = "\033[36m"; const char* FONT_STYLE_CLEAR = "\033[0m"; class Log::LogImpl { public: LogImpl(const char* dir, const char* fileName, uint32_t outputFlag, uint32_t logLevelFlag, bool detailMode, size_t maxDirSize = kDefaultMaxDirSize, size_t maxArchives = kDefaultMaxArchives, size_t maxArchiveTotal = kDefaultMaxArchiveTotalMB); ~LogImpl(); uint32_t GetOutputFlag() const; bool IsOutputTypeOn(OutputType type) const; void SetOutputTypeOn(OutputType outputType); void SetOutputTypeOff(OutputType outputType); void DisableLog(); bool IsLogSwitchOn(LogLevel level) const; void SetLogSwitchOn(LogLevel level); void SetLogSwitchOff(LogLevel level); void SetDetailMode(bool enable); bool IsDetailMode() const; void SetColorfulFont(bool enable); bool IsColorfulFont() const; void SetReverseFilter(bool enable); bool IsReverseFilter() const; void AddModule(int module, const std::string& name); void AddModule(const std::unordered_map& modules); void RemoveModule(int module); void ClearAllModule(); void AddAndFilter(const std::string& filterString); void AddAndFilter(const std::unordered_set& filterList); void ClearAndFilter(const std::string& filterString); void ClearAndFilter(const std::unordered_set& filterList); void ClearAndFilter(); bool IsFilteredByAndOp(const std::string& filterString) const; void AddOrFilter(const std::string& filterString); void AddOrFilter(const std::unordered_set& filterList); void ClearOrFilter(const std::string& filterString); void ClearOrFilter(const std::unordered_set& filterList); void ClearOrFilter(); bool IsFilteredByOrOp(const std::string& filterString) const; void AddModuleFilter(int module); void AddModuleFilter(const std::unordered_set& moduleList); void ClearModuleFilter(int module); void ClearModuleFilter(const std::unordered_set& moduleList); void ClearModuleFilter(); bool IsFilteredModule(int module) const; void ClearAllFilter(); void SetUserWriter(std::shared_ptr& m_userWriter); void SetRemoteWriter(std::shared_ptr& m_remoteWriter); bool IsLogQueEmpty() const; void Write(LogLevel level, int module, const char* fileName, int line, const char* funcName, std::thread::id threadId, const std::string& msg, WriteMode writeMode = WriteMode::Newline); // Close function should be called munually before the program exit. void Close(); public: std::string FormatLog(LogLevel level, int module, std::string_view fileName, int line, std::string_view funcName, uint64_t threadId, const std::string& info); void Write(LogLevel level, WriteMode writeMode, int module, const char* fileName, int line, const char* funcName, uint64_t threadId, const std::string& msg); bool NeedFilter(int module, const std::string& msg) const; bool NeedFilter(int module) const; bool NeedFilterWithAndRule(const std::string& msg) const; bool NeedFilterWithOrRule(const std::string& msg) const; void WriteToConsole(const std::string& msg); void WriteToLogFile(const std::string& msg); void WriteToUserWriter(const std::string& msg); void WriteToRemoteWriter(const std::string& msg); const char* LogLevelToStr(LogLevel level); void WritingWorker(); void UpdateCurrentDate(const std::string& currentTime); std::string GetModuleName(int module) const; const char* GetFontColor(char levelFlag) const; void CreateLogDir(const std::string& logDir) const; private: std::string getProcessName(pid_t); void cleanupArchives(); void gzipCompress(const std::string&); void checkAndRollAll(); private: std::string m_logDir; std::string m_logFileName; std::string m_currentDate; std::string m_progressName; pid_t m_progressId; uint32_t m_outputFlag; uint32_t m_logLevelFlag; size_t m_maxDirSize = kDefaultMaxDirSize; size_t m_maxArchives = kDefaultMaxArchives; size_t m_maxArchiveTotal = kDefaultMaxArchiveTotalMB; bool m_detailMode = true; bool m_exit = false; bool m_dateChanged = false; bool m_colorfulFont = true; // only use in console terminal. bool m_reverseFilter = false; // if m_reverseFilter == true, only the logs that match filters are printed. std::queue m_logQue; std::thread m_writerThread; mutable std::mutex m_writeMutex; mutable std::mutex m_queMutex; mutable std::shared_mutex m_miscMutex; std::unordered_map m_modulesMap; std::unordered_set m_andFilters; std::unordered_set m_orFilters; std::unordered_set m_moduleFilters; std::ofstream m_fileWriter; std::shared_ptr m_userWriter = nullptr; std::shared_ptr m_remoteWriter = nullptr; }; Log::LogImpl::LogImpl(const char* dir, const char* fileName, uint32_t outputFlag, uint32_t logLevelFlag, bool detailMode, size_t maxDirSize, size_t maxArchives, size_t maxArchiveTotal) : m_logDir(dir), m_logFileName(fileName), m_outputFlag(outputFlag), m_logLevelFlag(logLevelFlag), m_detailMode(detailMode), m_maxDirSize(maxDirSize), m_maxArchives(maxArchives), m_maxArchiveTotal(maxArchiveTotal) { m_currentDate = GetLocalDate(); std::string filePath = m_logDir + "/" + m_currentDate + "_" + fileName; CreateLogDir(m_logDir); m_fileWriter.open(filePath, std::ios::out | std::ios::app); m_writerThread = std::thread(&Log::LogImpl::WritingWorker, this); m_progressId = getpid(); m_progressName = getProcessName(m_progressId); checkAndRollAll(); } Log::LogImpl::~LogImpl() { Close(); } uint32_t Log::LogImpl::GetOutputFlag() const { return m_outputFlag; } bool Log::LogImpl::IsOutputTypeOn(OutputType type) const { return (m_outputFlag & ((uint32_t)type)) != 0; } void Log::LogImpl::SetOutputTypeOn(OutputType outputType) { m_outputFlag |= (uint32_t)outputType; } void Log::LogImpl::SetOutputTypeOff(OutputType outputType) { m_outputFlag &= ~(uint32_t)outputType; } void Log::LogImpl::DisableLog() { m_outputFlag = 0; } bool Log::LogImpl::IsLogSwitchOn(LogLevel level) const { return (m_logLevelFlag & (uint32_t)level) != 0; } void Log::LogImpl::SetLogSwitchOn(LogLevel level) { m_logLevelFlag |= (uint32_t)level; } void Log::LogImpl::SetLogSwitchOff(LogLevel level) { m_logLevelFlag &= ~(uint32_t)level; } void Log::LogImpl::SetDetailMode(bool enable) { m_detailMode = enable; } bool Log::LogImpl::IsDetailMode() const { return m_detailMode; } void Log::LogImpl::SetColorfulFont(bool enable) { m_colorfulFont = enable; } bool Log::LogImpl::IsColorfulFont() const { return m_colorfulFont; } void Log::LogImpl::SetReverseFilter(bool enable) { m_reverseFilter = enable; } bool Log::LogImpl::IsReverseFilter() const { return m_reverseFilter; } void Log::LogImpl::AddModule(int module, const std::string& name) { std::lock_guard lock(m_miscMutex); m_modulesMap[module] = name; } void Log::LogImpl::AddModule(const std::unordered_map& modules) { std::lock_guard lock(m_miscMutex); std::copy(modules.begin(), modules.end(), std::inserter(m_modulesMap, m_modulesMap.end())); } void Log::LogImpl::RemoveModule(int module) { std::lock_guard lock(m_miscMutex); m_modulesMap.erase(module); } void Log::LogImpl::ClearAllModule() { std::lock_guard lock(m_miscMutex); m_modulesMap.clear(); } void Log::LogImpl::AddAndFilter(const std::string& filterString) { std::lock_guard lock(m_miscMutex); m_andFilters.insert(filterString); } void Log::LogImpl::AddAndFilter(const std::unordered_set& filterList) { std::lock_guard lock(m_miscMutex); std::copy(filterList.begin(), filterList.end(), std::inserter(m_andFilters, m_andFilters.end())); } void Log::LogImpl::ClearAndFilter(const std::string& filterString) { std::lock_guard lock(m_miscMutex); m_andFilters.erase(filterString); } void Log::LogImpl::ClearAndFilter(const std::unordered_set& filterList) { std::for_each(filterList.begin(), filterList.end(), [this] (const std::string& filterString) { ClearAndFilter(filterString); }); } void Log::LogImpl::ClearAndFilter() { std::lock_guard lock(m_miscMutex); m_andFilters.clear(); } bool Log::LogImpl::IsFilteredByAndOp(const std::string& filterString) const { std::lock_guard lock(m_miscMutex); return m_andFilters.find(filterString) != m_andFilters.end(); } void Log::LogImpl::AddOrFilter(const std::string& filterString) { std::lock_guard lock(m_miscMutex); m_orFilters.insert(filterString); } void Log::LogImpl::AddOrFilter(const std::unordered_set& filterList) { std::lock_guard lock(m_miscMutex); std::copy(filterList.begin(), filterList.end(), std::inserter(m_orFilters, m_orFilters.end())); } void Log::LogImpl::ClearOrFilter(const std::string& filterString) { std::lock_guard lock(m_miscMutex); m_orFilters.erase(filterString); } void Log::LogImpl::ClearOrFilter(const std::unordered_set& filterList) { std::for_each(filterList.begin(), filterList.end(), [this] (const std::string& filterString) { ClearOrFilter(filterString); }); } void Log::LogImpl::ClearOrFilter() { std::lock_guard lock(m_miscMutex); m_orFilters.clear(); } bool Log::LogImpl::IsFilteredByOrOp(const std::string& filterString) const { std::lock_guard lock(m_miscMutex); return m_orFilters.find(filterString) != m_orFilters.end(); } void Log::LogImpl::AddModuleFilter(int module) { std::lock_guard lock(m_miscMutex); m_moduleFilters.insert(module); } void Log::LogImpl::AddModuleFilter(const std::unordered_set& moduleList) { std::lock_guard lock(m_miscMutex); std::copy(moduleList.begin(), moduleList.end(), std::inserter(m_moduleFilters, m_moduleFilters.end())); } void Log::LogImpl::ClearModuleFilter(int module) { std::lock_guard lock(m_miscMutex); m_moduleFilters.erase(module); } void Log::LogImpl::ClearModuleFilter(const std::unordered_set& moduleList) { std::for_each(moduleList.begin(), moduleList.end(), [this] (int module) { ClearModuleFilter(module); }); } void Log::LogImpl::ClearModuleFilter() { std::lock_guard lock(m_miscMutex); m_moduleFilters.clear(); } bool Log::LogImpl::IsFilteredModule(int module) const { std::lock_guard lock(m_miscMutex); return m_moduleFilters.find(module) != m_moduleFilters.end(); } void Log::LogImpl::ClearAllFilter() { ClearAndFilter(); ClearOrFilter(); ClearModuleFilter(); } bool Log::LogImpl::IsLogQueEmpty() const { return m_logQue.empty(); } bool Log::LogImpl::NeedFilter(int module) const { if (m_moduleFilters.empty()) { return false; } std::shared_lock lock(m_miscMutex); auto itr = m_moduleFilters.find(module); return m_reverseFilter ? itr == m_moduleFilters.end() : itr != m_moduleFilters.end(); } bool Log::LogImpl::NeedFilterWithAndRule(const std::string& msg) const { if (m_andFilters.empty()) { return false; } bool found = false; std::shared_lock lock(m_miscMutex); for (const std::string& filter : m_andFilters) { if (msg.find(filter) == std::string::npos) { lock.unlock(); found = true; break; } } return m_reverseFilter ? found : !found; } bool Log::LogImpl::NeedFilterWithOrRule(const std::string& msg) const { if (m_orFilters.empty()) { return false; } bool found = false; std::shared_lock lock(m_miscMutex); for (const std::string& filter : m_orFilters) { if (msg.find(filter) != std::string::npos) { lock.unlock(); found = true; break; } } return m_reverseFilter ? !found : found; } bool Log::LogImpl::NeedFilter(int module, const std::string& msg) const { return NeedFilter(module) || NeedFilterWithAndRule(msg) || NeedFilterWithOrRule(msg); } void Log::LogImpl::Write(LogLevel level, int module, const char* fileName, int line, const char* funcName, std::thread::id threadId, const std::string& msg, WriteMode writeMode) { if (NeedFilter(module, msg)) { return; } std::stringstream ss; ss << threadId; uint64_t id = 0; ss >> id; Write(level, writeMode, module, fileName, line, funcName, id, msg); } void Log::LogImpl::UpdateCurrentDate(const std::string& currentTime) { if (currentTime[9] != m_currentDate[9]) { m_currentDate = currentTime.substr(0, 10); m_dateChanged = true; } } std::string Log::LogImpl::getProcessName(pid_t pid) { std::ostringstream oss; oss << "/proc/" << pid << "/exe"; std::string procPath = oss.str(); try { std::filesystem::path targetPath = std::filesystem::read_symlink(procPath); std::string path = targetPath.string(); // 提取进程名(路径的最后一部分) size_t pos = path.rfind('/'); if (pos != std::string::npos) { return path.substr(pos + 1); } else { return path; } } catch (const std::filesystem::filesystem_error& e) { return "Unknown"; } } /** * 日志文件清理策略,当归档文件超过m_maxArchives(默认为7个)时, * 清理对应的归档日志文件 */ void Log::LogImpl::cleanupArchives() { std::vector archives; for (const auto& e : std::filesystem::directory_iterator(m_logDir)) { if (e.is_regular_file() && e.path().extension() == ".gz") archives.emplace_back(e); } if (archives.size() <= m_maxArchives) return; std::sort(archives.begin(), archives.end(), [](const auto& a, const auto& b) { return a.last_write_time() < b.last_write_time(); }); size_t total = 0; for (const auto& e : archives) total += e.file_size(); for (auto it = archives.begin(); it != archives.end(); ) { if (archives.size() <= m_maxArchives && total <= m_maxArchiveTotal) break; if (std::filesystem::exists(*it)) { total -= it->file_size(); std::filesystem::remove(it->path()); } it = archives.erase(it); } } /** * 日志文件回滚策略,当日志文件大小超过kDefaultMaxDirSize(默认为50M)大小时, * 将日志文件进行归档操作 */ void Log::LogImpl::gzipCompress(const std::string& src) { std::string cmd; cmd = "gzip -c \"" + src + "\" > \"" + src + ".gz\""; if (std::system(cmd.c_str()) == 0) std::filesystem::remove(src); } /** * 日志文件清理策略 * 1. 当日志目录下的.log文件总大小会超过50M或者日志文件超过7个时会进行日志归档操作 * 2. 当日志目录下的.gz归档文件超过7个时会清理日期最早的归档文件 */ void Log::LogImpl::checkAndRollAll() { using namespace std::filesystem; std::vector log_files; for (const auto& e : directory_iterator(m_logDir)) { if (e.is_regular_file() && e.path().extension() == ".log") log_files.emplace_back(e); } if (log_files.empty()) return; size_t total = std::accumulate(log_files.begin(), log_files.end(), std::size_t(0), [](size_t sum, const auto& e) { return sum + e.file_size(); }); bool need_roll = (total > m_maxDirSize) || (log_files.size() > 7); if (!need_roll) return; std::sort(log_files.begin(), log_files.end(), [](const auto& a, const auto& b) { return a.path().filename() < b.path().filename(); }); m_fileWriter.close(); for (const auto& e : log_files) { gzipCompress(e.path().string()); } m_fileWriter.open(m_logDir + "/" + m_currentDate + "_" + m_logFileName, std::ios::out | std::ios::app); cleanupArchives(); } std::string Log::LogImpl::FormatLog(LogLevel level, int module, const std::string_view fileName, int line, const std::string_view funcName, uint64_t threadId, const std::string& info) { std::string currentTime = GetLocalDateTimeWithMilliSecond(); UpdateCurrentDate(currentTime); if (!m_detailMode) { return FORMAT("{} {}[{}] [{}] [{}]: {}", currentTime, m_progressName, m_progressId, LogLevelToStr(level), GetModuleName(module), info); } return FORMAT("{} {}[{}] [{}] [{}] [{}(line: {}, method: {}, thread: {})]: {}", currentTime, m_progressName, m_progressId, LogLevelToStr(level), GetModuleName(module), fileName.substr(fileName.find_last_of(PATH_SEPERATOR) + 1), line, funcName, threadId, info); } void Log::LogImpl::Write(LogLevel level, WriteMode writeMode, int module, const char* fileName, int line, const char* funcName, uint64_t threadId, const std::string& msg) { if (m_exit || !IsLogSwitchOn(level)) { return; } std::string&& str = FormatLog(level, module, fileName, line, funcName, threadId, msg); if (writeMode == WriteMode::Newline) { str.append("\r\n"); } std::unique_lock lock(m_queMutex); m_logQue.emplace(str); return; } void Log::LogImpl::WritingWorker() { std::unique_lock locker(m_queMutex, std::defer_lock); while (!m_exit) { if (m_logQue.empty()) { std::this_thread::sleep_for(std::chrono::milliseconds(300)); continue; } locker.lock(); std::string logStr = std::move(m_logQue.front()); m_logQue.pop(); locker.unlock(); // Only one writing thread, no need to lock for the below action. WriteToConsole(logStr); WriteToLogFile(logStr); WriteToUserWriter(logStr); WriteToRemoteWriter(logStr); } } void Log::LogImpl::WriteToConsole(const std::string& msg) { if (!IsOutputTypeOn(OutputType::Console)) { return; } if (!m_colorfulFont) { std::unique_lock lock(m_writeMutex); std::cout << msg; return; } std::string coloredMsg = FORMAT("{}{}{}", GetFontColor(msg[25]), msg, FONT_STYLE_CLEAR); std::unique_lock lock(m_writeMutex); std::cout << coloredMsg; } void Log::LogImpl::WriteToLogFile(const std::string& msg) { if (!IsOutputTypeOn(OutputType::LogFile)) { return; } if (m_dateChanged) { m_fileWriter.close(); m_fileWriter.open(m_logDir + m_currentDate + "_" + m_logFileName, std::ios::out | std::ios::app); checkAndRollAll(); m_dateChanged = false; } m_fileWriter << msg; m_fileWriter.flush(); // 实时更新日志 } void Log::LogImpl::WriteToUserWriter(const std::string& msg) { if (!IsOutputTypeOn(OutputType::UserDefined) || m_userWriter == nullptr) { return; } m_userWriter->Write(msg); } void Log::LogImpl::WriteToRemoteWriter(const std::string& msg) { if (!IsOutputTypeOn(OutputType::RemoteServer) || m_remoteWriter == nullptr) { return; } m_remoteWriter->Write(msg); } void Log::LogImpl::SetUserWriter(std::shared_ptr& m_fileWriter) { m_userWriter = m_fileWriter; } void Log::LogImpl::SetRemoteWriter(std::shared_ptr& m_fileWriter) { m_remoteWriter = m_fileWriter; } const char* Log::LogImpl::LogLevelToStr(LogLevel level) { switch (level) { case LogLevel::Debug: return "Debug"; case LogLevel::Info: return "Info"; case LogLevel::Warn: return "Warn"; case LogLevel::Error: return "Error"; case LogLevel::Fatal: return "Fatal"; } return "Unknown"; } std::string Log::LogImpl::GetModuleName(int module) const { std::shared_lock lock(m_miscMutex); auto itr = m_modulesMap.find(module); return itr != m_modulesMap.end() ? itr->second : ""; } void Log::LogImpl::Close() { if (m_exit) { return; } m_exit = true; if (m_writerThread.joinable()) { m_writerThread.join(); } m_fileWriter.flush(); m_fileWriter.close(); if (m_userWriter != nullptr) { m_userWriter->Close(); } if (m_remoteWriter != nullptr) { m_remoteWriter->Close(); } } const char* Log::LogImpl::GetFontColor(char levelFlag) const { switch (levelFlag) { case 'D': // debug level return FONT_STYLE_GREEN; case 'I': // info level return FONT_STYLE_CYAN; case 'W': // warning level return FONT_STYLE_YELLOW; case 'E': // error level return FONT_STYLE_RED; case 'F': // fatal level return FONT_STYLE_PURPLE; default: return FONT_STYLE_CLEAR; } } // Log public function implementation. Log::Log(const char* dir, const char* fileName, uint32_t outputFlag, uint32_t logLevelFlag, bool detailMode) : m_impl(std::make_unique(dir, fileName, outputFlag, logLevelFlag, detailMode)) { } Log::Log(std::string_view dir, std::string_view fileName, uint32_t outputFlag, uint32_t logLevelFlag, bool detailMode) : m_impl(std::make_unique(dir.data(), fileName.data(), outputFlag, logLevelFlag, detailMode)) { } Log::~Log() { Close(); } uint32_t Log::Log::GetOutputFlag() const { return m_impl->GetOutputFlag(); } bool Log::IsOutputTypeOn(OutputType type) const { return m_impl->IsOutputTypeOn(type); } void Log::SetOutputTypeOn(OutputType outputType) { m_impl->SetOutputTypeOn(outputType); } void Log::SetOutputTypeOff(OutputType outputType) { m_impl->SetOutputTypeOff(outputType); } void Log::DisableLog() { m_impl->DisableLog(); } bool Log::IsLogSwitchOn(LogLevel level) const { return m_impl->IsLogSwitchOn(level); } void Log::SetLogSwitchOn(LogLevel level) { m_impl->SetLogSwitchOn(level); } void Log::SetLogSwitchOff(LogLevel level) { m_impl->SetLogSwitchOff(level); } void Log::SetDetailMode(bool enable) { m_impl->SetDetailMode(enable); } bool Log::IsDetailMode() const { return m_impl->IsDetailMode(); } void Log::SetColorfulFont(bool enable) { m_impl->SetColorfulFont(enable); } bool Log::IsColorfulFont() const { return m_impl->IsColorfulFont(); } void Log::SetReverseFilter(bool enable) { m_impl->SetReverseFilter(enable); } bool Log::IsReverseFilter() const { return m_impl->IsReverseFilter(); } void Log::AddModule(int module, const std::string& name) { m_impl->AddModule(module, name); } void Log::AddModule(const std::unordered_map& modules) { m_impl->AddModule(modules); } void Log::RemoveModule(int module) { m_impl->RemoveModule(module); } void Log::ClearAllModule() { m_impl->ClearAllModule(); } void Log::AddAndFilter(const std::string& filterString) { m_impl->AddAndFilter(filterString); } void Log::AddAndFilter(const std::unordered_set& filterList) { m_impl->AddAndFilter(filterList); } void Log::ClearAndFilter(const std::string& filterString) { m_impl->ClearAndFilter(filterString); } void Log::ClearAndFilter(const std::unordered_set& filterList) { m_impl->ClearAndFilter(filterList); } void Log::ClearAndFilter() { m_impl->ClearAndFilter(); } bool Log::IsFilteredByAndOp(const std::string& filterString) const { return m_impl->IsFilteredByAndOp(filterString); } void Log::AddOrFilter(const std::string& filterString) { m_impl->AddOrFilter(filterString); } void Log::AddOrFilter(const std::unordered_set& filterList) { m_impl->AddOrFilter(filterList); } void Log::ClearOrFilter(const std::string& filterString) { m_impl->ClearOrFilter(filterString); } void Log::ClearOrFilter(const std::unordered_set& filterList) { m_impl->ClearOrFilter(filterList); } void Log::ClearOrFilter() { m_impl->ClearOrFilter(); } bool Log::IsFilteredByOrOp(const std::string& filterString) const { return m_impl->IsFilteredByOrOp(filterString); } void Log::AddModuleFilter(int module) { m_impl->AddModuleFilter(module); } void Log::AddModuleFilter(const std::unordered_set& moduleList) { m_impl->AddModuleFilter(moduleList); } void Log::ClearModuleFilter(int module) { m_impl->ClearModuleFilter(module); } void Log::ClearModuleFilter(const std::unordered_set& moduleList) { m_impl->ClearModuleFilter(moduleList); } void Log::ClearModuleFilter() { m_impl->ClearModuleFilter(); } bool Log::IsFilteredModule(int module) const { return m_impl->IsFilteredModule(module); } void Log::ClearAllFilter() { m_impl->ClearAllFilter(); } void Log::SetUserWriter(std::shared_ptr userWriter) { m_impl->SetUserWriter(userWriter); } void Log::SetRemoteWriter(std::shared_ptr remoteWriter) { m_impl->SetRemoteWriter(remoteWriter); } bool Log::IsLogQueEmpty() const { return m_impl->IsLogQueEmpty(); } void Log::Write(LogLevel level, int module, const char* fileName, int line, const char* funcName, std::thread::id threadId, const std::string& msg, WriteMode writeMode) { if (m_impl != nullptr) { m_impl->Write(level, module, fileName, line, funcName, threadId, msg, writeMode); } } void Log::Close() { if (m_impl != nullptr) { return m_impl->Close(); } } #ifdef WIN32 void Log::LogImpl::CreateLogDir(const std::string& logDir) const { if (_access(logDir.c_str(), F_OK) == -1) { // The folder doesn't exist. _mkdir(logDir.c_str()); } } #else void Log::LogImpl::CreateLogDir(const std::string& logDir) const { namespace fs = std::filesystem; std::error_code ec; fs::create_directories(logDir, ec); // 递归且幂等 if (ec) { throw std::runtime_error("Cannot create log dir '" + logDir + "': " + ec.message()); } fs::permissions(logDir, fs::perms::owner_all | fs::perms::group_read | fs::perms::group_exec, ec); } #endif } ukui-volume-control/backend/simple_logger/.gitignore0000664000175000017500000000041615171074677021667 0ustar fengfeng# Prerequisites *.d # Compiled Object files *.slo *.lo *.o *.obj # Precompiled Headers *.gch *.pch # Compiled Dynamic libraries *.so *.dylib *.dll # Fortran module files *.mod *.smod # Compiled Static libraries *.lai *.la *.a *.lib # Executables *.exe *.out *.app ukui-volume-control/backend/simple_logger/Logger.h0000664000175000017500000001453615171074677021277 0ustar fengfeng// Copyright(c) 2023-present, Dreams Chen. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef LOGGER_H #define LOGGER_H #if defined(_MSC_VER) && (_MSC_VER >= 1200) # pragma once #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) #include #include #include #include #include #include #include "Formatter.h" // used with module level print micro to simplify coding, for example: // #define EXAMPLE_DEBUG(fmt, ...) DBG_DEBUG(ExampleContext::GetInstance().GetLogger(), ExampleContext::GetInstance().GetModuleValue(), FORMAT(fmt, ##__VA_ARGS__)) // EXAMPLE_DEBUG("This is a print example. str={}", "test"); // see ../example/Example.cpp for more detail. #define DBG_DEBUG(log, mod, fmt, ...) log.Write(simple_logger::LogLevel::Debug, mod, __FILE__, __LINE__, __FUNCTION__, std::this_thread::get_id(), FORMAT(fmt, ##__VA_ARGS__)) #define DBG_INFO(log, mod, fmt, ...) log.Write(simple_logger::LogLevel::Info, mod, __FILE__, __LINE__, __FUNCTION__, std::this_thread::get_id(), FORMAT(fmt, ##__VA_ARGS__)) #define DBG_WARN(log, mod, fmt, ...) log.Write(simple_logger::LogLevel::Warn, mod, __FILE__, __LINE__, __FUNCTION__, std::this_thread::get_id(), FORMAT(fmt, ##__VA_ARGS__)) #define DBG_ERROR(log, mod, fmt, ...) log.Write(simple_logger::LogLevel::Error, mod, __FILE__, __LINE__, __FUNCTION__, std::this_thread::get_id(), FORMAT(fmt, ##__VA_ARGS__)) #define DBG_FATAL(log, mod, fmt, ...) log.Write(simple_logger::LogLevel::Fatal, mod, __FILE__, __LINE__, __FUNCTION__, std::this_thread::get_id(), FORMAT(fmt, ##__VA_ARGS__)) // micro definition for time performent evaluation #define START_TIME() simple_logger::Now _begin = simple_logger::GetCurrentTime(); #define END_TIME() simple_logger::Now _end = simple_logger::GetCurrentTime(); #define USED_TIME(_msg) \ auto _duration = std::chrono::duration_cast(_end - _begin); \ std::cout << _msg << ": " << _duration.count() << "us, or " << _duration.count() / 1000 << "ms, or " << _duration.count() / 1000000 << "s" << std::endl; namespace simple_logger { enum class LogLevel { Debug = 1, Info = 2, Warn = 4, Error = 8, Fatal = 16, }; enum class OutputType { None = 0, Console = 1, LogFile = 2, RemoteServer = 4, UserDefined = 8, }; enum class WriteMode { Append = 0, Newline, }; const size_t kDefaultMaxDirSize = 50 * 1024 * 1024; // 文件大小阈值:50M const size_t kDefaultMaxArchives = 7; // 总归档数量:7 个 const size_t kDefaultMaxArchiveTotalMB = 500 * 1024 * 1024; // 总归档容量:500M class UserDefinedWriter { public: virtual ~UserDefinedWriter() = default; public: virtual void Write(const std::string& str) = 0; virtual void Close() {}; }; template inline uint32_t MakeFlag(Args... args) { return (... | static_cast(args)); } class Log { public: Log(const char* dir, const char* fileName, uint32_t outputFlag = MakeFlag(OutputType::LogFile), uint32_t logLevelFlag = MakeFlag(LogLevel::Info, LogLevel::Warn, LogLevel::Error, LogLevel::Fatal), bool detailMode = true); Log(std::string_view dir, std::string_view fileName, uint32_t outputFlag = MakeFlag(OutputType::LogFile), uint32_t logLevelFlag = MakeFlag(LogLevel::Info, LogLevel::Warn, LogLevel::Error, LogLevel::Fatal), bool detailMode = true); Log(const Log& log) = delete; Log(const Log&& log) = delete; Log& operator=(const Log& log) = delete; Log& operator=(const Log&& log) = delete; ~Log(); public: uint32_t GetOutputFlag() const; bool IsOutputTypeOn(OutputType type) const; void SetOutputTypeOn(OutputType outputType); void SetOutputTypeOff(OutputType outputType); void DisableLog(); bool IsLogSwitchOn(LogLevel level) const; void SetLogSwitchOn(LogLevel level); void SetLogSwitchOff(LogLevel level); void SetDetailMode(bool enable); bool IsDetailMode() const; void SetColorfulFont(bool enable); bool IsColorfulFont() const; void SetReverseFilter(bool enable); bool IsReverseFilter() const; void AddModule(int module, const std::string& name); void AddModule(const std::unordered_map& modules); void RemoveModule(int module); void ClearAllModule(); void AddAndFilter(const std::string& filterString); void AddAndFilter(const std::unordered_set& filterList); void ClearAndFilter(const std::string& filterString); void ClearAndFilter(const std::unordered_set& filterList); void ClearAndFilter(); bool IsFilteredByAndOp(const std::string& filterString) const; void AddOrFilter(const std::string& filterString); void AddOrFilter(const std::unordered_set& filterList); void ClearOrFilter(const std::string& filterString); void ClearOrFilter(const std::unordered_set& filterList); void ClearOrFilter(); bool IsFilteredByOrOp(const std::string& filterString) const; void AddModuleFilter(int module); void AddModuleFilter(const std::unordered_set& moduleList); void ClearModuleFilter(int module); void ClearModuleFilter(const std::unordered_set& moduleList); void ClearModuleFilter(); bool IsFilteredModule(int module) const; void ClearAllFilter(); void SetUserWriter(std::shared_ptr userWriter); void SetRemoteWriter(std::shared_ptr remoteWriter); bool IsLogQueEmpty() const; void Write(LogLevel level, int module, const char* fileName, int line, const char* funcName, std::thread::id threadId, const std::string& msg, WriteMode writeMode = WriteMode::Newline); // Close function should be called munually before the program exit. void Close(); private: class LogImpl; std::unique_ptr m_impl; }; }; #endif // !LOGGER_H ukui-volume-control/backend/MultiAudioCombineModule.cpp0000664000175000017500000001032115171074712022254 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "MultiAudioCombineModule.h" #include "AudioMethod.h" #include "AudioContext.h" namespace UkuiAudioFramwork { MultiAudioCombineModule& MultiAudioCombineModule::getInstance() { static MultiAudioCombineModule instance; return instance; } void MultiAudioCombineModule::setJson(std::shared_ptr json) { m_pJson = json; } bool MultiAudioCombineModule::getMultiAudioCombineStatus(const NodeType& type) const { auto str = EnumToInt(type) ? "output-combine" : "input-combine"; auto status = m_pJson->getValue(str, "status"); if (status) return *status; AUDIO_ERROR("Get {} type multi audio combine status failed.", EnumToInt(type)); return false; } void MultiAudioCombineModule::setMultiAudioCombine(const NodeType& type, bool status, const std::vector& list) { if (NodeType::OUTPUT != type) { AUDIO_ERROR("Devices with combined {} types are not currently supported.", EnumToInt(type)); return; } MultiAudioCombineInfo info; info.type = EnumToInt(type); info.status = status; m_pJson->setValue("output-combine", "status", status); if (status) { auto devList = AudioMethod::getInstance().getSinkList(); std::vector combineDevList {}; for (const auto& dev : devList) { if (std::string::npos != dev.name.find("a2dp_sink")) { combineDevList.push_back(dev.name); } } /** * When setting the Bluetooth combined output device, when waking up from sleep or actively * reconnecting Bluetooth, since Bluetooth is not sure when to connect to the Bluetooth audio, * a reset operation is performed here. If the setting fails to take effect after more than 3 times, * it means that the number of connected Bluetooth audios is less than 2 and cannot be combined output. */ if (combineDevList.size() < 2) { if (m_retryTimes >= 3) { AUDIO_ERROR("Set the number of combinations to more than {} times, Unable to combine Bluetooth devices with device number {}, " "Please check the number of Bluetooth devices connected.", m_retryTimes, combineDevList.size()); m_retryTimes = 0; return; } std::thread([type, status, list, combineDevList, this]() { std::this_thread::sleep_for(std::chrono::milliseconds(1000)); ++m_retryTimes; AUDIO_WARN("Failed to set the combined output device. The currently connected device is less than 2. Trying to " "reset the combined output device for the {} time", m_retryTimes); setMultiAudioCombine(type, status, list); }).detach(); return; } std::string param = "slaves="; param.append(combineDevList.at(0)); param.append(","); param.append(combineDevList.at(1)); AUDIO_DEBUG("Turn on the combined device function with {}.", param); AudioMethod::getInstance().loadModule("module-combine-sink", param); info.deviceList = combineDevList; m_pJson->insert(info); } else { // 卸载组合输出模块 m_pJson->insert(info); AUDIO_DEBUG("Turn off the combined device function."); AudioMethod::getInstance().unloadModule("module-combine-sink"); AudioMethod::getInstance().deviceAdjust(NodeType::OUTPUT); } } } ukui-volume-control/backend/InitVolumeModule.h0000664000175000017500000000272715171074712020456 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 INITVOLUMEMODULE_H #define INITVOLUMEMODULE_H #include "CustomAudioVolumeJson.h" namespace UkuiAudioFramwork { class InitVolumeModule { public: static InitVolumeModule& getInstance(); bool isNeedInitVolume(const std::string&, const std::string&); void insert(const std::string&, const std::string&, uint32_t); private: InitVolumeModule() = default; InitVolumeModule(const InitVolumeModule&) = delete; InitVolumeModule(InitVolumeModule&&) = delete; InitVolumeModule operator=(const InitVolumeModule&) = delete; InitVolumeModule operator=(InitVolumeModule&&) = delete; virtual ~InitVolumeModule() = default; private: CustomAudioVolumeJson m_json; }; } #endif // INITVOLUMEMODULE_H ukui-volume-control/backend/PulseaudioBackendFactory.h0000664000175000017500000000220615171074712022117 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 PULSEAUDIOBACKENDFACTORY_H #define PULSEAUDIOBACKENDFACTORY_H #include "BackendFactory.h" namespace UkuiAudioFramwork { class PulseaudioBackendFactory : public BackendFactory { public: PulseaudioBackendFactory() = default; virtual auto createBackend() -> std::shared_ptr override; ~PulseaudioBackendFactory() = default; }; } #endif // PULSEAUDIOBACKENDFACTORY_H ukui-volume-control/backend/PipewireBackend.h0000664000175000017500000000205615171074712020244 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 PIPEWIREBACKEND_H #define PIPEWIREBACKEND_H #include "IBackend.h" namespace UkuiAudioFramwork { class PipewireBackend : public IBackend { public: PipewireBackend(); ~PipewireBackend() = default; public: virtual void init(); virtual bool detect(); }; } #endif // PIPEWIREBACKEND_H ukui-volume-control/backend/PipewireAlsaNode.h0000664000175000017500000000225515171074712020404 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 . */ #pragma once #include "PipewireNode.h" namespace UkuiAudioFramwork { class PipewireAlsaNode : public PipewireNode { public: PipewireAlsaNode() = default; explicit PipewireAlsaNode(Pipewire *parent, uint32_t id, const struct spa_dict *props); ~PipewireAlsaNode() override = default; // Q_INVOKABLE [[nodiscard]] bool isAlsa() const override { return true; } [[nodiscard]] bool isAlsa() const { return true; } }; } ukui-volume-control/backend/CMakeLists.txt0000664000175000017500000001377615171074677017624 0ustar fengfengcmake_minimum_required(VERSION 3.5) project(ukui-audio-service LANGUAGES CXX) include(CheckIncludeFileCXX) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD 20) find_package(PkgConfig) pkg_check_modules(GLIB2 REQUIRED glib-2.0 gio-2.0 libxml-2.0) pkg_check_modules(ALSA REQUIRED alsa) pkg_check_modules(PA REQUIRED libpulse libpulse-mainloop-glib) pkg_check_modules(PW REQUIRED libpipewire-0.3) pkg_check_modules(DBUS REQUIRED dbus-1) pkg_check_modules(PUGIXML REQUIRED pugixml) include_directories(${GLIB2_INCLUDE_DIRS}) include_directories(${ALSA_INCLUDE_DIRS}) include_directories(${PA_INCLUDE_DIRS}) include_directories(${PW_INCLUDE_DIRS}) include_directories(${DBUS_INCLUDE_DIRS}) include_directories(${PUGIXML_INCLUDE_DIRS}) set(SRC ${PROJECT_SOURCE_DIR}/IJson.h ${PROJECT_SOURCE_DIR}/InitVolumeModule.cpp ${PROJECT_SOURCE_DIR}/InitVolumeModule.h ${PROJECT_SOURCE_DIR}/DeviceManagerModule.cpp ${PROJECT_SOURCE_DIR}/DeviceManagerModule.h ${PROJECT_SOURCE_DIR}/CustomAudioVolumeJson.cpp ${PROJECT_SOURCE_DIR}/CustomAudioVolumeJson.h ${PROJECT_SOURCE_DIR}/RestoreVolumeJson.cpp ${PROJECT_SOURCE_DIR}/RestoreVolumeJson.h ${PROJECT_SOURCE_DIR}/ISetting.h ${PROJECT_SOURCE_DIR}/AudioSetting.cpp ${PROJECT_SOURCE_DIR}/AudioSetting.h ${PROJECT_SOURCE_DIR}/GlobalThemeSettings.cpp ${PROJECT_SOURCE_DIR}/GlobalThemeSettings.h ${PROJECT_SOURCE_DIR}/SoundThemePlayerSetting.cpp ${PROJECT_SOURCE_DIR}/SoundThemePlayerSetting.h ${PROJECT_SOURCE_DIR}/SessionManagerSetting.cpp ${PROJECT_SOURCE_DIR}/SessionManagerSetting.h ${PROJECT_SOURCE_DIR}/SoundThemeModule.cpp ${PROJECT_SOURCE_DIR}/SoundThemeModule.h ${PROJECT_SOURCE_DIR}/Util.cpp ${PROJECT_SOURCE_DIR}/Util.h ${PROJECT_SOURCE_DIR}/PaDataType.cpp ${PROJECT_SOURCE_DIR}/PaDataType.h ${PROJECT_SOURCE_DIR}/BaseType.h ${PROJECT_SOURCE_DIR}/BackendFactory.h ${PROJECT_SOURCE_DIR}/PulseaudioBackendFactory.cpp ${PROJECT_SOURCE_DIR}/PulseaudioBackendFactory.h ${PROJECT_SOURCE_DIR}/PipewireBackendFactory.cpp ${PROJECT_SOURCE_DIR}/PipewireBackendFactory.h ${PROJECT_SOURCE_DIR}/IBackend.h ${PROJECT_SOURCE_DIR}/PulseaudioBackend.cpp ${PROJECT_SOURCE_DIR}/PulseaudioBackend.h ${PROJECT_SOURCE_DIR}/PipewireBackend.cpp ${PROJECT_SOURCE_DIR}/PipewireBackend.h ${PROJECT_SOURCE_DIR}/VolumeControl.cpp ${PROJECT_SOURCE_DIR}/VolumeControl.h ${PROJECT_SOURCE_DIR}/ManagerFactory.h ${PROJECT_SOURCE_DIR}/PulseaudioManagerFactory.cpp ${PROJECT_SOURCE_DIR}/PulseaudioManagerFactory.h ${PROJECT_SOURCE_DIR}/PipewireManagerFactory.cpp ${PROJECT_SOURCE_DIR}/PipewireManagerFactory.h ${PROJECT_SOURCE_DIR}/AudioManager.cpp ${PROJECT_SOURCE_DIR}/AudioManager.h ${PROJECT_SOURCE_DIR}/IStream.h ${PROJECT_SOURCE_DIR}/PaSinkStream.cpp ${PROJECT_SOURCE_DIR}/PaSinkStream.h ${PROJECT_SOURCE_DIR}/PaSourceStream.cpp ${PROJECT_SOURCE_DIR}/PaSourceStream.h ${PROJECT_SOURCE_DIR}/PaSinkInputStream.cpp ${PROJECT_SOURCE_DIR}/PaSinkInputStream.h ${PROJECT_SOURCE_DIR}/PaSourceOutputStream.cpp ${PROJECT_SOURCE_DIR}/PaSourceOutputStream.h ${PROJECT_SOURCE_DIR}/PulseaudioManager.cpp ${PROJECT_SOURCE_DIR}/PulseaudioManager.h ${PROJECT_SOURCE_DIR}/PipewireAlsaNode.cpp ${PROJECT_SOURCE_DIR}/PipewireAlsaNode.h ${PROJECT_SOURCE_DIR}/PipewireClient.cpp ${PROJECT_SOURCE_DIR}/PipewireClient.h ${PROJECT_SOURCE_DIR}/PipewireDevice.cpp ${PROJECT_SOURCE_DIR}/PipewireDevice.h ${PROJECT_SOURCE_DIR}/PipewireLink.cpp ${PROJECT_SOURCE_DIR}/PipewireLink.h ${PROJECT_SOURCE_DIR}/PipewireMetadata.cpp ${PROJECT_SOURCE_DIR}/PipewireMetadata.h ${PROJECT_SOURCE_DIR}/PipewireNode.cpp ${PROJECT_SOURCE_DIR}/PipewireNode.h ${PROJECT_SOURCE_DIR}/PipewireNodeListmodel.cpp ${PROJECT_SOURCE_DIR}/PipewireNodeListmodel.h ${PROJECT_SOURCE_DIR}/PipewirePort.cpp ${PROJECT_SOURCE_DIR}/PipewirePort.h ${PROJECT_SOURCE_DIR}/PipewireProfiler.cpp ${PROJECT_SOURCE_DIR}/PipewireProfiler.h ${PROJECT_SOURCE_DIR}/PipewireSettings.cpp ${PROJECT_SOURCE_DIR}/PipewireSettings.h ${PROJECT_SOURCE_DIR}/PipewireManager1.cpp ${PROJECT_SOURCE_DIR}/PipewireManager1.h ${PROJECT_SOURCE_DIR}/PipewireManager.cpp ${PROJECT_SOURCE_DIR}/PipewireManager.h ${PROJECT_SOURCE_DIR}/utils.cpp ${PROJECT_SOURCE_DIR}/utils.h ${PROJECT_SOURCE_DIR}/SystemdService.cpp ${PROJECT_SOURCE_DIR}/SystemdService.h ${PROJECT_SOURCE_DIR}/DbusServer.h ${PROJECT_SOURCE_DIR}/DbusServer.cpp ${PROJECT_SOURCE_DIR}/AudioMethod.h ${PROJECT_SOURCE_DIR}/AudioMethod.cpp ${PROJECT_SOURCE_DIR}/AudioContext.h ${PROJECT_SOURCE_DIR}/AudioContext.cpp ${PROJECT_SOURCE_DIR}/MediaControler.cpp ${PROJECT_SOURCE_DIR}/MediaControler.h ${PROJECT_SOURCE_DIR}/MediaAutoPauseModule.cpp ${PROJECT_SOURCE_DIR}/MediaAutoPauseModule.h ${PROJECT_SOURCE_DIR}/MultiAudioCombineJson.cpp ${PROJECT_SOURCE_DIR}/MultiAudioCombineJson.h ${PROJECT_SOURCE_DIR}/MultiAudioCombineModule.cpp ${PROJECT_SOURCE_DIR}/MultiAudioCombineModule.h ${PROJECT_SOURCE_DIR}/SingleApplication.cpp ${PROJECT_SOURCE_DIR}/SingleApplication.h ${PROJECT_SOURCE_DIR}/JackDetect.cpp ${PROJECT_SOURCE_DIR}/JackDetect.h ${PROJECT_SOURCE_DIR}/simple_logger/DateTime.h ${PROJECT_SOURCE_DIR}/simple_logger/Formatter.h ${PROJECT_SOURCE_DIR}/simple_logger/Logger.h ${PROJECT_SOURCE_DIR}/simple_logger/DateTime.cpp ${PROJECT_SOURCE_DIR}/simple_logger/Formatter.cpp ${PROJECT_SOURCE_DIR}/simple_logger/Logger.cpp) include_directories(${PROJECT_SOURCE_DIR}/include) include_directories(${PROJECT_SOURCE_DIR}/simple_logger) add_executable(ukui-audio-service main.cpp ${SRC}) target_link_libraries(${PROJECT_NAME} ${GLIB2_LIBRARIES} ${ALSA_LIBRARIES} ${PA_LIBRARIES} ${PW_LIBRARIES} ${DBUS_LIBRARIES} ${PUGIXML_LIBRARIES} -lpthread ) set(CMAKE_INSTALL_LIBDIR /usr/bin) install(TARGETS ${PROJECT_NAME} DESTINATION "${CMAKE_INSTALL_LIBDIR}") ukui-volume-control/backend/PipewireMetadata.cpp0000664000175000017500000000571615171074712020776 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "PipewireMetadata.h" #include "PipewireManager1.h" namespace UkuiAudioFramwork { #define PIPEWIRE_CAST(x) PipewireMetadata* _this = static_cast(x); //----------------------------------------------------------------------------- static int metadata_property(void *data, uint32_t id, const char* key, const char* type, const char* value) { PIPEWIRE_CAST(data); return _this->_metadata_property(id, key, type, value); } int PipewireMetadata::_metadata_property(uint32_t id, const char* key, const char* type, const char* value) { // if (key == nullptr) { // emit onAllKeysRemoved(id); // } else if (value == nullptr) { // emit onKeyRemoved(id, key); // } else { // emit onKeyUpdated(id, key, type, value); // } return 0; } static const pw_metadata_events metadata_events { .version = PW_VERSION_METADATA_EVENTS, .property = metadata_property, }; //----------------------------------------------------------------------------- PipewireMetadata::PipewireMetadata(Pipewire *parent, uint32_t id, const spa_dict* props) : pipewire(parent) { metadata = static_cast( pw_registry_bind(parent->registry, id, PW_TYPE_INTERFACE_Metadata, PW_VERSION_METADATA, 0)); pw_metadata_add_listener(metadata, &metadata_listener, &metadata_events, this); parent->resync(); } PipewireMetadata::~PipewireMetadata() { spa_hook_remove(&metadata_listener); if (metadata != nullptr) { pw_proxy_destroy((struct pw_proxy*) metadata); } } //----------------------------------------------------------------------------- void PipewireMetadata::setProperty(const char *key, const char *value) { pw_metadata_set_property(metadata, 0, key, nullptr, value); pipewire->round_trip(); } } ukui-volume-control/backend/PipewireLink.h0000664000175000017500000000366415171074712017620 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 . */ #pragma once #include #include #include namespace UkuiAudioFramwork { class Pipewire; /** * @todo write docs */ class PipewireLink { void idChanged(); void inputPortChanged(); void outputPortChanged(); void inputNodeChanged(); void outputNodeChanged(); protected: Pipewire* pipewire = nullptr; const uint32_t m_id; const uint32_t m_input_port; const uint32_t m_output_port; const uint32_t m_input_node; const uint32_t m_output_node; pw_link* link = nullptr; public: /** * Default constructor */ PipewireLink(Pipewire* parent, uint32_t id, const spa_dict* props); /** * Destructor */ ~PipewireLink(); [[nodiscard]] int id() const { return m_id; } [[nodiscard]] uint32_t id_u32() const { return m_id; } [[nodiscard]] uint32_t inputPort() const { return m_input_port; } [[nodiscard]] uint32_t outputPort() const { return m_output_port; } [[nodiscard]] uint32_t inputNode() const { return m_input_node; } [[nodiscard]] uint32_t outputNode() const { return m_output_node; } }; std::ostream& operator<< (std::ostream& out, const PipewireLink& link); } ukui-volume-control/backend/AudioManager.h0000664000175000017500000001010015171074712017531 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 AUDIOMANAGER_H #define AUDIOMANAGER_H #include #include #include "Util.h" #include "PaDataType.h" #include "RestoreVolumeJson.h" namespace UkuiAudioFramwork { struct DeviceInfo; class AudioManager { public: AudioManager(); virtual ~AudioManager() = default; public: // Method std::string getBackend() const; void setBackend(BackendType); void waitBluezReady(int); void setBluezReadyStatus(bool); bool getBluezReady() const; public: virtual void init() = 0; virtual bool connect() = 0; // Interface virtual void loadModule(const std::string&, const std::string&) = 0; virtual void unloadModule(const std::string&) = 0; virtual void setDetailVolume(const NodeType&, const std::string&, const std::string&, int volume) = 0; virtual int getDetailVolume(const NodeType&, const std::string&, const std::string&) const = 0; virtual void setVolume(const NodeType&, int, int) = 0; virtual int getVolume(const NodeType&, int) const = 0; virtual void setBalance(const NodeType&, double) = 0; virtual double getBalance(const NodeType&) const = 0; virtual void setDetailMute(const NodeType&, const std::string&, const std::string&, bool) = 0; virtual bool getDetailMute(const NodeType&, const std::string&, const std::string&) const = 0; virtual void setMute(const NodeType&, int, bool) = 0; virtual bool getMute(const NodeType&, int = -1) const = 0; virtual DefaultDeviceInfo getDefaultDevice(const NodeType&, int) const = 0; virtual void setDefaultDevice(const NodeType&, int, const std::string&, const std::string&) = 0; virtual std::vector getDeviceList(const NodeType&) const = 0; virtual std::list getSinkList() const = 0; virtual std::list getSourceList() const = 0; virtual std::list> getAvailablePortList(const NodeType&) const = 0; virtual std::list getSinkInputList() const = 0; virtual std::list getSourceOutputList() const = 0; virtual std::list getStreamMediaList() const = 0; virtual std::list getModuleList() = 0; virtual void setEnabled(std::shared_ptr, const std::string&, const std::string&, bool) = 0; virtual bool isEnabled(const std::string&, const std::string&) const = 0; virtual bool isLoaded(const ModuleType&) const = 0; virtual bool isValidDevice(const NodeType&) const = 0; // signal virtual void deviceChanged(const NodeType&, const std::string&, const std::string&) = 0; virtual void portChanged(const NodeType&, const std::string&) = 0; virtual void cardRemoved(const std::string&) = 0; virtual void deviceAdjust(const NodeType&) = 0; virtual void addStream(int, int, const std::string&, const std::string&) = 0; virtual void removeStream(int) = 0; virtual void addStream(int, int, int, bool, const std::string&, const std::string&) = 0; virtual void removeStream(int, int) = 0; private: bool checkAudioService(const std::string&); private: BackendType m_type = BackendType::PULSEAUDIO; bool m_bBlueReady = false; mutable std::mutex m_blueMtx; mutable std::condition_variable m_blueCond; }; } #endif // AUDIOMANAGER_H ukui-volume-control/backend/media-session/0000775000175000017500000000000015171074712017574 5ustar fengfengukui-volume-control/backend/media-session/alsaproperties.h0000664000175000017500000000344115171074712023004 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 . */ #pragma once //#include //#include #include class PipewireClient; class AlsaProperties /*: public QObject*/ { // Q_OBJECT // Q_PROPERTY(bool batchDisabled READ batchDisabled WRITE setBatchDisabled NOTIFY batchDisabledChanged) // Q_PROPERTY(int periodSize READ periodSize WRITE setPeriodSize NOTIFY periodSizeChanged) //signals: void batchDisabledChanged(bool); void periodSizeChanged(int); private: std::string globalConf; std::string userConf; bool _batchDisabled = false; int _periodSize = 1024; public: explicit AlsaProperties(PipewireClient *client/*, QObject *parent = nullptr*/); virtual ~AlsaProperties() = default; [[nodiscard]] bool batchDisabled() const { return _batchDisabled; } [[nodiscard]] int periodSize() const { return _periodSize; } void setBatchDisabled(bool disabled); void setPeriodSize(int newPeriod); private: void readGlobalConf(); void readUserConf(); void readConf(const std::string& filename); void writeUserConf() const; }; ukui-volume-control/backend/media-session/alsaproperties.cpp0000664000175000017500000001702015171074677023347 0ustar fengfeng/* * This file is part of the pipecontrol project. * Copyright (c) 2022 Matteo De Carlo. * * 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, version 3. * * 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 "alsaproperties.h" #include "src/pw/qpipewireclient.h" #include #include #include #include #include #include #include #include bool is_valid_file(const std::string &path) { QFile file(path); if (!file.exists()) return false; file.open(QIODevice::ReadOnly | QIODevice::Text); return file.isReadable(); } AlsaProperties::AlsaProperties(PipewireClient *client/*, QObject *parent*/) // : QObject(parent) { std::string config_prefix = client->has_property("config.prefix") ? client->property("config.prefix").toStdString() : "media-session.d"; const char *home_folder = std::getenv("HOME"); if (home_folder == nullptr) { home_folder = ""; } std::ostringstream global_conf_filename; global_conf_filename << "/usr/share/pipewire/" << config_prefix << "/alsa-monitor.conf"; std::ostringstream user_conf_filename; user_conf_filename << home_folder << "/.config/pipewire/" << config_prefix << "/alsa-monitor.conf"; this->globalConf = global_conf_filename.str().c_str(); this->userConf = user_conf_filename.str().c_str(); bool has_global = is_valid_file(globalConf); bool has_user = is_valid_file(userConf); if (has_user) { this->readUserConf(); } else if (has_global) { this->readGlobalConf(); } else { throw std::runtime_error("Could not find pipewire alsa monitor config file!"); } } void AlsaProperties::setBatchDisabled(bool disabled) { if (_batchDisabled == disabled) return; _batchDisabled = disabled; writeUserConf(); // emit batchDisabledChanged(_batchDisabled); } void AlsaProperties::setPeriodSize(int newPeriod) { if (_periodSize == newPeriod) return; _periodSize = newPeriod; writeUserConf(); // emit periodSizeChanged(_periodSize); } void AlsaProperties::readGlobalConf() { readConf(globalConf); } void AlsaProperties::readUserConf() { readConf(userConf); } template T _parse(const std::string&/*value*/) { throw std::runtime_error("Unsupported conversion"); } template<> int _parse(const std::string& value) { return value.toInt(); } template<> bool _parse(const std::string& value) { const QString lower_case_value = value.toLower(); if (lower_case_value == "true") { return true; } else if (lower_case_value == "false") { return false; } else { throw std::runtime_error((QString("Could not parse bool type from: ") + value).toStdString()); } } template std::string _tostring(const T /*value*/) { throw std::runtime_error("diocane"); } template<> std::string _tostring(const int value) { return QString::number(value); } template<> std::string _tostring(const bool value) { if (value) { return "true"; } else { return "false"; } } template bool parse_line(const std::string&line, const std::string& target, T &value) { QRegularExpression re(QString("(#*)\\s*") + target + "\\s*=\\s*([\\d|\\w]+)"); QRegularExpressionMatch match = re.match(line); if(!match.hasMatch()) { throw std::runtime_error((QString("Could not find ") + target).toStdString()); } if (!match.captured(1).isEmpty()) { return false; } value = _parse(match.captured(2)); return true; } template void parse_and_change_line(std::string &line, const std::string &target, T value) { QRegularExpression re(QString("(#*)\\s*") + target + "\\s*=\\s*([\\d|\\w]+)"); QRegularExpressionMatch match = re.match(line); if(!match.hasMatch()) { throw std::runtime_error((QString("Could not find ") + target).toStdString()); } if (!match.captured(1).isEmpty()) { line.remove(match.capturedStart(1), match.capturedLength(1)); match = re.match(line); } line.remove(match.capturedStart(2), match.capturedLength(2)); line.insert(match.capturedStart(2), _tostring(value)); return; } void AlsaProperties::readConf(const std::string& filename) { QFile conf(filename); if (!conf.open(QIODevice::ReadOnly | QIODevice::Text)) { std::ostringstream error_message; error_message << "Could not open configuration file " << filename.toStdString(); std::cerr << error_message.str() << std::endl; throw std::runtime_error(error_message.str()); } while (!conf.atEnd()) { std::string line = conf.readLine().trimmed(); // Skip comments if (line.startsWith('#')) continue; if (line.contains("api.alsa.disable-batch")) { bool found = parse_line(line, "api.alsa.disable-batch", _batchDisabled); if (found) emit batchDisabledChanged(_batchDisabled); } else if (line.contains("api.alsa.period-size")) { bool found = parse_line(line, "api.alsa.period-size", _periodSize); if (found) emit periodSizeChanged(_periodSize); } } } void AlsaProperties::writeUserConf() const { QFileInfo confInfo(userConf); QDir folder = QDir(confInfo.absolutePath()); std::cout << "Creating user conf folder: " << folder.absolutePath().toStdString() << std::endl; // This will return true even if the folder already exists. if (!folder.mkpath(".")) { throw std::runtime_error("Could not create user conf folder"); } // Read file QList lines; { QFile userConfF(userConf); QFile globalConfF(globalConf); QFile *inputFile = &userConfF; if (!userConfF.exists()) { inputFile = &globalConfF; } if (!inputFile->open(QIODevice::ReadOnly | QIODevice::Text)) { throw std::runtime_error("Could not write user conf file"); } while (!inputFile->atEnd()) { QString line = inputFile->readLine(); QString trimmed = line.trimmed(); if (trimmed.contains("api.alsa.disable-batch")) { parse_and_change_line(line, "api.alsa.disable-batch", this->_batchDisabled); } else if (trimmed.contains("api.alsa.period-size")) { parse_and_change_line(line, "api.alsa.period-size", this->_periodSize); } lines.append(line); } inputFile->close(); } std::cout << "Writing user conf file: " << folder.absolutePath().toStdString() << std::endl; { // Write with lines changed QFile outFile(userConf); if (!outFile.open(QIODevice::WriteOnly | QIODevice::Text)) { throw std::runtime_error("Could not write user conf file"); } QTextStream out(&outFile); for(QString &line : lines) { out << line; } outFile.close(); } } ukui-volume-control/backend/main.cpp0000664000175000017500000000244115171074712016465 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 //#include #include "AudioContext.h" #include "SingleApplication.h" #include "VolumeControl.h" int main() { GMainLoop* mainloop; GMainContext* context = nullptr; mainloop = g_main_loop_new(context, FALSE); SingleApplication app("ukui-audio-service"); if (app.isAlreadyRuning()) { std::cerr << "ukui-audio-service is already running. Exiting." << std::endl; return 1; } UkuiAudioFramwork::VolumeControl::getInstance().init(); g_main_loop_run(mainloop); return 0; } ukui-volume-control/backend/PipewirePort.cpp0000664000175000017500000001020115171074712020163 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "PipewirePort.h" #include #include #include "PipewireManager1.h" namespace UkuiAudioFramwork { #define PIPEWIRE_CAST(x) PipewirePort* _this = static_cast(x); static void static_port_info(void *data, const struct pw_port_info *info) { PIPEWIRE_CAST(data); _this->_port_info(info); } static void static_port_param(void *data, int seq, uint32_t id, uint32_t index, uint32_t next, const struct spa_pod *param) { PIPEWIRE_CAST(data); _this->_port_param(seq, id, index, next, param); } static const pw_port_events port_events { .version = PW_VERSION_PORT_EVENTS, .info = static_port_info, .param = static_port_param, }; PipewirePort::PipewirePort(Pipewire* parent, uint32_t id, const spa_dict* props) : pipewire(parent) , m_id(id) { port = static_cast( pw_registry_bind(pipewire->registry, id, PW_TYPE_INTERFACE_Port, PW_VERSION_CLIENT, 0)); if (port == nullptr) { throw std::runtime_error("Error creating port proxy"); } pw_port_add_listener(port, &port_listener, &port_events, this); std::string portName = spa_dict_lookup(props, PW_KEY_PORT_PHYSICAL) ? spa_dict_lookup(props, PW_KEY_PORT_PHYSICAL) : ""; std::cout << "Adding port (" << id << ") with props:" << "port。name: " << portName << std::endl; const struct spa_dict_item *item; spa_dict_for_each(item, props) { std::cout << '\t' << item->key << ":" << item->value << std::endl; } } PipewirePort::~PipewirePort() { spa_hook_remove(&port_listener); if (port != nullptr) { pw_proxy_destroy((struct pw_proxy*) port); } } uint32_t PipewirePort::id() const { return m_id; } void PipewirePort::set_id(uint32_t id) { if (m_id == id) { return; } m_id = id; // emit idChanged(m_id); } void PipewirePort::_port_info(const struct pw_port_info *info) { std::cout << "Port info (" << this->m_id << "):" << std::endl; std::cout << "\t change_mask:" << info->change_mask << std::endl; std::cout << "\t direction:" << info->direction << std::endl; std::cout << "\t id:" << info->id << std::endl; std::cout << "\t n_params:" << info->n_params << std::endl; for(uint32_t i=0; in_params; i++) { const spa_param_info* param = info->params+i; std::cout << "\t params("<id << std::endl; std::cout << "\t\tflags:" << param->flags << std::endl; std::cout << "\t\tuser:" << param->user << std::endl; } std::cout << "\t params_id:" << info->params->id << std::endl; std::cout << "\t params_flags:" << info->params->flags << std::endl; std::cout << "\t params_user:" << info->params->user << std::endl; std::cout << "\t props:" << info->props << std::endl; const struct spa_dict_item *item; spa_dict_for_each(item, info->props) { std::cout << "\t\t" << item->key << ":" << item->value << std::endl; } } void PipewirePort::_port_param(int seq, uint32_t id, uint32_t index, uint32_t next, const struct spa_pod *param) { std::cout << "Port param (" << this->m_id << "):" << std::endl; std::cout << "\t seq:" << seq << std::endl; std::cout << "\t id:" << id; std::cout << "\t index:" << index; std::cout << "\t next:" << next; std::cout << "\t param:" << param; } } ukui-volume-control/backend/PipewireMetadata.h0000664000175000017500000000316315171074712020435 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 . */ #pragma once #include #include //#include namespace UkuiAudioFramwork { class Pipewire; class PipewireMetadata { private: void onAllKeysRemoved(uint32_t id); void onKeyRemoved(uint32_t id, const char* key); void onKeyUpdated(uint32_t id, const char* key, const char* type, const char* value); protected: Pipewire *pipewire = nullptr; struct pw_metadata *metadata = nullptr; struct spa_hook metadata_listener; public: explicit PipewireMetadata(Pipewire *parent, uint32_t id, const spa_dict* props); virtual ~PipewireMetadata(); void setProperty(const char *key, const char *value); void clear(); int _metadata_property(uint32_t id, const char* key, const char* type, const char* value); }; } ukui-volume-control/backend/PaSinkStream.cpp0000664000175000017500000004745415171074712020117 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "PaSinkStream.h" #include #include #include "AudioMethod.h" #include "PaSinkInputStream.h" namespace UkuiAudioFramwork { std::string PaSinkStream::toString() const { std::string vol = "["; for (int i = 0; i < volume.channels; ++i) { vol += std::to_string(volume.values[i]); vol += ", "; } vol += "]"; std::string activePortName = (active_port) ? active_port->name : ""; return FORMAT("name={}, index={}, m_bIsCombine={} description={}, card={}, n_ports={}, active_port={}, " "volume={}, mute={}, state={}, monitor_source={}, monitor_source_name={}", name, index, m_bIsCombine, description, card, n_ports, activePortName, vol, mute, state, monitor_source, monitor_source_name); } NodeType PaSinkStream::getNodeType() const { return NodeType::OUTPUT; } std::shared_ptr PaSinkStream::getActivePort() const { return active_port; } std::string PaSinkStream::getActivePortName() const { return (this && active_port) ? active_port->name : ""; } std::string PaSinkStream::getActivePortDescription() const { return (this && active_port) ? active_port->description : ""; } std::string PaSinkStream::getCardName() const { return cardName; } std::string PaSinkStream::getDeviceClass() const { return m_deviceClass; } void PaSinkStream::setMasterDevice(const std::string& master) { AUDIO_DEBUG("Set {} master device to {}.", name, master); m_masterDevice = master; } std::string PaSinkStream::getMasterDevice() const { AUDIO_DEBUG("Get {} master device {}.", name, m_masterDevice); return m_masterDevice; } void PaSinkStream::copy(const pa_sink_info& other) { auto it = std::find_if(m_info.cardInfoList.begin(), m_info.cardInfoList.end(), [other](const std::shared_ptr c) { return c->card.index == other.card; }); if (it != m_info.cardInfoList.end()) { cardName = (*it)->card.name; } else { cardName = ""; } if (PA_INVALID_INDEX != index) { if (!pa_cvolume_equal(&volume, &other.volume) && index == m_info.defaultSink->getIndex()) { volumeChanged(paCvolumeToValue(other.volume)); } if (mute != other.mute && index == other.index) { muteChanged(!mute); } if (active_port && other.active_port && g_strcmp0(active_port->name.c_str(), other.active_port->name)) { portChanged(other.active_port->name); } active_port = other.active_port ? std::make_shared(cardName, *other.active_port) : nullptr; } name = other.name; index = other.index; description = other.description; sample_spec = other.sample_spec; channel_map = other.channel_map; owner_module = other.owner_module; volume = other.volume; mute = other.mute; monitor_source = other.monitor_source; monitor_source_name = other.monitor_source_name; latency = other.latency; driver = other.driver; flags = other.flags; proplist = other.proplist; configured_latency = other.configured_latency; base_volume = other.base_volume; state = other.state; n_volume_steps = other.n_volume_steps; card = other.card; n_ports = other.n_ports; n_formats = other.n_formats; formats = other.formats; auto deviceClass = pa_proplist_gets(proplist, PA_PROP_DEVICE_CLASS); m_deviceClass = deviceClass ? deviceClass : ""; if (std::string::npos != name.find("mono")) { auto it = std::find_if(m_info.sinkInputList.begin(), m_info.sinkInputList.end(), [](const std::shared_ptr& s) { auto driver = std::dynamic_pointer_cast(s)->getDriver(); return driver == "module-remap-sink.c"; }); if (it != m_info.sinkInputList.end()) { auto sinkIdx = std::dynamic_pointer_cast(*it)->getSink(); for (const auto& c : m_info.cardInfoList) { auto sink = std::find_if(c->sinks.begin(), c->sinks.end(), [sinkIdx](const std::shared_ptr& s) { return sinkIdx == s->getIndex(); }); if (sink != c->sinks.end()) { setMasterDevice((*sink)->getName()); } } } } else { auto masterDevice = pa_proplist_gets(proplist, PA_PROP_DEVICE_MASTER_DEVICE); m_masterDevice = masterDevice ? masterDevice : ""; } ports.clear(); for (int i = 0; i < n_ports; ++i) { ports.push_back(std::make_shared(cardName, *other.ports[i])); } active_port = other.active_port ? std::make_shared(cardName, *other.active_port) : nullptr; } std::vector> PaSinkStream::getPorts() const { return ports; } bool PaSinkStream::getCombineStatus() const { std::lock_guard lock(m_mutex); return m_bIsCombine; } void PaSinkStream::setCombineStatus(bool status) { std::lock_guard lock(m_mutex); m_bIsCombine = status; } uint32_t PaSinkStream::getIndex() const { return index; } std::string PaSinkStream::getName() const { return name; } pa_proplist* PaSinkStream::getProplist() const { return proplist; } uint32_t PaSinkStream::getCard() const { return card; } void PaSinkStream::setVolume(int value) { auto v = volume; auto balance = getBalance(); for (int i = 0; i < v.channels; ++i) { v.values[i] = value; } pa_cvolume_set_balance(&v, &channel_map, balance); if (pa_cvolume_equal(&v, &volume)) return; AUDIO_DEBUG("Set {} volume from {} changed to {}.", name, paCvolumeToPaValue(volume), value); pa_context_set_sink_volume_by_index(m_pContext, index, &v, nullptr, nullptr); } const pa_cvolume PaSinkStream::getVolume() const { return volume; } void PaSinkStream::setBalance(double v) { if (channel_map.channels == 1) { AUDIO_ERROR("sink {} is not support set balance.", name); return; } if (v == getBalance()) return; pa_cvolume_set_balance(&volume, &channel_map, v); pa_context_set_sink_volume_by_index(m_pContext, index, &volume, nullptr, nullptr); } double PaSinkStream::getBalance() const { return pa_cvolume_get_balance(&volume, &channel_map); } void PaSinkStream::setMute(bool mute) { if (mute == this->mute) return; pa_context_set_sink_mute_by_index(m_pContext, index, mute, nullptr, nullptr); } bool PaSinkStream::getMute() const { return mute; } DefaultDeviceInfo PaSinkStream::getDefaultDevice() const { DefaultDeviceInfo info; info.idx = m_info.defaultSink->index; info.name = m_info.defaultSink->name; if ("filter" == m_info.defaultSink->getDeviceClass()) { if (std::string::npos != info.name.find("combined")) { info.activePortName = "output-combine"; info.cardName = "combine-device"; } else { std::string masterDevice = ""; for (const auto& c : m_info.cardInfoList) { auto it = std::find_if(c->sinks.begin(), c->sinks.end(), [this](const std::shared_ptr& s) { return m_info.defaultSink->getName() == s->getName(); }); if (it != c->sinks.end()) { auto sink = std::dynamic_pointer_cast(*it); masterDevice = sink->getMasterDevice(); AUDIO_DEBUG("Get {} master device: {}.", m_info.defaultSink->getName(), masterDevice); break; } } // 存在单声道音频所在的card和Master不在同一个card的情况因此需要重新查找,后续优化 for (const auto& c : m_info.cardInfoList) { auto at = std::find_if(c->sinks.begin(), c->sinks.end(), [masterDevice](const std::shared_ptr& s) { return masterDevice == s->getName(); }); if (at == c->sinks.end()) { AUDIO_WARN("Can not found {} master device {} on card {}.", m_info.defaultSink->getName(), masterDevice, c->card.name); continue; } else { info.cardName = c->card.name; auto sink = std::dynamic_pointer_cast(*at); if (sink) { info.activePortName = sink->getActivePortName(); info.activePortLabel = sink->getActivePortDescription(); AUDIO_DEBUG("Found {} master device {}, card: {}, port: {}, portDesc: {}.", sink->getName(), masterDevice, info.cardName, info.activePortName, info.activePortLabel); } break; } } } } else { info.cardName = cardName; info.activePortName = m_info.defaultSink->active_port ? m_info.defaultSink->active_port->name : ""; info.activePortLabel = m_info.defaultSink->active_port ? m_info.defaultSink->active_port->description : ""; } return info; } /*** * 1.如果需要切换的端口和当前默认的输出设备在同一个sink上,则只需要切换端口即可(pa_context_set_sink_port_by_index) * 2.需要切换的端口在不同的配置文件下,则需要切换一个高优先级的配置文件后,在设置默认的输出设备(pa_context_set_card_profile, pa_context_set_default_sink) * 3.需要切换的端口在相同的配置文件下,则只需要设置默认输出设备即可(pa_context_set_default_sink, 例如:HiFi) */ void PaSinkStream:: setDefaultDevice(const std::string& name, const std::string& dev) { auto found = false; auto setPort = false; auto setProfile = false; std::string profileName = ""; std::string sinkName = ""; auto sinkIdx = PA_INVALID_INDEX, cardIdx = PA_INVALID_INDEX; // 直接设置组合输出设备,后续存在多个组合设备时需要优化 if (std::string::npos != name.find("output-combine")) { pa_context_set_default_sink(m_pContext, "combined", nullptr, nullptr); return; } for (const auto& c : m_info.cardInfoList) { if (strcmp(dev.c_str(), c->card.name.c_str())) continue; cardIdx = c->card.index; for (const auto& p : c->card.ports) { if (PA_DIRECTION_INPUT == p->direction) continue; if (strcmp(name.c_str(), p->name.c_str())) continue; // 如果sink列表的可用端口存在需要切换的端口,则只需要进行setSinkPort操作即可 auto it = std::find_if(c->sinks.begin(), c->sinks.end(), [name](const std::shared_ptr& s) { auto ports = std::dynamic_pointer_cast(s)->getPorts(); auto at = std::find_if(ports.begin(), ports.end(), [name](const std::shared_ptr& p) { return p->name == name; }); return (at != ports.end()); }); if (it != c->sinks.end()) { setPort = true; sinkIdx = (*it)->getIndex(); pa_context_set_default_sink(m_pContext, (*it)->getName().c_str(), nullptr, nullptr); goto success; } else { auto it = std::find_if(c->sinks.begin(), c->sinks.end(), [p](const std::shared_ptr s) { return std::dynamic_pointer_cast(s)->getActivePortName() == p->name; }); if (it != c->sinks.end()) { sinkName = (*it)->getName(); } else { int pri = 0; for (const auto& profile : p->profiles2) { if (0 == profile->available) continue; if (pri < profile->priority) { setProfile = true; pri = profile->priority; profileName = profile->name; } } } } } } if (!found) AUDIO_DEBUG("Card: {} port: {} not found", dev, name); success: AUDIO_DEBUG("Set default device, setProfile: {}, setPort: {}, sinkName: {}.", setProfile, setPort, sinkName); if (setProfile) { // 由于pa_context_set_card_profile_by_index接口是异步的,使用detach会出现线程实际还没执行完就释放资源, // 会出现配置文件没有切换过去的问题,因此修改成join AUDIO_DEBUG("Set card: {} profile to {}.", dev, profileName); std::thread([cardIdx, profileName, this] () { // 设置配置文件需要重设m_operation为false防止设置完配置文件时还没有更新sinks/sources就去设置默认的sink/source std::unique_lock lock(m_info.m_mtx); m_info.m_operation = false; pa_operation* o = pa_context_set_card_profile_by_index(m_pContext, cardIdx, profileName.c_str(), nullptr, this); pa_operation_unref(o); }).join(); // 设置default sink线程,等待主线程cardInfoList更新完毕之后在去设置default sink std::thread([cardIdx, name, this] () { std::unique_lock lock(m_info.m_mtx); if (!m_info.m_operation) m_info.m_cond.wait_for(lock, std::chrono::milliseconds(3000)); if (m_info.m_operation) { m_info.m_operation = false; for (const auto& c: m_info.cardInfoList) { if (c->card.index != cardIdx) continue; for (const auto& s : c->sinks) { for (const auto& p : std::dynamic_pointer_cast(s)->ports) { if (p->name == name) { AUDIO_DEBUG("Set deault sink to {} port: {}.", s->getName(), name); pa_context_set_sink_port_by_name(m_pContext, s->getName().c_str(), name.c_str(), nullptr, nullptr); pa_context_set_default_sink(m_pContext, s->getName().c_str(), nullptr, nullptr); return; } } } } } }).detach(); } if (setPort) { AUDIO_DEBUG("Set sink port to {}", name); pa_context_set_sink_port_by_index(m_pContext, sinkIdx, name.c_str(), nullptr, nullptr); } if ("" != sinkName) { AUDIO_DEBUG("Set default sink to {}", sinkName); pa_context_set_default_sink(m_pContext, sinkName.c_str(), nullptr, nullptr); } } std::vector PaSinkStream::getDeviceList() const { } std::list> PaSinkStream::getAvailablePortList() const { std::list> info; for (const auto& c : m_info.cardInfoList) { for (const auto& d : c->card.deviceList) { if (PA_DIRECTION_OUTPUT == d->direction && PA_PORT_AVAILABLE_NO != d->available) { info.push_back(d); } } } return info; } void PaSinkStream::volumeChanged(int volume) { AudioMethod::getInstance().volumeChanged(NodeType::OUTPUT, index, volume); } void PaSinkStream::muteChanged(bool mute) { AudioMethod::getInstance().muteChanged(NodeType::OUTPUT, index, mute); } void PaSinkStream::portChanged(const std::string& name) { AudioMethod::getInstance().portChanged(NodeType::OUTPUT, name); } void PaSinkStream::deviceChanged(bool autoPause) { std::string portName, deviceName; if ("filter" == m_deviceClass) { if (name == "combined") { portName = "output-combine"; deviceName = "combine-device"; } else { for (const auto& c : m_info.cardInfoList) { auto it = std::find_if(c->sinks.begin(), c->sinks.end(), [this](const std::shared_ptr& s) { return m_masterDevice == s->getName(); }); if (it != c->sinks.end()) { deviceName = c->card.name; auto sink = std::dynamic_pointer_cast(*it); if (sink) { portName = sink->getActivePortName(); } break; } } } } else { portName = active_port ? active_port->name : ""; deviceName = cardName; } if (!AudioMethod::getInstance().getVolumeBoostStatus()) { int v = paCvolumeToPaValue(volume); if (v > PA_VOLUME_NORMAL) { AUDIO_DEBUG("Device changed. Volume-Increase is turned off, device: {} needs to be set volume: {} to 65536", name, v); AudioMethod::getInstance().setVolume(NodeType::OUTPUT, index, UKMEDIA_VOLUME_NORMAL); } } if ("filter" != m_deviceClass && AudioMethod::getInstance().isLoaded(ModuleType::MODULE_REMAP_SINK)) { auto it = std::find_if(m_info.sinkInputList.begin(), m_info.sinkInputList.end(), [](const std::shared_ptr& s) { auto driver = std::dynamic_pointer_cast(s)->getDriver(); return driver == "module-remap-sink.c"; }); if (it != m_info.sinkInputList.end()) { AUDIO_DEBUG("Device changed. module-remap-sink, portName: {} deviceName: {}. ", portName, deviceName); (*it)->setDefaultDevice(portName, deviceName); // 更新mono sink的master device // std::unique_lock lock(m_info.m_mtx); for (const auto& c : m_info.cardInfoList) { auto sink = std::find_if(c->sinks.begin(), c->sinks.end(), [](const std::shared_ptr& s) { return std::string::npos != s->getName().find("mono"); }); if (sink != c->sinks.end()) { auto masterDevice = m_info.defaultSink->getName(); #if 0 /* 暂时移除同步单声道音量的设置 // 同步default sink的音量和静音状态 (*sink)->setMute(m_info.defaultSink->getMute()); auto volume = m_info.defaultSink->getVolume(); auto value = paCvolumeToPaValue(volume); (*sink)->setVolume(value); */ #endif std::dynamic_pointer_cast(*sink)->setMasterDevice(masterDevice); break; } } pa_context_set_default_sink(m_pContext, "mono", nullptr, nullptr); } } AudioMethod::getInstance().deviceChanged(NodeType::OUTPUT, portName, deviceName, autoPause); } void PaSinkStream::setCardProfileCb(pa_context *ctx, int success, void *userdata) { // PaSinkStream* stream = static_cast(userdata); // if (!success) { // AUDIO_ERROR("Set card profile failed, error: {}.", pa_strerror(pa_context_errno(ctx))); // return; // } } } ukui-volume-control/backend/IBackend.h0000664000175000017500000000242715171074712016652 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 BACKEND_H #define BACKEND_H #include #include "AudioManager.h" namespace UkuiAudioFramwork { class IBackend { public: IBackend() = default; virtual ~IBackend() = default; public: virtual void init() = 0; virtual bool detect() = 0; // virtual auto getManager() -> std::unique_ptr = 0; private: IBackend(const IBackend&) = delete; IBackend(IBackend&&) = delete; void operator=(const IBackend&) = delete; void operator=(IBackend&&) = delete; }; } #endif // IBACKEND_H ukui-volume-control/backend/PipewireAlsaNode.cpp0000664000175000017500000000206115171074712020732 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 #include #include "PipewireAlsaNode.h" namespace UkuiAudioFramwork { PipewireAlsaNode::PipewireAlsaNode(Pipewire *parent, uint32_t id, const struct spa_dict *props) : PipewireNode(parent, id, props) { m_name = spa_dict_lookup(props, PW_KEY_NODE_NICK); } } ukui-volume-control/backend/IJson.h0000664000175000017500000000421415171074712016230 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 IJSON_H #define IJSON_H #include #include #include #include #include namespace UkuiAudioFramwork { template class IJson { public: IJson() = default; virtual ~IJson() = default; public: virtual bool isContain(const T&) const = 0; virtual void insert(const T&) = 0; protected: void loadJson() { std::ifstream file(m_fileName); if (file.is_open()) { /** Initializing nlohmann json without throwing exceptions */ m_json = nlohmann::json::parse(file, nullptr, false); /** Check again whether json is a valid json file to prevent abnormal * situations caused by the user's file status */ if (!m_json.is_object()) { m_json = nlohmann::json::object(); } } else { m_json = nlohmann::json::object(); } } void writeToJson() { std::filesystem::create_directories(std::filesystem::path(m_fileName).parent_path()); std::ofstream outFile(m_fileName); if (!outFile) { std::cerr << "Unable to open " << m_fileName << " for writing." << std::endl; return; } outFile << std::setw(4) << m_json << std::endl; } protected: mutable std::string m_fileName; mutable nlohmann::json m_json; }; } #endif // IJSON_H ukui-volume-control/backend/PipewireBackendFactory.cpp0000664000175000017500000000201215171074712022117 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "PipewireBackendFactory.h" #include "PipewireBackend.h" namespace UkuiAudioFramwork { PipewireBackendFactory::PipewireBackendFactory() { } std::shared_ptr PipewireBackendFactory::createBackend() { return std::make_shared(); } } ukui-volume-control/backend/RestoreVolumeJson.h0000664000175000017500000000441715171074712020660 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 RESTOREVOLUMEJSON_H #define RESTOREVOLUMEJSON_H #include #include "IJson.h" namespace UkuiAudioFramwork { #define RESTORE_VOLUME_JSON "/.config/ukui-volume-control/restore-volume.json" struct RestoreVolumeInfo { std::string cardName; std::string portName; uint32_t volume; uint32_t priority; double balance; bool enable; bool muted; }; using MemberVarPtr = std::variant< uint32_t RestoreVolumeInfo::*, double RestoreVolumeInfo::*, bool RestoreVolumeInfo::* >; using FieldMap = std::vector>; class RestoreVolumeJson : public IJson { public: RestoreVolumeJson(); ~RestoreVolumeJson() = default; public: virtual bool isContain(const RestoreVolumeInfo&) const override; virtual void insert(const RestoreVolumeInfo&) override; // template // std::optional getValue(const std::string&, const std::string&, const std::string&) const; std::optional getValue(const std::string&) const; bool getStatus(const std::string&, const std::string&, const std::string&) const; void setValue(const std::string&, bool); private: void initJson(); private: const FieldMap m_filedMap = { {"volume", &RestoreVolumeInfo::volume}, {"priority", &RestoreVolumeInfo::priority}, {"balance", &RestoreVolumeInfo::balance}, {"enable", &RestoreVolumeInfo::enable}, {"mute", &RestoreVolumeInfo::muted} }; }; } #endif // RESTOREVOLUMEJSON_H ukui-volume-control/backend/GlobalThemeSettings.h0000664000175000017500000000326015171074712021112 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 GLOBALTHEMESETTINGS_H #define GLOBALTHEMESETTINGS_H #include #include #include "ISetting.h" namespace UkuiAudioFramwork { #define GLOBAL_THEME_SCHEMA "org.ukui.globaltheme.settings" #define GLOBAL_THEME_PATH "/org/ukui/globaltheme/settings/" #define GLOBAL_THEME_NAME_KEY "global-theme-name" class GlobalThemeSettings : public ISetting { public: GlobalThemeSettings(); ~GlobalThemeSettings(); public: void initConnect(); virtual void initSettings() override; virtual GVariant* getValue(const char*) const override; virtual void setValue(const char*, GVariant*) override; virtual bool isContainKeys(const char*) const override; private: static void onSettingsChanged(GSettings*, const gchar*); private: GSettings* m_pSettings = nullptr; GSettingsSchema* m_pSchema = nullptr; GSettingsSchemaSource* m_pSource = nullptr; }; } #endif // GLOBALTHEMESETTINGS_H ukui-volume-control/backend/DeviceManagerModule.h0000664000175000017500000000336315171074712021052 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 DEVICEMANAGERMODULE_H #define DEVICEMANAGERMODULE_H #include "Util.h" #include "AudioManager.h" #include "RestoreVolumeJson.h" namespace UkuiAudioFramwork { class AudioManager; class DeviceManagerModule { public: static DeviceManagerModule& getInstance(); void setManager(std::shared_ptr); void setJson(std::shared_ptr); void setEnabled(const std::string&, const std::string&, bool); bool isEnabled(const std::string&, const std::string&) const; private: DeviceManagerModule() = default; DeviceManagerModule(const DeviceManagerModule&) = delete; DeviceManagerModule(DeviceManagerModule&&) = delete; DeviceManagerModule operator=(const DeviceManagerModule&) = delete; DeviceManagerModule operator=(DeviceManagerModule&&) = delete; virtual ~DeviceManagerModule() = default; private: std::shared_ptr m_pJson {}; std::shared_ptr m_pAudioManager {}; }; } #endif // DEVICEMANAGERMODULE_H ukui-volume-control/backend/SingleApplication.cpp0000664000175000017500000000251515171074712021150 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "SingleApplication.h" #include SingleApplication::SingleApplication(const std::string& name) : m_name(name) { m_lockFile = open(("/tmp/" + m_name + "-" + std::to_string(getuid())).c_str(), O_CREAT | O_RDWR, 0666); if (m_lockFile != -1 && flock(m_lockFile, LOCK_EX | LOCK_NB) == -1) { close(m_lockFile); m_lockFile = -1; } } SingleApplication::~SingleApplication() { if (m_lockFile != -1) { flock(m_lockFile, LOCK_UN); close(m_lockFile); } } bool SingleApplication::isAlreadyRuning() { return m_lockFile == -1; } ukui-volume-control/backend/VolumeControl.cpp0000664000175000017500000001014715171074712020353 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "VolumeControl.h" #include #include #include "CustomAudioVolumeJson.h" #include "JackDetect.h" namespace UkuiAudioFramwork { VolumeControl& VolumeControl::getInstance() { static VolumeControl instance; return instance; } void VolumeControl::init() { // 1. get backend // 2. created backend factory switch (m_type) { case BackendType::PIPEWIRE: { m_pBackendFactory = std::make_shared(); m_pManagerFactory = std::make_shared(); break; } case BackendType::PULSEAUDIO: { m_pBackendFactory = std::make_shared(); m_pManagerFactory = std::make_shared(); break; } case BackendType::ALSA: case BackendType::JACK: default: AUDIO_ERROR("Can not support {} backend, exit.", getBackendName(m_type)); return; } // 3. created backend m_pBackend = m_pBackendFactory->createBackend(); m_pAudioManager = m_pManagerFactory->createManager(); m_pAudioManager->init(); m_pAudioManager->connect(); AudioMethod::getInstance().setManager(m_pAudioManager); AudioMethod::getInstance().init(); auto moduleList = m_pAudioManager->getModuleList(); AUDIO_DEBUG("The size of the loaded module is {}.", moduleList.size()); std::thread([this, moduleList]() { for (const auto& [k, v] : m_settingsMap) { bool status = m_statusFunctions[v[0]](); if (!status) continue; auto it = std::find_if(moduleList.begin(), moduleList.end(), [v](const std::string& name) { return name == v[1]; }); if (it == moduleList.end()) { AudioMethod::getInstance().loadModule(v[1], v[2]); } else { AUDIO_DEBUG("Module {} is loaded.", v[1]); } } }).detach(); JackDetect monitor([](const std::string& card, const std::string& jack, AlsaJackType type, bool plugged) { AUDIO_DEBUG("Card: {} jack: {} plugged: {}.", card, jack, plugged); if (plugged) return; if (type == AlsaJackType::ALSA_JACK_FRONTMIC || type == AlsaJackType::ALSA_JACK_REARMIC || type == AlsaJackType::ALSA_JACK_LINEIN || type == AlsaJackType::ALSA_JACK_HEADPHONEMIC || type == AlsaJackType::ALSA_JACK_INPUTMIC) { auto list = AudioMethod::getInstance().getAvailablePortList(NodeType::INPUT); if (list.size() <= 1) { AUDIO_DEBUG("{} jack unplugin, unload module-echo-cancel.", jack); AudioMethod::getInstance().unloadModule("module-echo-cancel"); } } else if (type == AlsaJackType::ALSA_JACK_HEADPHONE || type == AlsaJackType::ALSA_JACK_HEADPHONE2 || type == AlsaJackType::ALSA_JACK_LINEOUT) { auto list = AudioMethod::getInstance().getAvailablePortList(NodeType::OUTPUT); if (list.size() <= 1) { AUDIO_DEBUG("{} jack unplugin, unload module-remap-sink.", jack); AudioMethod::getInstance().unloadModule("module-remap-sink"); } } }); monitor.start(); AudioMethod::getInstance().run(); } } ukui-volume-control/backend/ISetting.h0000664000175000017500000000246015171074712016735 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 ISETTING_H #define ISETTING_H #include namespace UkuiAudioFramwork { enum class SettingType { AUDIO_SETTING, GLOBALTHEME_SETTING, SESSIONMANAGER_SETTING, SOUDNTHEMEPLAYER_SETTING }; class ISetting { public: ISetting() = default; virtual ~ISetting() = default; public: virtual void initSettings() = 0; virtual GVariant* getValue(const char* key) const = 0; virtual void setValue(const char* key, GVariant* gvalue) = 0; virtual bool isContainKeys(const char* key) const = 0; }; } #endif // ISETTING_H ukui-volume-control/backend/PipewireNode.h0000664000175000017500000001622315171074712017603 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 . */ #pragma once //#include //#include //#include #include #include #include #include #include #include #include #include namespace UkuiAudioFramwork { class Pipewire; class PipewireProfiler; class driver; class PipewireNode { public: enum NodeType { NodeTypeNone = 0, NodeTypeInput, NodeTypeOutput, NodeTypeSink, NodeTypeSource, }; enum MediaType { MediaTypeNone = 0, MediaTypeAudio, MediaTypeVideo, MediaTypeMidi, }; struct driver { int64_t count = 0; float cpu_load[3] = {0,0,0}; struct spa_io_clock clock = {}; uint32_t xrun_count = 0; } info; struct measurement { int32_t index = 0; int32_t status = 0; int64_t quantum = 0; int64_t prev_signal = 0; int64_t signal = 0; int64_t awake = 0; int64_t finish = 0; struct spa_fraction latency = {0,0}; } measurement; uint32_t errors = 0; int32_t last_error_status = 0; private: void idChanged(); void nameChanged(); void nodeNameChanged(); void nodeDescriptionChanged(); void categoryChanged(); void mediaClassChanged(); void nodeTypeChanged(); void mediaTypeChanged(); void driverChanged(); void activeChanged(); void waitingChanged(); void busyChanged(); void quantumChanged(); void rateChanged(); void errorChanged(); void xrunChanged(); void volumeChanged(float); void stateChanged(); protected: Pipewire *pipewire = nullptr; uint32_t m_id = 0; pw_node_state m_state = pw_node_state::PW_NODE_STATE_CREATING; std::string m_name; std::string m_node_name; std::string m_node_description; std::string m_category; std::string m_media_class; NodeType m_node_type = NodeTypeNone; MediaType m_media_type = MediaTypeNone; struct pw_node *m_node = nullptr; struct spa_node *m_spa_node = nullptr; struct spa_node_info m_spa_node_info {}; // QHash m_properties; std::unordered_map m_properties; spa_hook object_listener; uint32_t _props_seq = 0; // struct measurement { // int32_t index = 0; // int32_t status = 0; // int64_t quantum = 0; // int64_t prev_signal = 0; // int64_t signal = 0; // int64_t awake = 0; // int64_t finish = 0; // struct spa_fraction latency = {0,0}; // } measurement; // struct driver { // int64_t count = 0; // float cpu_load[3] = {0,0,0}; // struct spa_io_clock clock = {}; // uint32_t xrun_count = 0; // } info; PipewireNode *m_driver = nullptr; // uint32_t errors = 0; // int32_t last_error_status = 0; float m_volume = 0.0; public: PipewireNode() = default; explicit PipewireNode(Pipewire *parent, uint32_t id, const struct spa_dict *props); virtual ~PipewireNode(); void _node_event_info(const pw_node_info *info); void _event_param(int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param); int id() const { return m_id; } uint32_t id_u32() const { return m_id; } std::string name() const { return m_name; } std::string nodeName() const { return m_node_name; } std::string nodeDescription() const { return m_node_description; } std::string category() const { return m_category; } std::string mediaClass() const { return m_media_class; } NodeType nodeType() const { return m_node_type; } MediaType mediaType() const { return m_media_type; } PipewireNode *driver() { return m_driver != this ? m_driver : nullptr; } bool active() const { return measurement.status == 3; } double waiting() const { return (measurement.awake - measurement.signal) / 1000000000.f; } double busy() const { return (measurement.finish - measurement.awake) / 1000000000.f; } int quantum() const { if (m_driver == this) { return info.clock.duration * info.clock.rate.num; } else { return measurement.latency.num; } } int rate() const { if (m_driver == this) { return info.clock.rate.denom; } else { return measurement.latency.denom; } } int error() const { return errors; } int xrun() const { return info.xrun_count; } float volume() const { return m_volume; } std::string state() const { switch (m_state) { case PW_NODE_STATE_ERROR: return "ERROR"; case PW_NODE_STATE_CREATING: return "CREATING"; case PW_NODE_STATE_SUSPENDED: return "SUSPENDED"; case PW_NODE_STATE_IDLE: return "IDLE"; case PW_NODE_STATE_RUNNING: return "RUNNING"; default: return "UNKNOWN"; } } // Q_INVOKABLE QString formatPercentage(float val, float quantum) const; // Q_INVOKABLE QIcon activeIcon(bool active) const; // Q_INVOKABLE virtual bool isAlsa() const { return false; } // /// Volume has to be between 0.0 and 1.0 (included) // Q_INVOKABLE void setVolume(float volume); std::string formatPercentage(float val, float quantum) const; // Q_INVOKABLE QIcon activeIcon(bool active) const; virtual bool isAlsa() const { return false; } // Volume has to be between 0.0 and 1.0 (included) void setVolume(float volume); // add void setDriver(PipewireNode *newDriver); void setMeasurement(const struct measurement &measure); void setInfo(const struct driver &info); protected: float _quantum() { if (info.clock.rate.denom) return info.clock.duration * info.clock.rate.num / info.clock.rate.denom; else return 0; } // void setDriver(PipewireNode *newDriver); // void setMeasurement(const struct measurement &measure); // void setInfo(const struct driver &info); // Q_INVOKABLE QVariant property(const char* key); // Q_INVOKABLE void setProperty(const char* key, QVariant value); GVariant* property(const char* key); void setProperty(const char* key, GVariant* value); void setProperties(struct spa_pod *properties); void enumParams(); friend class QPipewireProfiler; }; } //Q_DECLARE_METATYPE(QPipewireNode*); ukui-volume-control/backend/SystemdService.h0000664000175000017500000000300115171074712020150 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 . */ #pragma once //#include //#include #include namespace UkuiAudioFramwork { class SystemdService { void runningChanged(bool); private: std::string serviceName; // QDBusConnection bus; // QDBusInterface *systemd = nullptr; bool isRunning = false; public: SystemdService(const std::string& serviceName, bool userService = true); ~SystemdService(); bool running() { return isRunning; } void setRunning(bool); bool checkIsRunning(); // Q_INVOKABLE void start(); // Q_INVOKABLE void stop(); // Q_INVOKABLE void restart(); void start(); void stop(); void restart(); private: // Sets variable, emits signal but does not communicate with systemd void _setRunning(bool); }; } ukui-volume-control/backend/SessionManagerSetting.h0000664000175000017500000000352215171074712021463 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 SESSIONMANAGERSETTING_H #define SESSIONMANAGERSETTING_H #include #include #include "ISetting.h" namespace UkuiAudioFramwork { // UKUI Session //#define SESSION_MANAGER_SCHEMA "org.ukui.session" //#define SESSION_MANAGER_PATH "/org/ukui/desktop/session/" //#define STARTUP_MUSIC_KEY "startup-music" //#define POWEROFF_MUSIC_KEY "poweroff-music" //#define LOGOUT_MUSIC_KEY "logout-music" //#define WAKEUP_MUSIC_KEY "weakup-music" class SessionManagerSetting : public ISetting { public: SessionManagerSetting(); ~SessionManagerSetting(); public: void initConnect(); virtual void initSettings() override; virtual GVariant* getValue(const char*) const override; virtual void setValue(const char*, GVariant*) override; virtual bool isContainKeys(const char*) const override; private: static void onSettingsChanged(GSettings*, const gchar*); private: GSettings* m_pSettings = nullptr; GSettingsSchema* m_pSchema = nullptr; GSettingsSchemaSource* m_pSource = nullptr; }; } #endif // SESSIONMANAGERSETTING_H ukui-volume-control/backend/PipewireClient.cpp0000664000175000017500000000505315171074712020466 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "PipewireClient.h" #include "PipewireManager1.h" #include #include namespace UkuiAudioFramwork { #define PIPEWIRE_CAST(x) PipewireClient* _this = static_cast(x); //----------------------------------------------------------------------------- static void client_info(void *data, const pw_client_info *info) { PIPEWIRE_CAST(data); _this->_client_info(info); } void PipewireClient::_client_info(const struct pw_client_info *info) { const struct spa_dict_item *item; spa_dict_for_each(item, info->props) { const std::string key = item->key; const std::string value = item->value; m_properties[key] = value; // std::clog << "client_" << this->m_id << ":[" << key.toStdString() << "]=" << value.toStdString() << std::endl; // emit propertyChanged(key, value); } // emit propertiesChanged(); } static const pw_client_events client_events { .version = PW_VERSION_CLIENT_EVENTS, .info = client_info, }; //----------------------------------------------------------------------------- PipewireClient::PipewireClient(Pipewire *parent, uint32_t id, const spa_dict* props) : /*QObject(parent) , */pipewire(parent) , m_id(id) { client = static_cast( pw_registry_bind(pipewire->registry, id, PW_TYPE_INTERFACE_Client, PW_VERSION_CLIENT, 0)); if (client == nullptr) { throw std::runtime_error("Error creating client proxy"); } pw_client_add_listener(client, &client_listener, &client_events, this); pipewire->resync(); } PipewireClient::~PipewireClient() { spa_hook_remove(&client_listener); if (client != nullptr) { pw_proxy_destroy((struct pw_proxy*) client); } } } ukui-volume-control/backend/MediaControler.h0000664000175000017500000000264215171074712020120 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 MEDIACONTROLER_H #define MEDIACONTROLER_H #include #include #include #include namespace UkuiAudioFramwork { class MediaControler { public: static MediaControler& getInstance(); public: void pauseAllPlayers(); private: std::vector getMprisPlayers(GDBusConnection*) const; private: MediaControler() = default; MediaControler(const MediaControler&) = delete; MediaControler(MediaControler&&) = delete; MediaControler operator=(const MediaControler&) = delete; MediaControler operator=(MediaControler&&) = delete; virtual ~MediaControler() = default; }; } #endif // MEDIACONTROLER_H ukui-volume-control/backend/DbusServer.cpp0000664000175000017500000011424715171074712017635 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "DbusServer.h" #include "AudioMethod.h" #include "AudioContext.h" namespace UkuiAudioFramwork { const char* AudioDBusServer::m_pIntrospectionXml = R"( )"; const char* AudioDBusServer::m_pAudioSettingXml = R"( )"; const char* AudioDBusServer::m_pSoundThemeXml = R"( )"; GDBusInterfaceVTable AudioDBusServer::m_interfaceVTable = { &AudioDBusServer::handleMethodCall, nullptr, nullptr }; std::string getProcessName(pid_t pid) { std::ostringstream oss; oss << "/proc/" << pid << "/exe"; std::string procPath = oss.str(); try { std::filesystem::path targetPath = std::filesystem::read_symlink(procPath); std::string path = targetPath.string(); // 提取进程名(路径的最后一部分) size_t pos = path.rfind('/'); if (pos != std::string::npos) { return path.substr(pos + 1); } else { return path; } } catch (const std::filesystem::filesystem_error& e) { return "Unknown"; } } bool AudioDBusServer::initialize() { GError* error = nullptr; m_pConnection = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, &error); if (!m_pConnection) { AUDIO_ERROR("Failed to connect to DBus: {}.", error->message); g_error_free(error); return false; } auto proxy = g_dbus_proxy_new_sync( m_pConnection, G_DBUS_PROXY_FLAGS_NONE, nullptr, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", nullptr, &error); if (!proxy) { AUDIO_ERROR("Failed to create DBus proxy: {}.", error->message); g_error_free(error); return false; } auto result = g_dbus_proxy_call_sync( proxy, "RequestName", g_variant_new("(su)", AUDIO_OBJECT_SERVICE, 0), G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error); g_object_unref(proxy); if (!result) { AUDIO_ERROR("Failed to request service name: {}.", error->message); g_error_free(error); return false; } g_variant_unref(result); // Register object auto introspectionData = g_dbus_node_info_new_for_xml(m_pIntrospectionXml, &error); if (!introspectionData) { AUDIO_ERROR("Failed to parse introspection XML: {}.", error->message); g_error_free(error); return false; } g_dbus_connection_register_object( m_pConnection, AUDIO_OBJECT_PATH, introspectionData->interfaces[0], &m_interfaceVTable, this, nullptr, &error); if (error) { AUDIO_ERROR("Failed to register object: {}.", error->message); g_error_free(error); return false; } // Register object auto audioSettingData = g_dbus_node_info_new_for_xml(m_pAudioSettingXml, &error); if (!audioSettingData) { AUDIO_ERROR("Failed to parse audioSetting XML: {}.", error->message); g_error_free(error); return false; } g_dbus_connection_register_object( m_pConnection, AUDIO_OBJECT_PATH, audioSettingData->interfaces[0], &m_interfaceVTable, this, nullptr, &error); if (error) { AUDIO_ERROR("Failed to register object: {}.", error->message); g_error_free(error); return false; } // Register object auto soundThemeData = g_dbus_node_info_new_for_xml(m_pSoundThemeXml, &error); if (!soundThemeData) { AUDIO_ERROR("Failed to parse audioSetting XML: {}", error->message); g_error_free(error); return false; } g_dbus_connection_register_object( m_pConnection, AUDIO_OBJECT_PATH, soundThemeData->interfaces[0], &m_interfaceVTable, this, nullptr, &error); if (error) { AUDIO_ERROR("Failed to register object: {}.", error->message); g_error_free(error); return false; } m_pMainLoop = g_main_loop_new(nullptr, false); return true; } void AudioDBusServer::run() { g_main_loop_run(m_pMainLoop); } void AudioDBusServer::sendSignal(const char* objectPath, const char* interfaceName, const char* signalName, GVariant* param) { g_dbus_connection_emit_signal( m_pConnection, nullptr, objectPath, interfaceName, signalName, param, nullptr); } void AudioDBusServer::handleMethodCall(GDBusConnection* connection, const char* sender, const char* objectPath, const char* interfaceName, const char* methodName, GVariant* parameters, GDBusMethodInvocation* invocation, gpointer userData) { AudioDBusServer* self = static_cast(userData); GError *err = nullptr; guint pid = 0; GVariant *result = g_dbus_connection_call_sync(connection, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "GetConnectionUnixProcessID", g_variant_new("(s)", sender), G_VARIANT_TYPE("(u)"), G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &err); if (result) { g_variant_get(result, "(u)", &pid); g_variant_unref(result); } if (err) { AUDIO_ERROR("Unable to get the caller process, error: {}", err->message); g_error_free(err); } else { AUDIO_DEBUG("{} calling the {} method.", getProcessName(pid), methodName); } if (g_strcmp0(methodName, "getBackend") == 0) { std::string backend = AudioMethod::getInstance().getBackend(); g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", backend.c_str())); } else if (g_strcmp0(methodName, "setBackend") == 0) { uint32_t type; g_variant_get(parameters, "(i)", &type); AudioMethod::getInstance().setBackend(IntToEnum(type)); g_dbus_method_invocation_return_value(invocation, nullptr); } else if (g_strcmp0(methodName, "getDesktopEnvironmentVersion") == 0) { auto version = AudioMethod::getInstance().getDesktopEnvironmentVersion(); g_dbus_method_invocation_return_value(invocation, g_variant_new("(i)", version)); } else if (g_strcmp0(methodName, "loadModule") == 0) { const gchar* name, *param; g_variant_get(parameters, "(ss)", &name, ¶m); AudioMethod::getInstance().loadModule(name, param); g_dbus_method_invocation_return_value(invocation, nullptr); } else if (g_strcmp0(methodName, "unloadModule") == 0) { const gchar* name; g_variant_get(parameters, "(ss)", &name); AudioMethod::getInstance().unloadModule(name); g_dbus_method_invocation_return_value(invocation, nullptr); } else if (g_strcmp0(methodName, "getDetailVolume") == 0) { uint32_t type; const gchar* name; const gchar* dev; g_variant_get(parameters, "(iss)", &type, &name, &dev); auto volume = AudioMethod::getInstance().getDetailVolume(IntToEnum(type), name, dev); g_dbus_method_invocation_return_value(invocation, g_variant_new("(i)", volume)); } else if (g_strcmp0(methodName, "setDetailVolume") == 0) { uint32_t type, volume; const gchar* name; const gchar* dev; g_variant_get(parameters, "(issi)", &type, &name, &dev, &volume); AudioMethod::getInstance().setDetailVolume(static_cast(type), name, dev, volume); g_dbus_method_invocation_return_value(invocation, nullptr); } else if (g_strcmp0(methodName, "getVolume") == 0) { uint32_t type, idx; g_variant_get(parameters, "(ii)", &type, &idx); auto volume = AudioMethod::getInstance().getVolume(IntToEnum(type), idx); g_dbus_method_invocation_return_value(invocation, g_variant_new("(i)", volume)); } else if (g_strcmp0(methodName, "setVolume") == 0) { uint32_t type, idx, volume; g_variant_get(parameters, "(iii)", &type, &idx, &volume); AudioMethod::getInstance().setVolume(static_cast(type), idx, volume); g_dbus_method_invocation_return_value(invocation, nullptr); } else if (g_strcmp0(methodName, "getBalance") == 0) { uint32_t type; g_variant_get(parameters, "(i)", &type); auto volume = AudioMethod::getInstance().getBalance(IntToEnum(type)); g_dbus_method_invocation_return_value(invocation, g_variant_new("(d)", volume)); } else if (g_strcmp0(methodName, "setBalance") == 0) { uint32_t type; double volume; g_variant_get(parameters, "(id)", &type, &volume); AudioMethod::getInstance().setBalance(static_cast(type), volume); g_dbus_method_invocation_return_value(invocation, nullptr); } else if (g_strcmp0(methodName, "getDetailMute") == 0) { uint32_t type; const gchar* name; const gchar* dev; g_variant_get(parameters, "(iss)", &type, &name, &dev); auto mute = AudioMethod::getInstance().getDetailMute(IntToEnum(type), name, dev); g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", mute)); } else if (g_strcmp0(methodName, "setDetailMute") == 0) { uint32_t type; bool mute = false; const gchar* name; const gchar* dev; g_variant_get(parameters, "(issb)", &type, &name, &dev, &mute); AudioMethod::getInstance().setDetailMute(IntToEnum(type), name, dev, mute); g_dbus_method_invocation_return_value(invocation, nullptr); } else if (g_strcmp0(methodName, "getMute") == 0) { uint32_t type, idx; g_variant_get(parameters, "(ii)", &type, &idx); auto mute = AudioMethod::getInstance().getMute(IntToEnum(type), idx); g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", mute)); } else if (g_strcmp0(methodName, "setMute") == 0) { uint32_t type, idx; bool mute = false; g_variant_get(parameters, "(iib)", &type, &idx, &mute); AudioMethod::getInstance().setMute(IntToEnum(type), idx, mute); g_dbus_method_invocation_return_value(invocation, nullptr); } else if (g_strcmp0(methodName, "getDefaultDevice") == 0) { uint32_t type, idx; g_variant_get(parameters, "(ii)", &type, &idx); auto info = AudioMethod::getInstance().getDefaultDevice(IntToEnum(type), idx); g_dbus_method_invocation_return_value(invocation, g_variant_new("((issss))", info.idx, info.name.c_str(), info.cardName.c_str(), info.activePortName.c_str(), info.activePortLabel.c_str())); } else if (g_strcmp0(methodName, "setDefaultDevice") == 0) { uint32_t type, index; const gchar* dev; const gchar* name; g_variant_get(parameters, "(iiss)", &type, &index, &name, &dev); AudioMethod::getInstance().setDefaultDevice(IntToEnum(type), index, name, dev); g_dbus_method_invocation_return_value(invocation, nullptr); } else if (g_strcmp0(methodName, "getDeviceList") == 0) { uint32_t type; const gchar** dev; g_variant_get(parameters, "(i)", &type); // dev = AudioMethod::getInstance().getDeviceList(IntToEnum(type)); // g_dbus_method_invocation_return_value(invocation, g_variant_new("(as)", dev)); } else if (g_strcmp0(methodName, "getSinkList") == 0) { auto list = AudioMethod::getInstance().getSinkList(); GVariantBuilder ret; g_variant_builder_init(&ret, G_VARIANT_TYPE("a(issss)")); g_variant_builder_open(&ret, G_VARIANT_TYPE ("a(issss)")); for (const auto& s : list) { g_variant_builder_add(&ret, "(issss)", s.idx, s.name.c_str(), s.cardName.c_str(), s.activePortName.c_str(), s.activePortLabel.c_str()); } g_variant_builder_close(&ret); g_dbus_method_invocation_return_value(invocation, g_variant_new("(a(issss))", &ret)); } else if (g_strcmp0(methodName, "getSourceList") == 0) { auto list = AudioMethod::getInstance().getSourceList(); GVariantBuilder ret; g_variant_builder_init(&ret, G_VARIANT_TYPE("a(issss)")); g_variant_builder_open(&ret, G_VARIANT_TYPE ("a(issss)")); for (const auto& s : list) { g_variant_builder_add(&ret, "(issss)", s.idx, s.name.c_str(), s.cardName.c_str(), s.activePortName.c_str(), s.activePortLabel.c_str()); } g_variant_builder_close(&ret); g_dbus_method_invocation_return_value(invocation, g_variant_new("(a(issss))", &ret)); } else if (g_strcmp0(methodName, "getAvailablePortList") == 0) { uint32_t type; g_variant_get(parameters, "(i)", &type); auto devList = AudioMethod::getInstance().getAvailablePortList(IntToEnum(type)); GVariantBuilder ret; g_variant_builder_init(&ret, G_VARIANT_TYPE("a(ssssiiib)")); g_variant_builder_open(&ret, G_VARIANT_TYPE ("a(ssssiiib)")); for (const auto& d : devList) { g_variant_builder_add(&ret, "(ssssiiib)", d->cardName.c_str(), d->cardDesc.c_str(), d->portName.c_str(), d->portLabel.c_str(), d->priority, d->direction, d->available, d->enabled); } g_variant_builder_close(&ret); g_dbus_method_invocation_return_value(invocation, g_variant_new("(a(ssssiiib))", &ret)); } else if (g_strcmp0(methodName, "getSinkInputList") == 0) { auto siList = AudioMethod::getInstance().getSinkInputList(); GVariantBuilder ret; g_variant_builder_init(&ret, G_VARIANT_TYPE("a(siiss)")); g_variant_builder_open(&ret, G_VARIANT_TYPE ("a(siiss)")); for (const auto& si : siList) { g_variant_builder_add(&ret, "(siiss)", si.name.c_str(), si.index, si.volume, si.mediaRole.c_str(), si.iconName.c_str()); } g_variant_builder_close(&ret); g_dbus_method_invocation_return_value(invocation, g_variant_new("(a(siiss))", &ret)); } else if (g_strcmp0(methodName, "getSourceOutputList") == 0) { auto soList = AudioMethod::getInstance().getSourceOutputList(); GVariantBuilder ret; g_variant_builder_init(&ret, G_VARIANT_TYPE("a(siiss)")); g_variant_builder_open(&ret, G_VARIANT_TYPE ("a(siiss)")); for (const auto& so : soList) { g_variant_builder_add(&ret, "(siiss)", so.name.c_str(), so.index, so.volume, so.mediaRole.c_str(), so.iconName.c_str()); } g_variant_builder_close(&ret); g_dbus_method_invocation_return_value(invocation, g_variant_new("(a(siiss))", &ret)); } else if (g_strcmp0(methodName, "getStreamMediaList") == 0) { auto streamList = AudioMethod::getInstance().getStreamMediaList(); GVariantBuilder ret; g_variant_builder_init(&ret, G_VARIANT_TYPE("a(sssa(iiibs))")); g_variant_builder_open(&ret, G_VARIANT_TYPE("a(sssa(iiibs))")); for (const auto& s : streamList) { GVariantBuilder ret1; g_variant_builder_init(&ret1, G_VARIANT_TYPE("a(iiibs)")); g_variant_builder_open(&ret1, G_VARIANT_TYPE ("a(iiibs)")); for (const auto& media : s.mediaInfoList) { g_variant_builder_add(&ret1, "(iiibs)", media.direction, media.index, media.volume, media.mute, media.name.c_str()); } g_variant_builder_close(&ret1); g_variant_builder_add(&ret, "(sssa(iiibs))", s.mediaRole.c_str(), s.iconName.c_str(), s.binary.c_str(), &ret1); } g_variant_builder_close(&ret); g_dbus_method_invocation_return_value(invocation, g_variant_new("(a(sssa(iiibs)))", &ret)); } else if (g_strcmp0(methodName, "setEnabled") == 0) { bool enable; const gchar* port; const gchar* card; g_variant_get(parameters, "(ssb)", &card, &port, &enable); AudioMethod::getInstance().setEnabled(card, port, enable); g_dbus_method_invocation_return_value(invocation, nullptr); } else if (g_strcmp0(methodName, "isEnabled") == 0) { const gchar* port; const gchar* card; g_variant_get(parameters, "(ss)", &card, &port); auto status = AudioMethod::getInstance().isEnabled(card, port); g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", status)); } else if (g_strcmp0(methodName, "setAutoPauseStatus") == 0) { bool status; g_variant_get(parameters, "(b)", &status); AudioMethod::getInstance().setAutoPauseStatus(status); g_dbus_method_invocation_return_value(invocation, nullptr); } else if (g_strcmp0(methodName, "getAutoPauseStatus") == 0) { auto status = AudioMethod::getInstance().getAutoPauseStatus(); g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", status)); } else if (g_strcmp0(methodName, "setMultiAudioCombine") == 0) { int type; gboolean status; const gchar* s; GVariantIter* iter; std::vector devList{}; g_variant_get(parameters, "(ibas)", &type, &status, &iter); while (g_variant_iter_loop (iter, "s", &s)) devList.push_back(s); g_variant_iter_free (iter); AudioMethod::getInstance().setMultiAudioCombine(IntToEnum(type), status, devList); g_dbus_method_invocation_return_value(invocation, nullptr); } else if (g_strcmp0(methodName, "getMultiAudioCombineStatus") == 0) { int type; g_variant_get(parameters, "(i)", &type); auto status = AudioMethod::getInstance().getMultiAudioCombineStatus(IntToEnum(type)); g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", status)); } else if (g_strcmp0(methodName, "isValidDevice") == 0) { int type; g_variant_get(parameters, "(i)", &type); auto status = AudioMethod::getInstance().isValidDevice(IntToEnum(type)); g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", status)); } else if (g_strcmp0(methodName, "getVolumeBoostStatus") == 0) { auto status = AudioMethod::getInstance().getVolumeBoostStatus(); g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", status)); } else if (g_strcmp0(methodName, "setVolumeBoostStatus") == 0) { bool status = false; g_variant_get(parameters, "(b)", &status); AudioMethod::getInstance().setVolumeBoostStatus(status); g_dbus_method_invocation_return_value(invocation, nullptr); } else if (g_strcmp0(methodName, "getVolumeBoostValue") == 0) { auto value = AudioMethod::getInstance().getVolumeBoostValue(); g_dbus_method_invocation_return_value(invocation, g_variant_new("(i)", value)); } else if (g_strcmp0(methodName, "setVolumeBoostValue") == 0) { uint32_t value; g_variant_get(parameters, "(i)", &value); AudioMethod::getInstance().setVolumeBoostValue(value); g_dbus_method_invocation_return_value(invocation, nullptr); } else if (g_strcmp0(methodName, "getMonoStatus") == 0) { auto status = AudioMethod::getInstance().getMonoStatus(); g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", status)); } else if (g_strcmp0(methodName, "setMonoStatus") == 0) { bool status = false; g_variant_get(parameters, "(b)", &status); AudioMethod::getInstance().setMonoStatus(status); g_dbus_method_invocation_return_value(invocation, nullptr); } else if (g_strcmp0(methodName, "getEchoCancelStatus") == 0) { auto status = AudioMethod::getInstance().getEchoCancelStatus(); g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", status)); } else if (g_strcmp0(methodName, "setEchoCancelStatus") == 0) { bool status = false; g_variant_get(parameters, "(b)", &status); AudioMethod::getInstance().setEchoCancelStatus(status); g_dbus_method_invocation_return_value(invocation, nullptr); } else if (g_strcmp0(methodName, "getLoopbackStatus") == 0) { auto status = AudioMethod::getInstance().getLoopbackStatus(); g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", status)); } else if (g_strcmp0(methodName, "setLoopbackStatus") == 0) { bool status = false; g_variant_get(parameters, "(b)", &status); AudioMethod::getInstance().setLoopbackStatus(status); g_dbus_method_invocation_return_value(invocation, nullptr); } else if (g_strcmp0(methodName, "getAlertStatus") == 0) { auto status = AudioMethod::getInstance().getAlertStatus(); g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", status)); } else if (g_strcmp0(methodName, "setAlertStatus") == 0) { bool status = false; g_variant_get(parameters, "(b)", &status); AudioMethod::getInstance().setAlertStatus(status); g_dbus_method_invocation_return_value(invocation, nullptr); } else if (g_strcmp0(methodName, "getStartupStatus") == 0) { auto status = AudioMethod::getInstance().getStartupStatus(); g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", status)); } else if (g_strcmp0(methodName, "setStartupStatus") == 0) { auto status = false; g_variant_get(parameters, "(b)", &status); AudioMethod::getInstance().setStartupStatus(status); g_dbus_method_invocation_return_value(invocation, nullptr); } else if (g_strcmp0(methodName, "getPoweroffStatus") == 0) { auto status = AudioMethod::getInstance().getPoweroffStatus(); g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", status)); } else if (g_strcmp0(methodName, "setPoweroffStatus") == 0) { auto status = false; g_variant_get(parameters, "(b)", &status); AudioMethod::getInstance().setPoweroffStatus(status); g_dbus_method_invocation_return_value(invocation, nullptr); } else if (g_strcmp0(methodName, "getLogoutStatus") == 0) { auto status = AudioMethod::getInstance().getLogoutStatus(); g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", status)); } else if (g_strcmp0(methodName, "setLogoutStatus") == 0) { bool status = false; g_variant_get(parameters, "(b)", &status); AudioMethod::getInstance().setLogoutStatus(status); g_dbus_method_invocation_return_value(invocation, nullptr); } else if (g_strcmp0(methodName, "getWakeupStatus") == 0) { auto status = AudioMethod::getInstance().getWakeupStatus(); g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", status)); } else if (g_strcmp0(methodName, "setWakeupStatus") == 0) { auto status = false; g_variant_get(parameters, "(b)", &status); AudioMethod::getInstance().setWakeupStatus(status); g_dbus_method_invocation_return_value(invocation, nullptr); } else if (g_strcmp0(methodName, "getGlobalThemeName") == 0) { auto name = AudioMethod::getInstance().getGlobalThemeName(); g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", name.c_str())); } else if (g_strcmp0(methodName, "setGlobalThemeName") == 0) { std::string name; g_variant_get(parameters, "(s)", &name); AudioMethod::getInstance().setGlobalThemeName(name); g_dbus_method_invocation_return_value(invocation, nullptr); } else if (g_strcmp0(methodName, "getSoundThemeName") == 0) { auto name = AudioMethod::getInstance().getSoundThemeName(); g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", name.c_str())); } else if (g_strcmp0(methodName, "setSoundThemeName") == 0) { auto name = ""; g_variant_get(parameters, "(s)", &name); AudioMethod::getInstance().setSoundThemeName(name); g_dbus_method_invocation_return_value(invocation, nullptr); } else if (g_strcmp0(methodName, "getCustomThemeStatus") == 0) { auto status = AudioMethod::getInstance().getCustomThemeStatus(); g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", status)); } else if (g_strcmp0(methodName, "setCustomThemeStatus") == 0) { bool status; g_variant_get(parameters, "(b)", &status); AudioMethod::getInstance().setCustomThemeStatus(status); g_dbus_method_invocation_return_value(invocation, nullptr); } else if (g_strcmp0(methodName, "getNotifyGeneralName") == 0) { auto name = AudioMethod::getInstance().getNotifyGeneralName(); g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", name.c_str())); } else if (g_strcmp0(methodName, "setNotifyGeneralName") == 0) { auto name = ""; g_variant_get(parameters, "(s)", &name); AudioMethod::getInstance().setNotifyGeneralName(name); g_dbus_method_invocation_return_value(invocation, nullptr); } else if (g_strcmp0(methodName, "getVolumeChangedName") == 0) { auto name = AudioMethod::getInstance().getVolumeChangedName(); g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", name.c_str())); } else if (g_strcmp0(methodName, "setVolumeChangedName") == 0) { auto name = ""; g_variant_get(parameters, "(s)", &name); AudioMethod::getInstance().setVolumeChangedName(name); g_dbus_method_invocation_return_value(invocation, nullptr); } else if (g_strcmp0(methodName, "getSoundThemeList") == 0) { auto themeList = AudioMethod::getInstance().getSoundThemeList(); GVariantBuilder ret; g_variant_builder_init(&ret, G_VARIANT_TYPE("a(ss)")); g_variant_builder_open(&ret, G_VARIANT_TYPE ("a(ss)")); for (const auto& m : themeList) { g_variant_builder_add(&ret, "(ss)", m.name.c_str(), m.description.c_str()); } g_variant_builder_close(&ret); g_dbus_method_invocation_return_value(invocation, g_variant_new("(a(ss))", &ret)); } else if (g_strcmp0(methodName, "getSoundEffectFileList") == 0) { const gchar* name; g_variant_get(parameters, "(s)", &name); std::list fileList = AudioMethod::getInstance().getSoundEffectFileList(name); GVariantBuilder ret; g_variant_builder_init(&ret, G_VARIANT_TYPE("a(ss)")); g_variant_builder_open(&ret, G_VARIANT_TYPE ("a(ss)")); for (const auto& f : fileList) { g_variant_builder_add(&ret, "(ss)", f.name.c_str(), f.description.c_str()); } g_variant_builder_close(&ret); g_dbus_method_invocation_return_value(invocation, g_variant_new("(a(ss))", &ret)); } } AudioDBusServer::~AudioDBusServer() { if (m_pMainLoop) { g_main_loop_unref(m_pMainLoop); } } } ukui-volume-control/backend/AudioSetting.h0000664000175000017500000000272015171074712017605 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 AUDIOSETTING_H #define AUDIOSETTING_H #include #include #include "ISetting.h" namespace UkuiAudioFramwork { class AudioSetting : public ISetting { public: AudioSetting(); ~AudioSetting(); public: void initConnect(); virtual void initSettings() override; virtual GVariant* getValue(const char*) const override; virtual void setValue(const char*, GVariant*) override; virtual bool isContainKeys(const char*) const override; private: static void onSettingsChanged(GSettings*, const gchar*); private: GSettings* m_pSettings = nullptr; GSettingsSchema* m_pSchema = nullptr; GSettingsSchemaSource* m_pSource = nullptr; }; } #endif // AUDIOSETTING_H ukui-volume-control/backend/DbusServer.h0000664000175000017500000000417615171074712017301 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 AUDIODBUSSERVER_H #define AUDIODBUSSERVER_H #include #include #include #include #include "Util.h" //#define AUDIO_OBJECT_SERVICE "org.ukui.audio.framwork" //#define AUDIO_OBJECT_PATH "/org/ukui/audio/framwork" //#define AUDIO_INTERFACE "org.ukui.audio.framwork" //#define SETTINGS_INTERFACE "org.ukui.audio.settings" namespace UkuiAudioFramwork { class AudioMethod; class AudioDBusServer { public: AudioDBusServer() = default; virtual ~AudioDBusServer(); public: void run(); bool initialize(); void sendSignal(const char*, const char*, const char*, GVariant*); private: static void handleMethodCall(GDBusConnection* connection, const char* sender, const char* objectPath, const char* interfaceName, const char* methodName, GVariant* parameters, GDBusMethodInvocation* invocation, gpointer userData); private: static const char* m_pIntrospectionXml; static const char* m_pAudioSettingXml; static const char* m_pSoundThemeXml; static GDBusInterfaceVTable m_interfaceVTable; GDBusConnection* m_pConnection; GMainLoop* m_pMainLoop; }; } #endif // AUDIODBUSSERVER_H ukui-volume-control/tray/0000775000175000017500000000000015171074712014424 5ustar fengfengukui-volume-control/tray/Ukui2TrayIcon.cpp0000664000175000017500000000575615171074712017615 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "Ukui2TrayIcon.h" #include #include #include "IMainWindow.h" UkuiVersionTwoTrayIcon::UkuiVersionTwoTrayIcon(UkmediaTrayIcon* trayIcon, IMainWindow* window) : ISoundSystemTrayIcon(trayIcon), m_pMainWid(window) { } void UkuiVersionTwoTrayIcon::initTray() { m_pMenu = new QMenu(); m_pMenu->setAttribute(Qt::WA_NoMouseReplay); QAction* soundPreferenceAction = new QAction(tr("Sound preference ukui 2"), this); soundPreferenceAction->setIcon(QIcon::fromTheme("document-page-setup-symbolic")); QAction* muteAction = new QAction(tr("Mute 2"), this); m_pMenu->addAction(muteAction); m_pMenu->addAction(soundPreferenceAction); m_pTrayIcon->setContextMenu(m_pMenu); connect(soundPreferenceAction, SIGNAL(triggered()), this, SLOT(soundPreferenceActionTriggered())); connect(muteAction, SIGNAL(triggered()), this, SLOT(muteActionTriggered())); } void UkuiVersionTwoTrayIcon::initSlot() { connect(m_pTrayIcon, SIGNAL(wheelRollEventSignal(bool)), this, SLOT(trayWheelRollEventSlot(bool))); connect(m_pTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),\ this, SLOT(activatedSystemTrayIconSlot(QSystemTrayIcon::ActivationReason))); } void UkuiVersionTwoTrayIcon::setVisible(bool visible) { m_pTrayIcon->setVisible(visible); } void UkuiVersionTwoTrayIcon::setIcon(const QIcon& icon) { m_pTrayIcon->setIcon(icon); } void UkuiVersionTwoTrayIcon::soundPreferenceActionTriggered() { QProcess::startDetached(QString("ukui-control-center -m Audio")); } void UkuiVersionTwoTrayIcon::activatedSystemTrayIconSlot(QSystemTrayIcon::ActivationReason reason) { switch(reason) { // 鼠标中间键点击图标 case QSystemTrayIcon::MiddleClick: { break; } // 鼠标左键点击图标 case QSystemTrayIcon::Trigger: { m_pMainWid->triggerWidget(); std::cout << "QSystemTrayIcon::Trigger" << std::endl; break; } // 鼠标左键双击图标 case QSystemTrayIcon::DoubleClick: { std::cout << "QSystemTrayIcon::DoubleClick" << std::endl; } // 右键菜单 case QSystemTrayIcon::Context:{ std::cout << "QSystemTrayIcon::Context" << std::endl; break; } default: break; } } ukui-volume-control/tray/XatomHelper.h0000664000175000017500000000643615171074712017036 0ustar fengfeng/* * KWin Style UKUI * * Copyright (C) 2020, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: Yue Lan * */ #ifndef XATOMHELPER_H #define XATOMHELPER_H #include // 避免在头文件中包含 X11/NETWM,改到 .cpp 里以规避宏污染 typedef unsigned long Atom; // 前向声明 X11 Atom 类型 struct UnityCorners { ulong topLeft = 0; ulong topRight = 0; ulong bottomLeft = 0; ulong bottomRight = 0; }; typedef struct { ulong flags = 0; ulong functions = 0; ulong decorations = 0; long input_mode = 0; ulong status = 0; } MotifWmHints, MwmHints; #define MWM_HINTS_FUNCTIONS (1L << 0) #define MWM_HINTS_DECORATIONS (1L << 1) #define MWM_HINTS_INPUT_MODE (1L << 2) #define MWM_HINTS_STATUS (1L << 3) #define MWM_FUNC_ALL (1L << 0) #define MWM_FUNC_RESIZE (1L << 1) #define MWM_FUNC_MOVE (1L << 2) #define MWM_FUNC_MINIMIZE (1L << 3) #define MWM_FUNC_MAXIMIZE (1L << 4) #define MWM_FUNC_CLOSE (1L << 5) #define MWM_DECOR_ALL (1L << 0) #define MWM_DECOR_BORDER (1L << 1) #define MWM_DECOR_RESIZEH (1L << 2) #define MWM_DECOR_TITLE (1L << 3) #define MWM_DECOR_MENU (1L << 4) #define MWM_DECOR_MINIMIZE (1L << 5) #define MWM_DECOR_MAXIMIZE (1L << 6) #define MWM_INPUT_MODELESS 0 #define MWM_INPUT_PRIMARY_APPLICATION_MODAL 1 #define MWM_INPUT_SYSTEM_MODAL 2 #define MWM_INPUT_FULL_APPLICATION_MODAL 3 #define MWM_INPUT_APPLICATION_MODAL MWM_INPUT_PRIMARY_APPLICATION_MODAL #define MWM_TEAROFF_WINDOW (1L<<0) namespace UKUI { class Decoration; } class XAtomHelper : public QObject { // friend class UKUI::Decoration; Q_OBJECT public: static XAtomHelper *getInstance(); static bool isFrameLessWindow(int winId); static bool isWindowDecorateBorderOnly(int winId); static bool isWindowMotifHintDecorateBorderOnly(const MotifWmHints &hint); bool isUKUICsdSupported(); bool isUKUIDecorationWindow(int winId); UnityCorners getWindowBorderRadius(int winId); void setWindowBorderRadius(int winId, const UnityCorners &data); void setWindowBorderRadius(int winId, int topLeft, int topRight, int bottomLeft, int bottomRight); void setUKUIDecoraiontHint(int winId, bool set = true); void setWindowMotifHint(int winId, const MotifWmHints &hints); MotifWmHints getWindowMotifHint(int winId); private: explicit XAtomHelper(QObject *parent = nullptr); Atom registerUKUICsdNetWmSupportAtom(); void unregisterUKUICsdNetWmSupportAtom(); Atom m_motifWMHintsAtom = 0; Atom m_unityBorderRadiusAtom = 0; Atom m_ukuiDecorationAtion = 0; }; #endif // XATOMHELPER_H ukui-volume-control/tray/Ukui4TrayIcon.h0000664000175000017500000000275715171074712017262 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 UKUIVERSION4TRAYICON_H #define UKUIVERSION4TRAYICON_H #include #include #include #include "ISoundSystemTrayIcon.h" class IMainWindow; class UkuiVersion4TrayIcon : public QObject, public ISoundSystemTrayIcon { Q_OBJECT public: UkuiVersion4TrayIcon(UkmediaTrayIcon*, IMainWindow*); ~UkuiVersion4TrayIcon() = default; public: virtual void initTray() override; virtual void initSlot() override; virtual void setVisible(bool) override; virtual void setIcon(const QIcon&) override; private slots: void soundPreferenceActionTriggered(); void activatedSystemTrayIconSlot(QSystemTrayIcon::ActivationReason); private: IMainWindow* m_pMainWid; }; #endif // UKUIVERSION4TRAYICON_H ukui-volume-control/tray/Ukui2AppVolumeWidget.h0000664000175000017500000000500415171074712020570 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 UKUI2APPVOLUMEWIDGET_H #define UKUI2APPVOLUMEWIDGET_H #include #include #include "IAppVolumeWidget.h" #include "ITrayVolumeSliderItem.h" class IMainWindow; class Ukui2AppVolumeWidget : public QWidget, public IAppVolumeWidget { Q_OBJECT public: explicit Ukui2AppVolumeWidget(std::unordered_map>&, std::unordered_map>&, QPushButton*, QPushButton*, QPushButton*, IMainWindow*, QWidget* = nullptr); ~Ukui2AppVolumeWidget(); public: virtual void initUi() override; virtual void showWidget() override; virtual QWidget* getWidget() override; virtual void addAppWidget(int) override; virtual void removeAppWidget(int) override; virtual void addAppToWidget(const QString&, const QString&) override; virtual void removeAppfromWidget(const QString&); private: QLabel* m_pSysVolumeLabel = nullptr; // 系统音量标签 QLabel* m_pAppLabel = nullptr; // 应用音量标签 QScrollArea* m_pAppArea = nullptr; // 应用音量滚动区域 QWidget* m_pDisplayAppVolumeWidget = nullptr; // 应用音量显示区域 QVBoxLayout* m_pVlayout = nullptr; QPushButton* m_pSwitchModuleBtn = nullptr; QPushButton* m_pDeviceVolumeBtn = nullptr; QPushButton* m_pAppVolumeBtn = nullptr; QWidget* m_pBtnWidget = nullptr; std::unordered_map>& m_sysVolumeSliderItemMap; std::unordered_map>& m_appVolumeSliderItemMap; IMainWindow* m_pMainWid; }; #endif // UKUI2APPVOLUMEWIDGET_H ukui-volume-control/tray/TrayClientMethod.h0000664000175000017500000000351415171074712020017 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 TRAYCLIENTMETHOD_H #define TRAYCLIENTMETHOD_H #include #include "DBusClient.h" #include "TrayClientManager.h" class TrayClientMethod { public: static TrayClientMethod& getInstance(); public: void setManager(std::shared_ptr); void setValue(int, int, int) ; void setIcon(int, const QIcon&); void setChecked(int, bool); void setCurrentIndex(int); void addDeviceItem(int, const ClientDeviceInfo&); void removeDeviceItem(const QString&, const QString&); void addAppItem(int, const QString&, const QString&); void removeAppItem(int); void updateDescription(const QString&, const QString&); private: TrayClientMethod() = default; TrayClientMethod(const TrayClientMethod&) = delete; TrayClientMethod(TrayClientMethod&&) = delete; TrayClientMethod operator=(const TrayClientMethod&) = delete; TrayClientMethod operator=(TrayClientMethod&&) = delete; virtual ~TrayClientMethod() = default; private: std::shared_ptr m_pClientManager{nullptr}; }; #endif // TRAYCLIENTMETHOD_H ukui-volume-control/tray/ITrayVolumeSliderItem.cpp0000664000175000017500000000220015171074712021324 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "ITrayVolumeSliderItem.h" ITrayVolumeSliderItem::ITrayVolumeSliderItem(std::shared_ptr slider, QPushButton* btn) : m_pVolumeSlider(slider), m_pIconBtn(btn) { } std::shared_ptr ITrayVolumeSliderItem::getSlider() const { return m_pVolumeSlider; } QPushButton* ITrayVolumeSliderItem::getIconButton() const { return m_pIconBtn; } ukui-volume-control/tray/Ukui2MainWindow.cpp0000664000175000017500000002125515171074712020131 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "Ukui2MainWindow.h" #include #include "Ukui2AppVolumeWidget.h" #include "Ukui2SystemVolumeWidget.h" #include "DBusClient.h" #include "Ukui4CustomControl.h" using namespace UkuiAudioFramwork; Ukui2MainWindow::Ukui2MainWindow(std::unordered_map>& systemVolumeSliderMap, std::unordered_map>& appVolumeSliderMap, QWidget* parent) : m_sysVolumeSliderItemMap(systemVolumeSliderMap), m_appSliderItemMap(appVolumeSliderMap), QWidget(parent) { for (const auto [k, v] : m_sysVolumeKeys) { std::shared_ptr s = nullptr; std::shared_ptr item = nullptr; switch (k) { case VolumeSliderType::VOLUME_SLIDER_MINI_OUTPUT: case VolumeSliderType::VOLUME_SLIDER_APP_OUTPUT: { s = std::make_shared(new UkmediaVolumeSlider); item = std::make_shared(s, new QLabel(), new QPushButton(), k, this); break; } case VolumeSliderType::VOLUME_SLIDER_DEVICE_OUTPUT: case VolumeSliderType::VOLUME_SLIDER_DEVICE_INPUT: { s = std::make_shared(new UkmediaVolumeSlider); item = std::make_shared(s, new QLabel(), new QPushButton(), new QPushButton(), k, this); break; } default: break; } if (item) m_sysVolumeSliderItemMap.emplace(EnumToInt(k), item); } } void Ukui2MainWindow::initUi() { m_pSwitchModuleBtn = new QPushButton(); m_pSwitchModuleBtn->setFixedSize(36, 36); m_pSwitchModuleBtn->setIconSize(QSize(16, 16)); m_pSwitchModuleBtn->setIcon(QIcon("/usr/share/ukui-media/img/mini-module.svg")); m_pDeviceVolumeBtn = new QPushButton(); m_pDeviceVolumeBtn->setFlat(true); m_pDeviceVolumeBtn->setFocusPolicy(Qt::NoFocus); m_pDeviceVolumeBtn->setFixedSize(36, 36); m_pDeviceVolumeBtn->setIcon(QIcon("/usr/share/ukui-media/img/device.svg")); m_pAppVolumeBtn = new QPushButton(); m_pAppVolumeBtn->setFlat(true); m_pAppVolumeBtn->setFocusPolicy(Qt::NoFocus); m_pAppVolumeBtn->setFixedSize(36, 36); m_pAppVolumeBtn->setIcon(QIcon("/usr/share/ukui-media/img/application.svg")); m_pStackedWidget = new QStackedWidget(this); m_pAppVolumeWidget = std::make_shared(m_sysVolumeSliderItemMap, m_appSliderItemMap, m_pSwitchModuleBtn, m_pDeviceVolumeBtn, m_pAppVolumeBtn, this, m_pStackedWidget); m_pSysVolumeWidget = std::make_shared(m_sysVolumeSliderItemMap, m_pSwitchModuleBtn, m_pDeviceVolumeBtn, m_pAppVolumeBtn, this, m_pStackedWidget); m_pMiniWidget = new Ukui2MiniWidget(m_sysVolumeSliderItemMap, m_pSwitchModuleBtn, m_pStackedWidget); m_pSwitchModuleBtn->setParent(m_pMiniWidget); m_pSwitchModuleBtn->move(305, 6); m_pStackedWidget->addWidget(m_pMiniWidget); m_pStackedWidget->addWidget(m_pSysVolumeWidget->getWidget()); m_pStackedWidget->addWidget(m_pAppVolumeWidget->getWidget()); m_pStackedWidget->setFixedSize(420, 320); qDebug() << "m_pStackedWidget.size: " << m_pStackedWidget->size(); } void Ukui2MainWindow::initSlots() { connect(m_pSwitchModuleBtn, SIGNAL(pressed()), this, SLOT(siwtchModuleBtnPressedSlots())); connect(m_pDeviceVolumeBtn, SIGNAL(pressed()), this, SLOT(deviceVolumeBtnPressedSlots())); connect(m_pAppVolumeBtn, SIGNAL(pressed()), this, SLOT(appVolumeBtnPressedSlots())); } void Ukui2MainWindow::addAppItem(int idx, const QString& iconName, const QString& descName) { std::shared_ptr s = std::make_shared(new QSlider); std::shared_ptr item = std::make_shared(s, new QPushButton(), new QPushButton(), new QLabel(descName)); item->setIcon(QIcon::fromTheme(iconName)); m_appSliderItemMap.emplace(idx, item); m_pAppVolumeWidget->addAppWidget(idx); } void Ukui2MainWindow::removeAppItem(int idx) { m_pAppVolumeWidget->removeAppWidget(idx); } void Ukui2MainWindow::setParentWidget(QWidget* parent) { setParent(parent); } void Ukui2MainWindow::addAppToWidget(const QString& appName, const QString& iconName) { m_pAppVolumeWidget->addAppToWidget(appName, iconName); } void Ukui2MainWindow::removeAppFromWidget(const QString& appName) { m_pAppVolumeWidget->removeAppfromWidget(appName); } void Ukui2MainWindow::addDevToWidget([[maybe_unused]] const QString& iconName, [[maybe_unused]] const ClientDeviceInfo& info) { } void Ukui2MainWindow::removeDevFromWidget(const QString& portName, const QString& devName) { if (m_pSysVolumeWidget) m_pSysVolumeWidget->removeDevice(portName, devName); } void Ukui2MainWindow::setCurrentIndex(int idx) { if (m_pSysVolumeWidget) m_pSysVolumeWidget->setCurrentIndex(idx); } void Ukui2MainWindow::siwtchModuleBtnPressedSlots() { if (0 == m_pStackedWidget->currentIndex()) { m_pSwitchModuleBtn->move(361, 6); switch (m_prevType) { case StackedWidgetType::ADVANCE_SYS_WIDGET: { m_pSwitchModuleBtn->setParent(m_pSysVolumeWidget->getWidget()); break; } case StackedWidgetType::ADVANCE_APP_WIDGET: { m_pSwitchModuleBtn->setParent(m_pAppVolumeWidget->getWidget()); break; } default: break; } setStackWidgetCurrentIndex(EnumToInt(m_prevType)); } else { m_pSwitchModuleBtn->move(305, 6); m_pSwitchModuleBtn->setParent(m_pMiniWidget); setStackWidgetCurrentIndex(0); } } void Ukui2MainWindow::deviceVolumeBtnPressedSlots() { if (1 == m_pStackedWidget->currentIndex()) return; m_prevType = StackedWidgetType::ADVANCE_SYS_WIDGET; m_pDeviceVolumeBtn->setParent(m_pSysVolumeWidget->getWidget()); m_pAppVolumeBtn->setParent(m_pSysVolumeWidget->getWidget()); m_pSwitchModuleBtn->setParent(m_pSysVolumeWidget->getWidget()); setStackWidgetCurrentIndex(1); } void Ukui2MainWindow::appVolumeBtnPressedSlots() { if (2 == m_pStackedWidget->currentIndex()) return; m_prevType = StackedWidgetType::ADVANCE_APP_WIDGET; m_pDeviceVolumeBtn->setParent(m_pAppVolumeWidget->getWidget()); m_pAppVolumeBtn->setParent(m_pAppVolumeWidget->getWidget()); m_pSwitchModuleBtn->setParent(m_pAppVolumeWidget->getWidget()); setStackWidgetCurrentIndex(2); } void Ukui2MainWindow::setValue(int type, int idx, int value) { switch (IntToEnum(type)) { case NodeType::INPUT: { m_sysVolumeSliderItemMap[2]->setValue(value); break; } case NodeType::OUTPUT: { m_sysVolumeSliderItemMap[0]->setValue(value); m_sysVolumeSliderItemMap[1]->setValue(value); m_sysVolumeSliderItemMap[3]->setValue(value); break; } case NodeType::SINK_INPUT: case NodeType::SOURCE_OUTPUT: { auto it = m_appSliderItemMap.find(idx); if (it != m_appSliderItemMap.end()) { it->second->setValue(value); } else { qDebug() << "m_appVolumeSliderItemMap not found such idx: " << idx; } break; } default: break; } } void Ukui2MainWindow::updateDescription(const QString& input, const QString& output) { m_pMiniWidget->setDescription(output); std::dynamic_pointer_cast(m_sysVolumeSliderItemMap[1])->setDescription(output); std::dynamic_pointer_cast(m_sysVolumeSliderItemMap[2])->setDescription(input); } void Ukui2MainWindow::setStackWidgetCurrentIndex(int idx) { if (0 == idx) setSize(QSize(345, 100)); else setSize(QSize(420, 320)); m_pStackedWidget->setCurrentIndex(idx); } ukui-volume-control/tray/Ukui4TrayIcon.cpp0000664000175000017500000000626315171074712017611 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "Ukui4TrayIcon.h" #include #include #include "IMainWindow.h" UkuiVersion4TrayIcon::UkuiVersion4TrayIcon(UkmediaTrayIcon* trayIcon, IMainWindow* window) : ISoundSystemTrayIcon(trayIcon), m_pMainWid(window) { } void UkuiVersion4TrayIcon::initTray() { m_pMenu = new QMenu(); m_pMenu->setAttribute(Qt::WA_NoMouseReplay); QAction* soundPreferenceAction = new QAction(tr("Sound preference ukui 4"), this); soundPreferenceAction->setIcon(QIcon::fromTheme("document-page-setup-symbolic")); m_pMenu->addAction(soundPreferenceAction); m_pTrayIcon->setContextMenu(m_pMenu); connect(soundPreferenceAction, SIGNAL(triggered()), this, SLOT(soundPreferenceActionTriggered())); } void UkuiVersion4TrayIcon::initSlot() { connect(m_pTrayIcon, SIGNAL(wheelRollEventSignal(bool)), this, SLOT(trayWheelRollEventSlot(bool))); connect(m_pTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),\ this, SLOT(activatedSystemTrayIconSlot(QSystemTrayIcon::ActivationReason))); } void UkuiVersion4TrayIcon::setVisible(bool visible) { m_pTrayIcon->setVisible(visible); } void UkuiVersion4TrayIcon::setIcon(const QIcon& icon) { m_pTrayIcon->setIcon(icon); } void UkuiVersion4TrayIcon::activatedSystemTrayIconSlot(QSystemTrayIcon::ActivationReason reason) { switch(reason) { //鼠标中间键点击图标 case QSystemTrayIcon::MiddleClick: { // if (this->isHidden()) { // mouseMeddleClickedTraySlot(); // } // else { // hideWindow(); // } std::cout << "QSystemTrayIcon::MiddleClick" << std::endl; break; } //鼠标左键点击图标 case QSystemTrayIcon::Trigger: { m_pMainWid->triggerWidget(); std::cout << "QSystemTrayIcon::Trigger" << std::endl; break; } //鼠标左键双击图标 case QSystemTrayIcon::DoubleClick: { // hideWindow(); // break; std::cout << "QSystemTrayIcon::DoubleClick" << std::endl; } case QSystemTrayIcon::Context:{ // if(this ->isHidden()) { // menu->popup(QCursor::pos()); // } // else{ // this->hideWindow(); // } std::cout << "QSystemTrayIcon::Context" << std::endl; break; } default: break; } } void UkuiVersion4TrayIcon::soundPreferenceActionTriggered() { QProcess::startDetached(QString("ukui-control-center -m Audio")); } ukui-volume-control/tray/XatomHelper.cpp0000664000175000017500000001677615171074712017401 0ustar fengfeng/* * KWin Style UKUI * * Copyright (C) 2020, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: Yue Lan * */ #include "XatomHelper.h" #include #include #include #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #include #else #endif #include #include #include static XAtomHelper *global_instance = nullptr; XAtomHelper *XAtomHelper::getInstance() { if(!global_instance) global_instance = new XAtomHelper; return global_instance; } bool XAtomHelper::isFrameLessWindow(int winId) { auto hints = getInstance()->getWindowMotifHint(winId); if(hints.flags == MWM_HINTS_DECORATIONS && hints.functions == 1) { return true; } return false; } bool XAtomHelper::isWindowDecorateBorderOnly(int winId) { return isWindowMotifHintDecorateBorderOnly(getInstance()->getWindowMotifHint(winId)); } bool XAtomHelper::isWindowMotifHintDecorateBorderOnly(const MotifWmHints &hint) { bool isDeco = false; if(hint.flags & MWM_HINTS_DECORATIONS && hint.flags != MWM_HINTS_DECORATIONS) { if(hint.decorations == MWM_DECOR_BORDER) isDeco = true; } return isDeco; } bool XAtomHelper::isUKUICsdSupported() { // fixme: return false; } bool XAtomHelper::isUKUIDecorationWindow(int winId) { if(m_ukuiDecorationAtion == None) return false; Atom type; int format; ulong nitems; ulong bytes_after; uchar *data; bool isUKUIDecoration = false; Display* dpy = nullptr; #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) dpy = QX11Info::display(); #else dpy = XOpenDisplay(nullptr); #endif XGetWindowProperty(dpy, winId, m_ukuiDecorationAtion, 0, LONG_MAX, false, m_ukuiDecorationAtion, &type, &format, &nitems, &bytes_after, &data); if(type == m_ukuiDecorationAtion) { if(nitems == 1) { isUKUIDecoration = data[0]; } } return isUKUIDecoration; } UnityCorners XAtomHelper::getWindowBorderRadius(int winId) { UnityCorners corners; Atom type; int format; ulong nitems; ulong bytes_after; uchar *data; if(m_unityBorderRadiusAtom != None) { Display* dpy = nullptr; #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) dpy = QX11Info::display(); #else dpy = XOpenDisplay(nullptr); #endif XGetWindowProperty(dpy, winId, m_unityBorderRadiusAtom, 0, LONG_MAX, false, XA_CARDINAL, &type, &format, &nitems, &bytes_after, &data); if(type == XA_CARDINAL) { if(nitems == 4) { corners.topLeft = static_cast(data[0]); corners.topRight = static_cast(data[1 * sizeof(ulong)]); corners.bottomLeft = static_cast(data[2 * sizeof(ulong)]); corners.bottomRight = static_cast(data[3 * sizeof(ulong)]); } XFree(data); } } return corners; } void XAtomHelper::setWindowBorderRadius(int winId, const UnityCorners &data) { if(m_unityBorderRadiusAtom == None) return; ulong corners[4] = {data.topLeft, data.topRight, data.bottomLeft, data.bottomRight}; Display* dpy = nullptr; #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) dpy = QX11Info::display(); #else dpy = XOpenDisplay(nullptr); #endif XChangeProperty(dpy, winId, m_unityBorderRadiusAtom, XA_CARDINAL, 32, PropModeReplace, (const unsigned char *) &corners, sizeof(corners) / sizeof(corners[0])); } void XAtomHelper::setWindowBorderRadius(int winId, int topLeft, int topRight, int bottomLeft, int bottomRight) { if(m_unityBorderRadiusAtom == None) return; ulong corners[4] = {(ulong)topLeft, (ulong)topRight, (ulong)bottomLeft, (ulong)bottomRight}; Display* dpy = nullptr; #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) dpy = QX11Info::display(); #else dpy = XOpenDisplay(nullptr); #endif XChangeProperty(dpy, winId, m_unityBorderRadiusAtom, XA_CARDINAL, 32, PropModeReplace, (const unsigned char *) &corners, sizeof(corners) / sizeof(corners[0])); } void XAtomHelper::setUKUIDecoraiontHint(int winId, bool set) { if(m_ukuiDecorationAtion == None) return; Display* dpy = nullptr; #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) dpy = QX11Info::display(); #else if (auto x11 = qApp->nativeInterface()) { dpy = x11->display(); } else { dpy = XOpenDisplay(nullptr); } #endif XChangeProperty(dpy, winId, m_ukuiDecorationAtion, m_ukuiDecorationAtion, 32, PropModeReplace, (const unsigned char *) &set, 1); } void XAtomHelper::setWindowMotifHint(int winId, const MotifWmHints &hints) { if(m_unityBorderRadiusAtom == None) return; Display* dpy = nullptr; #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) dpy = QX11Info::display(); #else if (auto x11 = qApp->nativeInterface()) { dpy = x11->display(); } else { dpy = XOpenDisplay(nullptr); } #endif XChangeProperty(dpy, winId, m_motifWMHintsAtom, m_motifWMHintsAtom, 32, PropModeReplace, (const unsigned char *)&hints, sizeof(MotifWmHints) / sizeof(ulong)); } MotifWmHints XAtomHelper::getWindowMotifHint(int winId) { MotifWmHints hints; if(m_unityBorderRadiusAtom == None) return hints; uchar *data; Atom type; int format; ulong nitems; ulong bytes_after; Display* dpy = nullptr; #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) dpy = QX11Info::display(); #else dpy = XOpenDisplay(nullptr); #endif XGetWindowProperty(dpy, winId, m_motifWMHintsAtom, 0, sizeof(MotifWmHints) / sizeof(long), false, AnyPropertyType, &type, &format, &nitems, &bytes_after, &data); if(type == None) { return hints; } else { hints = *(MotifWmHints *)data; XFree(data); } return hints; } XAtomHelper::XAtomHelper(QObject *parent) : QObject(parent) { const QString platform = QGuiApplication::platformName(); if(!platform.contains(QLatin1String("x11"), Qt::CaseInsensitive) && !platform.contains(QLatin1String("xcb"), Qt::CaseInsensitive)) return; Display* dpy = nullptr; #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) dpy = QX11Info::display(); #else dpy = XOpenDisplay(nullptr); #endif m_motifWMHintsAtom = XInternAtom(dpy, "_MOTIF_WM_HINTS", true); m_unityBorderRadiusAtom = XInternAtom(dpy, "_UNITY_GTK_BORDER_RADIUS", false); m_ukuiDecorationAtion = XInternAtom(dpy, "_KWIN_UKUI_DECORAION", false); } Atom XAtomHelper::registerUKUICsdNetWmSupportAtom() { // fixme: return None; } void XAtomHelper::unregisterUKUICsdNetWmSupportAtom() { // fixme: } ukui-volume-control/tray/UkmediaMainWidget.cpp0000664000175000017500000005255315171074712020472 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "UkmediaMainWidget.h" #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #include #include #include #else #include #include #include #endif #include #include "TrayClientMethod.h" #include "DBusClient.h" #include "XatomHelper.h" UkmediaMainWidget::UkmediaMainWidget(QWidget* parent) : QWidget(parent) { getDesktopEnvironmentVersion(); initUi(); initSlots(); initClientManager(); initData(); setFocusPolicy(Qt::NoFocus); setProperty("useStyleWindowManager", false); // 禁用拖动 } void UkmediaMainWidget::initUi() { switch (m_version) { case DesktopEnvironmentVersion::DESKTOP_ENVIRONMENT_VERSION_UKUI2: m_pMainWid = new Ukui2MainWindow(m_sysVolumeSliderItemMap, m_appVolumeSliderItemMap); m_pTrayIcon = std::make_shared(new UkmediaTrayIcon(), m_pMainWid); break; case DesktopEnvironmentVersion::DESKTOP_ENVIRONMENT_VERSION_UKUI3: case DesktopEnvironmentVersion::DESKTOP_ENVIRONMENT_VERSION_UKUI4: m_pMainWid = new Ukui2MainWindow(m_sysVolumeSliderItemMap, m_appVolumeSliderItemMap); m_pTrayIcon = std::make_shared(new UkmediaTrayIcon(), m_pMainWid); // m_pMainWid = new Ukui4MainWindow(m_sysVolumeSliderItemMap, m_appVolumeSliderItemMap, m_devicePortItemMap); // m_pTrayIcon = std::make_shared(new UkmediaTrayIcon(), m_pMainWid); break; case DesktopEnvironmentVersion::DESKTOP_ENVIRONMENT_VERSION_UKUI5: break; default: break; } m_pTrayIcon->initTray(); m_pTrayIcon->initSlot(); m_pTrayIcon->setVisible(true); m_pMainWid->initUi(); m_pMainWid->initSlots(); m_pMainWid->setParentWidget(this); // KF6 API: 直接设置窗口属性;KF5 使用 KWindowInfo + KWindowSystem::setState // 使用 KX11Extras 设置窗口状态以兼容 KF5/KF6 KX11Extras::setState(this->winId(), NET::SkipTaskbar | NET::SkipPager); removeHeader(this); } void UkmediaMainWidget::initSystemDeviceList() { QDBusMessage replyList = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getAvailablePortList", EnumToInt(NodeType::OUTPUT)); if (replyList.arguments().isEmpty()) { qDebug() << "返回参数为空"; return; } qDebug() << " getAvailablePortList: " << replyList; const QDBusArgument& dbusArgs = replyList.arguments().at(0).value(); QList deviceList; dbusArgs.beginArray(); while (!dbusArgs.atEnd()) { ClientDeviceInfo info; dbusArgs >> info; deviceList.push_back(info); } dbusArgs.endArray(); for (const auto& v : deviceList) { TrayClientMethod::getInstance().addDeviceItem(EnumToInt(NodeType::OUTPUT), v); } } void UkmediaMainWidget::initCurrentDefaultDevice() { ClientDefaultDeviceInfo outputInfo = getDefaultDevice(1, ""); ClientDefaultDeviceInfo inputInfo = getDefaultDevice(0, ""); int index = -1; for (const auto& it : m_devicePortItemMap) { if (outputInfo.cardName == it.second->getCardName() && outputInfo.activePortName == it.second->getPortName()) { index = it.first; } } TrayClientMethod::getInstance().updateDescription(inputInfo.activePortDesc, outputInfo.activePortDesc); TrayClientMethod::getInstance().setCurrentIndex(index); } void UkmediaMainWidget::initAppVolumeList() { QDBusMessage replyList = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getSinkInputList"); if (replyList.arguments().isEmpty()) { qDebug() << "arguments is nullptr..."; return; } const QDBusArgument& siArgs = replyList.arguments().at(0).value(); QList streamList; siArgs.beginArray(); while (!siArgs.atEnd()) { StreamInfo info; siArgs >> info; streamList.push_back(info); } siArgs.endArray(); replyList = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getSourceOutputList"); if (replyList.arguments().isEmpty()) { qDebug() << "arguments is nullptr..."; return; } const QDBusArgument& soArgs = replyList.arguments().at(0).value(); soArgs.beginArray(); while (!siArgs.atEnd()) { StreamInfo info; soArgs >> info; streamList.push_back(info); } soArgs.endArray(); for (const auto& s : streamList) { if ("filter" != s.role) { TrayClientMethod::getInstance().addAppItem(s.index, s.iconName, s.name); } } } void UkmediaMainWidget::initSystemVolumeSlider() { for (const auto& [k, v] : m_sysVolumeSliderItemMap) { switch (IntToEnum(k)) { case VolumeSliderType::VOLUME_SLIDER_MINI_OUTPUT: case VolumeSliderType::VOLUME_SLIDER_DEVICE_OUTPUT: case VolumeSliderType::VOLUME_SLIDER_APP_OUTPUT: { QDBusReply muted = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getMute", EnumToInt(NodeType::OUTPUT), ""); QDBusReply reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getVolume", EnumToInt(NodeType::OUTPUT), ""); TrayClientMethod::getInstance().setValue(EnumToInt(NodeType::OUTPUT), k, reply); QString iconStr; if (muted) { iconStr = iconNameOutputs[0]; } else if(reply <= 0){ iconStr = iconNameOutputs[0]; } else if (reply > 0 && reply <= 33) { iconStr = iconNameOutputs[1]; } else if (reply >33 && reply <= 66) { iconStr = iconNameOutputs[2]; } else { iconStr = iconNameOutputs[3]; } m_pTrayIcon->setIcon(QIcon::fromTheme(iconStr)); break; } case VolumeSliderType::VOLUME_SLIDER_DEVICE_INPUT: { bool muted = getMute(EnumToInt(NodeType::INPUT), ""); int volume = getVolume(EnumToInt(NodeType::INPUT), ""); TrayClientMethod::getInstance().setValue(EnumToInt(NodeType::INPUT), k, volume); } default: break; } } } void UkmediaMainWidget::initAppVolumeSlider() { for (const auto& [k, v] : m_appVolumeSliderItemMap) { QDBusReply reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getVolume", EnumToInt(NodeType::SINK_INPUT), ""); TrayClientMethod::getInstance().setValue(EnumToInt(NodeType::SINK_INPUT), k, reply); } } void UkmediaMainWidget::initData() { if (DesktopEnvironmentVersion::DESKTOP_ENVIRONMENT_VERSION_UKUI2 == m_version) { static_cast(m_pMainWid)->setStackWidgetCurrentIndex(0); } else { m_pMainWid->setSize(QSize(420, 476)); } initSystemDeviceList(); initAppVolumeList(); initCurrentDefaultDevice(); initSystemVolumeSlider(); initAppVolumeSlider(); } void UkmediaMainWidget::initSlots() { if (m_pMainWid) { m_pMainWid->addVolumeObserver([this](int type, int value) { setVolume(type, "", value); }); m_pMainWid->addMuteObserver([this](int type, const QString& name) { setMute(type, name, !getMute(type, name)); }); m_pMainWid->addDeviceObserver([this](int type, const QString& portName, const QString& cardName) { setDefaultDevice(type, portName, cardName); }); m_pMainWid->addTrayIconTriggerObserver([this]() { if (this->isHidden()) { setGeometry(caculatePosition()); show(); } else { hide(); } }); m_pMainWid->addSizeChangedObserver([this](const QSize& size) { resize(size); setGeometry(caculatePosition()); }); } connect(&DBusClient::getInstance(), SIGNAL(volumeChangedSignal(int, int, const QDBusVariant&)), this, SLOT(volumeChangedSlots(int, int, const QDBusVariant&))); connect(&DBusClient::getInstance(), SIGNAL(deviceChangedSignal(int, const QString&, const QString&)), this, SLOT(deviceChangedSlots(int, const QString&, const QString&))); connect(&DBusClient::getInstance(), SIGNAL(deviceAdjustSignal(int)), this, SLOT(deviceAdjustSlots(int))); connect(&DBusClient::getInstance(), SIGNAL(settingsChangedSignal(const QString&, const QDBusVariant&)), this, SLOT(settingsChangedSlots(const QString&, const QDBusVariant&))); connect(&DBusClient::getInstance(), SIGNAL(addStreamSignal(int, int, const QString&, const QString&)), this, SLOT(addStreamSlots(int, int, const QString&, const QString&))); connect(&DBusClient::getInstance(), SIGNAL(removeStreamSignal(int)), this, SLOT(removeStreamSlots(int))); } void UkmediaMainWidget::initClientManager() { m_pClientManager = std::make_shared(m_sysVolumeSliderItemMap, m_appVolumeSliderItemMap, m_pMainWid); DBusClient::getInstance().initDbusConnect(); TrayClientMethod::getInstance().setManager(m_pClientManager); } QRect UkmediaMainWidget::caculatePosition() { QRect availableGeo = QGuiApplication::screenAt(QCursor::pos())->geometry(); int x, y; int margin = 8; int panelType = 0; // PanelSettings::getInstance().getPanelType(); // 经典任务栏 if (0 == panelType) { switch (m_panelPosition) { case PanelPosition::PANEL_POSITION_TOP: { x = availableGeo.x() + availableGeo.width() - this->width() - margin; y = availableGeo.y() + m_panelSize + margin; break; } case PanelPosition::PANEL_POSITION_BOTTOM: { x = availableGeo.x() + availableGeo.width() - this->width() - margin; y = availableGeo.y() + availableGeo.height() - m_panelSize - this->height() - margin; break; } case PanelPosition::PANEL_POSITION_LEFT: { x = availableGeo.x() + m_panelSize + margin; y = availableGeo.y() + availableGeo.height() - this->height() - margin; break; } case PanelPosition::PANEL_POSITION_RIGHT: { x = availableGeo.x() + availableGeo.width() - m_panelSize - this->width() - margin; y = availableGeo.y() + availableGeo.height() - this->height() - margin; break; } default: break; } } else if (1 == panelType) { } return QRect(x, y, this->width(), this->height()); } void UkmediaMainWidget::removeHeader(QWidget* widget) { if(!widget) return; QString platform = QGuiApplication::platformName(); if(platform.startsWith(QLatin1String("wayland"),Qt::CaseInsensitive)) { installEventFilter(this); } else { MotifWmHints hints; hints.flags = MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS; hints.functions = MWM_FUNC_ALL; hints.decorations = MWM_DECOR_BORDER; XAtomHelper::getInstance()->setWindowMotifHint(widget->winId(), hints); } } void UkmediaMainWidget::volumeChangedSlots(int type, int idx, const QDBusVariant& v) { TrayClientMethod::getInstance().setValue(type, idx, v.variant().toInt()); } void UkmediaMainWidget::deviceChangedSlots(int type, const QString& portName, const QString& cardName) { switch (IntToEnum(type)) { case NodeType::OUTPUT: { int index = -1; for (const auto& it : m_devicePortItemMap) { qDebug() << "UkmediaMainWidget::deviceChangedSlot, type: " << type << " cardName:" << cardName << " it.second->getCardName(): " << it.second->getCardName() << " \n" << " portName: " << portName << " it.second->getPortName(): " << it.second->getPortName(); if (cardName == it.second->getCardName() && portName == it.second->getPortName()) { index = it.first; break; } } qDebug() << "setcurrent index: " << index; TrayClientMethod::getInstance().setCurrentIndex(index); break; } case NodeType::INPUT: case NodeType::SINK_INPUT: case NodeType::SOURCE_OUTPUT: default: break; } } void UkmediaMainWidget::deviceAdjustSlots(int type) { switch (IntToEnum(type)) { case NodeType::OUTPUT: { // 暂时先删除在添加 // for (const auto& it : m_devicePortItemMap) { // TrayClientMethod::getInstance().removeDeviceItem(it.second->getPortName(), it.second->getCardName()); // } // for (auto it = m_devicePortItemMap.begin(); it != m_devicePortItemMap.end(); ) { // it = TrayClientMethod::getInstance().removeDeviceItem(it->second->getPortName(), it->second->getCardName()); // } TrayClientMethod::getInstance().removeDeviceItem("", ""); initSystemDeviceList(); break; } case NodeType::INPUT: case NodeType::SINK_INPUT: case NodeType::SOURCE_OUTPUT: default: break; } } void UkmediaMainWidget::settingsChangedSlots(const QString& key, const QDBusVariant& v) { if (EVENT_SOUND_KEY == key) { // ClientMethod::getInstance().setChecked(EnumToInt(SwitchButtonType::SWITCH_BUTTON_ALERT), v.variant().toBool()); } else if (THEME_NAME_KEY == key) { } else if (CUSTOM_THEME_KEY == key) { } else if (MONO_AUDIO_KEY == key) { // ClientMethod::getInstance().setChecked(EnumToInt(SwitchButtonType::SWITCH_BUTTON_MONO), v.variant().toBool()); DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "LoadModule", "module-mono", ""); } else if (NOISE_REDUCTION_KEY == key) { // ClientMethod::getInstance().setChecked(EnumToInt(SwitchButtonType::SWITCH_BUTTON_ECHOCANCEL), v.variant().toBool()); } else if (LOOPBACK_KEY == key) { // ClientMethod::getInstance().setChecked(EnumToInt(SwitchButtonType::SWITCH_BUTTON_LOOPBACK), v.variant().toBool()); } else if (VOLUME_BOOST_KEY == key) { // ClientMethod::getInstance().setChecked(EnumToInt(SwitchButtonType::SWITCH_BUTTON_VOLUMEBOOST), v.variant().toBool()); } else if (VOLUME_BOOST_VOLUME_KEY == key) { } else if (STARTUP_MUSIC_KEY == key) { // ClientMethod::getInstance().setChecked(EnumToInt(SwitchButtonType::SWITCH_BUTTON_STARTUP), v.variant().toBool()); } else if (NOTIFY_GENERAL_KEY == key) { } else if (VOLUME_CHANGED_KEY == key) { } else if (ALERT_VOLUME_KEY == key) { } } void UkmediaMainWidget::addStreamSlots(int idx, int value, const QString& iconName, const QString& descName) { TrayClientMethod::getInstance().addAppItem(idx, iconName, descName); } void UkmediaMainWidget::removeStreamSlots(int idx) { TrayClientMethod::getInstance().removeAppItem(idx); } DesktopEnvironmentVersion UkmediaMainWidget::getDesktopEnvironmentVersion() { QDBusReply reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getDesktopEnvironmentVersion"); return m_version = IntToEnum(reply); } int UkmediaMainWidget::getVolume(int type, const QString& name) { QDBusReply reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getVolume", type, name); return reply; } void UkmediaMainWidget::setVolume(int type, const QString& name, int vaule) { qDebug() << "UkmediaMainWidget::setVolume" << " type: " << type << " vaule: " << vaule; DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "setVolume", type, name, vaule); } bool UkmediaMainWidget::getMute(int type, const QString& name) { QDBusReply reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getMute", type, name); return reply; } void UkmediaMainWidget::setMute(int type, const QString& name, bool muted) { DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "setMute", type, name, muted); } ClientDefaultDeviceInfo UkmediaMainWidget::getDefaultDevice(int type, const QString& name) const { QDBusMessage replyList = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getDefaultDevice", type, name); if (replyList.arguments().isEmpty()) { qDebug() << "arguments is nullptr..."; return {}; } const QDBusArgument& siArgs = replyList.arguments().at(0).value(); ClientDefaultDeviceInfo info; siArgs >> info; return info; } void UkmediaMainWidget::setDefaultDevice(int type, const QString& portName, const QString& cardName) { switch (IntToEnum(type)) { case NodeType::OUTPUT: break; case NodeType::INPUT: case NodeType::SINK_INPUT: case NodeType::SOURCE_OUTPUT: default: qDebug() << type << " type is not expect"; return ; } DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "setDefaultDevice", type, portName, cardName); } ukui-volume-control/tray/Ukui2TrayIcon.h0000664000175000017500000000271515171074712017252 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 UKUIVERSIONTWOTRAYICON_H #define UKUIVERSIONTWOTRAYICON_H #include #include "ISoundSystemTrayIcon.h" class IMainWindow; class UkuiVersionTwoTrayIcon : public QObject, public ISoundSystemTrayIcon { Q_OBJECT public: UkuiVersionTwoTrayIcon(UkmediaTrayIcon*, IMainWindow*); ~UkuiVersionTwoTrayIcon() = default; public: virtual void initTray() override; virtual void initSlot() override; virtual void setVisible(bool) override; virtual void setIcon(const QIcon&); private Q_SLOTS: void soundPreferenceActionTriggered(); void activatedSystemTrayIconSlot(QSystemTrayIcon::ActivationReason); private: IMainWindow* m_pMainWid; }; #endif // UKUIVERSIONTWOTRAYICON_H ukui-volume-control/tray/IAppVolumeWidget.h0000664000175000017500000000245015171074712017763 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 IAPPVOLUMEWIDGET_H #define IAPPVOLUMEWIDGET_H #include #include class IAppVolumeWidget { public: IAppVolumeWidget() = default; virtual ~IAppVolumeWidget() {} public: virtual void initUi() = 0; virtual void showWidget() = 0; virtual QWidget* getWidget() = 0; virtual void addAppWidget(int) = 0; virtual void removeAppWidget(int) = 0; virtual void addAppToWidget(const QString&, const QString&) = 0; virtual void removeAppfromWidget(const QString&) = 0; }; #endif // IAPPVOLUMEWIDGET_H ukui-volume-control/tray/Ukui2SystemVolumeWidget.cpp0000664000175000017500000001013015171074712021663 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "Ukui2SystemVolumeWidget.h" #include #include #include #include "IMainWindow.h" Ukui2SystemVolumeWidget::Ukui2SystemVolumeWidget(std::unordered_map>& map, QPushButton* switchBtn, QPushButton* devVolumeBtn, QPushButton* appVolumeBtn, IMainWindow* window, QWidget* parent) : m_sysVolumeSliderItemMap(map), m_pSwitchModuleBtn(switchBtn), m_pDeviceVolumeBtn(devVolumeBtn), m_pAppVolumeBtn(appVolumeBtn), m_pMainWid(std::move(window)), QWidget(parent) { initUi(); initSlots(); } Ukui2SystemVolumeWidget::~Ukui2SystemVolumeWidget() { // delete m_pMainWid; } void Ukui2SystemVolumeWidget::initUi() { m_pOutputLabel = new QLabel(tr("Output Device")); m_pInputLabel = new QLabel(tr("Input Device")); m_pBtnWidget = new QWidget(this); m_pBtnWidget->setFixedSize(38, 320); QVBoxLayout* vLayout1 = new QVBoxLayout(); vLayout1->addWidget(m_pDeviceVolumeBtn); vLayout1->addWidget(m_pAppVolumeBtn); vLayout1->setSpacing(3); vLayout1->setContentsMargins(0, 0, 0, 200); m_pBtnWidget->setLayout(vLayout1); QVBoxLayout* vLayout = new QVBoxLayout(); vLayout->addWidget(m_pOutputLabel); vLayout->addWidget(m_sysVolumeSliderItemMap[1]->getWidget()); vLayout->addWidget(m_pInputLabel); vLayout->addWidget(m_sysVolumeSliderItemMap[2]->getWidget()); vLayout->setSpacing(14); vLayout->setContentsMargins(0, 0, 0, 0); m_sysVolumeSliderItemMap[1]->setIcon(QIcon::fromTheme("audio-card")); m_sysVolumeSliderItemMap[2]->setIcon(QIcon("/usr/share/ukui-media/img/audio-input-microphone.svg")); QHBoxLayout* hLayout = new QHBoxLayout(); hLayout->addWidget(m_pBtnWidget); hLayout->addLayout(vLayout); hLayout->setContentsMargins(0, 0, 30, 30); setLayout(hLayout); } void Ukui2SystemVolumeWidget::initSlots() { for (const auto& it : m_sysVolumeSliderItemMap) { connect(static_cast(it.second->getSlider()->getWidget()), &QSlider::valueChanged, this, [this, it](int value) { if (0 == it.first || 1 == it.first) m_pMainWid->setVolume(1, value); else if (2 == it.first) m_pMainWid->setVolume(0, value); }); connect(static_cast(it.second->getMuteButton()), &QPushButton::clicked, this, [this, it]() { if (0 == it.first || 1 == it.first) m_pMainWid->setMute(1, ""); else if (2 == it.first) m_pMainWid->setMute(0, ""); }); } } void Ukui2SystemVolumeWidget::showWidget() { this->show(); } QWidget* Ukui2SystemVolumeWidget::getWidget() { return this; } void Ukui2SystemVolumeWidget::setValue(int vaule) { } void Ukui2SystemVolumeWidget::addDevice(const QString& iconName, const QVariantList& list) { } void Ukui2SystemVolumeWidget::removeDevice(const QString& portName, const QString& devName) { } void Ukui2SystemVolumeWidget::addDevicePortItem(std::shared_ptr item) { } void Ukui2SystemVolumeWidget::removeDevicePortItem(const QString& name, const QVariantList& v) { } void Ukui2SystemVolumeWidget::setCurrentIndex(int idx) { } void Ukui2SystemVolumeWidget::vauleChangedSlots(int vaule) { Q_EMIT vauleChanged(vaule); } ukui-volume-control/tray/UkmediaTrayIcon.h0000664000175000017500000000211315171074712017622 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 UKMEDIATRAYICON_H #define UKMEDIATRAYICON_H #include class UkmediaTrayIcon : public QSystemTrayIcon { Q_OBJECT public: UkmediaTrayIcon(QWidget* = nullptr); ~UkmediaTrayIcon(); Q_SIGNALS: void wheelRollEventSignal(bool); protected: bool event(QEvent *e) ; }; #endif // UKMEDIATRAYICON_H ukui-volume-control/tray/Ukui2SystemVolumeWidget.h0000664000175000017500000000511015171074712021332 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 UKUI2SYSTEMVOLUMEWIDGET_H #define UKUI2SYSTEMVOLUMEWIDGET_H #include #include #include #include #include #include #include "ISystemVolumeWidget.h" #include "ITrayVolumeSliderItem.h" #include "ukui4.0/TrayListViewDelegate.h" #include "ukui4.0/TrayListViewItemModel.h" #include "IDevicePortSelectItem.h" class IMainWindow; class Ukui2SystemVolumeWidget : public QWidget, public ISystemVolumeWidget { Q_OBJECT public: explicit Ukui2SystemVolumeWidget(std::unordered_map>&, QPushButton*, QPushButton*, QPushButton*, IMainWindow*, QWidget* = nullptr); ~Ukui2SystemVolumeWidget(); public: virtual void initUi() override; virtual void initSlots() override; virtual void showWidget() override; virtual QWidget* getWidget() override; virtual void setValue(int) override; virtual void addDevice(const QString&, const QVariantList&) override; virtual void removeDevice(const QString&, const QString&) override; virtual void addDevicePortItem(std::shared_ptr) override; virtual void removeDevicePortItem(const QString&, const QVariantList&) override; virtual void setCurrentIndex(int) override; Q_SIGNALS: void vauleChanged(int); private Q_SLOTS: void vauleChangedSlots(int); private: QLabel* m_pOutputLabel = nullptr; QLabel* m_pInputLabel = nullptr; QWidget* m_pBtnWidget = nullptr; QPushButton* m_pSwitchModuleBtn = nullptr; QPushButton* m_pDeviceVolumeBtn = nullptr; QPushButton* m_pAppVolumeBtn = nullptr; std::unordered_map>& m_sysVolumeSliderItemMap; IMainWindow* m_pMainWid; }; #endif // UKUI2SYSTEMVOLUMEWIDGET_H ukui-volume-control/tray/Ukui2AppVolumeWidget.cpp0000664000175000017500000001425315171074712021131 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "Ukui2AppVolumeWidget.h" #include #include #include #include #include "IMainWindow.h" Ukui2AppVolumeWidget::Ukui2AppVolumeWidget(std::unordered_map>& map, std::unordered_map>& appMap, QPushButton* switchBtn, QPushButton* devVolumeBtn, QPushButton* appVolumeBtn, IMainWindow* window, QWidget* parent) : m_sysVolumeSliderItemMap(map), m_appVolumeSliderItemMap(appMap), m_pSwitchModuleBtn(switchBtn), m_pDeviceVolumeBtn(devVolumeBtn), m_pAppVolumeBtn(appVolumeBtn), m_pMainWid(std::move(window)), QWidget(parent) { initUi(); } Ukui2AppVolumeWidget::~Ukui2AppVolumeWidget() { // delete m_pMainWid; } void Ukui2AppVolumeWidget::initUi() { m_pSysVolumeLabel = new QLabel(tr("System Volume")); m_pAppLabel = new QLabel(tr("Application Volume")); m_pBtnWidget = new QWidget(this); m_pBtnWidget->setFixedSize(38, 320); // 应用音量widget m_pAppArea = new QScrollArea(this); m_pAppArea->setFixedSize(412, 245); m_pAppArea->setFrameShape(QFrame::NoFrame); m_pAppArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_pAppArea->verticalScrollBar()->setProperty("drawScrollBarGroove", false); // 滚动条背景透明 QPalette pal = m_pAppArea->palette(); pal.setColor(QPalette::Window, QColor(0x00, 0xff, 0x00, 0x00)); m_pAppArea->setPalette(pal); m_pDisplayAppVolumeWidget = new QWidget(m_pAppArea); m_pDisplayAppVolumeWidget->setFixedWidth(412); m_pAppArea->setWidget(m_pDisplayAppVolumeWidget); m_pVlayout = new QVBoxLayout(m_pDisplayAppVolumeWidget); m_pDisplayAppVolumeWidget->setLayout(m_pVlayout); // 整体布局 QVBoxLayout* vLayout = new QVBoxLayout; vLayout->addWidget(m_pAppLabel); vLayout->addSpacing(3); vLayout->addWidget(m_sysVolumeSliderItemMap[3]->getWidget()); vLayout->addSpacing(13); vLayout->addWidget(m_pSysVolumeLabel); vLayout->addSpacing(3); vLayout->addWidget(m_pAppArea); vLayout->setSpacing(0); vLayout->setContentsMargins(8, 12, 0, 0); QVBoxLayout* vLayout1 = new QVBoxLayout(); vLayout1->addWidget(m_pDeviceVolumeBtn); vLayout1->addWidget(m_pAppVolumeBtn); // vLayout1->addSpacerItem(new QSpacerItem(10, 30, QSizePolicy::Expanding)); vLayout1->setSpacing(3); vLayout1->setContentsMargins(0, 0, 0, 200); m_pBtnWidget->setLayout(vLayout1); QHBoxLayout* hLayout = new QHBoxLayout(); hLayout->addWidget(m_pBtnWidget); hLayout->addLayout(vLayout); hLayout->setContentsMargins(0, 0, 0, 0); setLayout(hLayout); } void Ukui2AppVolumeWidget::showWidget() { this->show(); } QWidget* Ukui2AppVolumeWidget::getWidget() { return this; } void Ukui2AppVolumeWidget::addAppWidget(int idx) { m_pVlayout->addWidget(m_appVolumeSliderItemMap[idx]->getWidget()); m_pVlayout->setSpacing(2); m_pVlayout->setContentsMargins(0, 0, 0, 0); // m_pDisplayAppVolumeWidget->adjustSize(); m_pDisplayAppVolumeWidget->resize(358, 14+m_pVlayout->count()*68); m_pVlayout->update(); auto it = m_appVolumeSliderItemMap.find(idx); if (it != m_appVolumeSliderItemMap.end()) { connect(static_cast(it->second->getSlider()->getWidget()), &QSlider::valueChanged, this, [this](int value) { m_pMainWid->setVolume(2, value); qDebug() << "AppVolumeWidgetUkui2 value changed , value: " << value; }); } else { qDebug() << "m_appVolumeSliderItemMap not found such as :" << idx; } qDebug() << "add wid..., displayVolumeWidge.size" << m_pDisplayAppVolumeWidget->rect(); } void Ukui2AppVolumeWidget::removeAppWidget(int idx) { auto it = m_appVolumeSliderItemMap.find(idx); if (it != m_appVolumeSliderItemMap.end()) { m_appVolumeSliderItemMap.erase(it); } else { qDebug() << "not found " << idx << " in m_appVolumeSliderItemMap."; } m_pVlayout->setSpacing(2); m_pVlayout->setContentsMargins(0, 0, 0, 0); m_pDisplayAppVolumeWidget->resize(404, m_appVolumeSliderItemMap.size()*2); m_pVlayout->update(); } void Ukui2AppVolumeWidget::addAppToWidget(const QString& appName, const QString& iconName) { m_pVlayout->addWidget(m_appVolumeSliderItemMap[0]->getWidget()); m_pVlayout->setSpacing(2); m_pVlayout->setContentsMargins(0, 0, 0, 0); // m_pDisplayAppVolumeWidget->adjustSize(); m_pDisplayAppVolumeWidget->resize(358, 14+m_pVlayout->count()*68); // m_pDisplayAppVolumeWidget->resize(412, m_pVlayout->count()*50); m_pVlayout->update(); } void Ukui2AppVolumeWidget::removeAppfromWidget(const QString& appName) { auto index = 1; if (-1 == index) { qDebug() << "can not found " << appName << " in output stream"; return; } if (m_pVlayout->takeAt(index) != 0) { auto wid = m_pVlayout->takeAt(index)->widget(); m_pVlayout->removeWidget(wid); wid->setParent(nullptr); delete wid; } m_pVlayout->setSpacing(2); m_pVlayout->setContentsMargins(0, 0, 0, 0); // m_pDisplayAppVolumeWidget->adjustSize(); m_pDisplayAppVolumeWidget->resize(404, m_pVlayout->count()*50); m_pVlayout->update(); qDebug() << "remove wid..., displayVolumeWidge.size" << m_pDisplayAppVolumeWidget->rect(); } ukui-volume-control/tray/IMainWindow.h0000664000175000017500000000757615171074712017001 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 MAINWINDOW_H #define MAINWINDOW_H #include #include #include #include "ISystemVolumeWidget.h" #include "IAppVolumeWidget.h" #include "DBusClient.h" class IMainWindow { public: IMainWindow() = default; virtual ~IMainWindow() {} public: using VolumeChangedCallback = std::function; using MuteChangedCallback = std::function; using DeviceChangedCallback = std::function; using TrayIconTriggerCallback = std::function; using SizeChangedCallback = std::function; std::shared_ptr addVolumeObserver(const VolumeChangedCallback&); void removeVolumeObserver(const std::shared_ptr&); void setVolume(int, int); std::shared_ptr addMuteObserver(const MuteChangedCallback&); void removeMuteObserver(const std::shared_ptr&); void setMute(int, const QString&); std::shared_ptr addDeviceObserver(const DeviceChangedCallback&); void removeDeviceObserver(const std::shared_ptr&); void setDefaultDevice(int, const QString&, const QString&); std::shared_ptr addTrayIconTriggerObserver(const TrayIconTriggerCallback&); void removeTrayIconTriggerObserver(const std::shared_ptr&); void triggerWidget(); std::shared_ptr addSizeChangedObserver(const SizeChangedCallback&); void removeSizeChangedObserver(const std::shared_ptr&); void setSize(const QSize&); public: virtual void initUi() = 0; virtual void initSlots() = 0; virtual void addAppItem(int, const QString&, const QString&) = 0; virtual void removeAppItem(int) = 0; virtual void addAppToWidget(const QString&, const QString&) = 0; virtual void removeAppFromWidget(const QString&) = 0; virtual void addDevToWidget(const QString&, const ClientDeviceInfo&) = 0; virtual void removeDevFromWidget(const QString&, const QString&) = 0; virtual void setCurrentIndex(int) = 0; virtual void setParentWidget(QWidget*) = 0; virtual void setValue(int, int, int) = 0; virtual void updateDescription(const QString&, const QString&) = 0; private: void volumeNotify(int, int); void muteNotify(int, const QString&); void defaultDeviceNotify(int, const QString&, const QString&); void trayIconTriggerNotify(); void sizeChangedNotify(const QSize&); protected: std::shared_ptr m_pSysVolumeWidget = nullptr; std::shared_ptr m_pAppVolumeWidget = nullptr; private: std::vector> m_volumeObservers; std::vector> m_muteObservers; std::vector> m_deviceObservers; std::vector> m_trayIconObservers; std::vector> m_sizeChangedObservers; }; #endif // MAINWINDOW_H ukui-volume-control/tray/TrayClientManager.h0000664000175000017500000000362615171074712020155 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 TRAYCLIENTMANAGER_H #define TRAYCLIENTMANAGER_H #include #include #include #include #include "ITrayVolumeSliderItem.h" #include "IMainWindow.h" #include "DBusClient.h" class TrayClientManager { public: TrayClientManager(std::unordered_map>&, std::unordered_map>&, IMainWindow*); virtual ~TrayClientManager() = default; public: void setValue(int, int, int); void setIcon(int, int, const QIcon&); void setCurrentIndex(int idx); void addDevicePortItem(int, const ClientDeviceInfo&); void removeDevicePortItem(const QString&, const QString&); void addAppItem(int, const QString&, const QString&); void removeAppItem(int); void updateDescription(const QString&, const QString&); private: std::unordered_map>& m_volumeSliderItemMap; std::unordered_map>& m_appVolumeSliderItemMap; IMainWindow* m_pMainWid = nullptr; }; #endif // TRAYCLIENTMANAGER_H ukui-volume-control/tray/TrayClientMethod.cpp0000664000175000017500000000475315171074712020360 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "TrayClientMethod.h" #include TrayClientMethod& TrayClientMethod::getInstance() { static TrayClientMethod instance; return instance; } void TrayClientMethod::setManager(std::shared_ptr manager) { m_pClientManager = manager; } void TrayClientMethod::setValue(int type, int idx, int value) { if (m_pClientManager) m_pClientManager->setValue(type, idx, value); } void TrayClientMethod::setIcon(int type, const QIcon &icon) { if (m_pClientManager) m_pClientManager->setIcon(type, 1, icon); } void TrayClientMethod::setChecked(int type, bool checked) { // if (m_pClientManager) // m_pClientManager->setChecked(type, checked); // else { // qDebug() << "m_pClientManager is null, pluse init client manager!"; // } } void TrayClientMethod::setCurrentIndex(int idx) { if (m_pClientManager) m_pClientManager->setCurrentIndex(idx); } void TrayClientMethod::addDeviceItem(int type, const ClientDeviceInfo& info) { if (m_pClientManager) m_pClientManager->addDevicePortItem(type, info); } void TrayClientMethod::removeDeviceItem(const QString& portName, const QString& devName) { if (m_pClientManager) m_pClientManager->removeDevicePortItem(portName, devName); } void TrayClientMethod::addAppItem(int idx, const QString& iconName, const QString& descName) { if (m_pClientManager) m_pClientManager->addAppItem(idx, iconName, descName); } void TrayClientMethod::removeAppItem(int idx) { if (m_pClientManager) m_pClientManager->removeAppItem(idx); } void TrayClientMethod::updateDescription(const QString& input, const QString& output) { if (m_pClientManager) m_pClientManager->updateDescription(input, output); } ukui-volume-control/tray/TrayClientManager.cpp0000664000175000017500000000556015171074712020507 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "TrayClientManager.h" #include #include "Util.h" TrayClientManager::TrayClientManager(std::unordered_map>& volumeSliderMap, std::unordered_map>& appvolumeSliderMap, IMainWindow* window) : m_volumeSliderItemMap(volumeSliderMap), m_appVolumeSliderItemMap(appvolumeSliderMap), m_pMainWid(std::move(window)) { } void TrayClientManager::setValue(int type, int idx, int value) { // check type is vaild m_pMainWid->setValue(type, idx, value); } void TrayClientManager::setIcon(int type, int idx, const QIcon& icon) { switch (type) { case 0: case 1: m_volumeSliderItemMap[type]->setIcon(icon); break; case 2: case 3: m_appVolumeSliderItemMap[idx]->setIcon(icon); break; default: break; } } void TrayClientManager::setCurrentIndex(int idx) { if (m_pMainWid) m_pMainWid->setCurrentIndex(idx); } void TrayClientManager::addDevicePortItem(int type, const ClientDeviceInfo& info) { QString iconStr = ""; if (info.portName.contains("headphone", Qt::CaseInsensitive)) iconStr = "audio-headphones-symbolic"; else if (info.portName.contains("headset", Qt::CaseInsensitive)) iconStr = "audio-headset-symbolic"; else iconStr = "audio-volume-high-symbolic"; if (m_pMainWid) m_pMainWid->addDevToWidget(iconStr, info); } void TrayClientManager::removeDevicePortItem(const QString& portName, const QString& devName) { if (m_pMainWid) m_pMainWid->removeDevFromWidget(portName, devName); } void TrayClientManager::addAppItem(int idx, const QString& iconName, const QString& descName) { if (m_pMainWid) m_pMainWid->addAppItem(idx, iconName, descName); } void TrayClientManager::removeAppItem(int idx) { if (m_pMainWid) m_pMainWid->removeAppItem(idx); } void TrayClientManager::updateDescription(const QString& input, const QString& output) { if (m_pMainWid) m_pMainWid->updateDescription(input, output); } ukui-volume-control/tray/IDevicePortSelectItem.h0000664000175000017500000000342715171074712020737 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 IDEVICEPORTITEM_H #define IDEVICEPORTITEM_H #include #include #include class IDevicePortItem { public: IDevicePortItem(const QString& cardName, const QString& portName) : m_devName(cardName), m_portName(portName) {} virtual ~IDevicePortItem() {} public: virtual QWidget* getWidget() = 0; virtual bool isChecked() = 0; virtual void setChecked(bool) = 0; virtual void setIcon(const QIcon&) = 0; QString getCardName() const {return m_devName;} QString getPortName() const {return m_portName;} protected: QString m_devName; QString m_portName; }; class IDevicePortSelectItem { public: virtual void initSlots() = 0; virtual void setCurrentIndex(int) = 0; virtual void insertItem(std::shared_ptr) = 0; virtual void removeItem(const QString&, const QString&) = 0; virtual QVariant currentData(int = Qt::UserRole) const = 0; virtual int count() const = 0; virtual QWidget* getWidget() = 0; }; #endif // IDEVICEPORTITEM_H ukui-volume-control/tray/UkmediaTrayIcon.cpp0000664000175000017500000000265415171074712020167 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "UkmediaTrayIcon.h" #include UkmediaTrayIcon::UkmediaTrayIcon(QWidget *parent) { Q_UNUSED(parent); } UkmediaTrayIcon::~UkmediaTrayIcon() { } /*! * \brief * \details * 处理托盘的滚动事件 */ bool UkmediaTrayIcon::event(QEvent *event) { bool value = false; QWheelEvent *e = static_cast(event); if (event->type() == QEvent::Wheel) { const QPoint numDegrees = e->angleDelta(); if (numDegrees.y() > 0) { value = true; } else if (numDegrees.y() < 0) { value = false; } Q_EMIT wheelRollEventSignal(value); } return QSystemTrayIcon::event(e); } ukui-volume-control/tray/UkmediaMainWidget.h0000664000175000017500000000663415171074712020136 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 UKMEDIAMAINWIDGET_H #define UKMEDIAMAINWIDGET_H #include #include #include #include #include "IMainWindow.h" #include "Ukui2TrayIcon.h" #include "Ukui4TrayIcon.h" #include "ukui4.0/Ukui4MainWindow.h" #include "ukui4.0/Ukui4SystemVolumeWidget.h" #include "ukui4.0/Ukui4AppVolumeWidget.h" #include "ITrayVolumeSliderItem.h" #include "IDevicePortSelectItem.h" #include "TrayClientManager.h" #include "Ukui2MainWindow.h" enum class PanelPosition { PANEL_POSITION_BOTTOM = 0, //!< The bottom side of the screen. PANEL_POSITION_TOP, //!< The top side of the screen. PANEL_POSITION_LEFT, //!< The left side of the screen. PANEL_POSITION_RIGHT //!< The right side of the screen. }; using namespace UkuiAudioFramwork; class UkmediaMainWidget : public QWidget { Q_OBJECT public: explicit UkmediaMainWidget(QWidget* = nullptr); ~UkmediaMainWidget() {} public: DesktopEnvironmentVersion getDesktopEnvironmentVersion(); int getVolume(int, const QString&); void setVolume(int, const QString&, int); bool getMute(int, const QString&); void setMute(int, const QString&, bool); ClientDefaultDeviceInfo getDefaultDevice(int, const QString&) const; void setDefaultDevice(int, const QString&, const QString&); private Q_SLOTS: void volumeChangedSlots(int, int, const QDBusVariant&); void deviceChangedSlots(int, const QString&, const QString&); void deviceAdjustSlots(int); void settingsChangedSlots(const QString&, const QDBusVariant&); void addStreamSlots(int, int, const QString&, const QString&); void removeStreamSlots(int); private: void initUi(); void initSystemVolumeSlider(); void initSystemDeviceList(); void initCurrentDefaultDevice(); void initAppVolumeList(); void initAppVolumeSlider(); void initData(); void initSlots(); void initClientManager(); QRect caculatePosition(); void removeHeader(QWidget*); private: int m_panelSize = 40; PanelPosition m_panelPosition = PanelPosition::PANEL_POSITION_BOTTOM; DesktopEnvironmentVersion m_version = DesktopEnvironmentVersion::DESKTOP_ENVIRONMENT_VERSION_UKUI2; std::shared_ptr m_pTrayIcon = nullptr; IMainWindow* m_pMainWid = nullptr; std::shared_ptr m_pClientManager = nullptr; std::unordered_map> m_devicePortItemMap {}; std::unordered_map> m_sysVolumeSliderItemMap {}; std::unordered_map> m_appVolumeSliderItemMap {}; }; #endif // UKMEDIAMAINWIDGET_H ukui-volume-control/tray/ISystemVolumeWidget.h0000664000175000017500000000301115171074712020521 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 ISYSTEMVOLUMEWIDGET_H #define ISYSTEMVOLUMEWIDGET_H #include #include "IDevicePortSelectItem.h" class ISystemVolumeWidget { public: virtual void initUi() = 0; virtual void initSlots() = 0; virtual void showWidget() = 0; virtual QWidget* getWidget() = 0; virtual void setValue(int) = 0; virtual void addDevice(const QString&, const QVariantList&) = 0; virtual void removeDevice(const QString&, const QString&) = 0; virtual void addDevicePortItem(std::shared_ptr) = 0; virtual void removeDevicePortItem(const QString&, const QVariantList&) = 0; virtual void setCurrentIndex(int) = 0; protected: std::shared_ptr m_pListItem = nullptr; }; #endif // ISYSTEMVOLUMEWIDGET_H ukui-volume-control/tray/CMakeLists.txt0000664000175000017500000000645415171074712017175 0ustar fengfengcmake_minimum_required(VERSION 3.10) project(ukui-volume-control-applet-tray LANGUAGES CXX) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(PkgConfig) find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Widgets DBus LinguistTools REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Widgets DBus LinguistTools REQUIRED) if(${QT_VERSION_MAJOR} EQUAL 5) find_package(Qt5 COMPONENTS X11Extras REQUIRED) endif() if(${QT_VERSION_MAJOR} EQUAL 6) find_package(KF6WindowSystem REQUIRED) else() find_package(KF5WindowSystem REQUIRED) endif() pkg_check_modules(X11 REQUIRED x11) pkg_check_modules(KYSDK REQUIRED kysdk-qtwidgets) include_directories(${X11_INCLUDE_DIRS}) include_directories(${KYSDK_INCLUDE_DIRS}) # 统一定义源文件列表 set(PROJECT_SOURCES main.cpp ISystemVolumeWidget.h IAppVolumeWidget.h ITrayVolumeSliderItem.cpp ITrayVolumeSliderItem.h IDevicePortSelectItem.h Ukui4DevicePortSelectItem.cpp Ukui4DevicePortSelectItem.h Ukui4SystemVolumeSliderItem.cpp Ukui4SystemVolumeSliderItem.h TrayClientManager.cpp TrayClientManager.h TrayClientMethod.cpp TrayClientMethod.h ukui4.0/Ukui4SystemVolumeWidget.cpp ukui4.0/Ukui4SystemVolumeWidget.h ukui4.0/Ukui4AppVolumeWidget.cpp ukui4.0/Ukui4AppVolumeWidget.h ukui4.0/TrayListViewItemModel.cpp ukui4.0/TrayListViewItemModel.h ukui4.0/TrayListViewDelegate.cpp ukui4.0/TrayListViewDelegate.h IMainWindow.cpp IMainWindow.h Ukui2MainWindow.cpp Ukui2MainWindow.h Ukui2MiniWidget.cpp Ukui2MiniWidget.h Ukui2SystemVolumeWidget.cpp Ukui2SystemVolumeWidget.h Ukui2AppVolumeWidget.cpp Ukui2AppVolumeWidget.h Ukui2SystemVolumeSliderItem.cpp Ukui2SystemVolumeSliderItem.h ukui4.0/Ukui4MainWindow.cpp ukui4.0/Ukui4MainWindow.h UkmediaMainWidget.cpp UkmediaMainWidget.h UkmediaTrayIcon.cpp UkmediaTrayIcon.h Ukui2TrayIcon.cpp Ukui2TrayIcon.h Ukui4TrayIcon.cpp Ukui4TrayIcon.h XatomHelper.cpp XatomHelper.h ../common/DBusClient.cpp ../common/DBusClient.h ../common/Ukui4CustomControl.cpp ../common/Ukui4CustomControl.h ) file(GLOB TS_FILES translations/*.ts) if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) qt_create_translation(QM_FILES ${CMAKE_CURRENT_SOURCE_DIR} ${TS_FILES} OPTIONS -no-obsolete -no-ui-lines) qt_add_executable(${PROJECT_NAME} MANUAL_FINALIZATION ${PROJECT_SOURCES} ${QM_FILES} ) else() qt5_create_translation(QM_FILES ${CMAKE_CURRENT_SOURCE_DIR} ${TS_FILES} OPTIONS -no-obsolete -no-ui-lines) add_executable(${PROJECT_NAME} ${PROJECT_SOURCES} ${QM_FILES} ) endif() target_include_directories(${PROJECT_NAME} PUBLIC ../backend ../common ) if(${QT_VERSION_MAJOR} EQUAL 6) target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Widgets Qt6::DBus KF6::WindowSystem ${X11_LIBRARIES} ${KYSDK_LIBRARIES} ) qt_finalize_executable(${PROJECT_NAME}) else() target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Widgets Qt5::DBus Qt5::X11Extras KF5::WindowSystem ${X11_LIBRARIES} ${KYSDK_LIBRARIES} ) endif() ukui-volume-control/tray/Ukui2MainWindow.h0000664000175000017500000000664715171074712017606 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 UKUI2MAINWINDOW_H #define UKUI2MAINWINDOW_H #include #include #include "IMainWindow.h" #include "ITrayVolumeSliderItem.h" #include "ISoundSystemTrayIcon.h" #include "Ukui2SystemVolumeSliderItem.h" #include "Ukui2MiniWidget.h" enum class StackedWidgetType { MINI_STACKED_WIDGET, ADVANCE_SYS_WIDGET, ADVANCE_APP_WIDGET }; class Ukui2MainWindow : public QWidget, public IMainWindow { Q_OBJECT public: explicit Ukui2MainWindow(std::unordered_map>&, std::unordered_map>&, QWidget* = nullptr); ~Ukui2MainWindow() = default; public: virtual void initUi() override; virtual void initSlots() override; virtual void addAppItem(int, const QString&, const QString&) override; virtual void removeAppItem(int) override; virtual void addAppToWidget(const QString&, const QString&) override; virtual void removeAppFromWidget(const QString&) override; virtual void addDevToWidget(const QString&, const ClientDeviceInfo&) override; virtual void removeDevFromWidget(const QString&, const QString&) override; virtual void setCurrentIndex(int) override; virtual void setParentWidget(QWidget*) override; virtual void setValue(int, int, int) override; virtual void updateDescription(const QString&, const QString&) override; void setStackWidgetCurrentIndex(int); Q_SIGNALS: void vauleChanged(int); private Q_SLOTS: void siwtchModuleBtnPressedSlots(); void deviceVolumeBtnPressedSlots(); void appVolumeBtnPressedSlots(); private: QStackedWidget* m_pStackedWidget = nullptr; std::shared_ptr m_pTrayIcon = nullptr; Ukui2MiniWidget* m_pMiniWidget = nullptr; QPushButton* m_pSwitchModuleBtn = nullptr; QPushButton* m_pDeviceVolumeBtn = nullptr; QPushButton* m_pAppVolumeBtn = nullptr; StackedWidgetType m_prevType = StackedWidgetType::ADVANCE_SYS_WIDGET; std::unordered_map m_sysVolumeKeys { {VolumeSliderType::VOLUME_SLIDER_MINI_OUTPUT, "mini widget output volume slider"}, {VolumeSliderType::VOLUME_SLIDER_DEVICE_OUTPUT, "device widget output volume slider"}, {VolumeSliderType::VOLUME_SLIDER_DEVICE_INPUT, "device widget input volume slider"}, {VolumeSliderType::VOLUME_SLIDER_APP_OUTPUT, "app widget output volume slider"} }; std::unordered_map>& m_sysVolumeSliderItemMap; std::unordered_map>& m_appSliderItemMap; }; #endif // UKUI2MAINWINDOW_H ukui-volume-control/tray/Ukui2MiniWidget.h0000664000175000017500000000264015171074712017557 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 UKUI2MINIWIDGET_H #define UKUI2MINIWIDGET_H #include #include #include "ITrayVolumeSliderItem.h" class Ukui2MiniWidget : public QWidget { public: Ukui2MiniWidget(std::unordered_map>&, QPushButton*, QWidget*); ~Ukui2MiniWidget() = default; public: void setDescription(const QString&); private: void initUi(); void initSlots(); private: QLabel* m_pLabel = nullptr; QPushButton* m_pIconBtn = nullptr; QPushButton* m_pSwitchModuleBtn = nullptr; std::unordered_map>& m_sysVolumeSliderItemMap; }; #endif // UKUI2MINIWIDGET_H ukui-volume-control/tray/main.cpp0000664000175000017500000000200615171074712016052 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 #include #include "UkmediaMainWidget.h" #include "ISystemVolumeWidget.h" #include "ukui4.0/Ukui4SystemVolumeWidget.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); UkmediaMainWidget w; return a.exec(); } ukui-volume-control/tray/ukui4.0/0000775000175000017500000000000015171074712015623 5ustar fengfengukui-volume-control/tray/ukui4.0/Ukui4MainWindow.cpp0000664000175000017500000001260515171074712021331 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "Ukui4MainWindow.h" #include #include "DBusClient.h" #include "Ukui4CustomControl.h" #include "Ukui4DevicePortSelectItem.h" using namespace UkuiAudioFramwork; Ukui4MainWindow::Ukui4MainWindow(std::unordered_map>& systemVolumeSliderMap, std::unordered_map>& appVolumeSliderMap, std::unordered_map>& devicePortItemMap, QWidget* parent) : m_sysVolumeSliderItemMap(systemVolumeSliderMap), m_appSliderItemMap(appVolumeSliderMap), m_devicePortItemMap(devicePortItemMap), QWidget(parent) { for (const auto [k, v] : m_sysVolumeKeys) { std::shared_ptr s = std::make_shared(new UkmediaVolumeSlider); std::shared_ptr item = std::make_shared(s, new QLabel(), new QPushButton(), k, this); m_sysVolumeSliderItemMap.emplace(EnumToInt(k), item); } } void Ukui4MainWindow::initUi() { m_pAppVolumeWidget = std::make_shared(m_sysVolumeSliderItemMap, m_appSliderItemMap, this, m_pTabWidget); m_pSysVolumeWidget = std::make_shared(m_sysVolumeSliderItemMap, m_devicePortItemMap, this, m_pTabWidget); m_pAppVolumeWidget->initUi(); // m_pSysVolumeWidget->initUi(); m_pTabWidget = new QTabWidget(this); m_pTabWidget->setFocusPolicy(Qt::StrongFocus); m_pTabWidget->addTab(m_pSysVolumeWidget->getWidget(), tr("System Volume")); m_pTabWidget->addTab(m_pAppVolumeWidget->getWidget(), tr("Application Volume")); m_pTabWidget->tabBar()->setProperty("setRadius", 12); // tabbar界面内圆角 m_pTabWidget->tabBar()->setFixedWidth(this->width()+1); m_pTabWidget->tabBar()->setProperty("useTabbarSeparateLine", false); // 去掉标签页中间的分隔线 m_pTabWidget->setAttribute(Qt::WA_TranslucentBackground, true); // 背景透明 解决切换黑屏问题 setFixedSize(420, 476); } void Ukui4MainWindow::initSlots() { } void Ukui4MainWindow::addAppItem(int idx, const QString& iconName, [[maybe_unuse]] const QString& descName) { std::shared_ptr s = std::make_shared(new QSlider); std::shared_ptr item = std::make_shared(s, new QPushButton(), new QLabel()); item->setIcon(QIcon::fromTheme(iconName)); qDebug() << "Ukui4MainWindow::addAppItem;" << iconName; m_appSliderItemMap.emplace(idx, item); m_pAppVolumeWidget->addAppWidget(idx); } void Ukui4MainWindow::removeAppItem(int idx) { m_pAppVolumeWidget->removeAppWidget(idx); } void Ukui4MainWindow::setParentWidget(QWidget* parent) { setParent(parent); } void Ukui4MainWindow::addAppToWidget(const QString& appName, const QString& iconName) { m_pAppVolumeWidget->addAppToWidget(appName, iconName); } void Ukui4MainWindow::removeAppFromWidget(const QString& appName) { m_pAppVolumeWidget->removeAppfromWidget(appName); } void Ukui4MainWindow::addDevToWidget(const QString& iconName, const ClientDeviceInfo& info) { QString displayStr = info.portLabel + " (" + info.cardDesc + ")"; std::shared_ptr item = std::make_shared(new QLabel(displayStr), new QPushButton(), info.cardName, info.portName, this); item->setIcon(QIcon::fromTheme(iconName)); m_devicePortItemMap.emplace(m_devicePortItemMap.size(), item); m_pSysVolumeWidget->addDevicePortItem(item); } void Ukui4MainWindow::removeDevFromWidget(const QString& portName, const QString& devName) { m_pSysVolumeWidget->removeDevice(portName, devName); } void Ukui4MainWindow::setCurrentIndex(int idx) { m_pSysVolumeWidget->setCurrentIndex(idx); } void Ukui4MainWindow::setValue(int type, int idx, int value) { switch (IntToEnum(type)) { case NodeType::OUTPUT: m_sysVolumeSliderItemMap[EnumToInt(VolumeSliderType::VOLUME_SLIDER_SYSTEM_OUTPUT)]->setValue(value); m_sysVolumeSliderItemMap[EnumToInt(VolumeSliderType::VOLUME_SLIDER_APP_OUTPUT)]->setValue(value); break; case NodeType::SINK_INPUT: case NodeType::SOURCE_OUTPUT: { auto it = m_appSliderItemMap.find(idx); if (it != m_appSliderItemMap.end()) { it->second->setValue(value); } else { qDebug() << "m_appVolumeSliderItemMap not found such idx: " << idx; } break; } default: break; } } void Ukui4MainWindow::updateDescription(const QString&, const QString&) { // TODO } ukui-volume-control/tray/ukui4.0/TrayListViewDelegate.cpp0000664000175000017500000000330415171074712022370 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "TrayListViewDelegate.h" #include void TrayListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionViewItem opt = option; initStyleOption(&opt, index); QRect rect = option.rect; if (opt.state & QStyle::State_Selected) { painter->fillRect(rect, opt.palette.highlight()); } QIcon icon = index.data(Qt::DecorationRole).value(); int iconSize = 32; QRect iconRect(rect.x() + 5, rect.y() + (rect.height() - iconSize) / 2, iconSize, iconSize); icon.paint(painter, iconRect); QString text = index.data(Qt::DisplayRole).toString(); QRect textRect(rect.x() + iconSize + 50, rect.y(), rect.width() - iconSize - 55, rect.height()); painter->drawText(textRect, Qt::AlignVCenter, text); } QSize TrayListViewDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { return QSize(150, 50); } ukui-volume-control/tray/ukui4.0/Ukui4SystemVolumeWidget.h0000664000175000017500000000554515171074712022547 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 UKUI4SYSTEMVOLUMEWIDGET_H #define UKUI4SYSTEMVOLUMEWIDGET_H #include #include #include #include #include #include #include "ISystemVolumeWidget.h" #include "ITrayVolumeSliderItem.h" #include "ukui4.0/TrayListViewDelegate.h" #include "ukui4.0/TrayListViewItemModel.h" #include "IDevicePortSelectItem.h" class IMainWindow; class Ukui4SystemVolumeWidget : public QWidget, public ISystemVolumeWidget { Q_OBJECT public: explicit Ukui4SystemVolumeWidget(std::unordered_map>&, std::unordered_map>&, IMainWindow*, QWidget* = nullptr); ~Ukui4SystemVolumeWidget(); public: virtual void initUi() override; virtual void initSlots() override; virtual void showWidget() override; virtual QWidget* getWidget() override; virtual void setValue(int) override; virtual void addDevice(const QString&, const QVariantList&) override; virtual void removeDevice(const QString&, const QString&) override; virtual void addDevicePortItem(std::shared_ptr) override; virtual void removeDevicePortItem(const QString&, const QVariantList&) override; virtual void setCurrentIndex(int) override; Q_SIGNALS: void vauleChanged(int); private Q_SLOTS: void vauleChangedSlots(int); private: QSlider* m_pSysVolumeSlider = nullptr; // 系统音量滑动条 QPushButton* m_pSysVolumeMuteBtn = nullptr; // 系统音量静按钮 QLabel* m_pSystemVolumeLabel = nullptr; // 系统音量标签 QLabel* m_pOutputLabel = nullptr; QLabel* m_pSystemVolumePercentLabel = nullptr; // 系统音量百分百标签 TrayListViewItemModel* m_pModel = nullptr; TrayListViewDelegate* m_pDelegate = nullptr; std::unordered_map>& m_devicePortItemMap; std::unordered_map>& m_sysVolumeSliderItemMap; IMainWindow* m_pMainWid; }; #endif // UKUI4SYSTEMVOLUMEWIDGET_H ukui-volume-control/tray/ukui4.0/Ukui4SystemVolumeWidget.cpp0000664000175000017500000001147215171074712023076 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "Ukui4SystemVolumeWidget.h" #include #include #include #include "IMainWindow.h" #include "Ukui4SystemVolumeSliderItem.h" #include "Ukui4DevicePortSelectItem.h" using namespace UkuiAudioFramwork; Ukui4SystemVolumeWidget::Ukui4SystemVolumeWidget(std::unordered_map>& map, std::unordered_map>& portMap, IMainWindow* window, QWidget* parent) : m_sysVolumeSliderItemMap(map), m_devicePortItemMap(portMap), m_pMainWid(std::move(window)), QWidget(parent) { initUi(); initSlots(); } Ukui4SystemVolumeWidget::~Ukui4SystemVolumeWidget() { // delete m_pMainWid; } void Ukui4SystemVolumeWidget::initUi() { m_pSystemVolumeLabel = new QLabel(); m_pSystemVolumeLabel->setText(tr("Volume")); m_pOutputLabel = new QLabel(tr("Output")); m_pListItem = std::make_shared(new QListWidget, this); QVBoxLayout* mainLay = new QVBoxLayout(); mainLay->addWidget(m_pSystemVolumeLabel); // mainLay->addLayout(sysVolumeLay); mainLay->addWidget(m_sysVolumeSliderItemMap[EnumToInt(VolumeSliderType::VOLUME_SLIDER_SYSTEM_OUTPUT)]->getWidget()); mainLay->addWidget(m_pOutputLabel); mainLay->addSpacing(10); // mainLay->addWidget(m_pListView); mainLay->addWidget(m_pListItem->getWidget()); this->setLayout(mainLay); qDebug() << "m_pListItem->size: " << m_pListItem->getWidget()->size(); } void Ukui4SystemVolumeWidget::initSlots() { for (const auto& it : m_sysVolumeSliderItemMap) { connect(static_cast(it.second->getSlider()->getWidget()), &QSlider::valueChanged, this, [this](int value) { m_pMainWid->setVolume(1, value); }); connect(static_cast(it.second->getIconButton()), &QPushButton::clicked, this, [it, this]() { if (0 == it.first || 1 == it.first) m_pMainWid->setMute(1, ""); else if (2 == it.first) m_pMainWid->setMute(0, ""); }); } connect(std::dynamic_pointer_cast(m_pListItem)->getListWidget(), &QListWidget::currentRowChanged, this, [this](int row) { auto item = std::dynamic_pointer_cast(m_pListItem)->getListWidget()->item(row); auto w = (Ukui4DevicePortItem*) std::dynamic_pointer_cast(m_pListItem)->getListWidget()->itemWidget(item); m_pMainWid->setDefaultDevice(1, w->getPortName(), w->getCardName()); }); } void Ukui4SystemVolumeWidget::showWidget() { this->show(); } QWidget* Ukui4SystemVolumeWidget::getWidget() { return this; } void Ukui4SystemVolumeWidget::setValue(int vaule) { m_pSysVolumeSlider->blockSignals(true); m_pSysVolumeSlider->setValue(vaule); m_pSysVolumeSlider->blockSignals(false); } void Ukui4SystemVolumeWidget::addDevice(const QString& iconName, const QVariantList& list) { m_pModel->addDevice(iconName, list); } void Ukui4SystemVolumeWidget::removeDevice(const QString& portName, const QString& devName) { // 暂时先全删除 for (auto it = m_devicePortItemMap.begin(); it != m_devicePortItemMap.end();) { m_pListItem->removeItem(it->second->getPortName(), it->second->getCardName()); it = m_devicePortItemMap.erase(it); } qDebug() << "not found port: " << portName << " devName: " << devName << " device." << " current list:" << m_pListItem->count(); } void Ukui4SystemVolumeWidget::addDevicePortItem(std::shared_ptr item) { m_pListItem->insertItem(item); } void Ukui4SystemVolumeWidget::removeDevicePortItem(const QString& name, const QVariantList& v) { } void Ukui4SystemVolumeWidget::setCurrentIndex(int idx) { if (idx >= m_pListItem->count() || idx == -1) { qDebug() << idx << " outof range, invaild index..."; return; } m_pListItem->setCurrentIndex(idx); } void Ukui4SystemVolumeWidget::vauleChangedSlots(int vaule) { Q_EMIT vauleChanged(vaule); } ukui-volume-control/tray/ukui4.0/Ukui4AppVolumeWidget.h0000664000175000017500000000454015171074712021775 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 UKUI4APPVOLUMEWIDGET_H #define UKUI4APPVOLUMEWIDGET_H #include #include #include #include #include #include "IAppVolumeWidget.h" #include "ITrayVolumeSliderItem.h" class IMainWindow; class Ukui4AppVolumeWidget : public QWidget, public IAppVolumeWidget { Q_OBJECT public: explicit Ukui4AppVolumeWidget(std::unordered_map>&, std::unordered_map>&, IMainWindow*, QWidget* = nullptr); ~Ukui4AppVolumeWidget(); public: virtual void initUi() override; virtual void showWidget() override; virtual QWidget* getWidget() override; virtual void addAppWidget(int) override; virtual void removeAppWidget(int) override; virtual void addAppToWidget(const QString&, const QString&) override; virtual void removeAppfromWidget(const QString&); private: QLabel* m_pSysVolumeLabel = nullptr; // 系统音量标签 QLabel* m_pAppVolumeLabel = nullptr; // 应用音量标签 QScrollArea* m_pAppArea = nullptr; // 应用音量滚动区域 QWidget* m_pDisplayAppVolumeWidget = nullptr; // 应用音量显示区域 QVBoxLayout* m_pVlayout = nullptr; int sinkInputCount = 0; std::unordered_map>& m_sysVolumeSliderItemMap; std::unordered_map>& m_appVolumeSliderItemMap; IMainWindow* m_pMainWid; }; #endif // UKUI4APPVOLUMEWIDGET_H ukui-volume-control/tray/ukui4.0/TrayListViewDelegate.h0000664000175000017500000000224115171074712022034 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 TRAYLISTVIEWDELEGATE_H #define TRAYLISTVIEWDELEGATE_H #include class TrayListViewDelegate : public QStyledItemDelegate { public: virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; }; #endif // TRAYLISTVIEWDELEGATE_H ukui-volume-control/tray/ukui4.0/TrayListViewItemModel.h0000664000175000017500000000323215171074712022202 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 TRAYLISTVIEWITEMMODEL_H #define TRAYLISTVIEWITEMMODEL_H #include #include class TrayListViewItemModel : public QAbstractItemModel { public: TrayListViewItemModel(QObject* parent = nullptr); void addDevice(const QString& iconPath, const QVariantList& list); void removeDevice(const QString& portName, const QString& devName); virtual QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; virtual QModelIndex parent(const QModelIndex&) const override; virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override; virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; private: std::vector> devices; }; #endif // TRAYLISTVIEWITEMMODEL_H ukui-volume-control/tray/ukui4.0/Ukui4AppVolumeWidget.cpp0000664000175000017500000001775715171074712022346 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "Ukui4AppVolumeWidget.h" #include #include #include #include #include "IMainWindow.h" #include "Ukui4SystemVolumeSliderItem.h" using namespace UkuiAudioFramwork; Ukui4AppVolumeWidget::Ukui4AppVolumeWidget(std::unordered_map>& map, std::unordered_map>& appMap, IMainWindow* window, QWidget* parent) : m_sysVolumeSliderItemMap(map), m_appVolumeSliderItemMap(appMap), m_pMainWid(std::move(window)), QWidget(parent) { } Ukui4AppVolumeWidget::~Ukui4AppVolumeWidget() { // delete m_pMainWid; } void Ukui4AppVolumeWidget::initUi() { m_pSysVolumeLabel = new QLabel(tr("System Volume")); m_pAppVolumeLabel = new QLabel(tr("Application Volume")); // 应用音量widget m_pAppArea = new QScrollArea(this); m_pAppArea->setFixedSize(412, 245); m_pAppArea->setFrameShape(QFrame::NoFrame); m_pAppArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_pAppArea->verticalScrollBar()->setProperty("drawScrollBarGroove", false); // 滚动条背景透明 QPalette pal = m_pAppArea->palette(); pal.setColor(QPalette::Window, QColor(0x00, 0xff, 0x00, 0x00)); m_pAppArea->setPalette(pal); m_pDisplayAppVolumeWidget = new QWidget(m_pAppArea); m_pDisplayAppVolumeWidget->setFixedWidth(412); m_pAppArea->setWidget(m_pDisplayAppVolumeWidget); m_pVlayout = new QVBoxLayout(m_pDisplayAppVolumeWidget); m_pDisplayAppVolumeWidget->setLayout(m_pVlayout); // 整体布局 QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(m_pSysVolumeLabel); mainLayout->addSpacing(3); mainLayout->addWidget(m_sysVolumeSliderItemMap[EnumToInt(VolumeSliderType::VOLUME_SLIDER_APP_OUTPUT)]->getWidget()); mainLayout->addSpacing(13); mainLayout->addWidget(m_pAppVolumeLabel); mainLayout->addSpacing(3); mainLayout->addWidget(m_pAppArea); mainLayout->setSpacing(0); this->setLayout(mainLayout); mainLayout->setContentsMargins(8, 12, 0, 0); } void Ukui4AppVolumeWidget::showWidget() { this->show(); } QWidget* Ukui4AppVolumeWidget::getWidget() { return this; } void Ukui4AppVolumeWidget::addAppWidget(int idx) { m_pVlayout->addWidget(m_appVolumeSliderItemMap[idx]->getWidget()); m_pVlayout->setSpacing(2); m_pVlayout->setContentsMargins(0, 0, 0, 0); // m_pDisplayAppVolumeWidget->adjustSize(); m_pDisplayAppVolumeWidget->resize(412, m_appVolumeSliderItemMap.size()*50); m_pVlayout->update(); auto it = m_appVolumeSliderItemMap.find(idx); if (it != m_appVolumeSliderItemMap.end()) { connect(static_cast(it->second->getSlider()->getWidget()), &QSlider::valueChanged, this, [this](int value) { m_pMainWid->setVolume(2, value); qDebug() << "AppVolumeWidgetUkui4 value changed , value: " << value; }); } else { qDebug() << "m_appVolumeSliderItemMap not found such as :" << idx; } qDebug() << "add wid..., displayVolumeWidge.size" << m_pDisplayAppVolumeWidget->rect(); } void Ukui4AppVolumeWidget::removeAppWidget(int idx) { auto it = m_appVolumeSliderItemMap.find(idx); if (it != m_appVolumeSliderItemMap.end()) { m_appVolumeSliderItemMap.erase(it); } else { qDebug() << "not found " << idx << " in m_appVolumeSliderItemMap."; } m_pVlayout->setSpacing(2); m_pVlayout->setContentsMargins(0, 0, 0, 0); m_pDisplayAppVolumeWidget->resize(404, m_appVolumeSliderItemMap.size()*2); m_pVlayout->update(); } void Ukui4AppVolumeWidget::addAppToWidget(const QString& appName, const QString& iconName) { ++sinkInputCount; // 显示应用音量 auto subAppwidget = new QWidget(m_pDisplayAppVolumeWidget); auto appVolumeLabel = new QLabel(subAppwidget); appVolumeLabel->setParent(subAppwidget); auto appIconBtn = new QPushButton(subAppwidget); auto appMuteBtn = new QPushButton(subAppwidget); appMuteBtn->setVisible(false); auto appSlider = new QSlider(subAppwidget); appSlider->setOrientation(Qt::Horizontal); appSlider->setProperty("needTranslucent", true); // Increase translucent effect appIconBtn->setFixedSize(32, 32); appIconBtn->setFlat(true); appIconBtn->setFocusPolicy(Qt::NoFocus); QSize icon_size(32, 32); appIconBtn->setIconSize(icon_size); appIconBtn->setIcon(QIcon::fromTheme(iconName)); appIconBtn->setAttribute(Qt::WA_TranslucentBackground); auto palete = appIconBtn->palette(); palete.setColor(QPalette::Highlight, Qt::transparent); palete.setBrush(QPalette::Button, QBrush(QColor(1, 1, 1, 0))); appIconBtn->setPalette(palete); auto paleteAppIcon = appIconBtn->palette(); paleteAppIcon.setColor(QPalette::Highlight, Qt::transparent); paleteAppIcon.setBrush(QPalette::Button, QBrush(QColor(0, 0, 0, 0))); appIconBtn->setPalette(paleteAppIcon); appSlider->setMaximum(100); appSlider->setFixedSize(286, 48); appIconBtn->adjustSize(); appIconBtn->setProperty("useIconHighlightEffect", true); appIconBtn->setProperty("iconHighlightEffectMode", true); appIconBtn->setStyleSheet("QPushButton{background:transparent;border:0px;" "padding-left:0px;}" "QPushButton:hover {" "background-color: #00000000;" "color: white;}"); //音量滑动条 auto appSliderLayout = new QHBoxLayout(subAppwidget); subAppwidget->setFixedSize(412, 48); appVolumeLabel->setText("0%"); appSliderLayout->addWidget(appIconBtn); appSliderLayout->addSpacing(3); appSliderLayout->addWidget(appSlider); appSliderLayout->addSpacing(0); appSliderLayout->addWidget(appVolumeLabel); appSliderLayout->setSpacing(0); subAppwidget->setLayout(appSliderLayout); appSliderLayout->setContentsMargins(10, 0, 30, 0); m_pVlayout->addWidget(m_appVolumeSliderItemMap[0]->getWidget()); // m_pVlayout->addWidget(subAppwidget); // 设置布局的垂直间距以及设置vlayout四周的间距 m_pVlayout->setSpacing(2); m_pVlayout->setContentsMargins(0, 0, 0, 0); // m_pDisplayAppVolumeWidget->adjustSize(); m_pDisplayAppVolumeWidget->resize(412, sinkInputCount*50); m_pVlayout->update(); qDebug() << "add wid..., displayVolumeWidge.size" << m_pDisplayAppVolumeWidget->rect(); } void Ukui4AppVolumeWidget::removeAppfromWidget(const QString& appName) { auto index = 1; --sinkInputCount; if (-1 == index) { qDebug() << "can not found " << appName << " in output stream"; return; } if (m_pVlayout->takeAt(index) != 0) { auto wid = m_pVlayout->takeAt(index)->widget(); m_pVlayout->removeWidget(wid); wid->setParent(nullptr); delete wid; } m_pVlayout->setSpacing(2); m_pVlayout->setContentsMargins(0, 0, 0, 0); // m_pDisplayAppVolumeWidget->adjustSize(); m_pDisplayAppVolumeWidget->resize(404, sinkInputCount*50); m_pVlayout->update(); qDebug() << "remove wid..., displayVolumeWidge.size" << m_pDisplayAppVolumeWidget->rect(); } ukui-volume-control/tray/ukui4.0/TrayListViewItemModel.cpp0000664000175000017500000000473515171074712022546 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "TrayListViewItemModel.h" #include #include #include TrayListViewItemModel::TrayListViewItemModel(QObject* parent) : QAbstractItemModel(parent) { } void TrayListViewItemModel::addDevice(const QString& iconPath, const QVariantList& list) { beginInsertRows(QModelIndex(), rowCount(), rowCount()); devices.push_back(std::make_pair(QIcon::fromTheme(iconPath), list)); endInsertRows(); } void TrayListViewItemModel::removeDevice(const QString& portName, const QString& devName) { // if (row >= 0 && row < rowCount()) // { // beginRemoveRows(QModelIndex(), row, row); // devices.erase(devices.begin() + row); // endRemoveRows(); // } std::erase_if(devices, [portName, devName] (const auto& devPair) { const auto& [currentIconName, variantList] = devPair; return variantList.at(0) == portName && variantList.at(1) == devName; }); } QModelIndex TrayListViewItemModel::index(int row, int column, const QModelIndex& parent) const { return createIndex(row, column); } QModelIndex TrayListViewItemModel::parent(const QModelIndex&) const { return QModelIndex(); } int TrayListViewItemModel::rowCount(const QModelIndex& parent) const { if (!parent.isValid()) return devices.size(); return 0; } int TrayListViewItemModel::columnCount(const QModelIndex&) const { return 1; } QVariant TrayListViewItemModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) return QVariant(); if (role == Qt::DisplayRole) return devices[index.row()].second.at(1); else if (role == Qt::DecorationRole && index.column() == 0) { return devices[index.row()].first; } return QVariant(); } ukui-volume-control/tray/ukui4.0/Ukui4MainWindow.h0000664000175000017500000000577615171074712021011 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 UKUI4MAINWINDOW_H #define UKUI4MAINWINDOW_H #include #include #include "IMainWindow.h" #include "ISoundSystemTrayIcon.h" #include "ukui4.0/Ukui4SystemVolumeWidget.h" #include "ukui4.0/Ukui4AppVolumeWidget.h" #include "ITrayVolumeSliderItem.h" #include "IDevicePortSelectItem.h" #include "Ukui4SystemVolumeSliderItem.h" class Ukui4MainWindow : public QWidget, public IMainWindow, public std::enable_shared_from_this { Q_OBJECT public: explicit Ukui4MainWindow(std::unordered_map>&, std::unordered_map>&, std::unordered_map>&, QWidget* = nullptr); ~Ukui4MainWindow() = default; public: virtual void initUi() override; virtual void initSlots() override; virtual void addAppItem(int, const QString&, const QString&) override; virtual void removeAppItem(int) override; virtual void addAppToWidget(const QString&, const QString&) override; virtual void removeAppFromWidget(const QString&) override; virtual void addDevToWidget(const QString&, const ClientDeviceInfo&) override; virtual void removeDevFromWidget(const QString&, const QString&) override; virtual void setCurrentIndex(int) override; virtual void setParentWidget(QWidget*) override; virtual void setValue(int, int, int) override; virtual void updateDescription(const QString&, const QString&); Q_SIGNALS: void vauleChanged(int); private: QTabWidget* m_pTabWidget = nullptr; std::shared_ptr m_pTrayIcon = nullptr; std::unordered_map m_sysVolumeKeys { {VolumeSliderType::VOLUME_SLIDER_SYSTEM_OUTPUT, "system widget output volume slider"}, {VolumeSliderType::VOLUME_SLIDER_APP_OUTPUT, "app widget output volume slider"} }; std::unordered_map>& m_devicePortItemMap; std::unordered_map>& m_sysVolumeSliderItemMap; std::unordered_map>& m_appSliderItemMap; }; #endif // UKUI4MAINWINDOW_H ukui-volume-control/tray/Ukui4DevicePortSelectItem.cpp0000664000175000017500000001211315171074712022073 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "Ukui4DevicePortSelectItem.h" #include #include Ukui4DevicePortItem::Ukui4DevicePortItem(QLabel* label, QPushButton* btn, const QString& devName, const QString& portName, QWidget* parent) : m_pDisplayLabel(label), m_pIconBtn(btn), IDevicePortItem(devName, portName), QWidget(parent) { initUi(); } void Ukui4DevicePortItem::initUi() { setFixedSize(404, 48); QHBoxLayout* hLayout = new QHBoxLayout; m_pIconBtn->setFixedSize(36, 36); m_pIconBtn->setCheckable(true); m_pIconBtn->setProperty("isRoundButton", true); m_pIconBtn->setProperty("useButtonPalette", true); m_pIconBtn->setProperty("needTranslucent", true); hLayout->addWidget(m_pIconBtn); hLayout->addSpacing(12); hLayout->addWidget(m_pDisplayLabel); hLayout->setSpacing(0); setLayout(hLayout); hLayout->setContentsMargins(14, 6, 24, 6); } bool Ukui4DevicePortItem::isChecked() { return m_pIconBtn->isChecked(); } void Ukui4DevicePortItem::setChecked(bool checked) { m_pIconBtn->setChecked(checked); } void Ukui4DevicePortItem::setIcon(const QIcon& icon) { m_pIconBtn->setIcon(icon); } QWidget* Ukui4DevicePortItem::getWidget() { return this; } Ukui4DevicePortSelectItem::Ukui4DevicePortSelectItem(QListWidget* w, QWidget* parent) : m_pListWidget(w), QWidget(parent) { setFixedSize(412, 245); m_pListWidget->setParent(this); m_pListWidget->setFixedSize(412, 245); m_pListWidget->setFrameShape(QFrame::Shape::NoFrame); QPalette pal = m_pListWidget->palette(); pal.setBrush(QPalette::Base, QColor(0, 0, 0, 0)); m_pListWidget->setPalette(pal); m_pListWidget->setProperty("needTranslucent", true); m_pListWidget->setSelectionMode(QAbstractItemView:: NoSelection); } QListWidget* Ukui4DevicePortSelectItem::getListWidget() const { return m_pListWidget; } void Ukui4DevicePortSelectItem::initSlots() { // connect(m_pListWidget, SIGNAL(currentRowChanged(int)), this, SLOT(currentRowChangedSlots(int))); } void Ukui4DevicePortSelectItem::setCurrentIndex(int idx) { if (m_pListWidget->count() < idx) { qDebug() << "invaild idx..."; return; } QModelIndex index = m_pListWidget->model()->index(idx, 0); m_pListWidget->setCurrentIndex(index); setListWidgetSelectItem(idx); } void Ukui4DevicePortSelectItem::insertItem(std::shared_ptr portItem) { m_pListWidget->update(); auto item = new QListWidgetItem(m_pListWidget); item->setSizeHint(QSize(200, 48)); m_pListWidget->setItemWidget(item, portItem->getWidget()); m_pListWidget->insertItem(m_pListWidget->count(), item); } void Ukui4DevicePortSelectItem::removeItem(const QString& portName, const QString& devName) { for (int i = 0; i < m_pListWidget->count(); ++i) { auto item = m_pListWidget->item(i); auto wid = (Ukui4DevicePortItem*) m_pListWidget->itemWidget(item); if (portName == wid->getPortName() && devName == wid->getCardName()) { qDebug() << "before remove listwidget.size(): " << m_pListWidget->count(); m_pListWidget->blockSignals(true); auto item = m_pListWidget->takeItem(i); m_pListWidget->removeItemWidget(item); m_pListWidget->blockSignals(false); wid = nullptr; delete wid; qDebug() << "remove item, portName: " << portName << " cardName: " << devName << " listwidget.size: " << m_pListWidget->count(); break; } } } QVariant Ukui4DevicePortSelectItem::currentData(int role) const { } int Ukui4DevicePortSelectItem::count() const { return m_pListWidget->count(); } QWidget* Ukui4DevicePortSelectItem::getWidget() { return this; } void Ukui4DevicePortSelectItem::currentRowChangedSlots(int row) { auto w = (Ukui4DevicePortItem*) m_pListWidget->itemWidget(m_pListWidget->item(row)); setListWidgetSelectItem(row); // Q_EMIT deviceChangedSignals(w->getPortName(), w->getCardName()); } void Ukui4DevicePortSelectItem::setListWidgetSelectItem(int row) { for (int i = 0; i < m_pListWidget->count(); ++i) { auto item = m_pListWidget->item(i); auto wid = (Ukui4DevicePortItem*) m_pListWidget->itemWidget(item); if (i == row && !wid->isChecked()) { wid->setChecked(true); } else { wid->setChecked(false); } } } ukui-volume-control/tray/Ukui2MiniWidget.cpp0000664000175000017500000000470015171074712020111 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "Ukui2MiniWidget.h" #include #include Ukui2MiniWidget::Ukui2MiniWidget(std::unordered_map>& systemVolumeSliderMap, QPushButton* switchBtn, QWidget* parent) : m_sysVolumeSliderItemMap(systemVolumeSliderMap), m_pSwitchModuleBtn(switchBtn), QWidget(parent) { initUi(); initSlots(); } void Ukui2MiniWidget::initUi() { setFixedSize(345, 100); m_pLabel = new QLabel(); m_pIconBtn = new QPushButton(); // m_pSwithModuleBtn->setIconSize(QSize(32, 32)); m_pIconBtn->setFixedSize(36, 36); m_pIconBtn->setIconSize(QSize(16, 16)); m_pIconBtn->setIcon(QIcon::fromTheme("audio-card")); m_pIconBtn->setStyleSheet("QPushButton{background:transparent;border:0px;" "padding-left:0px;}" "QPushButton:hover {" "background-color: #00000000;" "color: white;}"); QHBoxLayout* hLayout = new QHBoxLayout(); hLayout->addWidget(m_pLabel); hLayout->addSpacing(4); hLayout->addWidget(m_pIconBtn); hLayout->addSpacerItem(new QSpacerItem(20, 10, QSizePolicy::Expanding)); hLayout->setContentsMargins(16, 16, 8, 0); QVBoxLayout* vLayout = new QVBoxLayout(); vLayout->addLayout(hLayout); vLayout->addWidget(m_sysVolumeSliderItemMap[0]->getWidget()); vLayout->setContentsMargins(0, 0, 0, 8); setLayout(vLayout); } void Ukui2MiniWidget::initSlots() { // connect(m_pSwithModuleBtn, SIGNAL(pressed()), this, SLOT()); } void Ukui2MiniWidget::setDescription(const QString& name) { m_pLabel->setText(name); } ukui-volume-control/tray/ISoundSystemTrayIcon.h0000664000175000017500000000250015171074712020651 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 UKUIVOLUMECONTROLAPPLETTRAY_H #define UKUIVOLUMECONTROLAPPLETTRAY_H #include #include #include "UkmediaTrayIcon.h" class ISoundSystemTrayIcon { public: ISoundSystemTrayIcon(UkmediaTrayIcon* icon) : m_pTrayIcon(icon) {} ~ISoundSystemTrayIcon() = default; public: virtual void initTray() = 0; virtual void initSlot() = 0; virtual void setVisible(bool) = 0; virtual void setIcon(const QIcon&) = 0; protected: QMenu* m_pMenu = nullptr; UkmediaTrayIcon* m_pTrayIcon = nullptr; }; #endif // UKUIVOLUMECONTROLAPPLETTRAY_H ukui-volume-control/tray/Ukui4SystemVolumeSliderItem.cpp0000664000175000017500000001411415171074712022511 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "Ukui4SystemVolumeSliderItem.h" #include #include Ukui4SystemVolumeSlider::Ukui4SystemVolumeSlider(QSlider *s) : m_pSlider(s) { m_pSlider->setOrientation(Qt::Horizontal); m_pSlider->setOrientation(Qt::Horizontal); m_pSlider->setFocusPolicy(Qt::StrongFocus); m_pSlider->setProperty("needTranslucent", true); // Increase translucent effect m_pSlider->setFixedSize(286, 48); m_pSlider->setRange(0, 100); } void Ukui4SystemVolumeSlider::setRang(int min, int max) { m_pSlider->setRange(min, max); } void Ukui4SystemVolumeSlider::setValue(int value) { if (m_pSlider) { m_pSlider->blockSignals(true); m_pSlider->setValue(value); m_pSlider->blockSignals(false); } } QWidget* Ukui4SystemVolumeSlider::getWidget() { return m_pSlider; } Ukui4SystemVolumeSliderItem::Ukui4SystemVolumeSliderItem(std::shared_ptr slider, QLabel* percLabel, QPushButton* iconBtn, const VolumeSliderType& type, QWidget* parent) : ITrayVolumeSliderItem(slider, iconBtn), m_pPercentLabel(percLabel), m_type(type), QWidget(parent) { initUi(); initSlots(); } void Ukui4SystemVolumeSliderItem::initUi() { m_pIconBtn->setFixedSize(36, 36); m_pIconBtn->setCheckable(true); m_pIconBtn->setProperty("isRoundButton", true); // 圆形按钮 m_pIconBtn->setProperty("useButtonPalette", true); // 灰色按钮 m_pIconBtn->setProperty("needTranslucent", true); // 灰色半透明按钮 m_pPercentLabel->setText("0%"); // m_pPercentLabel->setFixedSize(52, 48); QHBoxLayout* hLayout = new QHBoxLayout(); hLayout->addWidget(m_pIconBtn); hLayout->addSpacing(3); hLayout->addWidget(m_pVolumeSlider->getWidget()); hLayout->addWidget(m_pPercentLabel); hLayout->setSpacing(0); setLayout(hLayout); hLayout->setContentsMargins(11, 0, 24, 0); } void Ukui4SystemVolumeSliderItem::initSlots() { connect(m_pVolumeSlider->getWidget(), SIGNAL(valueChanged(int)), this, SLOT(valueChangedSlots(int))); } QWidget* Ukui4SystemVolumeSliderItem::getWidget() { return this; } void Ukui4SystemVolumeSliderItem::setIcon(const QIcon& icon) { m_pIconBtn->setIcon(icon); } void Ukui4SystemVolumeSliderItem::setValue(int value) { updateMuteBtnIcon(value); m_pVolumeSlider->setValue(value); setPercent(value); } QPushButton* Ukui4SystemVolumeSliderItem::getMuteButton() { return m_pIconBtn; } void Ukui4SystemVolumeSliderItem::setPercent(int percent) { m_pPercentLabel->setText(QString("%1").arg(percent) + "%"); } void Ukui4SystemVolumeSliderItem::valueChangedSlots(int value) { updateMuteBtnIcon(value); setPercent(value); } void Ukui4SystemVolumeSliderItem::updateMuteBtnIcon(int value, bool muted) { QString iconStr; if (muted) { iconStr = iconNameOutputs[0]; } else if(value <= 0){ iconStr = iconNameOutputs[0]; } else if (value > 0 && value <= 33) { iconStr = iconNameOutputs[1]; } else if (value >33 && value <= 66) { iconStr = iconNameOutputs[2]; } else { iconStr = iconNameOutputs[3]; } m_pIconBtn->setIcon(QIcon::fromTheme(iconStr)); } Ukui4AppVolumeSlider::Ukui4AppVolumeSlider(QSlider *s) : m_pSlider(s) { m_pSlider->setOrientation(Qt::Horizontal); } void Ukui4AppVolumeSlider::setRang(int min, int max) { m_pSlider->setRange(min, max); } void Ukui4AppVolumeSlider::setValue(int value) { if (m_pSlider) { m_pSlider->blockSignals(true); m_pSlider->setValue(value); m_pSlider->blockSignals(false); } } QWidget* Ukui4AppVolumeSlider::getWidget() { return m_pSlider; } Ukui4AppVolumeSliderItem::Ukui4AppVolumeSliderItem(std::shared_ptr slider, QPushButton* btn, QLabel* percentLabel, QWidget* parent) : ITrayVolumeSliderItem(slider, btn), m_pPercentLabel(percentLabel), QWidget(parent) { initUi(); initSlots(); } void Ukui4AppVolumeSliderItem::initUi() { m_pIconBtn->setFixedSize(32,32); m_pIconBtn->setFlat(true); m_pIconBtn->setFocusPolicy(Qt::NoFocus); QHBoxLayout* hLayout = new QHBoxLayout(); hLayout->addWidget(m_pIconBtn); hLayout->addSpacing(3); hLayout->addWidget(m_pVolumeSlider->getWidget()); hLayout->addWidget(m_pPercentLabel); hLayout->setSpacing(0); setLayout(hLayout); hLayout->setContentsMargins(11, 0, 24, 0); m_pIconBtn->setStyleSheet("QPushButton{background:transparent;border:0px;" "padding-left:0px;}" "QPushButton:hover {" "background-color: #00000000;" "color: white;}"); } void Ukui4AppVolumeSliderItem::initSlots() { connect(m_pVolumeSlider->getWidget(), SIGNAL(valueChanged(int)), this, SLOT(valueChangedSlots(int))); } void Ukui4AppVolumeSliderItem::setIcon(const QIcon &icon) { m_pIconBtn->setIcon(icon); } QWidget* Ukui4AppVolumeSliderItem::getWidget() { return this; } void Ukui4AppVolumeSliderItem::setValue(int value) { m_pVolumeSlider->setValue(value); setPercent(value); } QPushButton* Ukui4AppVolumeSliderItem::getMuteButton() { // 暂时未提供禁音操作 } void Ukui4AppVolumeSliderItem::setPercent(int percent) { m_pPercentLabel->setText(QString("%1").arg(percent) + "%"); } void Ukui4AppVolumeSliderItem::valueChangedSlots(int value) { setPercent(value); } ukui-volume-control/tray/Ukui2SystemVolumeSliderItem.cpp0000664000175000017500000002244615171074712022516 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "Ukui2SystemVolumeSliderItem.h" #include #include Ukui2SystemVolumeSlider::Ukui2SystemVolumeSlider(QSlider *s) : m_pSlider(s) { m_pSlider->setOrientation(Qt::Horizontal); m_pSlider->setFocusPolicy(Qt::StrongFocus); m_pSlider->setProperty("needTranslucent", true); // Increase translucent effect m_pSlider->setFixedSize(200, 22); m_pSlider->setRange(0, 100); } void Ukui2SystemVolumeSlider::setRang(int min, int max) { m_pSlider->setRange(min, max); } void Ukui2SystemVolumeSlider::setValue(int value) { if (m_pSlider) { m_pSlider->blockSignals(true); m_pSlider->setValue(value); m_pSlider->blockSignals(false); } } QWidget* Ukui2SystemVolumeSlider::getWidget() { return m_pSlider; } Ukui2SystemVolumeSliderItem::Ukui2SystemVolumeSliderItem(std::shared_ptr slider, QLabel* percLabel, QPushButton* iconBtn, const VolumeSliderType& type, QWidget* parent) : ITrayVolumeSliderItem(slider, iconBtn), m_pPercentLabel(percLabel), m_type(type), QWidget(parent) { initUi(); initSlots(); } void Ukui2SystemVolumeSliderItem::initUi() { m_pIconBtn->setFixedSize(36, 36); m_pIconBtn->setCheckable(true); m_pIconBtn->setProperty("isRoundButton", true); // 圆形按钮 m_pIconBtn->setProperty("useButtonPalette", true); // 灰色按钮 m_pIconBtn->setProperty("needTranslucent", true); // 灰色半透明按钮 m_pPercentLabel->setText("0%"); QHBoxLayout* hLayout = new QHBoxLayout(); hLayout->addSpacing(16); hLayout->addWidget(m_pIconBtn); hLayout->addSpacing(13); hLayout->addWidget(m_pVolumeSlider->getWidget()); hLayout->addSpacing(15); hLayout->addWidget(m_pPercentLabel); hLayout->addSpacing(19); setLayout(hLayout); hLayout->setSpacing(0); hLayout->setContentsMargins(0, 0, 0, 0); } void Ukui2SystemVolumeSliderItem::initSlots() { connect(m_pVolumeSlider->getWidget(), SIGNAL(valueChanged(int)), this, SLOT(valueChangedSlots(int))); } QWidget* Ukui2SystemVolumeSliderItem::getWidget() { return this; } void Ukui2SystemVolumeSliderItem::setIcon(const QIcon& icon) { m_pIconBtn->setIcon(icon); } void Ukui2SystemVolumeSliderItem::setValue(int value) { updateMuteBtnIcon(value); m_pVolumeSlider->setValue(value); setPercent(value); } QPushButton* Ukui2SystemVolumeSliderItem::getMuteButton() { return m_pIconBtn; } void Ukui2SystemVolumeSliderItem::setPercent(int percent) { m_pPercentLabel->setText(QString("%1").arg(percent) + "%"); } void Ukui2SystemVolumeSliderItem::valueChangedSlots(int value) { updateMuteBtnIcon(value); setPercent(value); } void Ukui2SystemVolumeSliderItem::updateMuteBtnIcon(int value, bool muted) { QString iconStr; if (muted) { iconStr = iconNameOutputs[0]; } else if(value <= 0){ iconStr = iconNameOutputs[0]; } else if (value > 0 && value <= 33) { iconStr = iconNameOutputs[1]; } else if (value >33 && value <= 66) { iconStr = iconNameOutputs[2]; } else { iconStr = iconNameOutputs[3]; } m_pIconBtn->setIcon(QIcon::fromTheme(iconStr)); } Ukui2DeviceVolumeSliderItem::Ukui2DeviceVolumeSliderItem(std::shared_ptr slider, QLabel* descLabel, QPushButton* muteBtn, QPushButton* iconBtn, const VolumeSliderType& type, QWidget* parent) : ITrayVolumeSliderItem(slider, iconBtn), m_pDescLabel(descLabel), m_pMuteBtn(muteBtn), m_type(type), QWidget(parent) { initUi(); initSlots(); } void Ukui2DeviceVolumeSliderItem::initUi() { m_pIconBtn->setFixedSize(36, 36); QHBoxLayout* hLayout = new QHBoxLayout(); hLayout->addSpacing(16); hLayout->addWidget(m_pIconBtn); hLayout->addSpacing(13); hLayout->addWidget(m_pVolumeSlider->getWidget()); hLayout->addSpacing(15); hLayout->addWidget(m_pMuteBtn); hLayout->addSpacing(19); hLayout->setSpacing(0); QVBoxLayout* vLayout = new QVBoxLayout(); vLayout->addWidget(m_pDescLabel); vLayout->addLayout(hLayout); vLayout->setSpacing(10); setLayout(vLayout); vLayout->setContentsMargins(0, 0, 0, 0); m_pIconBtn->setStyleSheet("QPushButton{background:transparent;border:0px;" "padding-left:0px;}" "QPushButton:hover {" "background-color: #00000000;" "color: white;}"); m_pMuteBtn->setStyleSheet("QPushButton{background:transparent;border:0px;" "padding-left:0px;}" "QPushButton:hover {" "background-color: #00000000;" "color: white;}"); } void Ukui2DeviceVolumeSliderItem::initSlots() { connect(m_pVolumeSlider->getWidget(), SIGNAL(valueChanged(int)), this, SLOT(valueChangedSlots(int))); } QWidget* Ukui2DeviceVolumeSliderItem::getWidget() { return this; } void Ukui2DeviceVolumeSliderItem::setIcon(const QIcon& icon) { m_pIconBtn->setIcon(icon); } void Ukui2DeviceVolumeSliderItem::setValue(int value) { updateMuteBtnIcon(value); m_pVolumeSlider->setValue(value); setPercent(value); } QPushButton* Ukui2DeviceVolumeSliderItem::getMuteButton() { return m_pMuteBtn; } void Ukui2DeviceVolumeSliderItem::setPercent(int percent) { // m_pPercentLabel->setText(QString("%1").arg(percent) + "%"); } void Ukui2DeviceVolumeSliderItem::setDescription(const QString& name) { m_pDescLabel->setText(name); } void Ukui2DeviceVolumeSliderItem::valueChangedSlots(int value) { updateMuteBtnIcon(value); setPercent(value); } void Ukui2DeviceVolumeSliderItem::updateMuteBtnIcon(int value, bool muted) { QString iconStr; if (muted) { iconStr = iconNameOutputs[0]; } else if(value <= 0){ iconStr = iconNameOutputs[0]; } else if (value > 0 && value <= 33) { iconStr = iconNameOutputs[1]; } else if (value >33 && value <= 66) { iconStr = iconNameOutputs[2]; } else { iconStr = iconNameOutputs[3]; } m_pMuteBtn->setIcon(QIcon::fromTheme(iconStr)); } Ukui2AppVolumeSlider::Ukui2AppVolumeSlider(QSlider *s) : m_pSlider(s) { m_pSlider->setOrientation(Qt::Horizontal); } void Ukui2AppVolumeSlider::setRang(int min, int max) { m_pSlider->setRange(min, max); } void Ukui2AppVolumeSlider::setValue(int value) { if (m_pSlider) { m_pSlider->blockSignals(true); m_pSlider->setValue(value); m_pSlider->blockSignals(false); } } QWidget* Ukui2AppVolumeSlider::getWidget() { return m_pSlider; } Ukui2AppVolumeSliderItem::Ukui2AppVolumeSliderItem(std::shared_ptr slider, QPushButton* iconBtn, QPushButton* muteBtn, QLabel* label, QWidget* parent) : ITrayVolumeSliderItem(slider, iconBtn), m_pMuteBtn(muteBtn), m_pDisplayLabel(label), QWidget(parent) { initUi(); initSlots(); } void Ukui2AppVolumeSliderItem::initUi() { m_pIconBtn->setFixedSize(32,32); m_pIconBtn->setFlat(true); m_pIconBtn->setFocusPolicy(Qt::NoFocus); QHBoxLayout* hLayout = new QHBoxLayout(); hLayout->addWidget(m_pIconBtn); hLayout->addSpacing(18); hLayout->addWidget(m_pVolumeSlider->getWidget()); hLayout->addSpacing(12); hLayout->addWidget(m_pMuteBtn); hLayout->setSpacing(0); // hLayout->setContentsMargins(11, 0, 24, 0); hLayout->setContentsMargins(0, 0, 0, 0); QVBoxLayout* vLayout = new QVBoxLayout(); vLayout->addWidget(m_pDisplayLabel); vLayout->addLayout(hLayout); vLayout->setSpacing(14); vLayout->setContentsMargins(0, 0, 0, 0); setLayout(vLayout); m_pIconBtn->setStyleSheet("QPushButton{background:transparent;border:0px;" "padding-left:0px;}" "QPushButton:hover {" "background-color: #00000000;" "color: white;}"); } void Ukui2AppVolumeSliderItem::initSlots() { connect(m_pVolumeSlider->getWidget(), SIGNAL(valueChanged(int)), this, SLOT(valueChangedSlots(int))); } void Ukui2AppVolumeSliderItem::setIcon(const QIcon &icon) { m_pIconBtn->setIcon(icon); } QWidget* Ukui2AppVolumeSliderItem::getWidget() { return this; } void Ukui2AppVolumeSliderItem::setValue(int value) { m_pVolumeSlider->setValue(value); setPercent(value); } QPushButton* Ukui2AppVolumeSliderItem::getMuteButton() { return m_pMuteBtn; } void Ukui2AppVolumeSliderItem::setPercent(int percent) { // m_pPercentLabel->setText(QString("%1").arg(percent) + "%"); } void Ukui2AppVolumeSliderItem::valueChangedSlots(int value) { setPercent(value); } ukui-volume-control/tray/IMainWindow.cpp0000664000175000017500000001350215171074712017316 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "IMainWindow.h" std::shared_ptr IMainWindow::addVolumeObserver(const VolumeChangedCallback& callback) { auto spCallback = std::make_shared(std::move(callback)); m_volumeObservers.push_back(spCallback); return spCallback; } void IMainWindow::removeVolumeObserver(const std::shared_ptr& callback) { m_volumeObservers.erase(std::remove_if(m_volumeObservers.begin(), m_volumeObservers.end(), [callback](const std::shared_ptr& existingCallback) { return existingCallback == callback; }), m_volumeObservers.end()); } void IMainWindow::setVolume(int type, int volume) { volumeNotify(type, volume); } std::shared_ptr IMainWindow::addMuteObserver(const MuteChangedCallback& callback) { auto spCallback = std::make_shared(std::move(callback)); m_muteObservers.push_back(spCallback); return spCallback; } void IMainWindow::removeMuteObserver(const std::shared_ptr& callback) { m_muteObservers.erase(std::remove_if(m_muteObservers.begin(), m_muteObservers.end(), [callback](const std::shared_ptr& existingCallback) { return existingCallback == callback; }), m_muteObservers.end()); } void IMainWindow::setMute(int type, const QString& name) { muteNotify(type, name); } std::shared_ptr IMainWindow::addDeviceObserver(const DeviceChangedCallback& callback) { auto spCallback = std::make_shared(std::move(callback)); m_deviceObservers.push_back(spCallback); return spCallback; } void IMainWindow::removeDeviceObserver(const std::shared_ptr& callback) { m_deviceObservers.erase(std::remove_if(m_deviceObservers.begin(), m_deviceObservers.end(), [callback](const std::shared_ptr& existingCallback) { return existingCallback == callback; }), m_deviceObservers.end()); } void IMainWindow::setDefaultDevice(int type, const QString& portName, const QString& cardName) { defaultDeviceNotify(type, portName, cardName); } std::shared_ptr IMainWindow::addTrayIconTriggerObserver(const TrayIconTriggerCallback& callback) { auto spCallback = std::make_shared(std::move(callback)); m_trayIconObservers.push_back(spCallback); return spCallback; } void IMainWindow::removeTrayIconTriggerObserver(const std::shared_ptr& callback) { m_trayIconObservers.erase(std::remove_if(m_trayIconObservers.begin(), m_trayIconObservers.end(), [callback](const std::shared_ptr& existingCallback) { return existingCallback == callback; }), m_trayIconObservers.end()); } void IMainWindow::triggerWidget() { trayIconTriggerNotify(); } std::shared_ptr IMainWindow::addSizeChangedObserver(const SizeChangedCallback& callback) { auto spCallback = std::make_shared(std::move(callback)); m_sizeChangedObservers.push_back(spCallback); return spCallback; } void IMainWindow::removeSizeChangedObserver(const std::shared_ptr& callback) { m_sizeChangedObservers.erase(std::remove_if(m_sizeChangedObservers.begin(), m_sizeChangedObservers.end(), [callback](const std::shared_ptr& existingCallback) { return existingCallback == callback; }), m_sizeChangedObservers.end()); } void IMainWindow::setSize(const QSize& size) { sizeChangedNotify(size); } void IMainWindow::volumeNotify(int type, int volume) { for (const auto& callback : m_volumeObservers) { if (callback) { (*callback)(type, volume); } } } void IMainWindow::muteNotify(int type, const QString& name) { for (const auto& callback : m_muteObservers) { if (callback) { (*callback)(type, name); } } } void IMainWindow::defaultDeviceNotify(int type, const QString& portName, const QString& cardName) { for (const auto& callback : m_deviceObservers) { if (callback) { (*callback)(type, portName, cardName); } } } void IMainWindow::trayIconTriggerNotify() { for (const auto& callback : m_trayIconObservers) { if (callback) { (*callback)(); } } } void IMainWindow::sizeChangedNotify(const QSize& size) { for (const auto& callback : m_sizeChangedObservers) { if (callback) { (*callback)(size); } } } ukui-volume-control/tray/ITrayVolumeSliderItem.h0000664000175000017500000000323415171074712021001 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 ITRAYVOLUMESLIDERITEM_H #define ITRAYVOLUMESLIDERITEM_H #include #include #include class ITrayVolumeSlider { public: virtual void setRang(int, int) = 0; virtual void setValue(int) = 0; virtual QWidget* getWidget() = 0; }; class ITrayVolumeSliderItem { public: ITrayVolumeSliderItem(std::shared_ptr, QPushButton*); virtual ~ITrayVolumeSliderItem() {} public: virtual void initUi() = 0; virtual void initSlots() = 0; virtual void setIcon(const QIcon&) = 0; virtual QWidget* getWidget() = 0; virtual void setValue(int) = 0; virtual QPushButton* getMuteButton() = 0; std::shared_ptr getSlider() const; QPushButton* getIconButton() const; protected: std::shared_ptr m_pVolumeSlider = nullptr; QPushButton* m_pIconBtn = nullptr; }; #endif // ITRAYVOLUMESLIDERITEM_H ukui-volume-control/tray/Ukui4DevicePortSelectItem.h0000664000175000017500000000443515171074712021550 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 UKUI4DEVICEPORTSELECTITEM_H #define UKUI4DEVICEPORTSELECTITEM_H #include #include #include #include #include "IDevicePortSelectItem.h" class Ukui4DevicePortItem : public QWidget, public IDevicePortItem { public: Ukui4DevicePortItem(QLabel*, QPushButton*, const QString&, const QString&, QWidget* = nullptr); ~Ukui4DevicePortItem() = default; public: void initUi(); virtual QWidget* getWidget() override; virtual bool isChecked() override; virtual void setChecked(bool) override; virtual void setIcon(const QIcon&) override; private: QLabel* m_pDisplayLabel = nullptr; QPushButton* m_pIconBtn = nullptr; }; class Ukui4DevicePortSelectItem : public QWidget, public IDevicePortSelectItem { public: Ukui4DevicePortSelectItem(QListWidget*, QWidget* = nullptr); public: QListWidget* getListWidget() const; virtual void initSlots() override; virtual void setCurrentIndex(int) override; virtual void insertItem(std::shared_ptr) override; virtual void removeItem(const QString&, const QString&) override; virtual QVariant currentData(int = Qt::UserRole) const override; virtual int count() const override; virtual QWidget* getWidget() override; Q_SIGNALS: void deviceChangedSignals(const QString&, const QString&); private Q_SLOTS: void currentRowChangedSlots(int); private: void setListWidgetSelectItem(int); private: QListWidget* m_pListWidget = nullptr; }; #endif // UKUI4DEVICEPORTSELECTITEM_H ukui-volume-control/tray/Ukui2SystemVolumeSliderItem.h0000664000175000017500000001022315171074712022151 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 UKUI2SYSTEMVOLUMESLIDERITEM_H #define UKUI2SYSTEMVOLUMESLIDERITEM_H #include #include "ITrayVolumeSliderItem.h" #include "Ukui4SystemVolumeSliderItem.h" class Ukui2SystemVolumeSlider : public ITrayVolumeSlider { public: Ukui2SystemVolumeSlider(QSlider*); ~Ukui2SystemVolumeSlider() = default; public: virtual void setRang(int, int) override; virtual void setValue(int) override; virtual QWidget* getWidget() override; private: QSlider* m_pSlider = nullptr; }; class Ukui2SystemVolumeSliderItem : public QWidget, public ITrayVolumeSliderItem { Q_OBJECT public: Ukui2SystemVolumeSliderItem(std::shared_ptr, QLabel*, QPushButton*, const VolumeSliderType&, QWidget* = nullptr); ~Ukui2SystemVolumeSliderItem() = default; public: virtual void initUi() override; virtual void initSlots() override; virtual QWidget* getWidget() override; virtual void setIcon(const QIcon&) override; virtual void setValue(int) override; virtual QPushButton* getMuteButton() override; void setPercent(int); Q_SIGNALS: void valueChangedSignals(int); private Q_SLOTS: void valueChangedSlots(int); private: void updateMuteBtnIcon(int, bool = false); private: VolumeSliderType m_type = VolumeSliderType::VOLUME_SLIDER_SYSTEM_OUTPUT; QLabel* m_pPercentLabel = nullptr; }; class Ukui2DeviceVolumeSliderItem : public QWidget, public ITrayVolumeSliderItem { Q_OBJECT public: Ukui2DeviceVolumeSliderItem(std::shared_ptr, QLabel*, QPushButton*, QPushButton*, const VolumeSliderType&, QWidget* = nullptr); ~Ukui2DeviceVolumeSliderItem() = default; public: virtual void initUi() override; virtual void initSlots() override; virtual QWidget* getWidget() override; virtual void setIcon(const QIcon&) override; virtual void setValue(int) override; virtual QPushButton* getMuteButton() override; void setPercent(int); void setDescription(const QString&); Q_SIGNALS: void valueChangedSignals(int); private Q_SLOTS: void valueChangedSlots(int); private: void updateMuteBtnIcon(int, bool = false); private: VolumeSliderType m_type = VolumeSliderType::VOLUME_SLIDER_SYSTEM_OUTPUT; QLabel* m_pDescLabel = nullptr; QPushButton* m_pMuteBtn = nullptr; }; class Ukui2AppVolumeSlider : public ITrayVolumeSlider { public: Ukui2AppVolumeSlider(QSlider*); ~Ukui2AppVolumeSlider() = default; public: virtual void setRang(int, int) override; virtual void setValue(int) override; virtual QWidget* getWidget() override; private: QSlider* m_pSlider = nullptr; }; class Ukui2AppVolumeSliderItem : public QWidget, public ITrayVolumeSliderItem { Q_OBJECT public: Ukui2AppVolumeSliderItem(std::shared_ptr, QPushButton*, QPushButton*, QLabel*, QWidget* = nullptr); ~Ukui2AppVolumeSliderItem() = default; public: virtual void initUi() override; virtual void initSlots() override; virtual void setIcon(const QIcon&) override; virtual QWidget* getWidget() override; virtual void setValue(int) override; virtual QPushButton* getMuteButton() override; private: void setPercent(int); Q_SIGNALS: void valueChangedSignals(int); private Q_SLOTS: void valueChangedSlots(int); private: QLabel* m_pDisplayLabel = nullptr; QPushButton* m_pMuteBtn = nullptr; }; #endif // UKUI2SYSTEMVOLUMESLIDERITEM_H ukui-volume-control/tray/Ukui4SystemVolumeSliderItem.h0000664000175000017500000000721415171074712022161 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 UKUI4SYSTEMVOLUMESLIDERITEM_H #define UKUI4SYSTEMVOLUMESLIDERITEM_H #include #include #include #include #include "ITrayVolumeSliderItem.h" static const char* iconNameOutputs[] = { "audio-volume-muted-symbolic", "audio-volume-low-symbolic", "audio-volume-medium-symbolic", "audio-volume-high-symbolic", NULL }; /** * @brief 包含VolumeSlider子项的类型 */ enum class VolumeSliderType { VOLUME_SLIDER_MINI_OUTPUT = 0, // 输出音量子项 VOLUME_SLIDER_DEVICE_OUTPUT, VOLUME_SLIDER_DEVICE_INPUT, VOLUME_SLIDER_APP_OUTPUT, VOLUME_SLIDER_SYSTEM_OUTPUT }; class Ukui4SystemVolumeSlider : public ITrayVolumeSlider { public: Ukui4SystemVolumeSlider(QSlider*); ~Ukui4SystemVolumeSlider() = default; public: virtual void setRang(int, int) override; virtual void setValue(int) override; virtual QWidget* getWidget() override; private: QSlider* m_pSlider = nullptr; }; class Ukui4SystemVolumeSliderItem : public QWidget, public ITrayVolumeSliderItem { Q_OBJECT public: Ukui4SystemVolumeSliderItem(std::shared_ptr, QLabel*, QPushButton*, const VolumeSliderType&, QWidget* = nullptr); ~Ukui4SystemVolumeSliderItem() = default; public: virtual void initUi() override; virtual void initSlots() override; virtual QWidget* getWidget() override; virtual void setIcon(const QIcon&) override; virtual void setValue(int) override; virtual QPushButton* getMuteButton() override; void setPercent(int); Q_SIGNALS: void valueChangedSignals(int); private Q_SLOTS: void valueChangedSlots(int); private: void updateMuteBtnIcon(int, bool = false); private: VolumeSliderType m_type = VolumeSliderType::VOLUME_SLIDER_SYSTEM_OUTPUT; QLabel* m_pPercentLabel = nullptr; }; class Ukui4AppVolumeSlider : public ITrayVolumeSlider { public: Ukui4AppVolumeSlider(QSlider*); ~Ukui4AppVolumeSlider() = default; public: virtual void setRang(int, int) override; virtual void setValue(int) override; virtual QWidget* getWidget() override; private: QSlider* m_pSlider = nullptr; }; class Ukui4AppVolumeSliderItem : public QWidget, public ITrayVolumeSliderItem { Q_OBJECT public: Ukui4AppVolumeSliderItem(std::shared_ptr, QPushButton*, QLabel*, QWidget* = nullptr); ~Ukui4AppVolumeSliderItem() = default; public: virtual void initUi() override; virtual void initSlots() override; virtual void setIcon(const QIcon&) override; virtual QWidget* getWidget() override; virtual void setValue(int) override; virtual QPushButton* getMuteButton() override; private: void setPercent(int); Q_SIGNALS: void valueChangedSignals(int); private Q_SLOTS: void valueChangedSlots(int); private: QLabel* m_pPercentLabel = nullptr; }; #endif // UKUI4SYSTEMVOLUMESLIDERITEM_H ukui-volume-control/data/0000775000175000017500000000000015171074677014370 5ustar fengfengukui-volume-control/data/ukui-volume-control.desktop0000664000175000017500000000024615171074677021725 0ustar fengfeng[Desktop Entry] _Name=Ukui Volume Control _Comment=Show desktop volume control Icon=multimedia-volume-control Exec=ukui-audio-service Type=Application NoDisplay=true ukui-volume-control/data/ukui-volume-control.service0000664000175000017500000000035015171074677021710 0ustar fengfeng[Unit] Description=Ukui Volume control daemon After=pulseaudio.service PartOf=graphical-session.target [Service] Type=simple Restart=on-failure RestartSec=5s ExecStart=/usr/bin/ukui-audio-service [Install] WantedBy=default.target ukui-volume-control/CMakeLists.txt0000664000175000017500000000141315171074712016204 0ustar fengfengcmake_minimum_required(VERSION 3.5) project(ukui-volume-control) # Find Qt6 first (required for AUTOMOC/AUTORCC/AUTOUIC) find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core REQUIRED) # Find includes in corresponding build directories set(CMAKE_INCLUDE_CURRENT_DIR ON) # Instruct CMake to run moc automatically when needed set(CMAKE_AUTOMOC ON) # Handle the Qt rcc code generator automatically. set(CMAKE_AUTORCC ON) # Create code from a list of Qt designer ui files set(CMAKE_AUTOUIC ON) add_subdirectory(audio) add_subdirectory(backend) add_subdirectory(tray) add_subdirectory(ukui-shortcut-audio) set(SERVICE_FILES data/ukui-volume-control.service) install(FILES ${SERVICE_FILES} DESTINATION "/usr/lib/systemd/user/") ukui-volume-control/COPYING0000664000175000017500000010451515171074712014506 0ustar fengfeng GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ukui-volume-control/audio/0000775000175000017500000000000015171074712014546 5ustar fengfengukui-volume-control/audio/AppManagerSelectComboxItem.h0000664000175000017500000000356515171074712022072 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 APPMANAGERSELECTCOMBOXITEM_H #define APPMANAGERSELECTCOMBOXITEM_H #include #include "ISelectComboxItem.h" class AppManagerSelectComboxItem : public QFrame, public ISelectComboxItem { Q_OBJECT public: AppManagerSelectComboxItem(std::shared_ptr, QLabel*, QWidget* = nullptr); ~AppManagerSelectComboxItem() = default; public: virtual void initUi() override; virtual void initSlots() override; virtual void setEnabled(bool) override; virtual QWidget* getWidget() override; }; class AppSelectComboxWithDescItem : public QFrame, public ISelectComboxItem { Q_OBJECT public: AppSelectComboxWithDescItem(std::shared_ptr, QLabel*, QLabel*, QWidget* = nullptr); ~AppSelectComboxWithDescItem() = default; public: virtual void initUi() override; virtual void initSlots() override; virtual void setEnabled(bool) override; virtual QWidget* getWidget() override; private: QLabel* m_pHintLabel {}; QLabel* m_pHintIconLabel {}; QVBoxLayout* m_pVLayout {}; QHBoxLayout* m_pHintHLayout {}; }; #endif // APPMANAGERSELECTCOMBOXITEM_H ukui-volume-control/audio/ClientManager.h0000664000175000017500000000427115171074712017434 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 CLIENTMANAGER_H #define CLIENTMANAGER_H #include #include "ISwitchButtonItem.h" #include "IVolumeSliderItem.h" #include "IDetailSettingsItem.h" #include "TitleLabelItem.h" #include "ukui4.0/Ukui4SelectComboxItem.h" #include "ukui4.0/Ukui4SwitchButtonItem.h" #include "ukui4.0/Ukui4VolumeSliderItem.h" #include "ISelectComboxItem.h" class ClientManager { public: ClientManager(std::unordered_map&, std::unordered_map&, std::unordered_map&); virtual ~ClientManager() = default; public: void setValue(int, int); void setMute(int, bool); void setEnabled(int, bool); void setBalanceEnabled(bool); void setRange(int, int, int); void setChecked(int, bool); // void setIcon(int, const QIcon&); void setCurrentIndex(int, int); int findData(int, const QVariant&, int = Qt::UserRole) const; int findText(int, const QString&) const; void addItem(int, const QString&, const QVariant& = QVariant()); void removeItem(int, int); void clear(int); private: std::unordered_map& m_switchBtnItemMap; std::unordered_map& m_volumeSliderItemMap; std::unordered_map& m_selectComboxItemMap; }; #endif // CLIENTMANAGER_H ukui-volume-control/audio/AppManagerItemWidget.h0000664000175000017500000000572015171074712020721 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 APPMANAGERITEMWIDGET_H #define APPMANAGERITEMWIDGET_H #include #include "ISelectComboxItem.h" #include "AppManagerSelectComboxItem.h" #include "AppManagerSliderItem.h" #include "ISelectComboxItem.h" #include "BaseType.h" using namespace UkuiAudioFramwork; class AppManagerItemWidget; typedef struct AppManagerItem { uint32_t index; QString iconName; std::vector inputStreamVec; std::vector outputStreamVec; AppManagerItemWidget* wid; } AppManagerItem; class AppManagerItemWidget : public QWidget { Q_OBJECT public: AppManagerItemWidget(QLabel*, const QString&, QWidget* parent = nullptr); ~AppManagerItemWidget() = default; public: void setRange(const VolumeSliderType&, int, int); void setValue(const VolumeSliderType&, int); void setMuted(const VolumeSliderType&, bool); void setCurrentIndex(const NodeType&, int); int findData(const NodeType&, const QVariant&); void insertItem(const NodeType&, const QString&, const QVariant& = QVariant()); void removeItem(const NodeType&, int); void setEnabled(const NodeType&, bool); IVolumeSliderItem* getSliderItem() const; std::unordered_map getSelectComboxItems() const; Q_SIGNALS: void closeWindowSignals(); private Q_SLOTS: void closeWindowSlots(); private: void createItems(); void initUi(); void initSlots(); private: QString m_name {}; QLabel* m_pTitileLabel {}; QPushButton* m_pConfirmBtn {}; std::unordered_map m_volumeKeys { {VolumeSliderType::VOLUME_SLIDER_SINKINPUT, tr("Output Volume")}, }; std::unordered_map m_selectKeys { {NodeType::OUTPUT, QStringList{std::initializer_list{tr("Output Device"), QObject::tr("This case does not support setting the output device")}}}, {NodeType::INPUT, QStringList{std::initializer_list{tr("Input Device"), QObject::tr("This case does not support setting the input device")}}}, }; std::unordered_map m_volumeSliderItemMap; std::unordered_map m_selectComboxItemMap; }; #endif // APPMANAGERITEMWIDGET_H ukui-volume-control/audio/AppManagerMainWidget.cpp0000664000175000017500000003122215171074712021236 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "AppManagerMainWidget.h" #include #include #include "Ukui4CustomControl.h" AppManagerMainWidget::AppManagerMainWidget(std::list& appManagerList, QWidget* parent) : m_appWidgetList(appManagerList), KWidget(parent) { initUi(); initSlots(); } QList getAvailablePortList(const NodeType& type) { QList list; QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getAvailablePortList", EnumToInt(type)); if (reply.arguments().isEmpty()) { qDebug() << "返回参数为空"; return {}; } const QDBusArgument& dbusArgs = reply.arguments().at(0).value(); dbusArgs.beginArray(); while (!dbusArgs.atEnd()) { ClientDeviceInfo info; dbusArgs >> info; list.push_back(info); } dbusArgs.endArray(); return list; } void AppManagerMainWidget::initUi() { setIcon(QIcon::fromTheme("ukui-control-center")); setWidgetName(tr("App Sound Control")); setWindowFlags(Qt::Dialog); setWindowModality(Qt::WindowModality::WindowModal); setContentsMargins(0, 0, 0, 0); setFixedSize(760, 520); m_pNavigationBar = new kdk::KNavigationBar(this); for (const auto& [k, v] : m_itemMap) { m_pNavigationBar->addItem(v); } m_pNavigationBar->setFixedSize(200, 478); m_pNavigationBar->setContentsMargins(0, 0, 0, 0); auto vLayout = new QVBoxLayout(this); vLayout->setContentsMargins(6, 20, 0, 0); vLayout->setSpacing(0); vLayout->addWidget(m_pNavigationBar); sideBar()->setLayout(vLayout); // 创建QStackedWidget用于管理不同的界面 m_pStackedWidget = new QStackedWidget(this); m_pStackedWidget->setContentsMargins(0, 0, 0, 0); m_pStackedWidget->setFixedSize(560, 478); for (const auto& w : m_appWidgetList) { m_pStackedWidget->addWidget(std::move(w.wid)); } // 添加右侧设备窗口布局 auto vLayout2 = new QVBoxLayout; vLayout2->setContentsMargins(0, 20, 0, 0); vLayout2->setSpacing(0); vLayout2->addWidget(m_pStackedWidget); baseBar()->setLayout(vLayout2); setLayoutType(kdk::HorizontalType); } void AppManagerMainWidget::initSlots() { connect(m_pNavigationBar->listview(), &QListView::clicked, this, [=](const QModelIndex &index){ auto idx = index.row(); m_pStackedWidget->setCurrentIndex(idx); }); // 键盘方向键 + enter键支持 connect(m_pNavigationBar->listview(), &QListView::activated, this, [=](const QModelIndex &index){ auto idx = index.row(); m_pStackedWidget->setCurrentIndex(idx); }); } void AppManagerMainWidget::addItem(int dire, int idx, int value, bool muted, const QString& icon, const QString& desc, const QList& outputList, const QList& inputList) { /** The application management interface does not display blacklisted applications */ if (desc.isEmpty() || m_blackList.contains(desc) || m_blackList.contains(icon)) return; AppManagerItem appItem; if (dire) appItem.outputStreamVec.emplace_back(idx); else appItem.inputStreamVec.emplace_back(idx); auto wid = new AppManagerItemWidget(new AutoOmitLabel(desc), icon, this); auto item = new QStandardItem(QIcon::fromTheme(icon), desc); wid->setMuted(VolumeSliderType::VOLUME_SLIDER_SINKINPUT, muted); wid->setValue(VolumeSliderType::VOLUME_SLIDER_SINKINPUT, value); qDebug() << QString("add %1 to app widget, value: %2, muted: %3.").arg(desc).arg(value).arg(muted); appItem.iconName = icon; appItem.index = m_itemMap.size(); appItem.wid = wid; m_appWidgetList.push_back(appItem); qDebug() << QString("add %1 to app widget, index: %2, icon: %3.").arg(desc).arg(idx).arg(icon); // 设置项的样式 QString itemStyle = "QStandardItem { min-width: 176px; max-width: 176px; min-height: 36px; max-height: 36px; }"; item->setData(itemStyle, Qt::UserRole); m_pStackedWidget->addWidget(wid); m_pNavigationBar->addItem(item); m_itemMap.emplace(icon, item); bool isEnableOutPut = false; for (const auto dev : outputList) { if (dev.enabled) { isEnableOutPut = true; addPortToAppItem(appItem.iconName, NodeType::OUTPUT, QString(dev.portLabel + " (" + dev.cardDesc + ")"), QVariant(std::initializer_list {dev.cardName, dev.portName})); } } if (!isEnableOutPut) { addPortToAppItem(appItem.iconName, NodeType::OUTPUT, tr("None"), QVariant(std::initializer_list {"None", "None"})); } bool isEnableInPut = false; for (const auto dev : inputList) { if (dev.enabled) { isEnableInPut = true; addPortToAppItem(appItem.iconName, NodeType::INPUT, QString(dev.portLabel + " (" + dev.cardDesc + ")"), QVariant(std::initializer_list {dev.cardName, dev.portName})); } } if (!isEnableInPut) { addPortToAppItem(appItem.iconName, NodeType::INPUT, tr("None"), QVariant(std::initializer_list {"None", "None"})); } auto s = static_cast(wid->getSliderItem()->getSlider()->getWidget()); auto btn = wid->getSliderItem()->getMuteBtn(); connect(wid, &AppManagerItemWidget::closeWindowSignals, this, [this]() { Q_EMIT closeWindowSignals(); }); if (s) { wid->setValue(VolumeSliderType::VOLUME_SLIDER_SINKINPUT, isEnableOutPut ? value : 0); wid->setMuted(VolumeSliderType::VOLUME_SLIDER_SINKINPUT, isEnableOutPut ? muted : true); s->setEnabled(isEnableOutPut ? true : false); btn->setEnabled(isEnableOutPut ? true : false); connect(s, &QSlider::valueChanged, this, [idx, icon, this](int val) { Q_EMIT valueChangedSignals(idx, val); //start qDebug() << "AppManagerMainWidget valueChanged icon:" << icon << "val:" << val; if ((icon.compare(QString("kylin-music")) == 0) || (icon.compare(QString("kylin-video")) == 0)) { QDBusMessage msg = QDBusMessage::createSignal(QString("/"), QString("org.ukui.media"), QString("sinkVolumeChanged")); msg << icon << val << false; QDBusConnection::sessionBus().send(msg); } //end }); } if (btn) { connect(btn, &QPushButton::clicked, this, [idx, this]() { Q_EMIT muteChangedSignals(idx); }); } auto selectComboxList = wid->getSelectComboxItems(); for (const auto& box : selectComboxList) { auto combox = static_cast(box.second->getSelectBox()->getWidget()); connect(combox, static_cast(&QComboBox::currentIndexChanged), this, [idx, box, desc, this](int) { auto data = box.second->getSelectBox()->currentData().toStringList(); if (data.size() < 2) { qWarning() << QString("Ivalid combox data."); return; } qDebug() << QString("%1 application change device to card: %2, port: %3").arg(desc).arg(data.at(0)).arg(data.at(1)); Q_EMIT currentIndexChangedSignals(box.first, idx, data.at(0), data.at(1)); }); } } void AppManagerMainWidget::removeItem(const QString& name) { auto it = std::find_if(m_appWidgetList.begin(), m_appWidgetList.end(), [name](const AppManagerItem& item) { return item.iconName == name; }); if (it != m_appWidgetList.end()) { m_pStackedWidget->removeWidget((*it).wid); m_appWidgetList.erase(it); } else qWarning() << QString("Can not found %1 stream in app manager widget, remove failed.").arg(name); auto at = m_itemMap.find(name); if (at != m_itemMap.end()) { m_pNavigationBar->model()->removeRow(at->second->row()); m_itemMap.erase(at); } else qWarning() << QString("Can not found %1 stream in app manager widget, remove failed.").arg(name); } void AppManagerMainWidget::setValue(const QString& name, int value) { auto it = std::find_if(m_appWidgetList.begin(), m_appWidgetList.end(), [name](const AppManagerItem& item) { return item.iconName == name; }); if (it != m_appWidgetList.end()) { (*it).wid->getSliderItem()->setValue(value); } else { qWarning() << QString("Does not exit %1 stream, set value failed.").arg(name); } } void AppManagerMainWidget::setMute(const QString& name, bool mute) { auto it = std::find_if(m_appWidgetList.begin(), m_appWidgetList.end(), [name](const AppManagerItem& item) { return item.iconName == name; }); if (it != m_appWidgetList.end()) { (*it).wid->getSliderItem()->setMute(mute); } else { qWarning() << QString("Does not exit %1 stream, set mute failed.").arg(name); } } void AppManagerMainWidget::setCurrentItem(int index) { auto model = m_pNavigationBar->model(); m_pNavigationBar->listview()->setCurrentIndex(model->item(index, 0)->index()); } void AppManagerMainWidget::setCurrentIndex(const QString& name, const NodeType& type, int index) { auto it = std::find_if(m_appWidgetList.begin(), m_appWidgetList.end(), [name](const AppManagerItem& item) { return item.iconName == name; }); if (it != m_appWidgetList.end()) { if (index < 0) { int noneIndex = (*it).wid->findData(type, QVariant(std::initializer_list {"None", "None"})); (*it).wid->setCurrentIndex(type, noneIndex); } else { (*it).wid->setCurrentIndex(type, index); } } else { qWarning() << QString("Does not exit %1 stream, set current index failed.").arg(name); } } void AppManagerMainWidget::addPortToAppItem(const QString& name, const NodeType& type, const QString& text, const QVariant& data) { auto it = std::find_if(m_appWidgetList.begin(), m_appWidgetList.end(), [name](const AppManagerItem& item) { return item.iconName == name; }); if (it != m_appWidgetList.end()) { (*it).wid->insertItem(type, text, data); } else { qWarning() << QString("Does not exit %1 stream, add port failed.").arg(name); } } void AppManagerMainWidget::setAppCbxEnabled(const QString& name, const NodeType& type, bool enable) { qDebug() << QString("Set %1 %2 combox enable to %3").arg(name).arg(EnumToInt(type)).arg(enable); auto it = std::find_if(m_appWidgetList.begin(), m_appWidgetList.end(), [name](const AppManagerItem& item) { return item.iconName == name; }); if (it != m_appWidgetList.end()) { (*it).wid->setEnabled(type, enable); } else { qWarning() << QString("Does not exit %1 stream, set combox enable failed.").arg(name); } } void AppManagerMainWidget::removeProtFromAppItem(const QString& name, const NodeType& type, int cbxIndex) { auto it = std::find_if(m_appWidgetList.begin(), m_appWidgetList.end(), [name](const AppManagerItem& item) { return item.iconName == name; }); if (it != m_appWidgetList.end()) { (*it).wid->removeItem(type, cbxIndex); } else { qWarning() << QString("Does not exit %1 stream, remove port failed.").arg(name); } } std::list& AppManagerMainWidget::getAppItems() { return m_appWidgetList; } AppManagerItemWidget* AppManagerMainWidget::getAppWidgetByIndex(int idx) { auto it = std::find_if(m_appWidgetList.begin(), m_appWidgetList.end(), [idx](const AppManagerItem& item) { return item.index == idx; }); if (it != m_appWidgetList.end()) { return (*it).wid; } return {}; } ukui-volume-control/audio/ISwitchButton.h0000664000175000017500000000150715171074712017470 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 ISWITCHBUTTON_H #define ISWITCHBUTTON_H #endif // ISWITCHBUTTON_H ukui-volume-control/audio/DeviceManagerWidget.h0000664000175000017500000000541715171074712020564 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 DEVICEMANAGERWIDGET_H #define DEVICEMANAGERWIDGET_H #include #include #include #include #include #include #include #include #include "IDeviceManagerItem.h" #include "DBusClient.h" #include "BaseType.h" using namespace UkuiAudioFramwork; class DeviceManagerItem : public QFrame, public IDeviceManagerItem { public: DeviceManagerItem(QLabel*, std::shared_ptr, const QString&, const QString&, QWidget* = nullptr); ~DeviceManagerItem() = default; public: virtual bool isChecked() const override; virtual void setChecked(bool) override; virtual QWidget* getWidget() override; virtual std::shared_ptr getSwitchBtn() const override; virtual QVariant getData() const override; private: void initUi(); private: QString m_cardName; QString m_portName; QLabel* m_pLabel = nullptr; std::shared_ptr m_pSwitchBtn = nullptr; }; class DeviceManagerWidget : public QWidget { Q_OBJECT public: DeviceManagerWidget(QLabel*, QWidget* = nullptr); ~DeviceManagerWidget() = default; public: void insertToWidget(int); void addItem(const QString&, const QString&, const QString&); void removeItem(int); void setChecked(int, bool); std::list getDataList() const; Q_SIGNALS: void enableDeviceSignals(const QString&, const QString&, bool); void closeWindowSignals(); private: void initUi(); void initSlots(); private Q_SLOTS: void closeWindow(); private: QLabel* m_pLabel = nullptr; QScrollArea* m_pScrollArea = nullptr; QWidget* m_pDisDevManagerWidget = nullptr; QVBoxLayout* m_pVlayout = nullptr; QPushButton* m_pConfirmBtn; QStackedWidget* m_pStackWidget; std::vector> m_devManagerItemVec {}; // std::unordered_map> m_devManagerItemMap {}; NodeType m_type; }; #endif // DEVICEMANAGERWIDGET_H ukui-volume-control/audio/ClientMethod.cpp0000664000175000017500000000607315171074712017637 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "ClientMethod.h" #include ClientMethod& ClientMethod::getInstance() { static ClientMethod instance; return instance; } void ClientMethod::setManager(std::shared_ptr manager) { m_pClientManager = manager; } void ClientMethod::setValue(int type, int value) { if (m_pClientManager) m_pClientManager->setValue(type, value); } void ClientMethod::setMute(int type, bool mute) { if (m_pClientManager) m_pClientManager->setMute(type, mute); } void ClientMethod::setEnabled(int type, bool enable) { qDebug() << __func__ << "type:" << type << "enable:" << enable; if (m_pClientManager) m_pClientManager->setEnabled(type, enable); } void ClientMethod::setBalanceEnabled(bool enable) { qDebug() << __func__ << "enable:" << enable; if (m_pClientManager) m_pClientManager->setBalanceEnabled(enable); } void ClientMethod::setRange(int type, int min, int max) { if (m_pClientManager) m_pClientManager->setRange(type, min, max); } void ClientMethod::setChecked(int type, bool checked) { if (m_pClientManager) m_pClientManager->setChecked(type, checked); else { qDebug() << "m_pClientManager is null, pluse init client manager!"; } } void ClientMethod::setCurrentIndex(int type, int idx) { if (-1 == idx) { qDebug() << QString("type: %1 idx: %2 is invalid").arg(type).arg(idx); return; } if (m_pClientManager) m_pClientManager->setCurrentIndex(type, idx); qDebug() << "ClientMethod::setCurrentIndex, type:" << type << " idx: " << idx; } int ClientMethod::findData(int type, const QVariant& value, int role) const { if (m_pClientManager) return m_pClientManager->findData(type, value, role); return -1; } int ClientMethod::findText(int type, const QString& text) const { if (m_pClientManager) return m_pClientManager->findText(type, text); return -1; } void ClientMethod::addItem(int type, const QString& text, const QVariant& userData) { if (m_pClientManager) m_pClientManager->addItem(type, text, userData); } void ClientMethod::removeItem(int type, int idx) { if (m_pClientManager) m_pClientManager->removeItem(type, idx); } void ClientMethod::clear(int type) { if (m_pClientManager) m_pClientManager->clear(type); } ukui-volume-control/audio/IVolumeSliderItem.h0000664000175000017500000000503615171074712020265 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 IVOLUMESLIDERITEM_H #define IVOLUMESLIDERITEM_H #include #include #include /** * @brief 包含VolumeSlider子项的类型 */ enum class VolumeSliderType { VOLUME_SLIDER_INPUT = 0, // 输入音量子项 VOLUME_SLIDER_OUTPUT, // 输出音量子项 VOLUME_SLIDER_BALANCE, // 平衡音量子项 VOLUME_SLIDER_INPUTLEVEL, // 输入反馈子项 VOLUME_SLIDER_SINKINPUT }; static const char* iconNameOutputs[] = { "audio-volume-muted-symbolic", "audio-volume-low-symbolic", "audio-volume-medium-symbolic", "audio-volume-high-symbolic", NULL }; static const char* iconNameInputs[] = { "microphone-sensitivity-muted-symbolic", "microphone-sensitivity-low-symbolic", "microphone-sensitivity-medium-symbolic", "microphone-sensitivity-high-symbolic", NULL }; class IVolumeSlider { public: virtual void setRange(int, int) = 0; virtual void setValue(int) = 0; virtual void setEnabled(bool) = 0; virtual bool isEnabled() const = 0; virtual uint32_t getValue() const = 0; virtual QWidget* getWidget() = 0; }; class IVolumeSliderItem { public: IVolumeSliderItem(std::shared_ptr, QLabel*); virtual ~IVolumeSliderItem() = default; public: virtual void initUi() = 0; virtual void initSlots() = 0; virtual QWidget* getWidget() = 0; virtual void setValue(int) = 0; virtual void setMute(bool) = 0; virtual void setEnabled(bool) = 0; virtual QPushButton* getMuteBtn() const = 0; std::shared_ptr getSlider() const; void setRange(int, int); protected: std::shared_ptr m_pVolumeSlider = nullptr; QLabel* m_pDisplayLabel = nullptr; }; #endif // IVOLUMESLIDERITEM_H ukui-volume-control/audio/include/0000775000175000017500000000000015171074712016171 5ustar fengfengukui-volume-control/audio/include/interface.h0000664000175000017500000000475615171074712020316 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 INTERFACE_H #define INTERFACE_H class QString; class QWidget; class QStringLiteral; enum FunType{ SYSTEM, DEVICES, PERSONALIZED, NETWORK, ACCOUNT, DATETIME, UPDATE, NOTICEANDTASKS, TOTALMODULES, }; enum SystemIndex{ DISPLAY, TOUCHSCREEN, DEFAULTAPP, POWER, AUTOBOOT, TOTALSYSFUNC, }; enum DevicesIndex{ PRINTER, PROJECTION, MOUSE, TOUCHPAD, KEYBOARD, SHORTCUT, AUDIO, BLUETOOTH, TOTALDEVICESFUNC, }; enum PersonalizedIndex{ BACKGROUND, THEME, SCREENLOCK, FONTS, SCREENSAVER, DESKTOP, TOTALPERSFUNC, }; enum NetworkIndex{ NETCONNECT, VPN, PROXY, VINO, TOTALNETFUNC, }; enum AccountIndex{ USERINFO, NETWORKACCOUNT, TOTALACCOUNTFUNC, }; enum DatetimeIndex{ DAT, AREA, TOTALDTFUNC, }; enum SeUpdatesIndex{ SECURITYCENTER, BACKUP, UPDATES, UPGRADE, TOTALSUFUNC, }; enum NoticeAndTasksIndex{ NOTICE, SEARCH, ABOUT, EXPERIENCEPLAN, TOTALNATFUNC, }; class CommonInterface{ public: virtual ~CommonInterface(){} virtual QString get_plugin_name() = 0; virtual int get_plugin_type() = 0; virtual QWidget * get_plugin_ui() = 0; virtual void plugin_delay_control() = 0; /** * \brief name * module name (用于搜索?) */ virtual const QString name() const = 0; /** * \brief translationPath * 获取多语言文件路径,用于搜索 * \return QString */ virtual QString translationPath()const { return QStringLiteral(":/i18n/%1.ts"); } }; #define CommonInterface_iid "org.kycc.CommonInterface" Q_DECLARE_INTERFACE(CommonInterface, CommonInterface_iid) #endif // INTERFACE_H ukui-volume-control/audio/include/SwitchButton.cpp0000664000175000017500000001776715171074712021354 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "SwitchButton.h" #include #define THEME_QT_SCHEMA "org.ukui.style" #define THEME_GTK_SCHEMA "org.mate.interface" SwitchButton::SwitchButton(QWidget *parent) : QWidget(parent) { // this->resize(QSize(52, 24)); this->setFixedSize(QSize(50, 24)); checked = false; hover = false; disabled = false; space = 4; // rectRadius = 5; step = width() / 40; startX = 0; endX= 0; timer = new QTimer(this); timer->setInterval(5); connect(timer, SIGNAL(timeout()), this, SLOT(updatevalue())); if (QGSettings::isSchemaInstalled(THEME_GTK_SCHEMA) && QGSettings::isSchemaInstalled(THEME_QT_SCHEMA)) { QByteArray qtThemeID(THEME_QT_SCHEMA); QByteArray gtkThemeID(THEME_GTK_SCHEMA); m_gtkThemeSetting = new QGSettings(gtkThemeID,QByteArray(),this); m_qtThemeSetting = new QGSettings(qtThemeID,QByteArray(),this); QString style = m_qtThemeSetting->get("styleName").toString(); changeColor(style); connect(m_qtThemeSetting,&QGSettings::changed, [this] (const QString &key) { QString style = m_qtThemeSetting->get("styleName").toString(); if (key == "styleName") { changeColor(style); } }); } } SwitchButton::~SwitchButton() { } void SwitchButton::paintEvent(QPaintEvent *){ //启用反锯齿 QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing, true); drawBg(&painter); drawSlider(&painter); } void SwitchButton::changeColor(const QString &themes) { if (hover) { return ; } if (themes == "ukui-dark" || themes == "ukui-black") { bgColorOff = QColor(OFF_BG_DARK_COLOR); bgColorOn = QColor(ON_BG_DARK_COLOR); rectColorEnabled = QColor(ENABLE_RECT_DARK_COLOR); rectColorDisabled = QColor(DISABLE_RECT_DARK_COLOR); sliderColorDisabled = QColor(DISABLE_RECT_DARK_COLOR); sliderColorEnabled = QColor(ENABLE_RECT_DARK_COLOR); bgHoverOnColor = QColor(ON_HOVER_BG_DARK_COLOR); bgHoverOffColor = QColor(OFF_HOVER_BG_DARK_COLOR); bgColorDisabled = QColor(DISABLE_DARK_COLOR); } else { bgColorOff = QColor(OFF_BG_LIGHT_COLOR); bgColorOn = QColor(ON_BG_LIGHT_COLOR); rectColorEnabled = QColor(ENABLE_RECT_LIGHT_COLOR); rectColorDisabled = QColor(DISABLE_RECT_LIGHT_COLOR); sliderColorDisabled = QColor(DISABLE_RECT_LIGHT_COLOR); sliderColorEnabled = QColor(ENABLE_RECT_LIGHT_COLOR); bgHoverOnColor = QColor(ON_HOVER_BG_LIGHT_COLOR); bgHoverOffColor = QColor(OFF_HOVER_BG_LIGHT_COLOR); bgColorDisabled = QColor(DISABLE_LIGHT_COLOR); } } void SwitchButton::drawBg(QPainter *painter){ painter->save(); // painter->setPen(Qt::NoPen); if (disabled) { painter->setPen(Qt::NoPen); painter->setBrush(bgColorDisabled); } else { if (!checked){ painter->setPen(Qt::NoPen); painter->setBrush(bgColorOff); } else { painter->setPen(Qt::NoPen); painter->setBrush(bgColorOn); } } //circle out // QRect rect(space, space, width() - space * 2, height() - space * 2); // painter->drawRoundedRect(rect, rectRadius, rectRadius); //circle in QRect rect(0, 0, width(), height()); //半径为高度的一半 int radius = rect.height() / 2; //圆的宽度为高度 int circleWidth = rect.height(); QPainterPath path; path.moveTo(radius, rect.left()); path.arcTo(QRectF(rect.left(), rect.top(), circleWidth, circleWidth), 90, 180); path.lineTo(rect.width() - radius, rect.height()); path.arcTo(QRectF(rect.width() - rect.height(), rect.top(), circleWidth, circleWidth), 270, 180); path.lineTo(radius, rect.top()); painter->drawPath(path); painter->restore(); } void SwitchButton::drawSlider(QPainter *painter){ painter->save(); painter->setPen(Qt::NoPen); if (!disabled){ painter->setBrush(sliderColorEnabled); } else painter->setBrush(sliderColorDisabled); //circle out // QRect rect(0, 0, width() - space, height() - space); // int sliderwidth = rect.height(); // QRect sliderRect(startX, space / 2, sliderwidth, sliderwidth); // painter->drawEllipse(sliderRect); //circle in if (disabled) { if (checked) { QRect smallRect(8, height() / 2 - 2, 10 , 4); painter->drawRoundedRect(smallRect,3,3); } else { QRect smallRect(width() - 8 * 2, height() / 2 - 2, 10 , 4); painter->drawRoundedRect(smallRect,3,3); } } QRect rect(0, 0, width(), height()); int sliderWidth = rect.height() - space * 2; QRect sliderRect(startX + space, space, sliderWidth, sliderWidth); painter->drawEllipse(sliderRect); painter->restore(); } void SwitchButton::mousePressEvent(QMouseEvent *event){ if (timer->isActive()) { return ; } if (!disabled){ checked = !checked; Q_EMIT checkedChanged(checked); step = width() / 40; if (checked){ //circle out // endX = width() - height() + space; //circle in endX = width() - height(); } else { endX = 0; } timer->start(); } else { endX = 0; } // return QWidget::mousePressEvent(event); } void SwitchButton::resizeEvent(QResizeEvent *){ // step = width() / 40; if (checked){ //circle out // startX = width() - height() + space; //circle in startX = width() - height(); } else startX = 0; update(); } #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) void SwitchButton::enterEvent(QEnterEvent *event) { #else void SwitchButton::enterEvent(QEvent *event) { #endif bgColorOn = bgHoverOnColor; bgColorOff = bgHoverOffColor; hover = true; update(); #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) return QWidget::enterEvent(event); #else return QWidget::enterEvent(event); #endif } void SwitchButton::leaveEvent(QEvent *event) { hover = false; QString style = m_qtThemeSetting->get("styleName").toString(); changeColor(style); update(); return QWidget::leaveEvent(event); } void SwitchButton::updatevalue(){ if (disabled) { return ; } if (checked) if (startX < endX){ startX = startX + step; } else { startX = endX; timer->stop(); } else { if (startX > endX){ startX = startX - step; } else { startX = endX; timer->stop(); } } update(); } void SwitchButton::setChecked(bool checked){ if (this->checked != checked){ this->checked = checked; Q_EMIT checkedChanged(checked); update(); } step = width() / 40; if (checked){ //circle out // endX = width() - height() + space; //circle in endX = width() - height(); } else { endX = 0; } timer->start(); } bool SwitchButton::isChecked(){ return this->checked; } void SwitchButton::setDisabledFlag(bool value) { disabled = value; update(); } bool SwitchButton::getDisabledFlag() { return disabled; } ukui-volume-control/audio/include/SwitchButton.h0000664000175000017500000000562715171074712021011 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 SWITCHBUTTON_H #define SWITCHBUTTON_H #include #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #include #endif #include #define OFF_BG_DARK_COLOR "#404040" #define OFF_HOVER_BG_DARK_COLOR "#666666" #define ON_BG_DARK_COLOR "#3790FA" #define ON_HOVER_BG_DARK_COLOR "#40A9FB" #define DISABLE_DARK_COLOR "#474747" #define DISABLE_RECT_DARK_COLOR "#6E6E6E" #define ENABLE_RECT_DARK_COLOR "#FFFFFF" #define OFF_BG_LIGHT_COLOR "#E0E0E0" #define OFF_HOVER_BG_LIGHT_COLOR "#B3B3B3" #define ON_BG_LIGHT_COLOR "#3790FA" #define ON_HOVER_BG_LIGHT_COLOR "#40A9FB" #define DISABLE_LIGHT_COLOR "#E9E9E9" #define DISABLE_RECT_LIGHT_COLOR "#B3B3B3" #define ENABLE_RECT_LIGHT_COLOR "#FFFFFF" class SwitchButton : public QWidget { Q_OBJECT public: SwitchButton(QWidget* = nullptr); ~SwitchButton(); public: void setChecked(bool); bool isChecked(); void setDisabledFlag(bool); bool getDisabledFlag(); protected: virtual void mousePressEvent(QMouseEvent*) override; virtual void resizeEvent(QResizeEvent*) override; virtual void paintEvent(QPaintEvent*) override; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) virtual void enterEvent(QEnterEvent*) override; #else virtual void enterEvent(QEvent*) override; #endif virtual void leaveEvent(QEvent*) override; private Q_SLOTS: void updatevalue(); Q_SIGNALS: void checkedChanged(bool checked); private: void drawBg(QPainter*); void drawSlider(QPainter*); void changeColor(const QString&); private: bool checked; bool disabled; QColor bgColorOff; QColor bgColorOn; QColor bgHoverOnColor; QColor bgHoverOffColor; QColor bgColorDisabled; QColor sliderColorEnabled; QColor sliderColorDisabled; QColor rectColorEnabled; QColor rectColorDisabled; QGSettings* m_qtThemeSetting; QGSettings* m_gtkThemeSetting; int space; //滑块离背景间隔 int rectRadius; //圆角角度 int step; //移动步长 int startX; int endX; bool hover; QTimer* timer; }; #endif // SWITCHBUTTON_H ukui-volume-control/audio/IVolumeSliderItem.cpp0000664000175000017500000000232515171074712020616 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "IVolumeSliderItem.h" IVolumeSliderItem::IVolumeSliderItem(std::shared_ptr slider, QLabel *label) : m_pVolumeSlider(slider), m_pDisplayLabel(label) { } //void IVolumeSliderItem::setValue(int value) //{ // m_pVolumeSlider->setValue(value); //} std::shared_ptr IVolumeSliderItem::getSlider() const { return m_pVolumeSlider; } void IVolumeSliderItem::setRange(int min, int max) { m_pVolumeSlider->setRange(min, max); } ukui-volume-control/audio/Ukui5DetailSettingsItem.h0000664000175000017500000000272315171074712021410 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 UKUI5DETAILSETTINGSITEM_H #define UKUI5DETAILSETTINGSITEM_H #include #include "IDetailSettingsItem.h" class Ukui5DetailSettings : public IDetailSettings { public: Ukui5DetailSettings(QPushButton*, const QString& = ""); ~Ukui5DetailSettings() = default; public: virtual QWidget* getWidget() override; private: QPushButton* m_pSettingBtn; }; class Ukui5DetailSettingsItem : public QFrame, public IDetailSettingsItem { public: Ukui5DetailSettingsItem(std::shared_ptr, QLabel*, QWidget* = nullptr); public: virtual void initUi() override; virtual void initSlots() override; virtual QWidget* getWidget() override; }; #endif // UKUI5DETAILSETTINGSITEM_H ukui-volume-control/audio/MainWidget.cpp0000664000175000017500000016036715171074712017317 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "MainWidget.h" #include #include #include #include "ukui2.0/Ukcc2MainWidget.h" #include "Ukui4MainWidget.h" #include "Ukui5MainWidget.h" #include "ClientMethod.h" #include "ConcreteStrategy.h" #include "DeviceManagerMainWidget.h" MainWidget::MainWidget(QWidget* parent) : QWidget(parent) { getDesktopEnvironmentVersion(); initUi(); initSlots(); initClientManager(); initData(); } void MainWidget::initUi() { switch (m_version) { case DesktopEnvironmentVersion::DESKTOP_ENVIRONMENT_VERSION_UKUI2: // m_pMainWid = new Ukcc2MainWidget(m_switchBtnItemMap, m_volumeSliderItemMap, m_selectComboxItemMap, this); // break; case DesktopEnvironmentVersion::DESKTOP_ENVIRONMENT_VERSION_UKUI3: case DesktopEnvironmentVersion::DESKTOP_ENVIRONMENT_VERSION_UKUI4: { m_pStrategy = std::make_shared(); break; } case DesktopEnvironmentVersion::DESKTOP_ENVIRONMENT_VERSION_UKUI5: { m_pStrategy = std::make_shared(); break; } default: m_pStrategy = std::make_shared(); break; } if (m_pStrategy) { m_pMainWid = m_pStrategy->createMainWindow(m_titleLabelItemMap, m_switchBtnItemMap, m_volumeSliderItemMap, m_selectComboxItemMap, m_detailSettingsItemMap, m_appWidgetList, this); m_pMainWid->initUi(); m_pMainWid->initSlots(); } else { QMessageBox::critical(this, tr("Audio"), tr("Unable to obtain system version information, please check the system version!"), QMessageBox::Ok); return; } QHBoxLayout* hLayout = new QHBoxLayout(); hLayout->addWidget(m_pMainWid->getWidget()); setLayout(hLayout); hLayout->setContentsMargins(0, 0, 0, 0); } void MainWidget::initSystemDeviceList(const NodeType& type) { auto deviceList = getAvailablePortList(type); auto list = m_pMainWid->getDevicePortList(EnumToInt(type)); int direction = (type == NodeType::OUTPUT) ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT; auto dev = std::find_if(deviceList.begin(), deviceList.end(), [direction](const ClientDeviceInfo& info) { return direction == info.direction && info.enabled; }); if (0 == deviceList.size() || dev == deviceList.end()) { ClientMethod::getInstance().clear(EnumToInt(type)); ClientMethod::getInstance().addItem(EnumToInt(type), tr("None"), QVariant(std::initializer_list {"none", "none"})); // 31268 系统未识别到声卡输出设备时,音量调节置灰不可调 ClientMethod::getInstance().setValue(EnumToInt(type), 0); ClientMethod::getInstance().setEnabled(EnumToInt(type), false); ClientMethod::getInstance().setCurrentIndex(EnumToInt(type), 0); if (NodeType::INPUT == type) m_pMainWid->updateUIByDevice(EnumToInt(NodeType::INPUT), false); return; } // 添加组合音频输出设备 bool isExitMultiAudioDevice = false; if (NodeType::OUTPUT == type) { auto status = getMultiAudioCombineStatus(type); if (status) { auto sinkList = getSinkList(); auto it = std::find_if(sinkList.begin(), sinkList.end(), [](const ClientDefaultDeviceInfo& info) { return info.name.contains("combine"); }); if (it != sinkList.end()) { isExitMultiAudioDevice = true; qDebug() << "Add Multiple Bluetooth outputs, (*it).activePortName " << (*it).activePortName << "(*it).cardName" << (*it).cardName; auto at = std::find_if(list.begin(), list.end(), [](const DevicePortInfo& info) { return info.cardName == "combine-device"; }); if (at == list.end()) { ClientMethod::getInstance().addItem(EnumToInt(NodeType::OUTPUT), tr("Multi Bluetooth Output"), QVariant(std::initializer_list {(*it).cardName, (*it).activePortName})); } } } } // 如果有可用的设备端口,查找界面中是否存在无端口,存在则删除 auto it = std::find_if(list.begin(), list.end(), [](const DevicePortInfo& info) { return "none" == info.cardName && "none" == info.portName; }); if (it != list.end()) { // 31268 系统未识别到声卡输出设备时,音量调节置灰不可调 ClientMethod::getInstance().setEnabled(EnumToInt(type), true); ClientMethod::getInstance().removeItem(EnumToInt(type), (*it).index); } for (const auto& dev : deviceList) { // 禁用的设备不显示在设备列表里面 if (!dev.enabled) continue; auto it = std::find_if(list.begin(), list.end(), [dev](const DevicePortInfo& info) { return dev.cardName == info.cardName && dev.portName == info.portName; }); // 没找到则添加 if (it == list.end()) ClientMethod::getInstance().addItem(EnumToInt(type), QString(dev.portLabel + " (" + dev.cardDesc + ")"), QVariant(std::initializer_list {dev.cardName, dev.portName})); } // 更新list list = m_pMainWid->getDevicePortList(EnumToInt(type)); for (auto it = list.rbegin(); it != list.rend(); ++it) { auto deviceIt = std::find_if(deviceList.begin(), deviceList.end(), [it, isExitMultiAudioDevice](const ClientDeviceInfo& info) { if (isExitMultiAudioDevice && "combine-device" == (*it).cardName) return true; else return info.enabled && (*it).cardName == info.cardName && (*it).portName == info.portName; }); // 没找到或者设备被禁用则移除 if (deviceIt == deviceList.end()) { qDebug() << QString("can not found device, remove card %1 port %2").arg((*it).cardName).arg((*it).portName); ClientMethod::getInstance().removeItem(EnumToInt(type), (*it).index); } } initCurrentDefaultDevice(type); } void MainWidget::initCurrentDefaultDevice(const NodeType& type) { auto devInfo = getDefaultDevice(EnumToInt(type), -1); auto idx = ClientMethod::getInstance().findData(EnumToInt(type), QVariant(std::initializer_list {devInfo.cardName, devInfo.activePortName})); if (idx == -1) { qDebug() << QString("Can not found card: %1 port: %2 in %3 type combox.").arg(devInfo.cardName).arg(devInfo.activePortName).arg(EnumToInt(type)); } qDebug() << QString("Set current default device, find card: %1 port: %2 on index: %3").arg(devInfo.cardName).arg(devInfo.activePortName).arg(idx); ClientMethod::getInstance().setCurrentIndex(EnumToInt(type), idx); if (NodeType::INPUT == type && !devInfo.name.contains(".monitor")) { m_pMainWid->startMonitor(devInfo.idx); } initSystemVolumeSlider(); // When default output is bluetooth device, need to hide balance and mono frame if (NodeType::OUTPUT == type && devInfo.cardName.contains("blue")) { m_pMainWid->updateUIByDevice(EnumToInt(type), false); } else if (NodeType::INPUT == type && (devInfo.name.contains("auto_null") || devInfo.activePortName.contains("internal") || devInfo.activePortDesc.contains("Digital"))) { m_pMainWid->updateUIByDevice(EnumToInt(type), false); } } void MainWidget::initDeviceManagerList(const NodeType& type) { auto deviceList = getAvailablePortList(type); auto list = m_pMainWid->getDeviceManagetPortList(EnumToInt(type)); for (const auto& dev : deviceList) { auto it = std::find_if(list.begin(), list.end(), [dev](const DevicePortInfo info) { return dev.cardName == info.cardName && dev.portName == info.portName; }); // 没找到则添加 if (it == list.end()) { m_pMainWid->addDeviceManagerItem(EnumToInt(type), QString(dev.portLabel + " (" + dev.cardDesc + ")"), QVariant(std::initializer_list {dev.cardName, dev.portName})); auto enabled = isEnabled(dev.cardName, dev.portName); list = m_pMainWid->getDeviceManagetPortList(EnumToInt(type)); qDebug() << QString("Set card: %1 port: %2 enable: %3.").arg(dev.cardName).arg(dev.portName).arg(enabled); m_pMainWid->setDeviceManagerItemStatus(EnumToInt(type), list.size() - 1, enabled); } } for (auto it = list.rbegin(); it != list.rend(); ++it) { auto deviceIt = std::find_if(deviceList.begin(), deviceList.end(), [it](const ClientDeviceInfo& info) { return (*it).cardName == info.cardName && (*it).portName == info.portName; }); // 没找到则移除 if (deviceIt == deviceList.end()) { qDebug() << QString("can not found device in device manager widget, remove card %1 port %2").arg((*it).cardName).arg((*it).portName); m_pMainWid->removeDeviceManagerItem(EnumToInt(type), (*it).index); } } } void MainWidget::updateAppDeviceList(QList& list1, const QList& list2) { for (auto it = list1.begin(); it != list1.end(); ) { auto matchIt = std::find_if(list2.begin(), list2.end(), [it](const ClientDefaultDeviceInfo& info) { return info.activePortName == it->portName; }); if (matchIt == list2.end()) { it = list1.erase(it); } else { ++it; } } } void MainWidget::initAppManagerList() { auto append = [&](int dir, int index, double vol, bool mute, const QString& icon, const QString& name) { addAppManagerItem(dir, index, vol, mute, icon, name); initAppCurrentDefaultDevice(m_appWidgetList.size() - 1, NodeType::OUTPUT, index); initAppCurrentDefaultDevice(m_appWidgetList.size() - 1, NodeType::INPUT, index); }; int volumeValue = getVolume(EnumToInt(NodeType::OUTPUT), -1); bool muteStatus = getMute(EnumToInt(NodeType::OUTPUT), -1); for (const auto& [k, v] : m_appKeys) { append(1, MAX_INDEX, volumeValue, muteStatus, v.at(0), v.at(1)); if (getValue("getVolumeBoostStatus").toBool()) { auto val = getValue("getVolumeBoostValue").toInt(); setAppManagerSliderRange(0, val); setAppManagerSliderValue(volumeValue); } m_pMainWid->setAppCbxEnabled(m_kylinSettingsSystemStr.data(), NodeType::OUTPUT, true); m_pMainWid->setAppCbxEnabled(m_kylinSettingsSystemStr.data(), NodeType::INPUT, true); } for (const auto& stream : getStreamMediaList()) { if ((stream.mediaRole.compare("filter") == 0) || (stream.mediaRole.compare("abstract") == 0)) { continue; } for (const auto& media : stream.mediaInfoList) { if ((stream.iconName.compare("ukui-control-center") == 0)) { continue; } append(media.direction, media.index, media.volume, media.mute, stream.iconName, media.name); } } if (!m_appKeys.empty()) m_pMainWid->setCurrentAppItem(0); } void MainWidget::initAppCurrentDefaultDevice(int itemIdx, const NodeType& type, int streamIdx) { if (!m_pMainWid->getAppManagerWidget()) return; if (m_appWidgetList.empty()) { return; } ClientDefaultDeviceInfo devInfo; if (itemIdx == 0) { devInfo = getDefaultDevice(EnumToInt(type), streamIdx); } else { auto t = EnumToInt(type == NodeType::OUTPUT ? NodeType::SINK_INPUT : NodeType::SOURCE_OUTPUT); devInfo = getDefaultDevice(t, streamIdx); } auto it = std::find_if(m_appWidgetList.begin(), m_appWidgetList.end(), [itemIdx](const AppManagerItem& item) { return itemIdx == item.index; }); if (it != m_appWidgetList.end()) { auto cbx = (*it).wid->getSelectComboxItems()[type]; auto index = cbx->findData(QVariant(std::initializer_list {devInfo.cardName, devInfo.activePortName})); m_pMainWid->setAppCurrentIndex((*it).iconName, type, index); qDebug() << QString("Set item %1 combox %2 current index to %3.").arg(itemIdx).arg(EnumToInt(type)).arg(index); } else qDebug() << QString("Invaild %1 index in app manager, set current index failed.").arg(itemIdx); // for (const auto& app : m_appWidgetList) { //// auto vec = (type == NodeType::OUTPUT) ? app.outputStreamVec : app.inputStreamVec; // auto it = std::find(vec.begin(), vec.end(), idx); // if (it != vec.end()) { // auto cbx = app.wid->getSelectComboxItems()[type]; // auto index = cbx->findData(QVariant(std::initializer_list {devInfo.cardName, devInfo.activePortName})); // m_pMainWid->setAppCurrentIndex(app.iconName, type, index); // qDebug() << QString("Set %1 current index to %2.").arg(app.index).arg(index); // return; // } // } } void MainWidget::initSwitchButtonStatus() { for (const auto& it : m_switchBtnItemMap) { QString method; switch (it.first) { case SwitchButtonType::SWITCH_BUTTON_VOLUMEBOOST: { method = "getVolumeBoostStatus"; break; } case SwitchButtonType::SWITCH_BUTTON_MONO: { method = "getMonoStatus"; break; } case SwitchButtonType::SWITCH_BUTTON_ECHOCANCEL: { method = "getEchoCancelStatus"; break; } case SwitchButtonType::SWITCH_BUTTON_LOOPBACK: { method = "getLoopbackStatus"; break; } case SwitchButtonType::SWITCH_BUTTON_STARTUP: { method = "getStartupStatus"; break; } case SwitchButtonType::SWITCH_BUTTON_POWEROFF: { method = "getPoweroffStatus"; break; } case SwitchButtonType::SWITCH_BUTTON_LOGOUT: { method = "getLogoutStatus"; break; } case SwitchButtonType::SWITCH_BUTTON_WAKEUP: { method = "getWakeupStatus"; break; } case SwitchButtonType::SWITCH_BUTTON_ALERT: { method = "getAlertStatus"; break; } case SwitchButtonType::SWITCH_BUTTON_AUTO_PAUSE: { ClientMethod::getInstance().setChecked(EnumToInt(it.first), getAutoPauseStatus()); break; } default: break; } if ("" != method) { auto status = getValue(method).toBool(); ClientMethod::getInstance().setChecked(EnumToInt(it.first), status); m_pMainWid->updateUIByButtonStatus(EnumToInt(it.first), status); if (status && SwitchButtonType::SWITCH_BUTTON_VOLUMEBOOST == it.first) { auto val = getValue("getVolumeBoostValue").toInt(); ClientMethod::getInstance().setRange(EnumToInt(VolumeSliderType::VOLUME_SLIDER_OUTPUT), 0, val); } else if (status && SwitchButtonType::SWITCH_BUTTON_MONO == it.first) { ClientMethod::getInstance().setBalanceEnabled(false); } } } } void MainWidget::initSoundThemeList() { auto list = getSoundThemeList(); for (const auto& it : list) { ClientMethod::getInstance().addItem(EnumToInt(SelectComboxType::SELECT_COMBOX_SOUNDEFFECT), it.description, QVariant(it.name)); } // 添加自定义音效 ClientMethod::getInstance().addItem(EnumToInt(SelectComboxType::SELECT_COMBOX_SOUNDEFFECT), tr("Custom"), QVariant("custom")); updateSoundTheme(); } /** void MainWidget::initAppVolumeList() { QDBusMessage replyList = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getSinkInputList"); if (replyList.arguments().isEmpty()) { qDebug() << "arguments is nullptr..."; return; } const QDBusArgument& siArgs = replyList.arguments().at(0).value(); QList streamList; siArgs.beginArray(); while (!siArgs.atEnd()) { StreamInfo info; siArgs >> info; streamList.push_back(info); } siArgs.endArray(); replyList = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getSourceOutputList"); if (replyList.arguments().isEmpty()) { qDebug() << "arguments is nullptr..."; return; } const QDBusArgument& soArgs = replyList.arguments().at(0).value(); soArgs.beginArray(); while (!siArgs.atEnd()) { StreamInfo info; soArgs >> info; streamList.push_back(info); } soArgs.endArray(); for (const auto& s : streamList) { if ("filter" != s.role) { TrayClientMethod::getInstance().addAppItem(s.index, s.iconName, s.name); } } } */ void MainWidget::initSystemVolumeSlider() { for (const auto& [k, v] : m_volumeSliderItemMap) { switch (k) { case VolumeSliderType::VOLUME_SLIDER_OUTPUT: case VolumeSliderType::VOLUME_SLIDER_INPUT: { auto muted = getMute(EnumToInt(k), -1); auto volume = getVolume(EnumToInt(k), -1); ClientMethod::getInstance().setValue(EnumToInt(k), volume); ClientMethod::getInstance().setMute(EnumToInt(k), muted); break; } case VolumeSliderType::VOLUME_SLIDER_BALANCE: { double v = getBalance(EnumToInt(NodeType::OUTPUT)); auto balanceValue = v * 100; ClientMethod::getInstance().setValue(EnumToInt(k), balanceValue); break; } default: break; } } } /** void MainWidget::initAppVolumeSlider() { for (const auto& [k, v] : m_appVolumeSliderItemMap) { QDBusReply reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getVolume", EnumToInt(NodeType::SINK_INPUT), ""); TrayClientMethod::getInstance().setValue(EnumToInt(NodeType::SINK_INPUT), k, reply); } } */ void MainWidget::initData() { initSystemDeviceList(NodeType::OUTPUT); initSystemDeviceList(NodeType::INPUT); initSwitchButtonStatus(); initSoundThemeList(); initSystemVolumeSlider(); // initAppVolumeSlider(); } void MainWidget::initSlots() { if (m_pMainWid) { m_pMainWid->addVolumeObserver([this](int type, int idx, int value) { // 如果是应用音量的系统对应的滑动条的值发生更改需要设置默认输出设备音量 if (type == 2 && idx == -1) { setMute(1, idx, false); setVolume(1, idx, value); } else { setMute(type, idx, false); setVolume(type, idx, value); } qDebug() << QString("Set %1 type volume, index: %2 volume changed to %3").arg(type).arg(idx).arg(value); // 暂时不记录应用管理的埋点数据 if (type > 1) return; auto slider = m_volumeSliderItemMap[IntToEnum(type)]->getSlider()->getWidget(); ukcc::UkccCommon::buriedSettings("Audio", slider->objectName(), QString("slider"), QString::number(value)); }); m_pMainWid->addBalanceVolumeObserver([this](int type, float value) { setBalance(1, value); // type = 2 VolumeSliderType::VOLUME_SLIDER_BALANCE qDebug() << QString("Set %1 type balance, balance changed to %2.").arg(type).arg(value); auto slider = m_volumeSliderItemMap[IntToEnum(type)]->getSlider()->getWidget(); ukcc::UkccCommon::buriedSettings("Audio", slider->objectName(), QString("slider"), QString::number(value)); }); m_pMainWid->addMuteObserver([this](int type, int idx) { // 如果是应用音量的系统对应的静音值发生更改需要设置默认输出设备静音状态 bool mute; if (type == 2 && idx == -1) { mute = getMute(1, idx); setMute(1, idx, !mute); } else { mute = getMute(type, idx); setMute(type, idx, !mute); } qDebug() << QString("Set %1 type mute, index: %2 mute changed to %3").arg(type).arg(idx).arg(mute); // 暂时不记录应用管理的埋点数据 if (type > 1) return; // m_volumeSliderItemMap[IntToEnum(type)]->setMute(!muteStatus); auto muteBtn = m_volumeSliderItemMap[IntToEnum(type)]->getMuteBtn(); ukcc::UkccCommon::buriedSettings("Audio", muteBtn->objectName(), QString("settings"), !mute ? "true" : "false"); }); m_pMainWid->addDeviceObserver([this](int type, int idx, const QString& portName, const QString& cardName) { // 如果是应用音量的系统对应的静音值发生更改需要设置默认输出设备静音状态 if (type == 2 && idx == -1) { setDefaultDevice(1, idx, portName, cardName); } else if (type == 3 && idx == -1) { setDefaultDevice(0, idx, portName, cardName); } else { setDefaultDevice(type, idx, portName, cardName); } qDebug() << QString("Set %1 type default device, index: %2 default device changed to card: %3 port %4").arg(type).arg(idx).arg(cardName).arg(portName); // 暂时不记录应用管理的埋点数据 if (type > 1) return; auto selectCbx = static_cast(m_selectComboxItemMap[IntToEnum(type)]->getSelectBox()->getWidget()); ukcc::UkccCommon::buriedSettings("Audio", selectCbx->objectName(), "select", selectCbx->currentItem()->text()); }); m_pMainWid->addSettingsObserver([this](const QString& key, const QVariant& value) { setValue(key, value); if (0 == strcmp(key.toLatin1().data(), "setSoundThemeName")) { updateSoundTheme(); } else if (0 == strcmp(key.toLatin1().data(), "setAutoPauseStatus")) { setAutoPauseStatus(value.toBool()); } qDebug() << QString("%1 setting key changed to %2").arg(key).arg(value.toString()); }); m_pMainWid->addDetailSettingsObserver([this](int type) { switch (IntToEnum(type)) { case DetailSettingType::DETAIL_SETTING_DEVMANAGER: { initDeviceManagerList(NodeType::INPUT); initDeviceManagerList(NodeType::OUTPUT); break; } case DetailSettingType::DETAIL_SETTING_APPMANAGER: { initAppManagerList(); break; } default: break; } qDebug() << QString("Click %1 detail settings.").arg(type); }); m_pMainWid->addEnableDeviceObserver([this](const QString& card, const QString& port, bool enable) { setEnabled(card, port, enable); qDebug() << QString("Set card: %1 port: %2 enabled to %3").arg(card).arg(port).arg(enable ? "enable" : "disable"); }); } connect(&DBusClient::getInstance(), SIGNAL(volumeChangedSignal(int, int, const QDBusVariant&)), this, SLOT(volumeChangedSlots(int, int, const QDBusVariant&))); connect(&DBusClient::getInstance(), SIGNAL(muteChangedSignal(int, int, bool)), this, SLOT(muteChangedSlots(int, int, bool))); connect(&DBusClient::getInstance(), SIGNAL(deviceChangedSignal(int, const QString&, const QString&)), this, SLOT(deviceChangedSlots(int, const QString&, const QString&))); connect(&DBusClient::getInstance(), SIGNAL(deviceAdjustSignal(int)), this, SLOT(deviceAdjustSlots(int))); connect(&DBusClient::getInstance(), SIGNAL(settingsChangedSignal(const QString&, const QDBusVariant&)), this, SLOT(settingsChangedSlots(const QString&, const QDBusVariant&))); connect(&DBusClient::getInstance(), SIGNAL(addStreamSignal(int, int, int, bool, const QString&, const QString&)), this, SLOT(addStreamSlots(int, int, int, bool, const QString&, const QString&))); connect(&DBusClient::getInstance(), SIGNAL(removeStreamSignal(int, int)), this, SLOT(removeStreamSlots(int, int))); } void MainWidget::initClientManager() { m_pClientManager = std::make_shared(m_switchBtnItemMap, m_volumeSliderItemMap, m_selectComboxItemMap); DBusClient::getInstance().initDbusConnect(); ClientMethod::getInstance().setManager(m_pClientManager); } void MainWidget::volumeChangedSlots(int type, int idx, const QDBusVariant& v) { // 输入输出设备音量更改 if (type <= 1) { ClientMethod::getInstance().setValue(type, v.variant().toInt()); // 同步输出声道平衡音量 if (type == 1) { m_pMainWid->setAppValue(m_kylinSettingsSystemStr.data(), v.variant().toInt()); double v = getBalance(type); auto balanceValue = v * 100; ClientMethod::getInstance().setValue(EnumToInt(VolumeSliderType::VOLUME_SLIDER_BALANCE), balanceValue); } } else { auto name = getAppStreamNameByIndex(IntToEnum(type), idx); m_pMainWid->setAppValue(name, v.variant().toInt()); } qDebug() << QString("Type %1 index: %2 volume changed to %3.").arg(type).arg(idx).arg(v.variant().toInt()); } void MainWidget::muteChangedSlots(int type, int idx, bool mute) { // 输入输出设备音量更改 if (type <= 1) { ClientMethod::getInstance().setMute(type, mute); m_pMainWid->setAppMute(m_kylinSettingsSystemStr.data(), mute); } else { auto name = getAppStreamNameByIndex(IntToEnum(type), idx); m_pMainWid->setAppMute(name, mute); } qDebug() << QString("Type %1 index %2 mute changed to %3.").arg(type).arg(idx).arg(mute); } void MainWidget::deviceChangedSlots(int type, const QString& portName, const QString& cardName) { qDebug() << QString("%1 type device changed, card: %2 port %3").arg(type).arg(cardName).arg(portName); switch (IntToEnum(type)) { case NodeType::INPUT: case NodeType::OUTPUT: { int idx = ClientMethod::getInstance().findData(type, QVariant(std::initializer_list{cardName, portName})); auto volume = getVolume(type, -1); auto muted = getMute(type, -1); ClientMethod::getInstance().setCurrentIndex(type, idx); ClientMethod::getInstance().setValue(type, volume); ClientMethod::getInstance().setMute(type, muted); if (IntToEnum(type) == NodeType::OUTPUT) { double v = getBalance(type); int balanceValue = v * 100; ClientMethod::getInstance().setValue(EnumToInt(VolumeSliderType::VOLUME_SLIDER_BALANCE), balanceValue); m_pMainWid->setAppValue(m_kylinSettingsSystemStr.data(), volume); // When default output is bluetooth device, need to hide balance and mono frame const bool isBluetoothDevice = cardName.contains("blue"); m_pMainWid->updateUIByDevice(EnumToInt(NodeType::OUTPUT), !isBluetoothDevice); } else { auto devInfo = getDefaultDevice(EnumToInt(NodeType::INPUT), -1); if (devInfo.name.contains(".monitor")) { m_pMainWid->stopMonitor(); } else { m_pMainWid->startMonitor(devInfo.idx); } const bool isNullorInternalDev = devInfo.name.contains("auto_null") || devInfo.activePortName.contains("internal") || devInfo.activePortDesc.contains("Digital"); // When default input is NULL or internal device, need to hide loopback frame m_pMainWid->updateUIByDevice(EnumToInt(NodeType::INPUT), !isNullorInternalDev); } initAppCurrentDefaultDevice(0, IntToEnum(type), -1); break; } case NodeType::SINK_INPUT: case NodeType::SOURCE_OUTPUT: { // TODO break; } default: break; } } void MainWidget::deviceAdjustSlots(int type) { qDebug() << __func__ << "type: " << type; initSystemDeviceList(NodeType::INPUT); initSystemDeviceList(NodeType::OUTPUT); initDeviceManagerList(NodeType::INPUT); initDeviceManagerList(NodeType::OUTPUT); updateAppWidgetDeviceList(NodeType::OUTPUT); updateAppWidgetDeviceList(NodeType::INPUT); } void MainWidget::settingsChangedSlots(const QString& key, const QDBusVariant& v) { qDebug() << "setting changed slot, key: " << key << " value:" << v.variant(); if (EVENT_SOUND_KEY == key) { ClientMethod::getInstance().setChecked(EnumToInt(SwitchButtonType::SWITCH_BUTTON_ALERT), v.variant().toBool()); } else if (THEME_NAME_KEY == key) { ClientMethod::getInstance().setCurrentIndex(EnumToInt(SelectComboxType::SELECT_COMBOX_SOUNDEFFECT), ClientMethod::getInstance().findData(2, v.variant())); } else if (CUSTOM_THEME_KEY == key) { if (v.variant().toBool()) { ClientMethod::getInstance().setCurrentIndex(EnumToInt(SelectComboxType::SELECT_COMBOX_SOUNDEFFECT), ClientMethod::getInstance().findData(2, QVariant("custom"))); } } else if (VOLUME_CHANGED_KEY == key) { updateSoundTheme(); // ClientMethod::getInstance().setCurrentIndex(EnumToInt(SelectComboxType::SELECT_COMBOX_VOLUMEADJUST), // ClientMethod::getInstance().findData(4, v.variant())); } else if (NOTIFY_GENERAL_KEY == key) { updateSoundTheme(); // ClientMethod::getInstance().setCurrentIndex(EnumToInt(SelectComboxType::SELECT_COMBOX_NOTIFICTION), // ClientMethod::getInstance().findData(4, v.variant())); } else if (MONO_AUDIO_KEY == key) { //start if (v.variant().toBool()) { setBalance(1, 0); } //end ClientMethod::getInstance().setChecked(EnumToInt(SwitchButtonType::SWITCH_BUTTON_MONO), v.variant().toBool()); DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "LoadModule", "module-mono", ""); ClientMethod::getInstance().setBalanceEnabled(!v.variant().toBool()); } else if (NOISE_REDUCTION_KEY == key) { ClientMethod::getInstance().setChecked(EnumToInt(SwitchButtonType::SWITCH_BUTTON_ECHOCANCEL), v.variant().toBool()); } else if (LOOPBACK_KEY == key) { ClientMethod::getInstance().setChecked(EnumToInt(SwitchButtonType::SWITCH_BUTTON_LOOPBACK), v.variant().toBool()); } else if (VOLUME_BOOST_KEY == key) { ClientMethod::getInstance().setChecked(EnumToInt(SwitchButtonType::SWITCH_BUTTON_VOLUMEBOOST), v.variant().toBool()); int value = 100; if (v.variant().toBool()) { value = getValue("getVolumeBoostValue").toInt(); } setAppManagerSliderRange(0, value); ClientMethod::getInstance().setRange(EnumToInt(VolumeSliderType::VOLUME_SLIDER_OUTPUT), 0, value); } else if (VOLUME_BOOST_VOLUME_KEY == key) { // TODO } else if (STARTUP_MUSIC_KEY == key) { ClientMethod::getInstance().setChecked(EnumToInt(SwitchButtonType::SWITCH_BUTTON_STARTUP), v.variant().toBool()); } else if (ALERT_VOLUME_KEY == key) { ClientMethod::getInstance().setChecked(EnumToInt(SwitchButtonType::SWITCH_BUTTON_ALERT), v.variant().toBool()); } } void MainWidget::addStreamSlots(int dire, int idx, int value, bool mute, const QString& iconName, const QString& desc) { addAppManagerItem(dire, idx, value, mute, iconName, desc); qDebug() << QString("Stream %1 added, stream name: %2, stream icon: %3.").arg(idx).arg(desc).arg(iconName); } void MainWidget::removeStreamSlots(int direction, int idx) { qDebug() << QString("Stream %1 removed.").arg(idx); for (auto& l : m_appWidgetList) { auto& vec = direction ? l.outputStreamVec : l.inputStreamVec; auto it = std::remove(vec.begin(), vec.end(), idx); if (it == vec.end()) continue; vec.erase(it, vec.end()); if (l.outputStreamVec.empty() && l.inputStreamVec.empty()) { m_pMainWid->removeAppManagerItem(l.iconName); break; } else if (vec.empty()) { m_pMainWid->setAppCbxEnabled(l.iconName, IntToEnum(direction), false); } } } DesktopEnvironmentVersion MainWidget::getDesktopEnvironmentVersion() { QDBusReply reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getDesktopEnvironmentVersion"); return m_version = IntToEnum(reply); } int MainWidget::getVolume(int type, int idx) { QDBusReply reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getVolume", type, idx); return reply; } void MainWidget::setVolume(int type, int idx, int vaule) { DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "setVolume", type, idx, vaule); } double MainWidget::getBalance(int type) const { QDBusReply reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getBalance", type); return reply; } void MainWidget::setBalance(int type, double v) { DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "setBalance", type, v); } bool MainWidget::getMute(int type, int idx) { QDBusReply reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getMute", type, idx); return reply; } void MainWidget::setMute(int type, int idx, bool muted) { DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "setMute", type, idx, muted); } void MainWidget::setEnabled(const QString& card, const QString& port, bool enable) { DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "setEnabled", card, port, enable); } bool MainWidget::isEnabled(const QString& card, const QString& port) const { QDBusReply reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "isEnabled", card, port); return reply; } ClientDefaultDeviceInfo MainWidget::getDefaultDevice(int type, int idx) { QDBusMessage replyList = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getDefaultDevice", type, idx); if (replyList.arguments().isEmpty()) { qWarning() << "Get default device failed, arguments is nullptr."; return {}; } qDebug() << "Get default device, deviceList: " << replyList; const QDBusArgument& siArgs = replyList.arguments().at(0).value(); ClientDefaultDeviceInfo info; siArgs >> info; return info; } QList MainWidget::getSinkList() const { QList list; QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getSinkList"); if (reply.arguments().isEmpty()) { qDebug() << "返回参数为空"; return {}; } const QDBusArgument& dbusArgs = reply.arguments().at(0).value(); dbusArgs.beginArray(); while (!dbusArgs.atEnd()) { ClientDefaultDeviceInfo info; dbusArgs >> info; list.push_back(info); } dbusArgs.endArray(); return list; } QList MainWidget::getSourceList() const { QList list; QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getSourceList"); if (reply.arguments().isEmpty()) { qDebug() << "返回参数为空"; return {}; } const QDBusArgument& dbusArgs = reply.arguments().at(0).value(); dbusArgs.beginArray(); while (!dbusArgs.atEnd()) { ClientDefaultDeviceInfo info; dbusArgs >> info; list.push_back(info); } dbusArgs.endArray(); return list; } void MainWidget::setDefaultDevice(int type, int idx, const QString& portName, const QString& cardName) { DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "setDefaultDevice", type, idx, portName, cardName); } void MainWidget::setValue(const QString& method, const QVariant& value) { qDebug() << "set " << method << " value:" << value; DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, SETTINGS_INTERFACE, method, value); } QVariant MainWidget::getValue(const QString& method) const { QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, SETTINGS_INTERFACE, method); return reply.arguments().at(0); } QList MainWidget::getSoundThemeList() { QList list; QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, SOUNDTHEME_INTERFACE, "getSoundThemeList"); if (reply.arguments().isEmpty()) { qDebug() << "返回参数为空"; return {}; } const QDBusArgument& dbusArgs = reply.arguments().at(0).value(); dbusArgs.beginArray(); while (!dbusArgs.atEnd()) { ClientSoundThemeInfo info; dbusArgs >> info; list.push_back(info); } dbusArgs.endArray(); return list; } QList MainWidget::getSoundEffectFileList(const QString& name) { QList list; QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, SOUNDTHEME_INTERFACE, "getSoundEffectFileList", name); if (reply.arguments().isEmpty()) { qDebug() << "返回参数为空"; return {}; } const QDBusArgument& dbusArgs = reply.arguments().at(0).value(); dbusArgs.beginArray(); while (!dbusArgs.atEnd()) { ClientSoundEffectFileInfo info; dbusArgs >> info; list.push_back(info); } dbusArgs.endArray(); return list; } QList MainWidget::getAvailablePortList(const NodeType& type) { QList list; QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getAvailablePortList", EnumToInt(type)); if (reply.arguments().isEmpty()) { qDebug() << "返回参数为空"; return {}; } const QDBusArgument& dbusArgs = reply.arguments().at(0).value(); dbusArgs.beginArray(); while (!dbusArgs.atEnd()) { ClientDeviceInfo info; dbusArgs >> info; list.push_back(info); } dbusArgs.endArray(); return list; } QList MainWidget::getSinkInputList() const { QList list; QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getSinkInputList"); if (reply.arguments().isEmpty()) { qDebug() << "返回参数为空"; return {}; } const QDBusArgument& dbusArgs = reply.arguments().at(0).value(); dbusArgs.beginArray(); while (!dbusArgs.atEnd()) { StreamInfo info; dbusArgs >> info; list.push_back(info); } dbusArgs.endArray(); return list; } QList MainWidget::getSourceOutputList() const { QList list; QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getSourceOutputList"); if (reply.arguments().isEmpty()) { qDebug() << "The returned parameter is empty."; return {}; } const QDBusArgument& dbusArgs = reply.arguments().at(0).value(); dbusArgs.beginArray(); while (!dbusArgs.atEnd()) { StreamInfo info; dbusArgs >> info; list.push_back(info); } dbusArgs.endArray(); return list; } QList MainWidget::getStreamMediaList() const { qRegisterMetaType(); qRegisterMetaType(); qDBusRegisterMetaType(); qDBusRegisterMetaType(); QList list; QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getStreamMediaList"); if (reply.arguments().isEmpty()) { qDebug() << "The returned parameter is empty."; return {}; } const QDBusArgument& dbusArgs = reply.arguments().at(0).value(); dbusArgs.beginArray(); while (!dbusArgs.atEnd()) { ClientStreamMediaInfo info; dbusArgs >> info; list.push_back(info); } dbusArgs.endArray(); return list; } void MainWidget::updateSoundTheme() { auto value = getValue("getSoundThemeName"); qDebug() << "updateSoundTheme value :" << value.toString(); auto fileList = getSoundEffectFileList(value.toString()); qDebug() << "updateSoundTheme fileList :" << fileList.size(); // 清空音量调节和通知音效中下拉框的内容 ClientMethod::getInstance().clear(EnumToInt(SelectComboxType::SELECT_COMBOX_VOLUMEADJUST)); ClientMethod::getInstance().clear(EnumToInt(SelectComboxType::SELECT_COMBOX_NOTIFICTION)); ClientMethod::getInstance().addItem(EnumToInt(SelectComboxType::SELECT_COMBOX_VOLUMEADJUST), tr("None"), QVariant(tr("None"))); ClientMethod::getInstance().addItem(EnumToInt(SelectComboxType::SELECT_COMBOX_NOTIFICTION), tr("None"), QVariant(tr("None"))); if (!fileList.isEmpty()) { // init volumeChanged/notifygeneral combox for (const auto& f : fileList) { ClientMethod::getInstance().addItem(EnumToInt(SelectComboxType::SELECT_COMBOX_VOLUMEADJUST), f.description, QVariant(f.name)); ClientMethod::getInstance().addItem(EnumToInt(SelectComboxType::SELECT_COMBOX_NOTIFICTION), f.description, QVariant(f.name)); } } auto idx = ClientMethod::getInstance().findData(EnumToInt(SelectComboxType::SELECT_COMBOX_SOUNDEFFECT), value); ClientMethod::getInstance().setCurrentIndex(EnumToInt(SelectComboxType::SELECT_COMBOX_SOUNDEFFECT), idx); if (fileList.isEmpty()) { ClientMethod::getInstance().setCurrentIndex(EnumToInt(SelectComboxType::SELECT_COMBOX_VOLUMEADJUST), 0); ClientMethod::getInstance().setCurrentIndex(EnumToInt(SelectComboxType::SELECT_COMBOX_NOTIFICTION), 0); } else { value = getValue("getVolumeChangedName"); idx = ClientMethod::getInstance().findData(EnumToInt(SelectComboxType::SELECT_COMBOX_VOLUMEADJUST), value); qDebug() << "updateSoundTheme getVolumeChangedName idx:" << idx << "value:" << value; if (idx < 0) { ClientMethod::getInstance().setCurrentIndex(EnumToInt(SelectComboxType::SELECT_COMBOX_VOLUMEADJUST), 0); } else{ ClientMethod::getInstance().setCurrentIndex(EnumToInt(SelectComboxType::SELECT_COMBOX_VOLUMEADJUST), idx); } value = getValue("getNotifyGeneralName"); idx = ClientMethod::getInstance().findData(EnumToInt(SelectComboxType::SELECT_COMBOX_NOTIFICTION), value); qDebug() << "updateSoundTheme getNotifyGeneralName idx:" << idx << "value:" << value; if (idx < 0) { ClientMethod::getInstance().setCurrentIndex(EnumToInt(SelectComboxType::SELECT_COMBOX_NOTIFICTION), 0); } else { ClientMethod::getInstance().setCurrentIndex(EnumToInt(SelectComboxType::SELECT_COMBOX_NOTIFICTION), idx); } } } bool MainWidget::getAutoPauseStatus() const { QDBusReply reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getAutoPauseStatus"); return reply; } void MainWidget::setAutoPauseStatus(bool status) { DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "setAutoPauseStatus", status); } bool MainWidget::getMultiAudioCombineStatus(const NodeType& type) const { QDBusReply reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "getMultiAudioCombineStatus", EnumToInt(type)); return reply; } void MainWidget::updateAppWidgetDeviceList(const NodeType& type) { auto deviceList = getAvailablePortList(type); if (m_appWidgetList.empty()) return; for (const auto& item : m_appWidgetList) { auto portList = m_pMainWid->getAppManagetPortList(item.index, EnumToInt(type)); for (const auto& dev : deviceList) { auto it = std::find_if(portList.begin(), portList.end(), [dev](const DevicePortInfo info) { return dev.cardName == info.cardName && dev.portName == info.portName; }); // 没找到则添加 if (it == portList.end()) { qDebug() << QString("Device adjust, add card: %1 port: %2 to %3.").arg(dev.cardName).arg(dev.portName).arg(item.index); m_pMainWid->addPortToAppItem(item.iconName, type, QString(dev.portLabel + " (" + dev.cardDesc + ")"), QVariant(std::initializer_list {dev.cardName, dev.portName})); } } for (auto it = portList.rbegin(); it != portList.rend(); ++it) { auto deviceIt = std::find_if(deviceList.begin(), deviceList.end(), [it](const ClientDeviceInfo& info) { return (*it).cardName == info.cardName && (*it).portName == info.portName; }); // 没找到则移除 if (deviceIt == deviceList.end() || ((*deviceIt).enabled == false)) { qDebug() << QString("can not found device in app manager widget, remove card %1 port %2").arg((*it).cardName).arg((*it).portName); m_pMainWid->removePortFromAppItem(item.iconName, type, (*it).index); } } auto portListReal = m_pMainWid->getAppManagetPortList(item.index, EnumToInt(type)); if (portListReal.empty()) { m_pMainWid->addPortToAppItem(item.iconName, type, tr("None"), QVariant(std::initializer_list {"None", "None"})); } } } QString MainWidget::getAppStreamNameByIndex(const NodeType& type, int index) { auto list = (type == NodeType::SINK_INPUT) ? getSinkInputList() : getSourceOutputList(); auto it = std::find_if(list.begin(), list.end(), [index](const StreamInfo& info) { return index == info.index; }); if (it != list.end()) return (*it).iconName; return {}; } void MainWidget::addAppManagerItem(int dire, int idx, int value, bool mute, const QString& iconName, const QString& desc) { if (!m_pMainWid->getAppManagerWidget()) return; qDebug() << QString("Add %1 direction %2 index %3 to app widget").arg(iconName).arg(dire).arg(idx); auto outputDevList = getAvailablePortList(NodeType::OUTPUT); auto inputDevList = getAvailablePortList(NodeType::INPUT); auto type = dire ? NodeType::OUTPUT : NodeType::INPUT; //start application item only show activeport if (idx >= 0) { auto sinkList = getSinkList(); auto sourceList = getSourceList(); updateAppDeviceList(outputDevList, sinkList); updateAppDeviceList(inputDevList, sourceList); } //end auto it = std::find_if(m_appWidgetList.begin(), m_appWidgetList.end(), [iconName](const AppManagerItem& item) { return iconName == item.iconName; }); if (it != m_appWidgetList.end()) { m_pMainWid->setAppCbxEnabled(iconName, type, true); initAppCurrentDefaultDevice((*it).index, type, idx); if (dire) (*it).outputStreamVec.emplace_back(idx); else (*it).inputStreamVec.emplace_back(idx); qDebug() << "stream list exit, enable combox..."; } else { m_pMainWid->addAppManagerItem(dire, idx, value, mute, iconName, desc, outputDevList, inputDevList); initAppCurrentDefaultDevice(m_appWidgetList.size() - 1, type, idx); m_pMainWid->setAppCbxEnabled(iconName, type, true); } } void MainWidget::setAppManagerSliderRange(int min, int max) { auto it = std::find_if(m_appWidgetList.begin(), m_appWidgetList.end(), [](const AppManagerItem& item) { return 0 == item.index; }); if (it != m_appWidgetList.end()) { (*it).wid->setRange(VolumeSliderType::VOLUME_SLIDER_SINKINPUT, min, max); } else { qDebug() << QString("Can not found app widget, set app manager slider range failed."); } } void MainWidget::setAppManagerSliderValue(int value) { auto it = std::find_if(m_appWidgetList.begin(), m_appWidgetList.end(), [](const AppManagerItem& item) { return 0 == item.index; }); if (it != m_appWidgetList.end()) { (*it).wid->setValue(VolumeSliderType::VOLUME_SLIDER_SINKINPUT, value); } else { qDebug() << QString("Can not found app widget, set app manager slider value failed."); } } ukui-volume-control/audio/ISelectComboxItem.h0000664000175000017500000000574715171074712020253 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 ISELECTSUBITEM_H #define ISELECTSUBITEM_H #include #include #include enum class ItemType { MAIN_WINDOW_ITEM, // 主界面的item DEVMANAGER_WINDOW_ITEM, // 设备管理item APPMANAGER_WINDOW_ITEM // 应用管理item }; /** * @brief 包含SelectCombox子项的类型 */ enum class SelectComboxType { SELECT_COMBOX_INPUT = 0, // 输入设备子项 SELECT_COMBOX_OUTPUT, // 输出设备子项 SELECT_COMBOX_SOUNDEFFECT, // 音效主题子项 SELECT_COMBOX_VOLUMEADJUST, // 音量调节子项 SELECT_COMBOX_NOTIFICTION, // 接收通知子项 SELECT_COMBOX_SINKINPUT, SELECT_COMBOX_SOURCEOUTPUT }; class ISelectBox { public: ISelectBox() = default; virtual ~ISelectBox() = default; public: virtual void setCurrentIndex(int) = 0; virtual void setEnabled(bool) = 0; virtual bool isEnabled() const = 0; virtual int findData(const QVariant&, int = Qt::UserRole) = 0; virtual int findText(const QString&) = 0; virtual void insertItem(const QString&, const QVariant& = QVariant()) = 0; virtual void removeItem(int) = 0; virtual QVariant currentData(int = Qt::UserRole) const = 0; virtual std::list getDataList() const = 0; virtual int count() const = 0; virtual void clear() = 0; virtual QWidget* getWidget() = 0; virtual void setParent(QWidget*) = 0; }; class ISelectComboxItem { public: ISelectComboxItem(std::shared_ptr, QLabel*); virtual ~ISelectComboxItem() = default; public: void setCurrentIndex(int); int findData(const QVariant&, int = Qt::UserRole); int findText(const QString&); void insertItem(const QString&, const QVariant& = QVariant()); void removeItem(int); QVariant currentData(int = Qt::UserRole) const; int count() const; void clear(); std::shared_ptr getSelectBox() const; std::list getDataList() const; virtual void initUi() = 0; virtual void initSlots() = 0; virtual void setEnabled(bool) = 0; virtual QWidget* getWidget() = 0; protected: std::shared_ptr m_pSelectBox = nullptr; QLabel* m_pDisplayLabel = nullptr; }; #endif // ISELECTSUBITEM_H ukui-volume-control/audio/DeviceManagerMainWidget.h0000664000175000017500000000427715171074712021374 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 DEVICEMANAGERMAINWIDGET_H #define DEVICEMANAGERMAINWIDGET_H #include #include #include #include "DeviceManagerWidget.h" enum class DeviceManagerType { Input = 0, Output }; class DeviceManagerMainWidget : public kdk::KWidget { Q_OBJECT public: DeviceManagerMainWidget(QWidget* = nullptr); ~DeviceManagerMainWidget() = default; public: void addItem(const DeviceManagerType&, const QString&, const QString&, const QString&); void removeItem(const DeviceManagerType&, int); void setChecked(const DeviceManagerType&, int, bool); DeviceManagerWidget* getManagerWidgetByType(const DeviceManagerType&); Q_SIGNALS: void enableDeviceSignals(const QString&, const QString&, bool); void closeWindowSignals(); private: void createItems(); void initUi(); void initSlots(); private: std::unordered_map m_keys { {DeviceManagerType::Output, QStringList(std::initializer_list{"audio-volume-overamplified-symbolic", tr("Output Device")})}, {DeviceManagerType::Input, QStringList(std::initializer_list{"ukui-microphone-on-symbolic", tr("Input Device")})} }; kdk::KNavigationBar* m_pNavigationBar = nullptr; std::unordered_map m_itemMap; std::unordered_map m_deviceWidgetMap; }; #endif // DEVICEMANAGERMAINWIDGET_H ukui-volume-control/audio/customstyle.h0000664000175000017500000001747215171074712017325 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 CUSTOMSTYLE_H #define CUSTOMSTYLE_H #include /*! * \brief The CustomStyle class * \details * 自定义QStyle * 基于QProxyStyle,默认使用QProxyStyle的实例绘制控件,你需要针对某一个控件重新实现若干对应的接口。 * QProxyStyle可以从现有的qt style实例化,我们只需要知道这个style的名字即可。 * 这种做法带来了不错的扩展性和自由度,因为我们不需要将某个style的代码直接引入我们的项目中, * 也能够“继承”这个style类进行二次开发。 * * 下面的方法展现了QStyle的所有的接口,使用QStyle进行控件的绘制使得qt应用能够进行风格的切换, * 从而达到不修改项目源码却对应用外观产生巨大影响的效果。 * * \note * 需要注意QStyle与QSS并不兼容,因为QSS本身其实上也是QStyle的一种实现,对一个控件而言,本身理论上只能 * 在同一时间调用唯一一个QStyle进行绘制。 */ class CustomStyle : public QProxyStyle { Q_OBJECT public: explicit CustomStyle(const QString& proxyStyleName = "windows", QObject* parent = nullptr); ~CustomStyle() = default; public: /*! * \brief drawComplexControl * \param control 比如ScrollBar,对应CC枚举类型 * \param option * \param painter * \param widget * \details * drawComplexControl用于绘制具有子控件的复杂控件,它本身一般不直接绘制控件, * 而是通过QStyle的其它方法将复杂控件分解成子控件再调用其它的draw方法绘制。 * 如果你需要重新实现一个复杂控件的绘制方法,首先考虑的应该是在不改变它原有的绘制流程的情况下, * 对它调用到的其它方法进行重写。 * * 如果你不想使用原有的绘制流程,那你需要重写这个接口,然后自己实现一切, * 包括背景的绘制,子控件的位置和状态计算,子控件的绘制等。 * 所以,你需要对这个控件有足够的了解之后再尝试直接重写这个接口。 */ virtual void drawComplexControl(QStyle::ComplexControl control, const QStyleOptionComplex* option, QPainter* painter, const QWidget* widget = nullptr) const; /*! * \brief drawControl * \param element 比如按钮,对应CE枚举类型 * \param option * \param painter * \param widget * \details * drawControl用于绘制基本控件元素,它本身一般只负责绘制控件的一部分或者一层。 * 如果你想要知道控件具体如何绘制,你需要同时研究这个控件的源码和QStyle中的源码, * 因为它们都有可能改变控件的绘制流程。 * * QStyle一般会遵循QCommonStyle的绘制流程,QCommenStyle是大部分主流style的最基类, * 它本身不能完全称之为一个主题,如果你直接使用它,你的控件将不能被正常绘制,因为它有可能只是 * 在特定的时候执行了特定却未实现的绘制方法,它更像一个框架或者规范。 */ virtual void drawControl(QStyle::ControlElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget = nullptr) const; virtual void drawItemPixmap(QPainter* painter, const QRect& rectangle, int alignment, const QPixmap& pixmap) const; virtual void drawItemText(QPainter* painter, const QRect& rectangle, int alignment, const QPalette& palette, bool enabled, const QString& text, QPalette::ColorRole textRole = QPalette::NoRole) const; /*! * \brief drawPrimitive * \param element 背景绘制,对应PE枚举类型 * \param option * \param painter * \param widget * \details * drawPrimitive用于绘制控件背景,比如按钮和菜单的背景, * 我们一般需要判断控件的状态来绘制不同的背景, * 比如按钮的hover和点击效果。 */ virtual void drawPrimitive(QStyle::PrimitiveElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget = nullptr) const; virtual QPixmap generatedIconPixmap(QIcon::Mode iconMode, const QPixmap& pixmap, const QStyleOption* option) const; virtual QStyle::SubControl hitTestComplexControl(QStyle::ComplexControl control, const QStyleOptionComplex* option, const QPoint& position, const QWidget* widget = nullptr) const; virtual QRect itemPixmapRect(const QRect& rectangle, int alignment, const QPixmap& pixmap) const; virtual QRect itemTextRect(const QFontMetrics& metrics, const QRect& rectangle, int alignment, bool enabled, const QString& text) const; //virtual int layoutSpacing(QSizePolicy::ControlType control1, QSizePolicy::ControlType control2, Qt::Orientation orientation, const QStyleOption* option, const QWidget* widget); virtual int pixelMetric(QStyle::PixelMetric metric, const QStyleOption* option = nullptr, const QWidget* widget = nullptr) const; /*! * \brief polish * \param widget * \details * polish用于对widget进行预处理,一般我们可以在polish中修改其属性, * 另外,polish是对动画和特效实现而言十分重要的一个方法, * 通过polish我们能够使widget和特效和动画形成对应关系。 */ virtual void polish(QWidget* widget); virtual void polish(QApplication* application); virtual void polish(QPalette& palette); virtual void unpolish(QWidget* widget); virtual void unpolish(QApplication* application); virtual QSize sizeFromContents(QStyle::ContentsType type, const QStyleOption* option, const QSize& contentsSize, const QWidget* widget = nullptr) const; virtual QIcon standardIcon(QStyle::StandardPixmap standardIcon, const QStyleOption* option, const QWidget* widget) const; virtual QPalette standardPalette() const; /*! * \brief styleHint * \param hint 对应的枚举是SH * \param option * \param widget * \param returnData * \return * \details * styleHint比较特殊,通过它我们能够改变一些控件的绘制流程或者方式,比如说QMenu是否可以滚动。 */ virtual int styleHint(QStyle::StyleHint hint, const QStyleOption* option = nullptr, const QWidget* widget = nullptr, QStyleHintReturn* returnData = nullptr) const; /*! * \brief subControlRect * \param control * \param option * \param subControl * \param widget * \return * \details * subControlRect返回子控件的位置和大小信息,这个方法一般在内置流程中调用, * 如果我们要重写某个绘制方法,可能需要用到它 */ virtual QRect subControlRect(QStyle::ComplexControl control, const QStyleOptionComplex* option, QStyle::SubControl subControl, const QWidget* widget = nullptr) const; /*! * \brief subElementRect * \param element * \param option * \param widget * \return * \details * 与subControlRect类似 */ virtual QRect subElementRect(QStyle::SubElement element, const QStyleOption* option, const QWidget* widget = nullptr) const; }; #endif // CUSTOMSTYLE_H ukui-volume-control/audio/TitleLabelItem.h0000664000175000017500000000255415171074712017565 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 TITLELABELITEM_H #define TITLELABELITEM_H #include /** * @brief 包含TitleLabel子项的类型 */ enum class TitleLabelType { TITLE_LABEL_OUTPUT = 0, // 输出标题 TITLE_LABEL_INPUT, // 输入标题 TITLE_LABEL_SOUNDEFFECT, // 系统音效标题 TITLE_LABEL_ADVANCESETTINGS // 高级设置标题 }; class TitleLabelItem : public QWidget { public: TitleLabelItem(QLabel*, QWidget* = nullptr); virtual ~TitleLabelItem() = default; public: void initUi(); QWidget* getWidget(); private: QLabel* m_pLabel; }; #endif // TITLELABELITEM_H ukui-volume-control/audio/AppManagerItemWidget.cpp0000664000175000017500000001272515171074712021257 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "AppManagerItemWidget.h" #include #include #include "Util.h" #include "ukui4.0/Ukui4VolumeSliderItem.h" #include "ukui4.0/Ukui4SelectComboxItem.h" #include "Ukui4CustomControl.h" #include "AppManagerSelectComboxItem.h" #include "AppManagerSliderItem.h" AppManagerItemWidget::AppManagerItemWidget(QLabel* label, const QString& name, QWidget* parent) : m_pTitileLabel(label), m_name(name), QWidget(parent) { createItems(); initUi(); initSlots(); } void AppManagerItemWidget::createItems() { for (const auto& [k, v] : m_volumeKeys) { std::shared_ptr slider = std::make_shared(new UkmediaVolumeSlider(this)); IVolumeSliderItem* item = new AppManagerSliderItem(slider, new AutoOmitLabel(v, this), new QLabel(this), new QPushButton(this), k, this); m_volumeSliderItemMap.emplace(k, item); } for (const auto& [k, v] : m_selectKeys) { std::shared_ptr box = std::make_shared(new QComboBox(this)); ISelectComboxItem* item = nullptr; item = new AppSelectComboxWithDescItem(box, new AutoOmitLabel(v.at(0)), new AutoOmitLabel(v.at(1)), this); m_selectComboxItemMap.emplace(k, item); } } void AppManagerItemWidget::initUi() { auto labelLayout = new QHBoxLayout; m_pTitileLabel->setFixedSize(480, 28); labelLayout->setSpacing(0); labelLayout->setContentsMargins(0, 0, 0, 0); labelLayout->addSpacing(41); labelLayout->addWidget(m_pTitileLabel); labelLayout->addSpacing(39); auto itemWidget = new QWidget(); itemWidget->setContentsMargins(0, 0, 0, 0); itemWidget->setFixedSize(512, 300); auto itemVLayout = new QVBoxLayout(itemWidget); itemVLayout->setSpacing(0); itemVLayout->setContentsMargins(0, 0, 0, 0); itemVLayout->addWidget(m_volumeSliderItemMap[VolumeSliderType::VOLUME_SLIDER_SINKINPUT]->getWidget()); itemVLayout->addWidget(m_selectComboxItemMap[NodeType::OUTPUT]->getWidget()); itemVLayout->addWidget(m_selectComboxItemMap[NodeType::INPUT]->getWidget()); itemVLayout->addStretch(); itemWidget->setLayout(itemVLayout); QHBoxLayout *itemHlayout = new QHBoxLayout(); itemHlayout->setSpacing(0); itemHlayout->setContentsMargins(0, 0, 0, 0); itemHlayout->addSpacing(25); itemHlayout->addWidget(itemWidget); itemHlayout->addSpacing(23); m_pConfirmBtn = new QPushButton(tr("Confirm"), this); m_pConfirmBtn->setFixedSize(96, 36); m_pConfirmBtn->setProperty("isImportant", true); QHBoxLayout* hLayout = new QHBoxLayout; hLayout->setSpacing(0); hLayout->setContentsMargins(0, 0, 0, 0); hLayout->addStretch(); hLayout->addWidget(m_pConfirmBtn); hLayout->addSpacing(25); auto vLayout = new QVBoxLayout; vLayout->setSpacing(0); vLayout->setContentsMargins(0, 0, 0, 0); vLayout->addLayout(labelLayout); vLayout->addSpacing(10); vLayout->addLayout(itemHlayout); vLayout->addStretch(); vLayout->addLayout(hLayout); vLayout->addSpacing(40); setLayout(vLayout); } void AppManagerItemWidget::initSlots() { connect(m_pConfirmBtn, &QPushButton::clicked, this, &AppManagerItemWidget::closeWindowSlots); } void AppManagerItemWidget::closeWindowSlots() { qDebug() << QString("App manager item confirm btn clicked, close window"); Q_EMIT closeWindowSignals(); } void AppManagerItemWidget::setRange(const VolumeSliderType& type, int min, int max) { m_volumeSliderItemMap[type]->setRange(min, max); } void AppManagerItemWidget::setValue(const VolumeSliderType& type, int value) { m_volumeSliderItemMap[type]->setValue(value); } void AppManagerItemWidget::setMuted(const VolumeSliderType& type, bool muted) { m_volumeSliderItemMap[type]->setMute(muted); } void AppManagerItemWidget::setCurrentIndex(const NodeType& type, int idx) { m_selectComboxItemMap[type]->setCurrentIndex(idx); } int AppManagerItemWidget::findData(const NodeType& type, const QVariant& value) { return m_selectComboxItemMap[type]->findData(value); } void AppManagerItemWidget::insertItem(const NodeType& type, const QString& text, const QVariant& userData) { m_selectComboxItemMap[type]->insertItem(text, userData); } void AppManagerItemWidget::removeItem(const NodeType& type, int idx) { m_selectComboxItemMap[type]->removeItem(idx); } void AppManagerItemWidget::setEnabled(const NodeType& type, bool enable) { m_selectComboxItemMap[type]->setEnabled(enable); } IVolumeSliderItem* AppManagerItemWidget::getSliderItem() const { return m_volumeSliderItemMap.at(VolumeSliderType::VOLUME_SLIDER_SINKINPUT); } std::unordered_map AppManagerItemWidget::getSelectComboxItems() const { return m_selectComboxItemMap; } ukui-volume-control/audio/ConcreteStrategy.cpp0000664000175000017500000000722115171074712020541 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "ConcreteStrategy.h" IAudioMainWindow* Ukui2ConcreteStrategy::createMainWindow(std::unordered_map& titleLabelMap, std::unordered_map& switchBtnMap, std::unordered_map& volumeSliderMap, std::unordered_map& selectComboxMap, std::unordered_map& detailSettingsMap, std::list& appManagerList, QWidget* parent) { return new Ukui4MainWidget(titleLabelMap, switchBtnMap, volumeSliderMap, selectComboxMap, detailSettingsMap, appManagerList, parent); } IAudioMainWindow* Ukui4ConcreteStrategy::createMainWindow(std::unordered_map& titleLabelMap, std::unordered_map& switchBtnMap, std::unordered_map& volumeSliderMap, std::unordered_map& selectComboxMap, std::unordered_map& detailSettingsMap, std::list& appManagerList, QWidget* parent) { return new Ukui4MainWidget(titleLabelMap, switchBtnMap, volumeSliderMap, selectComboxMap, detailSettingsMap, appManagerList, parent); } IAudioMainWindow* Ukui5ConcreteStrategy::createMainWindow(std::unordered_map& titleLabelMap, std::unordered_map& switchBtnMap, std::unordered_map& volumeSliderMap, std::unordered_map& selectComboxMap, std::unordered_map& detailSettingsMap, std::list& appManagerList, QWidget* parent) { return new Ukui5MainWidget(titleLabelMap, switchBtnMap, volumeSliderMap, selectComboxMap, detailSettingsMap, appManagerList, parent); } ukui-volume-control/audio/IDetailSettingsItem.cpp0000664000175000017500000000205715171074712021131 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "IDetailSettingsItem.h" IDetailSettingsItem::IDetailSettingsItem(std::shared_ptr setting, QLabel* label) : m_pDetailSettings(setting), m_pDisplayLabel(label) { } std::shared_ptr IDetailSettingsItem::getDetailSettings() const { return m_pDetailSettings; } ukui-volume-control/audio/Ukcc5DevicePortItem.h0000664000175000017500000000272015171074712020476 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 UKCC5DEVICEPORTITEM_H #define UKCC5DEVICEPORTITEM_H #include #include #include #include "IDevicePortSelectItem.h" class Ukcc5DevicePortItem : public QWidget, public IDevicePortItem { public: Ukcc5DevicePortItem(QLabel*, QLabel*, const QString&, const QString&, QWidget* = nullptr); ~Ukcc5DevicePortItem() = default; public: void initUi(); virtual QWidget* getWidget() override; virtual bool isChecked() override; virtual void setChecked(bool) override; virtual void setIcon(const QIcon&) override; private: QLabel* m_pDisplayLabel = nullptr; QLabel* m_pIconLabel = nullptr; bool m_bIsChecked = false; }; #endif // UKCC5DEVICEPORTITEM_H ukui-volume-control/audio/MainWidget.h0000664000175000017500000001202015171074712016742 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 MAINWIDGET_H #define MAINWIDGET_H #include #include //#include "IAudioMainWidow.h" #include "VersionStrategy.h" #include "Util.h" #include "DBusClient.h" using namespace UkuiAudioFramwork; #define MAX_INDEX 4294967295 class MainWidget : public QWidget { Q_OBJECT public: explicit MainWidget(QWidget* = nullptr); ~MainWidget() {} private Q_SLOTS: void volumeChangedSlots(int, int, const QDBusVariant&); void muteChangedSlots(int, int, bool); void deviceChangedSlots(int, const QString&, const QString&); void deviceAdjustSlots(int); void settingsChangedSlots(const QString&, const QDBusVariant&); void addStreamSlots(int, int, int, bool, const QString&, const QString&); void removeStreamSlots(int, int); private: DesktopEnvironmentVersion getDesktopEnvironmentVersion(); int getVolume(int, int); void setVolume(int, int, int); double getBalance(int) const; void setBalance(int, double); bool getMute(int, int); void setMute(int, int, bool); void setEnabled(const QString&, const QString&, bool); bool isEnabled(const QString&, const QString&) const; ClientDefaultDeviceInfo getDefaultDevice(int, int); QList getAvailablePortList(const NodeType&); QList getSinkInputList() const; QList getSourceOutputList() const; QList getStreamMediaList() const; QList getSinkList() const; QList getSourceList() const; void updateAppDeviceList(QList&, const QList&); void setDefaultDevice(int, int, const QString&, const QString&); void setValue(const QString&, const QVariant&); QVariant getValue(const QString&) const; QList getSoundThemeList(); QList getSoundEffectFileList(const QString&); void initUi(); void initSystemVolumeSlider(); void initSystemDeviceList(const NodeType&); void initCurrentDefaultDevice(const NodeType&); void initDeviceManagerList(const NodeType&); void initAppManagerList(); void initAppCurrentDefaultDevice(int, const NodeType&, int); void initSwitchButtonStatus(); void initSoundThemeList(); void initData(); void initSlots(); void initClientManager(); void updateSoundTheme(); bool getAutoPauseStatus() const; void setAutoPauseStatus(bool); bool getMultiAudioCombineStatus(const NodeType&) const; void updateAppWidgetDeviceList(const NodeType&); QString getAppStreamNameByIndex(const NodeType&, int); void addAppManagerItem(int, int, int, bool, const QString&, const QString&); void setAppManagerSliderRange(int, int); void setAppManagerSliderValue(int); private: static constexpr std::string_view m_kylinSettingsSystemStr = "kylin-settings-system"; DesktopEnvironmentVersion m_version = DesktopEnvironmentVersion::DESKTOP_ENVIRONMENT_VERSION_UKUI2; IAudioMainWindow* m_pMainWid = nullptr; std::list m_appWidgetList {}; std::shared_ptr m_pStrategy = nullptr; std::shared_ptr m_pClientManager = nullptr; std::unordered_map> m_selectComboxKeys { {SelectComboxType::SELECT_COMBOX_SOUNDEFFECT, std::tuple(THEME_NAME_KEY, "setSoundThemeName")}, {SelectComboxType::SELECT_COMBOX_VOLUMEADJUST, std::tuple(VOLUME_CHANGED_KEY, "setVolumeChangedName")}, {SelectComboxType::SELECT_COMBOX_NOTIFICTION, std::tuple(NOTIFY_GENERAL_KEY, "setNotifyGeneralName")}, }; std::unordered_map m_appKeys { {MAX_INDEX, QStringList{std::initializer_list{"kylin-settings-system", QObject::tr("System Volume")}}}, }; std::unordered_map m_titleLabelItemMap {}; std::unordered_map m_switchBtnItemMap {}; std::unordered_map m_volumeSliderItemMap {}; std::unordered_map m_selectComboxItemMap {}; std::unordered_map m_detailSettingsItemMap {}; }; #endif // MAINWIDGET_H ukui-volume-control/audio/ukui2.0/0000775000175000017500000000000015171074712015743 5ustar fengfengukui-volume-control/audio/ukui2.0/Ukui2SelectComboxItem.h0000664000175000017500000000460715171074712022251 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 UKUI2SELECTCOMBOXITEM_H #define UKUI2SELECTCOMBOXITEM_H #include #include "ISelectComboxItem.h" ///** //* @brief 包含SelectCombox子项的类型 //*/ //enum class SelectComboxType //{ // SELECT_COMBOX_OUTPUT = 0, // 输出设备子项 // SELECT_COMBOX_INPUT // 输入设备子项 //}; class Ukui2SelectCombox : public ISelectBox { public: Ukui2SelectCombox(QComboBox*); ~Ukui2SelectCombox() = default; public: virtual void setCurrentIndex(int) override; virtual void setEnabled(bool) override; virtual bool isEnabled() const override; virtual int findData(const QVariant&, int = Qt::UserRole) override; virtual int findText(const QString&) override; virtual void insertItem(const QString&, const QVariant& = QVariant()) override; virtual void removeItem(int) override; virtual QVariant currentData(int = Qt::UserRole) const override; virtual std::list getDataList() const override; virtual int count() const override; virtual void clear() override; virtual QWidget* getWidget() override; virtual void setParent(QWidget*) override; private: QComboBox* m_pCombox = nullptr; }; class Ukui2SelectComboxItem : public QFrame, public ISelectComboxItem { public: Ukui2SelectComboxItem(std::shared_ptr, QLabel*, QWidget* = nullptr); ~Ukui2SelectComboxItem() = default; public: virtual void initUi() override; virtual void initSlots() override; virtual void setEnabled(bool) override; virtual QWidget* getWidget() override; private Q_SLOTS: void currentIndexChangedSlots(int); }; #endif // UKUI2SELECTCOMBOXITEM_H ukui-volume-control/audio/ukui2.0/AudioPulginUkui2.cpp0000664000175000017500000000367415171074712021621 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "AudioPulginUkui2.h" #include #include #include #include #include "../ui_audio.h" #include "../MainWidget.h" AudioPulginUkui2::AudioPulginUkui2() : m_firstLoad(true) { /* ukui 2.0的控制面板加载插件的机制只会加载指定的插件列表, 这里的翻译需要和控制面板的翻译一致,即中文对应为声音 */ m_pluginName = tr("声音"); m_pluginType = DEVICES; } AudioPulginUkui2::~AudioPulginUkui2() { if (!m_firstLoad) { delete m_pUi; } } QString AudioPulginUkui2::get_plugin_name() { return m_pluginName; } int AudioPulginUkui2::get_plugin_type() { return m_pluginType; } QWidget* AudioPulginUkui2::get_plugin_ui() { if (m_firstLoad) { m_firstLoad = false; m_pUi = new Ui::Audio; m_pPluginWidget = new MainWidget(); QLabel* pLable = new QLabel("test", m_pPluginWidget); m_pPluginWidget->setAttribute(Qt::WA_DeleteOnClose); m_pUi->setupUi(m_pPluginWidget); } return m_pPluginWidget; } void AudioPulginUkui2::plugin_delay_control(){ } const QString AudioPulginUkui2::name() const { return QStringLiteral("audio"); } ukui-volume-control/audio/ukui2.0/Ukui2SwitchButtonItem.cpp0000664000175000017500000000735315171074712022653 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "Ukui2SwitchButtonItem.h" #include //void Ukui2SwitchButton::setChecked(bool checked) //{ // m_pSwitchBtn->setChecked(checked); //} //QWidget* Ukui2SwitchButton::getWidget() //{ // return m_pSwitchBtn; //} //Ukui2SwitchButtonItem::Ukui2SwitchButtonItem(std::shared_ptr btn, QLabel* label, QWidget* parent) // : ISwitchButtonItem(btn, label), QFrame(parent) //{ //} //void Ukui2SwitchButtonItem::initUi() //{ // QHBoxLayout* hlayout = new QHBoxLayout; // hlayout->addWidget(m_pDisplayLabel); // hlayout->addWidget(m_pSwitchBtn->getWidget()); // setLayout(hlayout); //} //QWidget* Ukui2SwitchButtonItem::getWidget() //{ // return this; //} Ukui2SwitchButton::Ukui2SwitchButton(SwitchButton *btn) : m_pSwitchBtn(btn) { } bool Ukui2SwitchButton::isChecked() const { return m_pSwitchBtn->isChecked(); } void Ukui2SwitchButton::setChecked(bool checked) { m_pSwitchBtn->setChecked(checked); } QWidget* Ukui2SwitchButton::getWidget() { return m_pSwitchBtn; } Ukui2SwitchButtonItem::Ukui2SwitchButtonItem(std::shared_ptr btn, QLabel* label, SwitchButtonType type, QWidget* parent) : ISwitchButtonItem(btn, label, type), QFrame(parent) { } void Ukui2SwitchButtonItem::initUi() { QHBoxLayout* hlayout = new QHBoxLayout(this); hlayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); hlayout->addWidget(m_pDisplayLabel); hlayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Expanding)); hlayout->addWidget(m_pSwitchBtn->getWidget()); hlayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); setLayout(hlayout); setFrameShape(QFrame::Shape::Box); } void Ukui2SwitchButtonItem::initSlots() { } QWidget* Ukui2SwitchButtonItem::getWidget() { return this; } Ukui2SwitchButtonWithDescItem::Ukui2SwitchButtonWithDescItem(std::shared_ptr btn, QLabel* disLabel, QLabel* descLabel, SwitchButtonType type, QWidget* parent) : ISwitchButtonItem(btn, disLabel, type), m_pDescLabel(descLabel), QFrame(parent) { } void Ukui2SwitchButtonWithDescItem::initUi() { QPalette palette = m_pDescLabel->palette(); QColor color = palette.color(QPalette::PlaceholderText); palette.setColor(QPalette::WindowText, color); m_pDescLabel->setPalette(palette); QVBoxLayout* vLayout = new QVBoxLayout; vLayout->addStretch(); vLayout->addWidget(m_pDisplayLabel); vLayout->addWidget(m_pDescLabel); vLayout->addStretch(); vLayout->setSpacing(0); QHBoxLayout* hLayout = new QHBoxLayout(this); hLayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); hLayout->addLayout(vLayout); hLayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Expanding)); hLayout->addWidget(m_pSwitchBtn->getWidget()); hLayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); setLayout(hLayout); setFrameShape(QFrame::Shape::Box); } void Ukui2SwitchButtonWithDescItem::initSlots() { } QWidget* Ukui2SwitchButtonWithDescItem::getWidget() { return this; } ukui-volume-control/audio/ukui2.0/Ukcc2MainWidget.h0000664000175000017500000000734515171074712021045 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 UKCC2MAINWIDGET_H #define UKCC2MAINWIDGET_H #include "IAudioMainWidow.h" #include "Ukui2SwitchButtonItem.h" #include "Ukui2VolumeSliderItem.h" #include "Ukui2SelectComboxItem.h" #include "AppManagerMainWidget.h" #include "Util.h" class Ukcc2MainWidget : public QWidget, public IAudioMainWindow { public: Ukcc2MainWidget(std::unordered_map&, std::unordered_map&, std::unordered_map&, std::unordered_map&, std::unordered_map&, std::list&, QWidget* = nullptr); ~Ukcc2MainWidget() = default; public: virtual void initUi() override ; virtual void initSlots() override; virtual void setValue(int, int) override; virtual void setAppValue(const QString&, int) override; virtual void setAppMute(const QString&, bool) override; virtual void setCurrentIndex(int, int) override; virtual void setAppCurrentIndex(const QString&, const NodeType&, int) override; virtual void setAppCbxEnabled(const QString&, const NodeType&, bool) override; virtual void setCurrentAppItem(int) override; virtual void setChecked(int, bool) override; virtual std::list getDevicePortList(int) const override; virtual std::list getDeviceManagetPortList(int) const override; virtual std::list& getAppItemList() override; virtual std::list getAppManagetPortList(int, int) const override; virtual QWidget* getWidget() override; virtual void addDeviceManagerItem(int, const QString&, const QVariant&) override; virtual void removeDeviceManagerItem(int, int) override; virtual void setDeviceManagerItemStatus(int, int, bool) override; virtual void updateUIByButtonStatus(int, bool) override; virtual void addAppManagerItem(int, int, int, bool, const QString&, const QString&, const QList&, const QList&) override; virtual void removeAppManagerItem(const QString&) override; virtual void addPortToAppItem(const QString&, const NodeType&, const QString&, const QVariant&) override; virtual void removePortFromAppItem(const QString&, const NodeType&, int) override; private: void createdItems(); QFrame* addLine(); private: std::list m_appWidgetList {}; std::unordered_map& m_titleLabelItemMap; std::unordered_map& m_switchBtnItemMap; std::unordered_map& m_volumeSliderItemMap; std::unordered_map& m_selectComboxItemMap; std::unordered_map& m_detailSettingsItemMap; }; #endif // UKCC2MAINWIDGET_H ukui-volume-control/audio/ukui2.0/AudioPulginUkui2.h0000664000175000017500000000627015171074712021261 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 AUDIOPULGINUKUI2_H #define AUDIOPULGINUKUI2_H #include #include #include #include #include #include #include "include/interface.h" #include "Ukui2SwitchButtonItem.h" #include "Ukui2VolumeSliderItem.h" #include "Ukui2SelectComboxItem.h" #if defined(LIBUKCC_LIBRARY) # define LIBUKCC_EXPORT Q_DECL_EXPORT #else # define LIBUKCC_EXPORT Q_DECL_IMPORT #endif namespace Ui { class Audio; } class AudioPulginUkui2 : public QObject, CommonInterface { Q_OBJECT Q_PLUGIN_METADATA(IID "org.kycc.CommonInterface") Q_INTERFACES(CommonInterface) public: AudioPulginUkui2(); ~AudioPulginUkui2(); public: QString get_plugin_name() Q_DECL_OVERRIDE; int get_plugin_type() Q_DECL_OVERRIDE; QWidget * get_plugin_ui() Q_DECL_OVERRIDE; void plugin_delay_control() Q_DECL_OVERRIDE; const QString name() const Q_DECL_OVERRIDE; private: bool m_firstLoad = false; int m_pluginType = DEVICES; QString m_pluginName = ""; Ui::Audio* m_pUi = nullptr; QWidget* m_pPluginWidget = nullptr; // std::unordered_map m_switchButtonkeys { // {SwitchButtonType::SWITCH_BUTTON_VOLUMEBOOST, tr("Volume Increase")}, // {SwitchButtonType::SWITCH_BUTTON_MONO, tr("Mono Audio")}, // {SwitchButtonType::SWITCH_BUTTON_ECHOCANCEL, tr("Noise Reduction")}, // {SwitchButtonType::SWITCH_BUTTON_LOOPBACK, tr("Voice Monitor")}, // {SwitchButtonType::SWITCH_BUTTON_STARTUP, tr("Startup Misic")}, // {SwitchButtonType::SWITCH_BUTTON_POWEROFF, tr("Poweroff Music")}, // {SwitchButtonType::SWITCH_BUTTON_LOGOUT, tr("Logout Music")}, // {SwitchButtonType::SWITCH_BUTTON_WAKEUP, tr("Wakeup Music")}, // {SwitchButtonType::SWITCH_BUTTON_ALERT, tr("Beep Switch")} // }; // std::unordered_map m_volumeSliderKeys { // {VolumeSliderType::VOLUME_SLIDER_INPUT, tr("Input")}, // {VolumeSliderType::VOLUME_SLIDER_OUTPUT, tr("Output")}, // {VolumeSliderType::VOLUME_SLIDER_INPUTLEVEL, tr("Input Level")}, // {VolumeSliderType::VOLUME_SLIDER_BALANCE, tr("Balance")} // }; // std::unordered_map m_selectComboxKeys { // {SelectComboxType::SELECT_COMBOX_INPUT, tr("Input")}, // {SelectComboxType::SELECT_COMBOX_OUTPUT, tr("Output")}, // }; }; #endif // AUDIOPULGINUKUI2_H ukui-volume-control/audio/ukui2.0/Ukui2VolumeSliderItem.cpp0000664000175000017500000000612315171074712022622 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "Ukui2VolumeSliderItem.h" #include #include Ukui2VolumeSlider::Ukui2VolumeSlider(QSlider *s) : m_pSlider(s) { m_pSlider->setOrientation(Qt::Horizontal); } void Ukui2VolumeSlider::setRange(int min, int max) { m_pSlider->setRange(min, max); } void Ukui2VolumeSlider::setValue(int value) { if (!m_pSlider) m_pSlider->setValue(value); } void Ukui2VolumeSlider::setEnabled(bool enable) { // TODO } bool Ukui2VolumeSlider::isEnabled() const { return m_pSlider->isEnabled(); } uint32_t Ukui2VolumeSlider::getValue() const { return m_pSlider->value(); } QWidget* Ukui2VolumeSlider::getWidget() { return m_pSlider; } Ukui2VolumeSliderItem::Ukui2VolumeSliderItem(std::shared_ptr slider, QLabel* disLabel, QLabel* percLabel, QPushButton* muteBtn, QWidget* parent) : IVolumeSliderItem(slider, disLabel), m_pPercentLabel(percLabel), m_pMuteBtn(muteBtn), QFrame(parent) { m_pMuteBtn->setFocusPolicy(Qt::NoFocus); } void Ukui2VolumeSliderItem::initUi() { QHBoxLayout* hlayout = new QHBoxLayout(this); hlayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); hlayout->addWidget(m_pDisplayLabel); hlayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Expanding)); hlayout->addWidget(m_pMuteBtn); hlayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); hlayout->addWidget(m_pVolumeSlider->getWidget()); hlayout->addItem(new QSpacerItem(13, 20, QSizePolicy::Maximum)); hlayout->addWidget(m_pPercentLabel); hlayout->addItem(new QSpacerItem(10, 20, QSizePolicy::Maximum)); hlayout->setSpacing(0); setLayout(hlayout); setFrameShape(QFrame::Shape::Box); } void Ukui2VolumeSliderItem::initSlots() { } QWidget* Ukui2VolumeSliderItem::getWidget() { return this; } void Ukui2VolumeSliderItem::setValue(int value) { m_pVolumeSlider->setValue(value); setPercent(value); setIcon(QIcon::fromTheme("firefox")); } void Ukui2VolumeSliderItem::setMute(bool) { // TODO } void Ukui2VolumeSliderItem::setEnabled(bool enable) { // TODO } QPushButton* Ukui2VolumeSliderItem::getMuteBtn() const { return m_pMuteBtn; } void Ukui2VolumeSliderItem::setIcon(const QIcon& icon) { m_pMuteBtn->setIcon(icon); } void Ukui2VolumeSliderItem::setPercent(int percent) { m_pPercentLabel->setText(QString("").arg(percent) + "%"); } ukui-volume-control/audio/ukui2.0/Ukui2VolumeSliderItem.h0000664000175000017500000000460415171074712022271 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 UKUI2VOLUMESLIDERITEM_H #define UKUI2VOLUMESLIDERITEM_H #include #include #include #include #include "IVolumeSliderItem.h" ///** //* @brief 包含VolumeSlider子项的类型 //*/ //enum class VolumeSliderType //{ // VOLUME_SLIDER_INPUT = 0, // 输入音量子项 // VOLUME_SLIDER_OUTPUT, // 输出音量子项 // VOLUME_SLIDER_BALANCE, // 平衡音量子项 // VOLUME_SLIDER_INPUTLEVEL // 输入反馈子项 //}; class Ukui2VolumeSlider : public IVolumeSlider { public: Ukui2VolumeSlider(QSlider*); ~Ukui2VolumeSlider() = default; public: virtual void setRange(int, int) override; virtual void setValue(int) override; virtual void setEnabled(bool) override; virtual bool isEnabled() const override; virtual uint32_t getValue() const override; virtual QWidget* getWidget() override; private: QSlider* m_pSlider = nullptr; }; class Ukui2VolumeSliderItem : public QFrame, public IVolumeSliderItem { public: Ukui2VolumeSliderItem(std::shared_ptr, QLabel*, QLabel*, QPushButton*, QWidget* = nullptr); ~Ukui2VolumeSliderItem() = default; public: virtual void initUi() override; virtual void initSlots() override; virtual QWidget* getWidget() override; virtual void setValue(int) override; virtual void setMute(bool) override; virtual void setEnabled(bool) override; virtual QPushButton* getMuteBtn() const override; void setIcon(const QIcon&); void setPercent(int); private: QLabel* m_pPercentLabel = nullptr; QPushButton* m_pMuteBtn = nullptr; }; #endif // UKUI2VOLUMESLIDERITEM_H ukui-volume-control/audio/ukui2.0/Ukui2SwitchButtonItem.h0000664000175000017500000000635415171074712022320 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 UKUI2SWITCHBUTTONITEM_H #define UKUI2SWITCHBUTTONITEM_H #include #include #include "ISwitchButtonItem.h" #include "include/SwitchButton.h" ///** //* @brief 包含SwitchButton子项的类型 //*/ //enum class SwitchButtonType //{ // SWITCH_BUTTON_VOLUMEBOOST = 0, // 音量增强子项 // SWITCH_BUTTON_MONO, // 单声道子项 // SWITCH_BUTTON_ECHOCANCEL, // 智能降噪子项 // SWITCH_BUTTON_LOOPBACK, // 侦听子项 // SWITCH_BUTTON_STARTUP, // 开机音乐子项 // SWITCH_BUTTON_POWEROFF, // 关机音乐子项 // SWITCH_BUTTON_LOGOUT, // 注销音乐子项 // SWITCH_BUTTON_WAKEUP, // 唤醒音乐子项 // SWITCH_BUTTON_ALERT // 提示音子项 //}; class Ukui2SwitchButton : public ISwitchButton { public: Ukui2SwitchButton(SwitchButton*); ~Ukui2SwitchButton() = default; public: virtual bool isChecked() const override; virtual void setChecked(bool) override; virtual QWidget* getWidget() override; private: SwitchButton* m_pSwitchBtn = nullptr; }; class Ukui2SwitchButtonItem : public QFrame, public ISwitchButtonItem { Q_OBJECT public: Ukui2SwitchButtonItem(std::shared_ptr, QLabel*, SwitchButtonType, QWidget* = nullptr); ~Ukui2SwitchButtonItem() = default; public: virtual void initUi() override; virtual void initSlots() override; virtual QWidget* getWidget() override; }; class Ukui2SwitchButtonWithDescItem : public QFrame, public ISwitchButtonItem { Q_OBJECT public: Ukui2SwitchButtonWithDescItem(std::shared_ptr, QLabel*, QLabel*, SwitchButtonType, QWidget* = nullptr); ~Ukui2SwitchButtonWithDescItem() = default; public: virtual void initUi() override; virtual void initSlots() override; virtual QWidget* getWidget() override; private: QLabel* m_pDescLabel; }; //class Ukui2SwitchButton : public ISwitchButton //{ //public: // virtual void setChecked(bool checked) override; // virtual QWidget* getWidget() override; //private: // SwitchButton* m_pSwitchBtn = nullptr; //}; //class Ukui2SwitchButtonItem : public QFrame, public ISwitchButtonItem //{ // Q_OBJECT //public: // Ukui2SwitchButtonItem(std::shared_ptr btn, QLabel* label, QWidget* parent = nullptr); // ~Ukui2SwitchButtonItem() = default; //public: // virtual void initUi() override; // virtual QWidget* getWidget() override; //}; #endif // UKUI2SWITCHBUTTONITEM_H ukui-volume-control/audio/ukui2.0/Ukcc2MainWidget.cpp0000664000175000017500000002723015171074712021373 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "Ukcc2MainWidget.h" #include #include #include "include/SwitchButton.h" using namespace UkuiAudioFramwork; Ukcc2MainWidget::Ukcc2MainWidget(std::unordered_map& titleLabelMap, std::unordered_map& switchBtnMap, std::unordered_map& volumeSliderMap, std::unordered_map& selectComboxMap, std::unordered_map& detailItemMap, std::list& appManagerList, QWidget* parent) : m_titleLabelItemMap(titleLabelMap), m_switchBtnItemMap(switchBtnMap), m_volumeSliderItemMap(volumeSliderMap), m_selectComboxItemMap(selectComboxMap), m_detailSettingsItemMap(detailItemMap), IAudioMainWindow(appManagerList), QWidget(parent) { createdItems(); initUi(); } void Ukcc2MainWidget::initUi() { // 添加布局 QVBoxLayout* vLayout = new QVBoxLayout; vLayout->addWidget(m_titleLabelItemMap[TitleLabelType::TITLE_LABEL_OUTPUT]->getWidget()); vLayout->addWidget(m_selectComboxItemMap[SelectComboxType::SELECT_COMBOX_OUTPUT]->getWidget()); vLayout->addWidget(addLine()); vLayout->addWidget(m_volumeSliderItemMap[VolumeSliderType::VOLUME_SLIDER_OUTPUT]->getWidget()); vLayout->addWidget(addLine()); vLayout->addWidget(m_switchBtnItemMap[SwitchButtonType::SWITCH_BUTTON_VOLUMEBOOST]->getWidget()); vLayout->addItem(new QSpacerItem(16, 16, QSizePolicy::Fixed)); vLayout->addWidget(m_volumeSliderItemMap[VolumeSliderType::VOLUME_SLIDER_BALANCE]->getWidget()); vLayout->addWidget(addLine()); vLayout->addWidget(m_switchBtnItemMap[SwitchButtonType::SWITCH_BUTTON_MONO]->getWidget()); vLayout->addItem(new QSpacerItem(16, 40, QSizePolicy::Fixed)); vLayout->addWidget(m_titleLabelItemMap[TitleLabelType::TITLE_LABEL_INPUT]->getWidget()); vLayout->addWidget(m_selectComboxItemMap[SelectComboxType::SELECT_COMBOX_INPUT]->getWidget()); vLayout->addWidget(addLine()); vLayout->addWidget(m_volumeSliderItemMap[VolumeSliderType::VOLUME_SLIDER_INPUT]->getWidget()); vLayout->addWidget(addLine()); vLayout->addWidget(m_volumeSliderItemMap[VolumeSliderType::VOLUME_SLIDER_INPUTLEVEL]->getWidget()); vLayout->addWidget(addLine()); vLayout->addWidget(m_switchBtnItemMap[SwitchButtonType::SWITCH_BUTTON_ECHOCANCEL]->getWidget()); vLayout->addWidget(addLine()); vLayout->addWidget(m_switchBtnItemMap[SwitchButtonType::SWITCH_BUTTON_LOOPBACK]->getWidget()); vLayout->addItem(new QSpacerItem(16, 40, QSizePolicy::Fixed)); vLayout->addWidget(m_titleLabelItemMap[TitleLabelType::TITLE_LABEL_SOUNDEFFECT]->getWidget()); vLayout->addWidget(m_switchBtnItemMap[SwitchButtonType::SWITCH_BUTTON_STARTUP]->getWidget()); vLayout->addWidget(addLine()); vLayout->addWidget(m_switchBtnItemMap[SwitchButtonType::SWITCH_BUTTON_POWEROFF]->getWidget()); vLayout->addWidget(addLine()); vLayout->addWidget(m_switchBtnItemMap[SwitchButtonType::SWITCH_BUTTON_LOGOUT]->getWidget()); vLayout->addWidget(addLine()); vLayout->addWidget(m_switchBtnItemMap[SwitchButtonType::SWITCH_BUTTON_WAKEUP]->getWidget()); vLayout->addWidget(m_switchBtnItemMap[SwitchButtonType::SWITCH_BUTTON_ALERT]->getWidget()); vLayout->addWidget(addLine()); vLayout->addWidget(m_selectComboxItemMap[SelectComboxType::SELECT_COMBOX_SOUNDEFFECT]->getWidget()); vLayout->addWidget(addLine()); vLayout->addWidget(m_selectComboxItemMap[SelectComboxType::SELECT_COMBOX_VOLUMEADJUST]->getWidget()); vLayout->addWidget(addLine()); vLayout->addWidget(m_selectComboxItemMap[SelectComboxType::SELECT_COMBOX_NOTIFICTION]->getWidget()); vLayout->setSpacing(0); setLayout(vLayout); vLayout->setContentsMargins(0, 0, 0, 0); } void Ukcc2MainWidget::initSlots() { for (const auto& it : m_volumeSliderItemMap) { connect(static_cast(it.second->getSlider()->getWidget()), &QSlider::valueChanged, this, [this](int value) { setVolume(1, -1, value); }); if (VolumeSliderType::VOLUME_SLIDER_INPUT != it.first && VolumeSliderType::VOLUME_SLIDER_OUTPUT != it.first) continue; connect(static_cast(it.second)->getMuteBtn(), &QPushButton::clicked, this, [it, this]() { setMute(EnumToInt(it.first), -1); }); } } void Ukcc2MainWidget::setValue(int type , int value) { m_volumeSliderItemMap[IntToEnum(type)]->setValue(value); } void Ukcc2MainWidget::setAppValue(const QString&, int) { // TODO } void Ukcc2MainWidget::setAppMute(const QString&, bool) { // TODO } void Ukcc2MainWidget::setCurrentIndex(int type, int idx) { m_selectComboxItemMap[IntToEnum(type)]->setCurrentIndex(idx); } void Ukcc2MainWidget::setAppCurrentIndex(const QString&, const NodeType&, int) { // TODO } void Ukcc2MainWidget::setAppCbxEnabled(const QString&, const NodeType&, bool) { // TODO } void Ukcc2MainWidget::setCurrentAppItem(int idx) { // TODO } void Ukcc2MainWidget::setChecked(int type, bool status) { m_switchBtnItemMap[IntToEnum(type)]->setChecked(status); } std::list Ukcc2MainWidget::getDevicePortList(int) const { // TODO return {}; } std::list Ukcc2MainWidget::getDeviceManagetPortList(int) const { // TODO return {}; } //std::unordered_map Ukcc2MainWidget::getAppItemList() const std::list& Ukcc2MainWidget::getAppItemList() { // TODO return m_appWidgetList; } std::list Ukcc2MainWidget::getAppManagetPortList(int, int) const { // TODO return {}; } QWidget* Ukcc2MainWidget::getWidget() { return this; } void Ukcc2MainWidget::addDeviceManagerItem(int type, const QString& displayStr, const QVariant& data) { // TODO } void Ukcc2MainWidget::removeDeviceManagerItem(int type, int idx) { // TODO } void Ukcc2MainWidget::setDeviceManagerItemStatus(int type, int idx, bool checked) { // TODO } void Ukcc2MainWidget::addAppManagerItem(int, int, int, bool, const QString&, const QString&, const QList&, const QList&) { // TODO } void Ukcc2MainWidget::removeAppManagerItem(const QString&) { // TODO } void Ukcc2MainWidget::addPortToAppItem(const QString&, const NodeType&, const QString&, const QVariant&) { // TODO } void Ukcc2MainWidget::removePortFromAppItem(const QString&, const NodeType&, int) { // TODO } void Ukcc2MainWidget::updateUIByButtonStatus(int, bool) { } void Ukcc2MainWidget::createdItems() { for (const auto& [k, v] : m_titleLabelKeys) { m_titleLabelItemMap.emplace(k, new TitleLabelItem(new QLabel(v), this)); } // 创建SwitchButton子项 for (const auto& [k, v] : m_switchButtonkeys) { ISwitchButtonItem* item = nullptr; std::shared_ptr btn = nullptr; btn = std::make_shared(new SwitchButton); switch (k) { case SwitchButtonType::SWITCH_BUTTON_VOLUMEBOOST: { item = new Ukui2SwitchButtonWithDescItem(btn, new QLabel(v.at(0), this), new QLabel(v.at(1)), k, this); break; } case SwitchButtonType::SWITCH_BUTTON_MONO: { item = new Ukui2SwitchButtonWithDescItem(btn, new QLabel(v.at(0), this), new QLabel(v.at(1)), k, this); break; } case SwitchButtonType::SWITCH_BUTTON_LOOPBACK: { item = new Ukui2SwitchButtonWithDescItem(btn, new QLabel(v.at(0), this), new QLabel(v.at(1)), k, this); break; } case SwitchButtonType::SWITCH_BUTTON_ECHOCANCEL: case SwitchButtonType::SWITCH_BUTTON_STARTUP: case SwitchButtonType::SWITCH_BUTTON_POWEROFF: case SwitchButtonType::SWITCH_BUTTON_LOGOUT: case SwitchButtonType::SWITCH_BUTTON_WAKEUP: case SwitchButtonType::SWITCH_BUTTON_ALERT: case SwitchButtonType::SWITCH_BUTTON_AUTO_PAUSE: { item = new Ukui2SwitchButtonItem(btn, new QLabel(v.at(0), this), k, this); break; } default: break; } if (item && btn) { m_switchBtnItemMap.emplace(k, item); } } // 创建VolumeSlider子项 for (const auto& [k, v] : m_volumeSliderKeys) { IVolumeSliderItem* item = nullptr; std::shared_ptr slider = nullptr; switch (k) { case VolumeSliderType::VOLUME_SLIDER_OUTPUT: case VolumeSliderType::VOLUME_SLIDER_INPUT: { slider = std::make_shared(new QSlider(this)); slider->setRange(0, 100); item = new Ukui2VolumeSliderItem(slider, new QLabel(v.at(0), this), new QLabel(this), new QPushButton(this), this); break; } case VolumeSliderType::VOLUME_SLIDER_BALANCE: { slider = std::make_shared(new kdk::KSlider(this)); slider->setRange(0, 100); item = new Ukui4BalanceVolumeSliderItem(slider, new QLabel(v.at(0), this), new QLabel(v.at(1)), new QLabel(v.at(2)), this); break; } case VolumeSliderType::VOLUME_SLIDER_INPUTLEVEL: { slider = std::make_shared(new QProgressBar(this)); slider->setRange(0, 99); item = new Ukui4InputLevelVolumeSliderItem(slider, new QLabel(v.at(0), this), this); m_pMonitorStream = std::make_shared(slider); break; } default: break; } if (item && slider) { m_volumeSliderItemMap.emplace(k, item); } } // 创建SelectCombox子项 for (const auto& [k, v] : m_selectComboxKeys) { ISelectComboxItem* item = nullptr; std::shared_ptr box = nullptr; switch (k) { case SelectComboxType::SELECT_COMBOX_INPUT: case SelectComboxType::SELECT_COMBOX_OUTPUT: case SelectComboxType::SELECT_COMBOX_SOUNDEFFECT: case SelectComboxType::SELECT_COMBOX_VOLUMEADJUST: case SelectComboxType::SELECT_COMBOX_NOTIFICTION:{ box = std::make_shared(new QComboBox(this)); item = new Ukui2SelectComboxItem(box, new QLabel(v.at(0), this), this); break; } default: break; } if (item && box) { m_selectComboxItemMap.emplace(k, item); } } } QFrame* Ukcc2MainWidget::addLine() { auto line = new QFrame(this); line->setMinimumSize(QSize(0, 1)); line->setMaximumSize(QSize(16777215, 1)); line->setLineWidth(0); line->setFrameShape(QFrame::HLine); line->setFrameShadow(QFrame::Sunken); return line; } ukui-volume-control/audio/ukui2.0/Ukui2SelectComboxItem.cpp0000664000175000017500000000604715171074712022604 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "Ukui2SelectComboxItem.h" #include Ukui2SelectCombox::Ukui2SelectCombox(QComboBox* box) : m_pCombox(box) { } void Ukui2SelectCombox::setCurrentIndex(int idx) { m_pCombox->setCurrentIndex(idx); } void Ukui2SelectCombox::setEnabled(bool enable) { m_pCombox->setEnabled(enable); } bool Ukui2SelectCombox::isEnabled() const { return m_pCombox->isEnabled(); } int Ukui2SelectCombox::findData(const QVariant& value, int role) { return m_pCombox->findData(value, role); } int Ukui2SelectCombox::findText(const QString& text) { return m_pCombox->findText(text); } void Ukui2SelectCombox::insertItem(const QString& text, const QVariant& userData) { m_pCombox->blockSignals(true); m_pCombox->insertItem(m_pCombox->count(), text, userData); m_pCombox->blockSignals(false); } void Ukui2SelectCombox::removeItem(int index) { m_pCombox->removeItem(index); } QVariant Ukui2SelectCombox::currentData(int role) const { return m_pCombox->currentData(); } std::list Ukui2SelectCombox::getDataList() const { // TODO } int Ukui2SelectCombox::count() const { return m_pCombox->count(); } void Ukui2SelectCombox::clear() { m_pCombox->clear(); } QWidget* Ukui2SelectCombox::getWidget() { return m_pCombox; } void Ukui2SelectCombox::setParent(QWidget* parent) { m_pCombox->setParent(parent); } Ukui2SelectComboxItem::Ukui2SelectComboxItem(std::shared_ptr box, QLabel* label, QWidget* parent) : ISelectComboxItem(box, label), QFrame(parent) { } void Ukui2SelectComboxItem::initUi() { QHBoxLayout* hLayout = new QHBoxLayout(); hLayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); hLayout->addWidget(m_pDisplayLabel); hLayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Expanding)); hLayout->addWidget(m_pSelectBox->getWidget()); hLayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); hLayout->setSpacing(0); setLayout(hLayout); setFrameShape(QFrame::Shape::Box); } void Ukui2SelectComboxItem::initSlots() { connect(m_pSelectBox->getWidget(), SIGNAL(currentIndexChanged(int)), this, SLOT(currentIndexChangedSlots(int))); } void Ukui2SelectComboxItem::setEnabled(bool enable) { m_pSelectBox->setEnabled(enable); } QWidget* Ukui2SelectComboxItem::getWidget() { return this; } ukui-volume-control/audio/Ukui5MainWidget.cpp0000664000175000017500000007077715171074712020247 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "Ukui5MainWidget.h" #include #include #include #include "Ukcc5DevicePortItem.h" #include "PaDataType.h" #include "ukui4.0/Ukui4SwitchButtonItem.h" #include "ukui4.0/Ukui4VolumeSliderItem.h" #include "ukui4.0/Ukui4SelectComboxItem.h" #include "Ukui4CustomControl.h" #include "Ukui5DetailSettingsItem.h" Ukui5MainWidget::Ukui5MainWidget(std::unordered_map& titleLabelMap, std::unordered_map& switchBtnMap, std::unordered_map& volumeSliderMap, std::unordered_map& selectComboxMap, std::unordered_map& detailMap, std::list& appManagerList, QWidget* parent) : m_titleLabelItemMap(titleLabelMap), m_switchBtnItemMap(switchBtnMap), m_volumeSliderItemMap(volumeSliderMap), m_selectComboxItemMap(selectComboxMap), m_detailSettingsItemMap(detailMap), IAudioMainWindow(appManagerList), QWidget(parent) { createdItems(); } void Ukui5MainWidget::initUi() { // 添加布局 auto vMainLayout = new QVBoxLayout; auto outputFrame1 = new QFrame(); auto outputVLayout1 = new QVBoxLayout(); outputVLayout1->addWidget(m_selectComboxItemMap[SelectComboxType::SELECT_COMBOX_OUTPUT]->getWidget()); outputVLayout1->addWidget(addLine()); outputVLayout1->addWidget(m_volumeSliderItemMap[VolumeSliderType::VOLUME_SLIDER_OUTPUT]->getWidget()); outputVLayout1->addWidget(addLine()); outputVLayout1->addWidget(m_switchBtnItemMap[SwitchButtonType::SWITCH_BUTTON_VOLUMEBOOST]->getWidget()); outputVLayout1->setSpacing(0); outputFrame1->setLayout(outputVLayout1); outputVLayout1->setContentsMargins(0, 0, 0, 0); outputFrame1->setFrameShape(QFrame::Shape::Box); auto outputFrame2 = new QFrame(); auto outputVLayout2 = new QVBoxLayout(); outputVLayout2->addWidget(m_volumeSliderItemMap[VolumeSliderType::VOLUME_SLIDER_BALANCE]->getWidget()); outputVLayout2->addWidget(addLine()); outputVLayout2->addWidget(m_switchBtnItemMap[SwitchButtonType::SWITCH_BUTTON_MONO]->getWidget()); outputVLayout2->setSpacing(0); outputFrame2->setLayout(outputVLayout2); outputVLayout2->setContentsMargins(0, 0, 0, 0); outputFrame2->setFrameShape(QFrame::Shape::Box); auto inputFrame1 = new QFrame(); auto inputVLayout1 = new QVBoxLayout(); inputVLayout1->addWidget(m_selectComboxItemMap[SelectComboxType::SELECT_COMBOX_INPUT]->getWidget()); inputVLayout1->addWidget(addLine()); inputVLayout1->addWidget(m_volumeSliderItemMap[VolumeSliderType::VOLUME_SLIDER_INPUT]->getWidget()); inputVLayout1->addWidget(addLine()); inputVLayout1->addWidget(m_volumeSliderItemMap[VolumeSliderType::VOLUME_SLIDER_INPUTLEVEL]->getWidget()); inputVLayout1->addWidget(addLine()); inputVLayout1->addWidget(m_switchBtnItemMap[SwitchButtonType::SWITCH_BUTTON_ECHOCANCEL]->getWidget()); auto loopbackFrame = new QFrame(inputFrame1); auto loopbackVLayout = new QVBoxLayout(inputFrame1); loopbackVLayout->addWidget(addLine()); loopbackVLayout->addWidget(m_switchBtnItemMap[SwitchButtonType::SWITCH_BUTTON_LOOPBACK]->getWidget()); loopbackVLayout->setSpacing(0); loopbackVLayout->setContentsMargins(0, 0, 0, 0); loopbackFrame->setLayout(loopbackVLayout); inputVLayout1->addWidget(loopbackFrame); inputVLayout1->setSpacing(0); inputFrame1->setLayout(inputVLayout1); inputVLayout1->setContentsMargins(0, 0, 0, 0); inputFrame1->setFrameShape(QFrame::Shape::Box); auto soundThemeFrame = new QFrame(); auto soundThemeVLayout = new QVBoxLayout(); soundThemeVLayout->addWidget(addLine()); soundThemeVLayout->addWidget(m_selectComboxItemMap[SelectComboxType::SELECT_COMBOX_SOUNDEFFECT]->getWidget()); soundThemeVLayout->addWidget(addLine()); soundThemeVLayout->addWidget(m_selectComboxItemMap[SelectComboxType::SELECT_COMBOX_VOLUMEADJUST]->getWidget()); soundThemeVLayout->addWidget(addLine()); soundThemeVLayout->addWidget(m_selectComboxItemMap[SelectComboxType::SELECT_COMBOX_NOTIFICTION]->getWidget()); soundThemeFrame->setLayout(soundThemeVLayout); soundThemeVLayout->setSpacing(0); soundThemeVLayout->setContentsMargins(0, 0, 0, 0); soundThemeFrame->setFrameShape(QFrame::Shape::Box); auto soundEffectFrame = new QFrame(); auto soundEffectVLayout = new QVBoxLayout(); soundEffectVLayout->addWidget(m_switchBtnItemMap[SwitchButtonType::SWITCH_BUTTON_STARTUP]->getWidget()); soundEffectVLayout->addWidget(addLine()); soundEffectVLayout->addWidget(m_switchBtnItemMap[SwitchButtonType::SWITCH_BUTTON_POWEROFF]->getWidget()); soundEffectVLayout->addWidget(addLine()); soundEffectVLayout->addWidget(m_switchBtnItemMap[SwitchButtonType::SWITCH_BUTTON_LOGOUT]->getWidget()); soundEffectVLayout->addWidget(addLine()); soundEffectVLayout->addWidget(m_switchBtnItemMap[SwitchButtonType::SWITCH_BUTTON_WAKEUP]->getWidget()); soundEffectVLayout->addWidget(addLine()); soundEffectVLayout->addWidget(m_switchBtnItemMap[SwitchButtonType::SWITCH_BUTTON_ALERT]->getWidget()); soundEffectVLayout->addWidget(soundThemeFrame); soundEffectFrame->setLayout(soundEffectVLayout); soundEffectVLayout->setSpacing(0); soundEffectVLayout->setContentsMargins(0, 0, 0, 0); soundEffectFrame->setFrameShape(QFrame::Shape::Box); auto advanceSettingsFrame = new QFrame(); auto vLayout5 = new QVBoxLayout(); vLayout5->addWidget(m_switchBtnItemMap[SwitchButtonType::SWITCH_BUTTON_AUTO_PAUSE]->getWidget()); vLayout5->addWidget(addLine()); vLayout5->addWidget(m_detailSettingsItemMap[DetailSettingType::DETAIL_SETTING_DEVMANAGER]->getWidget()); vLayout5->addWidget(addLine()); vLayout5->addWidget(m_detailSettingsItemMap[DetailSettingType::DETAIL_SETTING_APPMANAGER]->getWidget()); advanceSettingsFrame->setLayout(vLayout5); vLayout5->setContentsMargins(0, 0, 0, 0); advanceSettingsFrame->setFrameShape(QFrame::Shape::Box); vMainLayout->addWidget(m_titleLabelItemMap[TitleLabelType::TITLE_LABEL_OUTPUT]->getWidget()); vMainLayout->addWidget(outputFrame1); vMainLayout->addSpacing(8); vMainLayout->addWidget(outputFrame2); vMainLayout->addSpacing(40); vMainLayout->addWidget(m_titleLabelItemMap[TitleLabelType::TITLE_LABEL_INPUT]->getWidget()); vMainLayout->addWidget(inputFrame1); vMainLayout->addSpacing(40); vMainLayout->addWidget(m_titleLabelItemMap[TitleLabelType::TITLE_LABEL_SOUNDEFFECT]->getWidget()); vMainLayout->addWidget(soundEffectFrame); vMainLayout->addSpacing(40); vMainLayout->addWidget(m_titleLabelItemMap[TitleLabelType::TITLE_LABEL_ADVANCESETTINGS]->getWidget()); vMainLayout->addWidget(advanceSettingsFrame); vMainLayout->setSpacing(0); vMainLayout->setContentsMargins(0, 0, 0, 0); setLayout(vMainLayout); // 将所有子frame添加到m_frameVector m_FrameVector.push_back(outputFrame2); m_FrameVector.push_back(loopbackFrame); m_FrameVector.push_back(soundThemeFrame); } void Ukui5MainWidget::initSlots() { m_pVolumeDebounceTimer.setSingleShot(true); m_pVolumeDebounceTimer.setInterval(300); for (const auto& it : m_volumeSliderItemMap) { if (VolumeSliderType::VOLUME_SLIDER_INPUTLEVEL == it.first) continue; auto slider = static_cast(it.second->getSlider()->getWidget()); if (slider) { connect(slider, &QSlider::valueChanged, this, [it, this](int value) { if (VolumeSliderType::VOLUME_SLIDER_BALANCE == it.first) { double v = value / 100.0; setBalance(EnumToInt(it.first), v); } else { m_pVolumeType = EnumToInt(it.first); m_pVolumeIdx = -1; m_pVolumeValue = value; m_pVolumeDebounceTimer.stop(); m_pVolumeDebounceTimer.start(); // setVolume(EnumToInt(it.first), -1, value); } }); } auto muteBtn = it.second->getMuteBtn(); if (muteBtn) { connect(muteBtn, &QPushButton::clicked, this, [it, this]() { setMute(EnumToInt(it.first), -1); }); } } connect(&m_pVolumeDebounceTimer, &QTimer::timeout, this, [this]() { setVolume(m_pVolumeType, m_pVolumeIdx, m_pVolumeValue); }); for (const auto& it : m_selectComboxItemMap) { switch (it.first) { case SelectComboxType::SELECT_COMBOX_INPUT: case SelectComboxType::SELECT_COMBOX_OUTPUT: { auto listWidget = static_cast(it.second->getSelectBox()->getWidget()); if (listWidget) { connect(listWidget, &QListWidget::currentRowChanged, this, [listWidget, it, this](int row) { auto item = listWidget->item(row); auto wid = (Ukcc5DevicePortItem*) listWidget->itemWidget(item); setDefaultDevice(EnumToInt(it.first), -1, wid->getPortName(), wid->getCardName()); qDebug() << QString("Set type %1 combox to card: %2 port: %3.").arg(EnumToInt(it.first)) .arg(wid->getPortName()).arg(wid->getCardName()); }); } break; } case SelectComboxType::SELECT_COMBOX_SOUNDEFFECT: { auto box = static_cast(it.second->getSelectBox()->getWidget()); if (box) { connect(box, static_cast(&QComboBox::currentIndexChanged), this, [box, it, this](int row) { // 如果是切换到自定义则只修改自定义主题的状态,切换到其他音效主题时才更新音效主题 if ("custom" == box->currentData()) { setSettingsValue("setCustomThemeStatus", true); } else { setSettingsValue("setCustomThemeStatus", false); } setSettingsValue(m_comboxMethodKeys[it.first], box->currentData()); ukcc::UkccCommon::buriedSettings("Audio", box->objectName(), "select", box->currentText()); }); } break; } case SelectComboxType::SELECT_COMBOX_VOLUMEADJUST: { auto box = static_cast(it.second->getSelectBox()->getWidget()); if (box) { connect(box, static_cast(&QComboBox::currentIndexChanged), this, [box, it, this](int row) { setSettingsValue(m_comboxMethodKeys[it.first], box->currentData()); ukcc::UkccCommon::buriedSettings("Audio", box->objectName(), "select", box->currentText()); }); } break; } case SelectComboxType::SELECT_COMBOX_NOTIFICTION: { auto box = static_cast(it.second->getSelectBox()->getWidget()); if (box) { connect(box, static_cast(&QComboBox::currentIndexChanged), this, [box, it, this](int row) { setSettingsValue(m_comboxMethodKeys[it.first], box->currentData()); ukcc::UkccCommon::buriedSettings("Audio", box->objectName(), "select", box->currentText()); }); } break; } default: break; } } for (const auto& it : m_switchBtnItemMap) { auto switchBtn = static_cast(it.second->getSwitchButton()->getWidget()); if (switchBtn) { connect(switchBtn, &QPushButton::clicked, this, [switchBtn, it, this]() { setSettingsValue(m_switchMethodKeys[it.first], QVariant(switchBtn->isChecked())); ukcc::UkccCommon::buriedSettings("Audio", switchBtn->objectName(), QString("settings"), switchBtn->isChecked() ? "true" : "false"); if ((it.first == SwitchButtonType::SWITCH_BUTTON_ALERT)) { qDebug() << QString("setAlertStatus %1 .").arg(switchBtn->isChecked()); setFrameVisible(EnumToInt(FrameType::SOUND_THEME_FRAME), switchBtn->isChecked()); } }); } } for (const auto& it : m_detailSettingsItemMap) { auto switchBtn = static_cast(it.second->getDetailSettings()->getWidget()); if (switchBtn) { connect(switchBtn, &QPushButton::clicked, this, [it, this]() { if (DetailSettingType::DETAIL_SETTING_DEVMANAGER == it.first) { if (!m_pDevManagerWidget) { m_pDevManagerWidget = new DeviceManagerMainWidget(this); setDetailSettings(EnumToInt(it.first)); connect(m_pDevManagerWidget, &DeviceManagerMainWidget::enableDeviceSignals, this, [it, this](const QString& card, const QString& port, bool enable) { qDebug() << QString("enableDeviceSignals, card: %1, port: %2, enable: %3").arg(card).arg(port).arg(enable); IAudioMainWindow::setEnabled(card, port, enable); }); connect(m_pDevManagerWidget, &DeviceManagerMainWidget::closeWindowSignals, this, [this]() { auto window = m_pDevManagerWidget->window(); if (window) { window->close(); m_pDevManagerWidget = nullptr; } qDebug() << QString("Close device Manager Widget."); }); } m_pDevManagerWidget->show(); } else { if (!m_pAppManagerWidget) { m_pAppManagerWidget = new AppManagerMainWidget(m_appWidgetList, this); setDetailSettings(EnumToInt(it.first)); connect(m_pAppManagerWidget, &AppManagerMainWidget::valueChangedSignals, this, [it, this](int idx, int value) { IAudioMainWindow::setVolume(2, idx, value); }); connect(m_pAppManagerWidget, &AppManagerMainWidget::muteChangedSignals, this, [it, this](int idx) { IAudioMainWindow::setMute(2, idx); }); connect(m_pAppManagerWidget, &AppManagerMainWidget::currentIndexChangedSignals, this, [this] (const NodeType& type, int idx, const QString& card, const QString& port) { if (NodeType::OUTPUT == type) IAudioMainWindow::setDefaultDevice(2, idx, port, card); else IAudioMainWindow::setDefaultDevice(3, idx, port, card); }); connect(m_pAppManagerWidget, &AppManagerMainWidget::closeWindowSignals, this, [this]() { auto window = m_pAppManagerWidget->window(); if (window) { window->close(); m_pAppManagerWidget->deleteLater(); m_appWidgetList.clear(); m_pAppManagerWidget = nullptr; } qDebug() << QString("Close application manager Widget."); }); } m_pAppManagerWidget->show(); } }); } } } void Ukui5MainWidget::setValue(int type, int value) { m_volumeSliderItemMap[IntToEnum(type)]->setValue(value); } void Ukui5MainWidget::setAppValue(const QString& name, int value) { if (m_pAppManagerWidget) m_pAppManagerWidget->setValue(name, value); } void Ukui5MainWidget::setAppMute(const QString& name, bool mute) { if (m_pAppManagerWidget) m_pAppManagerWidget->setMute(name, mute); } void Ukui5MainWidget::setCurrentIndex(int type, int idx) { m_selectComboxItemMap[IntToEnum(type)]->setCurrentIndex(idx); } void Ukui5MainWidget::setAppCurrentIndex(const QString& name, const NodeType& type, int cbxIndex) { if (m_pAppManagerWidget) m_pAppManagerWidget->setCurrentIndex(name, type, cbxIndex); } void Ukui5MainWidget::setAppCbxEnabled(const QString& name, const NodeType& type, bool enable) { if (m_pAppManagerWidget) m_pAppManagerWidget->setAppCbxEnabled(name, type, enable); } void Ukui5MainWidget::setCurrentAppItem(int idx) { if (m_pAppManagerWidget) m_pAppManagerWidget->setCurrentItem(idx); } void Ukui5MainWidget::setChecked(int type, bool status) { m_switchBtnItemMap[IntToEnum(type)]->setChecked(status); } std::list Ukui5MainWidget::getDevicePortList(int direction) const { std::list list; auto dataList = m_selectComboxItemMap[IntToEnum(direction)]->getDataList(); for (const auto& item : dataList) { if (item.canConvert()) { QVariantList l = item.toList(); if (l.size() >= 3) { DevicePortInfo info; info.index = l.at(0).toInt(); info.cardName = l.at(1).toString(); info.portName = l.at(2).toString(); list.push_back(info); } else { qDebug() << "Invalid data list size: " << l.size(); } } else { qDebug() << "Item is not a QVariantList: " << item; } } return list; } std::list Ukui5MainWidget::getDeviceManagetPortList(int direction) const { std::list list; if (!m_pDevManagerWidget) return list; auto dataList = m_pDevManagerWidget->getManagerWidgetByType(IntToEnum(direction))->getDataList(); for (const auto& item : dataList) { if (item.canConvert()) { QVariantList l = item.toList(); if (l.size() >= 3) { DevicePortInfo info; info.index = l.at(0).toInt(); info.cardName = l.at(1).toString(); info.portName = l.at(2).toString(); list.push_back(info); } else { qDebug() << "Invalid device manager data, list size: " << l.size(); } } else { qDebug() << "Item is not a QVariantList: " << item; } } return list; } std::list& Ukui5MainWidget::getAppItemList() { std::list list {}; if (m_pAppManagerWidget) list = m_pAppManagerWidget->getAppItems(); return list; } std::list Ukui5MainWidget::getAppManagetPortList(int idx, int direction) const { std::list list; if (!m_pAppManagerWidget) return list; auto dataList = m_pAppManagerWidget->getAppWidgetByIndex(idx)->getSelectComboxItems()[IntToEnum(direction)]->getDataList(); for (const auto& item : dataList) { if (item.canConvert()) { QVariantList l = item.toList(); if (l.size() >= 3) { DevicePortInfo info; info.index = l.at(0).toInt(); info.cardName = l.at(1).toString(); info.portName = l.at(2).toString(); list.push_back(info); } else { qDebug() << "Invalid app widget data, dataList: " << l; } } else { qDebug() << "Item is not a QVariantList: " << item; } } return list; } QWidget* Ukui5MainWidget::getWidget() { return this; } void Ukui5MainWidget::addDeviceManagerItem(int type, const QString& displayStr, const QVariant& data) { if (m_pDevManagerWidget) { auto list = data.toStringList(); m_pDevManagerWidget->addItem(IntToEnum(type), displayStr, list.at(0), list.at(1)); } } void Ukui5MainWidget::removeDeviceManagerItem(int type, int idx) { if (m_pDevManagerWidget) { m_pDevManagerWidget->removeItem(IntToEnum(type), idx); } } void Ukui5MainWidget::setDeviceManagerItemStatus(int type, int idx, bool checked) { if (m_pDevManagerWidget) { m_pDevManagerWidget->setChecked(IntToEnum(type), idx, checked); } } void Ukui5MainWidget::updateUIByButtonStatus(int type, bool status) { if (IntToEnum(type) == SwitchButtonType::SWITCH_BUTTON_ALERT) { qDebug() << QString("setChecked type: %1 , status: %2 .").arg(type).arg(status); setFrameVisible(EnumToInt(FrameType::SOUND_THEME_FRAME), status); } } /* 1. When default output is bluetooth device, need to hide balance mono frame 2. When defualt input is NULL or Internal Mic, need to hide loopback frame */ void Ukui5MainWidget::updateUIByDevice(int type, bool status) { if (IntToEnum(type) == NodeType::OUTPUT) { qDebug() << "Set Balance and Mono frame visible status:" << status; setFrameVisible(EnumToInt(FrameType::BALANCE_MONO_FRAME), status); } else if (IntToEnum(type) == NodeType::INPUT) { qDebug() << "Set Loopback frame visible status:" << status; setFrameVisible(EnumToInt(FrameType::LOOP_BACK_FRAME), status); } } void Ukui5MainWidget::addAppManagerItem(int dire, int idx, int value, bool mute, const QString& icon, const QString& desc, const QList& outputList, const QList& inputList) { if (m_pAppManagerWidget) m_pAppManagerWidget->addItem(dire, idx, value, mute, icon, desc, outputList, inputList); } void Ukui5MainWidget::removeAppManagerItem(const QString& name) { if (m_pAppManagerWidget) m_pAppManagerWidget->removeItem(name); } void Ukui5MainWidget::addPortToAppItem(const QString& name, const NodeType& type, const QString& text, const QVariant& data) { if (m_pAppManagerWidget) m_pAppManagerWidget->addPortToAppItem(name, type, text, data); } void Ukui5MainWidget::removePortFromAppItem(const QString& name, const NodeType& type, int cbxIdx) { if (m_pAppManagerWidget) m_pAppManagerWidget->removeProtFromAppItem(name, type, cbxIdx); } void Ukui5MainWidget::createdItems() { for (const auto& [k, v] : m_titleLabelKeys) { m_titleLabelItemMap.emplace(k, new TitleLabelItem(new QLabel(v), this)); } // 创建SwitchButton子项 for (const auto& [k, v] : m_switchButtonkeys) { ISwitchButtonItem* item = nullptr; std::shared_ptr btn = nullptr; btn = std::make_shared(new kdk::KSwitchButton, v.at(v.size() - 1)); switch (k) { case SwitchButtonType::SWITCH_BUTTON_VOLUMEBOOST: case SwitchButtonType::SWITCH_BUTTON_MONO: case SwitchButtonType::SWITCH_BUTTON_LOOPBACK: { item = new Ukui4SwitchButtonWithDescItem(btn, new QLabel(v.at(0), this), new QLabel(v.at(1)), k, this); break; } case SwitchButtonType::SWITCH_BUTTON_ECHOCANCEL: case SwitchButtonType::SWITCH_BUTTON_STARTUP: case SwitchButtonType::SWITCH_BUTTON_POWEROFF: case SwitchButtonType::SWITCH_BUTTON_LOGOUT: case SwitchButtonType::SWITCH_BUTTON_WAKEUP: case SwitchButtonType::SWITCH_BUTTON_ALERT: case SwitchButtonType::SWITCH_BUTTON_AUTO_PAUSE: { item = new Ukui4SwitchButtonItem(btn, new QLabel(v.at(0), this), k, this); break; } default: break; } if (item && btn) { m_switchBtnItemMap.emplace(k, item); } } // 创建VolumeSlider子项 for (const auto& [k, v] : m_volumeSliderKeys) { IVolumeSliderItem* item = nullptr; std::shared_ptr slider = nullptr; switch (k) { case VolumeSliderType::VOLUME_SLIDER_OUTPUT: case VolumeSliderType::VOLUME_SLIDER_INPUT: { slider = std::make_shared(new UkmediaVolumeSlider(this), v.at(v.size() - 1)); slider->setRange(0, 100); item = new Ukui4VolumeSliderItem(slider, new QLabel(v.at(0), this), new QLabel(this), new QPushButton(this), k, this); break; } case VolumeSliderType::VOLUME_SLIDER_BALANCE: { slider = std::make_shared(new UkBalanceVolumeSlider(this), v.at(v.size() - 1)); item = new Ukui4BalanceVolumeSliderItem(slider, new QLabel(v.at(0), this), new QLabel(v.at(1)), new QLabel(v.at(2)), this); break; } case VolumeSliderType::VOLUME_SLIDER_INPUTLEVEL: { slider = std::make_shared(new QProgressBar(this)); slider->setRange(0, 101); item = new Ukui4InputLevelVolumeSliderItem(slider, new QLabel(v.at(0), this), this); m_pMonitorStream = std::make_shared(slider); break; } default: break; } if (item && slider) { m_volumeSliderItemMap.emplace(k, item); } } // 创建SelectCombox子项 for (const auto& [k, v] : m_selectComboxKeys) { ISelectComboxItem* item = nullptr; std::shared_ptr box = nullptr; switch (k) { case SelectComboxType::SELECT_COMBOX_INPUT: case SelectComboxType::SELECT_COMBOX_OUTPUT: { qDebug() << "m_selectComboxKeys.size: " << m_selectComboxKeys.size() << " k:" << EnumToInt(k) << " v:" << v; box = std::make_shared(new QListWidget(this), v.at(v.size() - 1)); item = new Ukui5SelectComboxItem(box, new QLabel(v.at(0), this), k, this); box->setParent(item->getWidget()); break; } case SelectComboxType::SELECT_COMBOX_SOUNDEFFECT: case SelectComboxType::SELECT_COMBOX_VOLUMEADJUST: case SelectComboxType::SELECT_COMBOX_NOTIFICTION: { box = std::make_shared(new QComboBox(this), v.at(v.size() - 1)); item = new Ukui4SelectComboxItem(box, new QLabel(v.at(0), this), k, this); break; } default: break; } if (item && box) { qDebug() << "emplace, k:" << EnumToInt(k) << "m_selectComboxItemMap。size: " << m_selectComboxItemMap.size(); m_selectComboxItemMap.emplace(k, item); } } for (const auto& [k, v] : m_detailSettingsKeys) { IDetailSettingsItem* item = nullptr; std::shared_ptr setting = nullptr; switch (k) { case DetailSettingType::DETAIL_SETTING_DEVMANAGER: case DetailSettingType::DETAIL_SETTING_APPMANAGER: { setting = std::make_shared(new QPushButton(QObject::tr("Details")), v.at(v.size() - 1)); item = new Ukui5DetailSettingsItem(setting, new QLabel(v.at(0)), this); break; } default: break; } if (item && setting) { m_detailSettingsItemMap.emplace(k, item); } } } QFrame* Ukui5MainWidget::addLine() { auto line = new QFrame(this); line->setMinimumSize(QSize(550, 1)); line->setMaximumSize(QSize(16777215, 1)); line->setFrameShape(QFrame::HLine); line->setFrameShadow(QFrame::Sunken); return line; } void Ukui5MainWidget::setFrameVisible(int index, bool visible) { if (index >= 0 && index < m_FrameVector.size()) { m_FrameVector[index]->setVisible(visible); } } ukui-volume-control/audio/ClientManager.cpp0000664000175000017500000001035615171074712017770 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "ClientManager.h" #include #include "Util.h" using namespace UkuiAudioFramwork; ClientManager::ClientManager(std::unordered_map& switchBtnMap, std::unordered_map& volumeSliderMap, std::unordered_map& selectComboxMap) : m_switchBtnItemMap(switchBtnMap), m_volumeSliderItemMap(volumeSliderMap), m_selectComboxItemMap(selectComboxMap) { } void ClientManager::setValue(int type, int value) { // check type is vaild m_volumeSliderItemMap[IntToEnum(type)]->setValue(value); } void ClientManager::setMute(int type, bool mute) { m_volumeSliderItemMap[IntToEnum(type)]->setMute(mute); } void ClientManager::setEnabled(int type, bool enable) { qDebug() << __func__ << "type:" << type << "enable:" << enable; if (type == 0) { m_volumeSliderItemMap[VolumeSliderType::VOLUME_SLIDER_INPUT]->setEnabled(enable); m_volumeSliderItemMap[VolumeSliderType::VOLUME_SLIDER_INPUTLEVEL]->setValue(0); m_volumeSliderItemMap[VolumeSliderType::VOLUME_SLIDER_INPUTLEVEL]->setEnabled(enable); } else if (type == 1) { m_volumeSliderItemMap[VolumeSliderType::VOLUME_SLIDER_OUTPUT]->setEnabled(enable); //start if (m_switchBtnItemMap[SwitchButtonType::SWITCH_BUTTON_MONO]->getSwitchButton()->isChecked()) { qDebug() << __func__ << "mono true , set balance false."; m_volumeSliderItemMap[VolumeSliderType::VOLUME_SLIDER_BALANCE]->setEnabled(false); } else { qDebug() << __func__ << "mono false , set balance."; m_volumeSliderItemMap[VolumeSliderType::VOLUME_SLIDER_BALANCE]->setEnabled(enable); } //end } } void ClientManager::setBalanceEnabled(bool enable) { qDebug() << __func__ << "enable:" << enable; if (enable) { m_volumeSliderItemMap[VolumeSliderType::VOLUME_SLIDER_BALANCE]->setEnabled(true); } else { m_volumeSliderItemMap[VolumeSliderType::VOLUME_SLIDER_BALANCE]->setValue(0); m_volumeSliderItemMap[VolumeSliderType::VOLUME_SLIDER_BALANCE]->setEnabled(enable); } } void ClientManager::setRange(int type, int min, int max) { m_volumeSliderItemMap[IntToEnum(type)]->setRange(min, max); } void ClientManager::setChecked(int type, bool checked) { m_switchBtnItemMap[IntToEnum(type)]->setChecked(checked); } void ClientManager::setCurrentIndex(int type, int idx) { m_selectComboxItemMap[IntToEnum(type)]->setCurrentIndex(idx); } int ClientManager::findData(int type, const QVariant& value, int role) const { return m_selectComboxItemMap[IntToEnum(type)]->findData(value, role); } int ClientManager::findText(int type, const QString& text) const { m_selectComboxItemMap[IntToEnum(type)]->findText(text); } void ClientManager::addItem(int type, const QString& text, const QVariant& userData) { qDebug() << QString("add %1 data: %2 to %3 combox").arg(text).arg(userData.toString()).arg(type); m_selectComboxItemMap[IntToEnum(type)]->insertItem(text, userData); } void ClientManager::removeItem(int type, int index) { m_selectComboxItemMap[IntToEnum(type)]->removeItem(index); } void ClientManager::clear(int type) { m_selectComboxItemMap[IntToEnum(type)]->clear(); } ukui-volume-control/audio/DeviceManagerWidget.cpp0000664000175000017500000001640115171074712021112 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "DeviceManagerWidget.h" #include #include #include #include #include "Ukui4CustomControl.h" #include "ukui4.0/Ukui4SwitchButtonItem.h" DeviceManagerItem::DeviceManagerItem(QLabel* label, std::shared_ptr switchBtn, const QString& cardName, const QString& portName, QWidget* parent) : m_pLabel(label), m_pSwitchBtn(switchBtn), m_cardName(cardName), m_portName(portName), QFrame(parent) { initUi(); } void DeviceManagerItem::initUi() { setFixedSize(512, 60); setFrameShape(QFrame::Shape::Box); m_pLabel->setFixedHeight(28); QHBoxLayout* hLayout = new QHBoxLayout(); hLayout->setContentsMargins(0, 0, 0, 0); hLayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); hLayout->addWidget(m_pLabel); hLayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Expanding)); hLayout->addWidget(m_pSwitchBtn->getWidget()); hLayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); hLayout->setSpacing(0); setLayout(hLayout); QString theme = "ukui-default"; if (QGSettings::isSchemaInstalled("org.ukui.style")) { QGSettings* gst = new QGSettings("org.ukui.style"); if (gst->keys().contains("styleName")) { theme = gst->get("style-name").toString(); } } auto pal = palette(); if (theme == "ukui-default" || theme == "ukui-light") { pal.setBrush(QPalette::Base, QColor("#eeeeee")); } else if (theme == "ukui-dark") { pal.setBrush(QPalette::Base, QColor("#333333")); } setPalette(pal); } bool DeviceManagerItem::isChecked() const { return m_pSwitchBtn->isChecked(); } void DeviceManagerItem::setChecked(bool checked) { m_pSwitchBtn->setChecked(checked); } QWidget* DeviceManagerItem::getWidget() { return this; } std::shared_ptr DeviceManagerItem::getSwitchBtn() const { return m_pSwitchBtn; } QVariant DeviceManagerItem::getData() const { return QVariant(std::initializer_list {m_cardName, m_portName}); } DeviceManagerWidget::DeviceManagerWidget(QLabel* label, QWidget* parent) : m_pLabel(label), QWidget(parent) { initUi(); initSlots(); } void DeviceManagerWidget::initUi() { auto labelLayout = new QHBoxLayout(); m_pLabel->setFixedSize(480, 28); labelLayout->setSpacing(0); labelLayout->setContentsMargins(0, 0, 0, 0); labelLayout->addSpacing(41); labelLayout->addWidget(m_pLabel); labelLayout->addSpacing(39); m_pScrollArea = new QScrollArea(this); m_pScrollArea->setContentsMargins(0, 0, 0, 0); m_pScrollArea->setFixedSize(512, 300); m_pScrollArea->setFrameShape(QFrame::NoFrame); m_pScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_pDisDevManagerWidget = new QWidget(m_pScrollArea); m_pDisDevManagerWidget->setContentsMargins(0, 0, 0, 0); m_pDisDevManagerWidget->setFixedWidth(512); m_pScrollArea->setWidget(m_pDisDevManagerWidget); m_pVlayout = new QVBoxLayout(m_pDisDevManagerWidget); m_pVlayout->setSpacing(0); m_pVlayout->setContentsMargins(0, 0, 0, 0); m_pDisDevManagerWidget->setLayout(m_pVlayout); QHBoxLayout *hlayout = new QHBoxLayout(); hlayout->setSpacing(0); hlayout->setContentsMargins(0, 0, 0, 0); hlayout->addSpacing(25); hlayout->addWidget(m_pScrollArea); hlayout->addSpacing(23); m_pConfirmBtn = new QPushButton(tr("Confirm"), this); m_pConfirmBtn->setFixedSize(96, 36); m_pConfirmBtn->setProperty("isImportant", true); QHBoxLayout* hLayout = new QHBoxLayout(); hLayout->setSpacing(0); hLayout->setContentsMargins(0, 0, 0, 0); hLayout->addStretch(); hLayout->addWidget(m_pConfirmBtn); hLayout->addSpacing(25); // 进行整体布局 auto* vLayout = new QVBoxLayout(this); vLayout->setSpacing(0); vLayout->setContentsMargins(0, 0, 0, 0); vLayout->addLayout(labelLayout); vLayout->addSpacing(10); vLayout->addLayout(hlayout); vLayout->addStretch(); vLayout->addLayout(hLayout); vLayout->addSpacing(40); setLayout(vLayout); } void DeviceManagerWidget::initSlots() { connect(m_pConfirmBtn, &QPushButton::clicked, this, &DeviceManagerWidget::closeWindow); } void DeviceManagerWidget::closeWindow() { Q_EMIT closeWindowSignals(); } void DeviceManagerWidget::addItem(const QString& displayStr, const QString& cardName, const QString& portName) { std::shared_ptr switchBtn = std::make_shared(new kdk::KSwitchButton); std::shared_ptr item = std::make_shared(new AutoOmitLabel(displayStr), switchBtn, cardName, portName, this); auto idx = m_devManagerItemVec.size(); m_devManagerItemVec.emplace_back(item); qDebug() << "DeviceManagerMainWidget::addItem " << displayStr << " cardName:" << cardName << " portName: " << portName; insertToWidget(idx); // connect auto btn = static_cast(switchBtn->getWidget()); connect(btn, &QPushButton::clicked, this, [this, cardName, portName, btn]() { qDebug() << QString("btn enableDeviceSignals, card: %1, port: %2, enable: %3").arg(cardName).arg(portName).arg(btn->isChecked()); Q_EMIT enableDeviceSignals(cardName, portName, btn->isChecked()); }); } void DeviceManagerWidget::insertToWidget(int idx) { m_pVlayout->addWidget(m_devManagerItemVec[idx]->getWidget()); m_pDisDevManagerWidget->resize(512, m_devManagerItemVec.size() * 60); } void DeviceManagerWidget::setChecked(int idx, bool checked) { if (idx >= m_devManagerItemVec.size()) { qWarning() << QString("Invaild index %1, set device manager item checked failed.").arg(idx); return; } m_devManagerItemVec[idx]->setChecked(checked); } void DeviceManagerWidget::removeItem(int idx) { if (idx > m_devManagerItemVec.size()) { qDebug() << QString("invaild index %1 , can not remove item in device manager widget.").arg(idx); return; } m_devManagerItemVec.erase(m_devManagerItemVec.begin() + idx); m_pDisDevManagerWidget->resize(512, m_devManagerItemVec.size() * 60); } std::list DeviceManagerWidget::getDataList() const { std::list list; for (int i = 0; i < m_devManagerItemVec.size(); ++i) { auto data = m_devManagerItemVec[i]->getData(); QList variantList; variantList.push_back(i); variantList.push_back(data.toStringList().at(0)); variantList.push_back(data.toStringList().at(1)); list.push_back(QVariant(variantList)); } return list; } ukui-volume-control/audio/IDetailSettings.h0000664000175000017500000000217315171074712017756 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 IDETAILSETTINGS_H #define IDETAILSETTINGS_H /** * @brief 包含DetailSettings子项的类型 */ enum class DetailSettingType { DETAIL_SETTING_DEVMANAGER = 0, // 设备管理子项 DETAIL_SETTING_APPMANAGER, // 应用声音子项 }; class IDetailSettings { public: IDetailSettings(); virtual ~IDetailSettings() = default; }; #endif // IDETAILSETTINGS_H ukui-volume-control/audio/IDetailSettings.cpp0000664000175000017500000000150015171074712020302 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "IDetailSettings.h" IDetailSettings::IDetailSettings() { } ukui-volume-control/audio/ClientMethod.h0000664000175000017500000000347615171074712017310 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 CLIENTMETHOD_H #define CLIENTMETHOD_H #include #include "DBusClient.h" #include "ClientManager.h" class ClientMethod { public: static ClientMethod& getInstance(); void setManager(std::shared_ptr); void setValue(int, int); void setMute(int, bool); void setEnabled(int, bool); void setBalanceEnabled(bool); void setRange(int, int, int); void setChecked(int, bool); void setCurrentIndex(int, int); int findData(int, const QVariant&, int = Qt::UserRole) const; int findText(int, const QString&) const; void addItem(int, const QString&, const QVariant& = QVariant()); void removeItem(int, int); void clear(int); private: ClientMethod() = default; ClientMethod(const ClientMethod&) = delete; ClientMethod(ClientMethod&&) = delete; ClientMethod operator=(const ClientMethod&) = delete; ClientMethod operator=(ClientMethod&&) = delete; virtual ~ClientMethod() = default; private: std::shared_ptr m_pClientManager{nullptr}; }; #endif // CLIENTMETHOD_H ukui-volume-control/audio/IDeviceManagerItem.h0000664000175000017500000000236115171074712020343 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 IDEVICEMANAGERITEM_H #define IDEVICEMANAGERITEM_H #include #include "ISwitchButtonItem.h" class IDeviceManagerItem { public: IDeviceManagerItem() = default; virtual ~IDeviceManagerItem() = default; public: virtual bool isChecked() const = 0; virtual void setChecked(bool) = 0; virtual QWidget* getWidget() = 0; virtual std::shared_ptr getSwitchBtn() const = 0; virtual QVariant getData() const = 0; }; #endif // IDEVICEMANAGERITEM_H ukui-volume-control/audio/customstyle.cpp0000664000175000017500000003516315171074712017655 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "customstyle.h" #include #include #include #include #include CustomStyle::CustomStyle(const QString& proxyStyleName, QObject* parent) : QProxyStyle (proxyStyleName) { Q_UNUSED(parent); } void CustomStyle::drawComplexControl(QStyle::ComplexControl control, const QStyleOptionComplex* option, QPainter* painter, const QWidget* widget) const { if (control == CC_ToolButton) { /// 我们需要获取ToolButton的详细信息,通过qstyleoption_cast可以得到 /// 对应的option,通过拷贝构造函数得到一份备份用于绘制子控件 /// 我们一般不用在意option是怎么得到的,大部分的Qt控件都能够提供了option的init方法 } return QProxyStyle::drawComplexControl(control, option, painter, widget); } void CustomStyle::drawControl(QStyle::ControlElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const { switch (element) { case CE_ProgressBar: { if (const QStyleOptionProgressBar* pb = qstyleoption_cast(option)) { QStyleOptionProgressBar subopt = *pb; subopt.rect = subElementRect(SE_ProgressBarGroove, pb, widget); proxy()->drawControl(CE_ProgressBarGroove, &subopt, painter, widget); subopt.rect = subElementRect(SE_ProgressBarContents, pb, widget); proxy()->drawControl(CE_ProgressBarContents, &subopt, painter, widget); //控件的当前进度的文字,看情况是否需要绘制 // if (pb->textVisible) { // subopt.rect = subElementRect(SE_ProgressBarLabel, pb, widget); // proxy()->drawControl(CE_ProgressBarLabel, &subopt, painter, widget); // } return; } break; } case CE_ProgressBarGroove: { //控件的背景,看情况是否绘制 return; if (const QStyleOptionProgressBar* pbg = qstyleoption_cast(option)) { const bool enable = pbg->state & State_Enabled; painter->save(); painter->setRenderHint(QPainter::Antialiasing, true); painter->setPen(Qt::NoPen); painter->setBrush(pbg->palette.brush(enable ? QPalette::Active : QPalette::Disabled, QPalette::Window)); painter->drawRect(pbg->rect); painter->restore(); return; } break; } case CE_ProgressBarContents: { if (const QStyleOptionProgressBar* bar = qstyleoption_cast(option)) { if (bar->progress == bar->maximum) return; const bool enable = bar->state & QStyle::State_Enabled; // Qt6 removed orientation member, use rect dimensions instead #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) const bool vertical = bar->rect.height() > bar->rect.width(); #else const bool vertical = bar->orientation == Qt::Vertical; #endif const bool inverted = bar->invertedAppearance; qint64 minimum = qint64(bar->minimum); qint64 maximum = qint64(bar->maximum); qint64 progress = qint64(bar->progress); qint64 totalSteps = qMax(Q_INT64_C(1), maximum - minimum); qint64 progressSteps = progress - bar->minimum; qint64 progressBarWidth = progressSteps * (vertical ? bar->rect.height() : bar->rect.width()) / totalSteps; int ProgressBarItem_Width = 4; int ProgressBarItem_Distance = 16; int distance = ProgressBarItem_Distance + ProgressBarItem_Width; int num = progressBarWidth / distance; int totalnum = (vertical ? bar->rect.height() : bar->rect.width()) / distance; bool reverse = (!vertical && (bar->direction == Qt::RightToLeft)) || vertical; if (inverted) reverse = !reverse; int ProgressBarItem_Hight = 16; QRect drawRect(bar->rect); if (vertical) { drawRect.setWidth(ProgressBarItem_Hight); } else { drawRect.setHeight(ProgressBarItem_Hight); } drawRect.moveCenter(bar->rect.center()); QRect itemRect(drawRect); painter->save(); painter->setPen(Qt::NoPen); painter->setRenderHints(QPainter::Antialiasing, true); for (int var = 0; var < totalnum; ++var) { if (var < num) { if (enable) painter->setBrush(bar->palette.brush(QPalette::Active, QPalette::Highlight)); else #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) painter->setBrush(bar->palette.color(QPalette::Active, QPalette::Highlight).lighter(150)); #else painter->setBrush(bar->palette.color(QPalette::Active, QPalette::Highlight).light(150)); #endif } else { painter->setBrush(bar->palette.brush(enable ? QPalette::Active : QPalette::Disabled, QPalette::Button)); } if (vertical) itemRect.setRect(drawRect.left(), !reverse ? drawRect.top() + var * distance : drawRect.bottom() - ProgressBarItem_Width - var * distance, drawRect.width(), ProgressBarItem_Width); else itemRect.setRect(reverse ? drawRect.right() - ProgressBarItem_Width - var * distance : drawRect.left() + var * distance, drawRect.top(), ProgressBarItem_Width, drawRect.height()); painter->drawRoundedRect(itemRect, ProgressBarItem_Width/2, ProgressBarItem_Width/2); } painter->restore();; return; } break; } default: break; } return QProxyStyle::drawControl(element, option, painter, widget); } void CustomStyle::drawItemPixmap(QPainter* painter, const QRect& rectangle, int alignment, const QPixmap& pixmap) const { return QProxyStyle::drawItemPixmap(painter, rectangle, alignment, pixmap); } void CustomStyle::drawItemText(QPainter* painter, const QRect& rectangle, int alignment, const QPalette& palette, bool enabled, const QString& text, QPalette::ColorRole textRole) const { return QProxyStyle::drawItemText(painter, rectangle, alignment, palette, enabled, text, textRole); } //绘制简单的颜色圆角等 void CustomStyle::drawPrimitive(QStyle::PrimitiveElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const { switch (element) { //绘制 ToolButton case PE_PanelButtonTool: { painter->save(); painter->setRenderHint(QPainter::Antialiasing, true); painter->setPen(Qt::NoPen); painter->setBrush(QColor(0xff, 0xff, 0xff, 0x00)); painter->drawRoundedRect(option->rect, 4, 4); if (option->state & State_MouseOver) { if (option->state & State_Sunken) { painter->setRenderHint(QPainter::Antialiasing,true); painter->setPen(Qt::NoPen); painter->setBrush(QColor(0xff, 0xff, 0xff, 0x14)); painter->drawRoundedRect(option->rect, 4, 4); } else { painter->setRenderHint(QPainter::Antialiasing, true); painter->setPen(Qt::NoPen); painter->setBrush(QColor(0xff, 0xff, 0xff, 0x1f)); painter->drawRoundedRect(option->rect, 4, 4); } } painter->restore(); return; } case PE_PanelTipLabel: { painter->save(); painter->setRenderHint(QPainter::Antialiasing, true); painter->setPen(Qt::NoPen); painter->setBrush(QColor(0xff, 0xff, 0x00, 0xff)); painter->drawRoundedRect(option->rect, 4, 4); painter->restore(); return; }break; case PE_PanelButtonCommand: { painter->save(); painter->setRenderHint(QPainter::TextAntialiasing, true); painter->setPen(Qt::NoPen); painter->setBrush(QColor(0xff, 0xff, 0xff, 0x00)); if (option->state & State_MouseOver) { if (option->state & State_Sunken) { painter->setRenderHint(QPainter::Antialiasing, true); painter->setPen(Qt::NoPen); painter->setBrush(QColor(0x3d, 0x6b, 0xe5, 0xff)); painter->drawRoundedRect(option->rect, 4, 4); } else { painter->setRenderHint(QPainter::Antialiasing, true); painter->setPen(Qt::NoPen); painter->setBrush(QColor(0xff, 0xff, 0xff, 0x1f)); painter->drawRoundedRect(option->rect.adjusted(2, 2, -2, -2), 4, 4); } } painter->restore(); return; }break; } return QProxyStyle::drawPrimitive(element, option, painter, widget); } QPixmap CustomStyle::generatedIconPixmap(QIcon::Mode iconMode, const QPixmap& pixmap, const QStyleOption* option) const { return QProxyStyle::generatedIconPixmap(iconMode, pixmap, option); } QStyle::SubControl CustomStyle::hitTestComplexControl(QStyle::ComplexControl control, const QStyleOptionComplex* option, const QPoint& position, const QWidget* widget) const { return QProxyStyle::hitTestComplexControl(control, option, position, widget); } QRect CustomStyle::itemPixmapRect(const QRect& rectangle, int alignment, const QPixmap& pixmap) const { return QProxyStyle::itemPixmapRect(rectangle, alignment, pixmap); } QRect CustomStyle::itemTextRect(const QFontMetrics& metrics, const QRect& rectangle, int alignment, bool enabled, const QString& text) const { return QProxyStyle::itemTextRect(metrics, rectangle, alignment, enabled, text); } // int CustomStyle::pixelMetric(QStyle::PixelMetric metric, const QStyleOption* option, const QWidget* widget) const { switch (metric){ case PM_ProgressBarChunkWidth: { int ProgressBarItem_Width = 4; int ProgressBarItem_Distance = 16; return ProgressBarItem_Width + ProgressBarItem_Distance; } case PM_ToolBarIconSize: { return (int)48*qApp->devicePixelRatio(); } default: break; } return QProxyStyle::pixelMetric(metric, option, widget); } // void CustomStyle::polish(QWidget* widget) { if (widget) { if (widget->inherits("QTipLabel")) { widget->setAttribute(Qt::WA_TranslucentBackground); QPainterPath path; auto rect = widget->rect(); rect.adjust(0, 0, 0, 0); path.addRoundedRect(rect, 6, 6); widget->setProperty("blurRegion", QRegion(path.toFillPolygon().toPolygon())); } } if (widget) { if (widget->inherits("QLable")) { const_cast (widget)->setAttribute(Qt::WA_TranslucentBackground); widget->setAttribute(Qt::WA_TranslucentBackground); QPainterPath path; auto rect = widget->rect(); rect.adjust(0, 0, 0, 0); path.addRoundedRect(rect, 6, 6); widget->setProperty("blurRegion", QRegion(path.toFillPolygon().toPolygon())); } } if (widget) { widget->setAttribute(Qt::WA_Hover); widget->inherits("QSlider"); widget->installEventFilter(this); } return QProxyStyle::polish(widget); } void CustomStyle::polish(QApplication* application) { return QProxyStyle::polish(application); } // void CustomStyle::polish(QPalette& palette) { QColor lightBlue(200, 0, 0); palette.setBrush(QPalette::Highlight, lightBlue); } void CustomStyle::unpolish(QWidget* widget) { return QProxyStyle::unpolish(widget); } void CustomStyle::unpolish(QApplication* application) { return QProxyStyle::unpolish(application); } QSize CustomStyle::sizeFromContents(QStyle::ContentsType type, const QStyleOption* option, const QSize& contentsSize, const QWidget* widget) const { QSize newSize = contentsSize; switch (type) { case CT_ProgressBar: { int ProgressBarItem_Num = 20; int cw = proxy()->pixelMetric(QStyle::PM_ProgressBarChunkWidth, option, widget); newSize.setWidth(cw * ProgressBarItem_Num); return newSize; } default: break; } return QProxyStyle::sizeFromContents(type, option, contentsSize, widget); } QIcon CustomStyle::standardIcon(QStyle::StandardPixmap standardIcon, const QStyleOption* option, const QWidget* widget) const { return QProxyStyle::standardIcon(standardIcon, option, widget); } QPalette CustomStyle::standardPalette() const { return QProxyStyle::standardPalette(); } //如果需要背景透明也许需要用到这个函数 int CustomStyle::styleHint(QStyle::StyleHint hint, const QStyleOption* option, const QWidget* widget, QStyleHintReturn* returnData) const { switch (hint) { /// 让ScrollView viewport的绘制区域包含scrollbar和corner widget /// 这个例子中没有什么作用,如果我们需要绘制一个背景透明的滚动条 /// 这个style hint对我们的意义应该很大,因为我们希望视图能够帮助 /// 我们填充滚动条的背景区域,否则当背景透明时底下会出现明显的分割 case SH_ScrollView_FrameOnlyAroundContents: { return false; } default: break; } return QProxyStyle::styleHint(hint, option, widget, returnData); } QRect CustomStyle::subControlRect(QStyle::ComplexControl control, const QStyleOptionComplex* option, QStyle::SubControl subControl, const QWidget* widget) const { return QProxyStyle::subControlRect(control, option, subControl, widget); } QRect CustomStyle::subElementRect(QStyle::SubElement element, const QStyleOption* option, const QWidget* widget) const { switch (element) { case SE_ProgressBarGroove: case SE_ProgressBarContents: return option->rect; default: break; } return QProxyStyle::subElementRect(element, option, widget); } ukui-volume-control/audio/AppManagerSliderItem.h0000664000175000017500000000342715171074712020722 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 APPMANAGERSLIDERITEM_H #define APPMANAGERSLIDERITEM_H #include #include "IVolumeSliderItem.h" class AppManagerSliderItem : public QFrame, public IVolumeSliderItem { Q_OBJECT public: AppManagerSliderItem(std::shared_ptr, QLabel*, QLabel*, QPushButton*, VolumeSliderType, QWidget* = nullptr); ~AppManagerSliderItem() = default; public: virtual void initUi() override; virtual void initSlots() override; virtual QWidget* getWidget() override; virtual void setValue(int) override; virtual void setEnabled(bool) override; virtual QPushButton* getMuteBtn() const override; virtual void setMute(bool) override; private Q_SLOTS: void valueChangedSlots(int); private: void setPercent(int); void setIcon(const QIcon&); void updateIcon(int); private: VolumeSliderType m_type = VolumeSliderType::VOLUME_SLIDER_INPUT; QLabel* m_pPercentLabel = nullptr; QPushButton* m_pMuteBtn = nullptr; bool m_muted = false; }; #endif // APPMANAGERSLIDERITEM_H ukui-volume-control/audio/AppManagerSliderItem.cpp0000664000175000017500000001104615171074712021251 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "AppManagerSliderItem.h" #include #include #include #include #include AppManagerSliderItem::AppManagerSliderItem(std::shared_ptr slider, QLabel* disLabel, QLabel* percLabel, QPushButton* muteBtn, VolumeSliderType type, QWidget* parent) : IVolumeSliderItem(slider, disLabel), m_pPercentLabel(percLabel), m_pMuteBtn(muteBtn), m_type(type), QFrame(parent) { initUi(); initSlots(); } void AppManagerSliderItem::initUi() { setFixedSize(512, 60); setFrameShape(QFrame::Shape::Box); m_pVolumeSlider->setRange(0, 100); m_pMuteBtn->setFocusPolicy(Qt::NoFocus); m_pMuteBtn->setFixedSize(16, 16); m_pMuteBtn->setFlat(true); m_pMuteBtn->setProperty("setClickPen", QPen(Qt::NoPen)); m_pMuteBtn->setProperty("setHoverPen", QPen(Qt::NoPen)); m_pMuteBtn->setProperty("setClickBrush", QBrush(Qt::transparent)); m_pMuteBtn->setProperty("setHoverBrush", QBrush(Qt::transparent)); m_pDisplayLabel->setFixedSize(120, 36); m_pDisplayLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); // m_pVolumeSlider->getWidget()->setFixedSize(264, 55); m_pPercentLabel->setFixedSize(48, 28); m_pPercentLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); auto hLayout = new QHBoxLayout; hLayout->setSpacing(0); hLayout->setContentsMargins(0, 0, 0, 0); hLayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); hLayout->addWidget(m_pDisplayLabel); hLayout->addItem(new QSpacerItem(10, 20, QSizePolicy::Fixed)); hLayout->addWidget(m_pMuteBtn); // hLayout->addItem(new QSpacerItem(10, 20, QSizePolicy::Fixed)); hLayout->addWidget(m_pVolumeSlider->getWidget()); hLayout->addWidget(m_pPercentLabel); hLayout->addItem(new QSpacerItem(20, 20, QSizePolicy::Fixed)); setLayout(hLayout); QString theme = "ukui-default"; if (QGSettings::isSchemaInstalled("org.ukui.style")) { QGSettings* gst = new QGSettings("org.ukui.style"); if (gst->keys().contains("styleName")) { theme = gst->get("style-name").toString(); } } auto pal = palette(); if (theme == "ukui-default" || theme == "ukui-light") { pal.setBrush(QPalette::Base, QColor("#eeeeee")); } else if (theme == "ukui-dark") { pal.setBrush(QPalette::Base, QColor("#333333")); } setPalette(pal); } void AppManagerSliderItem::initSlots() { // connect(m_pVolumeSlider->getWidget(), SIGNAL(valueChanged(int)), this, SLOT(valueChangedSlots(int))); } QWidget* AppManagerSliderItem::getWidget() { return this; } void AppManagerSliderItem::setValue(int value) { if (m_pVolumeSlider->isEnabled()) { m_pVolumeSlider->setValue(value); setPercent(value); updateIcon(value); } } void AppManagerSliderItem::setEnabled(bool enable) { m_pMuteBtn->setEnabled(enable); m_pVolumeSlider->setEnabled(enable); } QPushButton* AppManagerSliderItem::getMuteBtn() const { return m_pMuteBtn; } void AppManagerSliderItem::setMute(bool muted) { m_muted = muted; updateIcon(m_pVolumeSlider->getValue()); } void AppManagerSliderItem::setPercent(int percent) { m_pPercentLabel->setText(QString("%1").arg(percent) + "%"); } void AppManagerSliderItem::setIcon(const QIcon& icon) { m_pMuteBtn->setIcon(icon); } void AppManagerSliderItem::updateIcon(int value) { auto iconStr = "application-x-desktop"; if (m_muted || value <= 0) { iconStr = iconNameOutputs[0]; } else if (value > 0 && value <= 33) { iconStr = iconNameOutputs[1]; } else if (value >33 && value <= 66) { iconStr = iconNameOutputs[2]; } else { iconStr = iconNameOutputs[3]; } setIcon(QIcon::fromTheme(QString(iconStr))); } void AppManagerSliderItem::valueChangedSlots(int value) { // TODO } ukui-volume-control/audio/ISwitchButtonItem.h0000664000175000017500000000433015171074712020304 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 ISWITCHBUTTONITEM_H #define ISWITCHBUTTONITEM_H #include #include #include /** * @brief 包含SwitchButton子项的类型 */ enum class SwitchButtonType { SWITCH_BUTTON_VOLUMEBOOST = 0, // 音量增强子项 SWITCH_BUTTON_MONO, // 单声道子项 SWITCH_BUTTON_ECHOCANCEL, // 智能降噪子项 SWITCH_BUTTON_LOOPBACK, // 侦听子项 SWITCH_BUTTON_STARTUP, // 开机音乐子项 SWITCH_BUTTON_POWEROFF, // 关机音乐子项 SWITCH_BUTTON_LOGOUT, // 注销音乐子项 SWITCH_BUTTON_WAKEUP, // 唤醒音乐子项 SWITCH_BUTTON_ALERT, // 提示音子项 SWITCH_BUTTON_AUTO_PAUSE // 断开音频自动暂停子项 }; class ISwitchButton { public: virtual bool isChecked() const = 0; virtual void setChecked(bool) = 0; virtual QWidget* getWidget() = 0; }; class ISwitchButtonItem { public: ISwitchButtonItem(std::shared_ptr, QLabel*, SwitchButtonType); virtual ~ISwitchButtonItem() = default; public: virtual void initUi() = 0; virtual void initSlots() = 0; virtual QWidget* getWidget() = 0; void setChecked(bool); std::shared_ptr getSwitchButton() const; protected: SwitchButtonType m_type = SwitchButtonType::SWITCH_BUTTON_VOLUMEBOOST; std::shared_ptr m_pSwitchBtn = nullptr; QLabel* m_pDisplayLabel = nullptr; }; #endif // ISWITCHBUTTONITEM_H ukui-volume-control/audio/Ukui4MainWidget.cpp0000664000175000017500000003340215171074712020226 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "Ukui4MainWidget.h" #include #include #include #include "ukui4.0/Ukui4SwitchButtonItem.h" #include "ukui4.0/Ukui4VolumeSliderItem.h" #include "ukui4.0/Ukui4SelectComboxItem.h" Ukui4MainWidget::Ukui4MainWidget(std::unordered_map& titleLabelMap, std::unordered_map& switchBtnMap, std::unordered_map& volumeSliderMap, std::unordered_map& selectComboxMap, std::unordered_map& detailMap, std::list& appManagerList, QWidget* parent) : m_titleLabelItemMap(titleLabelMap), m_switchBtnItemMap(switchBtnMap), m_volumeSliderItemMap(volumeSliderMap), m_selectComboxItemMap(selectComboxMap), m_detailSettingsItemMap(detailMap), IAudioMainWindow(appManagerList), QWidget(parent) { createdItems(); } void Ukui4MainWidget::initUi() { // 添加布局 auto vLayout = new QVBoxLayout; vLayout->addWidget(m_titleLabelItemMap[TitleLabelType::TITLE_LABEL_OUTPUT]->getWidget()); vLayout->addWidget(m_selectComboxItemMap[SelectComboxType::SELECT_COMBOX_OUTPUT]->getWidget()); vLayout->addWidget(addLine()); vLayout->addWidget(m_volumeSliderItemMap[VolumeSliderType::VOLUME_SLIDER_OUTPUT]->getWidget()); vLayout->addWidget(addLine()); vLayout->addWidget(m_switchBtnItemMap[SwitchButtonType::SWITCH_BUTTON_VOLUMEBOOST]->getWidget()); vLayout->addItem(new QSpacerItem(16, 16, QSizePolicy::Fixed)); vLayout->addWidget(m_volumeSliderItemMap[VolumeSliderType::VOLUME_SLIDER_BALANCE]->getWidget()); vLayout->addWidget(addLine()); vLayout->addWidget(m_switchBtnItemMap[SwitchButtonType::SWITCH_BUTTON_MONO]->getWidget()); vLayout->addItem(new QSpacerItem(16, 40, QSizePolicy::Fixed)); vLayout->addWidget(m_titleLabelItemMap[TitleLabelType::TITLE_LABEL_INPUT]->getWidget()); vLayout->addWidget(m_selectComboxItemMap[SelectComboxType::SELECT_COMBOX_INPUT]->getWidget()); vLayout->addWidget(addLine()); vLayout->addWidget(m_volumeSliderItemMap[VolumeSliderType::VOLUME_SLIDER_INPUT]->getWidget()); vLayout->addWidget(addLine()); vLayout->addWidget(m_volumeSliderItemMap[VolumeSliderType::VOLUME_SLIDER_INPUTLEVEL]->getWidget()); vLayout->addWidget(addLine()); vLayout->addWidget(m_switchBtnItemMap[SwitchButtonType::SWITCH_BUTTON_ECHOCANCEL]->getWidget()); vLayout->addWidget(addLine()); vLayout->addWidget(m_switchBtnItemMap[SwitchButtonType::SWITCH_BUTTON_LOOPBACK]->getWidget()); vLayout->addItem(new QSpacerItem(16, 40, QSizePolicy::Fixed)); vLayout->addWidget(m_titleLabelItemMap[TitleLabelType::TITLE_LABEL_SOUNDEFFECT]->getWidget()); vLayout->addWidget(m_switchBtnItemMap[SwitchButtonType::SWITCH_BUTTON_STARTUP]->getWidget()); vLayout->addWidget(addLine()); vLayout->addWidget(m_switchBtnItemMap[SwitchButtonType::SWITCH_BUTTON_POWEROFF]->getWidget()); vLayout->addWidget(addLine()); vLayout->addWidget(m_switchBtnItemMap[SwitchButtonType::SWITCH_BUTTON_LOGOUT]->getWidget()); vLayout->addWidget(addLine()); vLayout->addWidget(m_switchBtnItemMap[SwitchButtonType::SWITCH_BUTTON_WAKEUP]->getWidget()); vLayout->addWidget(m_switchBtnItemMap[SwitchButtonType::SWITCH_BUTTON_ALERT]->getWidget()); vLayout->addWidget(addLine()); vLayout->addWidget(m_selectComboxItemMap[SelectComboxType::SELECT_COMBOX_SOUNDEFFECT]->getWidget()); vLayout->addWidget(addLine()); vLayout->addWidget(m_selectComboxItemMap[SelectComboxType::SELECT_COMBOX_VOLUMEADJUST]->getWidget()); vLayout->addWidget(addLine()); vLayout->addWidget(m_selectComboxItemMap[SelectComboxType::SELECT_COMBOX_NOTIFICTION]->getWidget()); vLayout->setSpacing(0); setLayout(vLayout); vLayout->setContentsMargins(0, 0, 0, 0); } void Ukui4MainWidget::initSlots() { for (const auto& it : m_volumeSliderItemMap) { if (VolumeSliderType::VOLUME_SLIDER_INPUTLEVEL == it.first) continue; auto slider = static_cast(it.second->getSlider()->getWidget()); if (slider) { connect(slider, &QSlider::valueChanged, this, [it, this](int value) { if (VolumeSliderType::VOLUME_SLIDER_BALANCE == it.first) { double v = value / 100.0; setBalance(EnumToInt(it.first), v); } else { setVolume(EnumToInt(it.first), -1, value); } }); } auto muteBtn = it.second->getMuteBtn(); if (muteBtn) { connect(muteBtn, &QPushButton::clicked, this, [it, this]() { setMute(EnumToInt(it.first), -1); }); } } for (const auto& it : m_selectComboxItemMap) { switch (it.first) { case SelectComboxType::SELECT_COMBOX_INPUT: case SelectComboxType::SELECT_COMBOX_OUTPUT: { QComboBox* combox = static_cast(it.second->getSelectBox()->getWidget()); connect(combox, static_cast(&QComboBox::currentIndexChanged), this, [combox, it, this](int row) { setDefaultDevice(EnumToInt(it.first), -1, combox->currentData().toStringList().at(0), combox->currentData().toStringList().at(1)); qDebug() << "set default port: " << combox->currentData().toStringList().at(0) << " card:" << combox->currentData().toStringList().at(1); }); break; } case SelectComboxType::SELECT_COMBOX_NOTIFICTION: break; case SelectComboxType::SELECT_COMBOX_SOUNDEFFECT: break; case SelectComboxType::SELECT_COMBOX_VOLUMEADJUST: break; } } for (const auto& it : m_switchBtnItemMap) { connect(static_cast(it.second->getSwitchButton()->getWidget()), &QPushButton::clicked, this, [it, this](int row) { setSettingsValue(m_switchMethodKeys[it.first], QVariant(static_cast(it.second->getSwitchButton()->getWidget())->isChecked())); }); } } void Ukui4MainWidget::setValue(int type , int value) { m_volumeSliderItemMap[IntToEnum(type)]->setValue(value); } void Ukui4MainWidget::setAppValue(const QString&, int) { // TODO } void Ukui4MainWidget::setAppMute(const QString&, bool) { // TODO } void Ukui4MainWidget::setCurrentIndex(int type, int idx) { m_selectComboxItemMap[IntToEnum(type)]->setCurrentIndex(idx); } void Ukui4MainWidget::setAppCurrentIndex(const QString&, const NodeType&, int) { // TODO } void Ukui4MainWidget::setAppCbxEnabled(const QString&, const NodeType&, bool) { // TODO } void Ukui4MainWidget::setCurrentAppItem(int) { // TODO } void Ukui4MainWidget::setChecked(int type, bool status) { m_switchBtnItemMap[IntToEnum(type)]->setChecked(status); } std::list Ukui4MainWidget::getDevicePortList(int) const { // TODO return {}; } std::list Ukui4MainWidget::getDeviceManagetPortList(int) const { // TODO return {}; } //std::unordered_map Ukui4MainWidget::getAppItemList() const std::list& Ukui4MainWidget::getAppItemList() { // TODO return m_appWidgetList; } std::list Ukui4MainWidget::getAppManagetPortList(int, int) const { // TODO return {}; } QWidget* Ukui4MainWidget::getWidget() { return this; } void Ukui4MainWidget::addDeviceManagerItem(int type, const QString& displayStr, const QVariant& data) { // TODO } void Ukui4MainWidget::removeDeviceManagerItem(int type, int idx) { // TODO } void Ukui4MainWidget::setDeviceManagerItemStatus(int type, int idx, bool checked) { // TODO } void Ukui4MainWidget::addAppManagerItem(int, int, int, bool, const QString&, const QString&, const QList&, const QList&) { // TODO } void Ukui4MainWidget::removeAppManagerItem(const QString&) { // TODO } void Ukui4MainWidget::addPortToAppItem(const QString&, const NodeType&, const QString&, const QVariant&) { // TODO } void Ukui4MainWidget::removePortFromAppItem(const QString&, const NodeType&, int) { // TODO } void Ukui4MainWidget::updateUIByButtonStatus(int, bool) { } void Ukui4MainWidget::updateUIByDevice(int type, bool status) { } void Ukui4MainWidget::createdItems() { for (const auto& [k, v] : m_titleLabelKeys) { m_titleLabelItemMap.emplace(k, new TitleLabelItem(new QLabel(v), this)); } // 创建SwitchButton子项 for (const auto& [k, v] : m_switchButtonkeys) { ISwitchButtonItem* item = nullptr; std::shared_ptr btn = nullptr; btn = std::make_shared(new kdk::KSwitchButton, v.at(v.size() - 1)); switch (k) { case SwitchButtonType::SWITCH_BUTTON_VOLUMEBOOST: { item = new Ukui4SwitchButtonWithDescItem(btn, new QLabel(v.at(0), this), new QLabel(v.at(1)), k, this); break; } case SwitchButtonType::SWITCH_BUTTON_MONO: { item = new Ukui4SwitchButtonWithDescItem(btn, new QLabel(v.at(0), this), new QLabel(v.at(1)), k, this); break; } case SwitchButtonType::SWITCH_BUTTON_LOOPBACK: { item = new Ukui4SwitchButtonWithDescItem(btn, new QLabel(v.at(0), this), new QLabel(v.at(1)), k, this); break; } case SwitchButtonType::SWITCH_BUTTON_ECHOCANCEL: case SwitchButtonType::SWITCH_BUTTON_STARTUP: case SwitchButtonType::SWITCH_BUTTON_POWEROFF: case SwitchButtonType::SWITCH_BUTTON_LOGOUT: case SwitchButtonType::SWITCH_BUTTON_WAKEUP: case SwitchButtonType::SWITCH_BUTTON_ALERT: case SwitchButtonType::SWITCH_BUTTON_AUTO_PAUSE: { item = new Ukui4SwitchButtonItem(btn, new QLabel(v.at(0), this), k, this); break; } default: break; } if (item && btn) { m_switchBtnItemMap.emplace(k, item); } } // 创建VolumeSlider子项 for (const auto& [k, v] : m_volumeSliderKeys) { IVolumeSliderItem* item = nullptr; std::shared_ptr slider = nullptr; switch (k) { case VolumeSliderType::VOLUME_SLIDER_OUTPUT: case VolumeSliderType::VOLUME_SLIDER_INPUT: { slider = std::make_shared(new QSlider(this), v.at(v.size() - 1)); slider->setRange(0, 100); item = new Ukui4VolumeSliderItem(slider, new QLabel(v.at(0), this), new QLabel(this), new QPushButton(this), k, this); break; } case VolumeSliderType::VOLUME_SLIDER_BALANCE: { slider = std::make_shared(new kdk::KSlider(this), v.at(v.size() - 1)); slider->setRange(0, 100); item = new Ukui4BalanceVolumeSliderItem(slider, new QLabel(v.at(0), this), new QLabel(v.at(1)), new QLabel(v.at(2)), this); break; } case VolumeSliderType::VOLUME_SLIDER_INPUTLEVEL: { slider = std::make_shared(new QProgressBar(this)); slider->setRange(0, 99); item = new Ukui4InputLevelVolumeSliderItem(slider, new QLabel(v.at(0), this), this); m_pMonitorStream = std::make_shared(slider); break; } default: break; } if (item && slider) { m_volumeSliderItemMap.emplace(k, item); } } // 创建SelectCombox子项 for (const auto& [k, v] : m_selectComboxKeys) { ISelectComboxItem* item = nullptr; std::shared_ptr box = nullptr; switch (k) { case SelectComboxType::SELECT_COMBOX_INPUT: case SelectComboxType::SELECT_COMBOX_OUTPUT: case SelectComboxType::SELECT_COMBOX_SOUNDEFFECT: case SelectComboxType::SELECT_COMBOX_VOLUMEADJUST: case SelectComboxType::SELECT_COMBOX_NOTIFICTION:{ qDebug() << "m_selectComboxKeys.size: " << m_selectComboxKeys.size() << " k:" << EnumToInt(k) << " v:" << v; box = std::make_shared(new QComboBox(this), v.at(v.size() - 1)); item = new Ukui4SelectComboxItem(box, new QLabel(v.at(0), this), k, this); break; } default: break; } if (item && box) { qDebug() << "emplace, k:" << EnumToInt(k) << "m_selectComboxItemMap。size: " << m_selectComboxItemMap.size(); m_selectComboxItemMap.emplace(k, item); } } } QFrame* Ukui4MainWidget::addLine() { auto line = new QFrame(this); line->setMinimumSize(QSize(0, 1)); line->setMaximumSize(QSize(16777215, 1)); line->setLineWidth(0); line->setFrameShape(QFrame::HLine); line->setFrameShadow(QFrame::Sunken); return line; } ukui-volume-control/audio/VersionStrategy.h0000664000175000017500000000313215171074712020066 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 VERSIONSTRATEGY_H #define VERSIONSTRATEGY_H #include "IAudioMainWidow.h" class VersionStrategy { public: virtual IAudioMainWindow* createMainWindow(std::unordered_map&, std::unordered_map&, std::unordered_map&, std::unordered_map&, std::unordered_map&, std::list&, QWidget* = nullptr) = 0; virtual ~VersionStrategy() = default; }; #endif // VERSIONSTRATEGY_H ukui-volume-control/audio/IAudioMainWidow.cpp0000664000175000017500000002166715171074712020257 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "IAudioMainWidow.h" #include IAudioMainWindow::IAudioMainWindow(std::list& appWidgetList) : m_appWidgetList(appWidgetList) { } std::shared_ptr IAudioMainWindow::addVolumeObserver(const VolumeChangedCallback& callback) { auto spCallback = std::make_shared(std::move(callback)); m_volumeObservers.push_back(spCallback); return spCallback; } void IAudioMainWindow::removeVolumeObserver(const std::shared_ptr& callback) { m_volumeObservers.erase(std::remove_if(m_volumeObservers.begin(), m_volumeObservers.end(), [callback](const std::shared_ptr& existingCallback) { return existingCallback == callback; }), m_volumeObservers.end()); } void IAudioMainWindow::setVolume(int type, int idx, int volume) { volumeNotify(type, idx, volume); } std::shared_ptr IAudioMainWindow::addBalanceVolumeObserver(const BalanceVolumeChangedCallback& callback) { auto spCallback = std::make_shared(std::move(callback)); m_balanceVolumeObservers.push_back(spCallback); return spCallback; } void IAudioMainWindow::removeBalanceVolumeObserver(const std::shared_ptr& callback) { m_balanceVolumeObservers.erase(std::remove_if(m_balanceVolumeObservers.begin(), m_balanceVolumeObservers.end(), [callback](const std::shared_ptr& existingCallback) { return existingCallback == callback; }), m_balanceVolumeObservers.end()); } void IAudioMainWindow::setBalance(int type, double volume) { balanceVolumeNotify(type, volume); } std::shared_ptr IAudioMainWindow::addMuteObserver(const MuteChangedCallback& callback) { auto spCallback = std::make_shared(std::move(callback)); m_muteObservers.push_back(spCallback); return spCallback; } void IAudioMainWindow::removeMuteObserver(const std::shared_ptr& callback) { m_muteObservers.erase(std::remove_if(m_muteObservers.begin(), m_muteObservers.end(), [callback](const std::shared_ptr& existingCallback) { return existingCallback == callback; }), m_muteObservers.end()); } void IAudioMainWindow::setMute(int type, int idx) { muteNotify(type, idx); } std::shared_ptr IAudioMainWindow::addDeviceObserver(const DeviceChangedCallback& callback) { auto spCallback = std::make_shared(std::move(callback)); m_deviceObservers.push_back(spCallback); return spCallback; } void IAudioMainWindow::removeDeviceObserver(const std::shared_ptr& callback) { m_deviceObservers.erase(std::remove_if(m_deviceObservers.begin(), m_deviceObservers.end(), [callback](const std::shared_ptr& existingCallback) { return existingCallback == callback; }), m_deviceObservers.end()); } void IAudioMainWindow::setDefaultDevice(int type, int idx, const QString& portName, const QString& cardName) { defaultDeviceNotify(type, idx, portName, cardName); } std::shared_ptr IAudioMainWindow::addSettingsObserver(const SettingsChangedCallback& callback) { auto spCallback = std::make_shared(std::move(callback)); m_settingsObservers.push_back(spCallback); return spCallback; } void IAudioMainWindow::removeSettingsObserver(const std::shared_ptr& callback) { m_settingsObservers.erase(std::remove_if(m_settingsObservers.begin(), m_settingsObservers.end(), [callback](const std::shared_ptr& existingCallback) { return existingCallback == callback; }), m_settingsObservers.end()); } void IAudioMainWindow::setSettingsValue(const QString& key, const QVariant& value) { settingsChangedNotify(key, value); } std::shared_ptr IAudioMainWindow::addDetailSettingsObserver(const DetailSettingsClickedCallback& callback) { auto spCallback = std::make_shared(std::move(callback)); m_detailSettingsObservers.push_back(spCallback); return spCallback; } void IAudioMainWindow::removeDetailSettingsObserver(const std::shared_ptr& callback) { m_detailSettingsObservers.erase(std::remove_if(m_detailSettingsObservers.begin(), m_detailSettingsObservers.end(), [callback](const std::shared_ptr& existingCallback) { return existingCallback == callback; }), m_detailSettingsObservers.end()); } void IAudioMainWindow::setDetailSettings(int type) { detailSettingsClickNotify(type); } std::shared_ptr IAudioMainWindow::addEnableDeviceObserver(const EnableDeviceCallback& callback) { auto spCallback = std::make_shared(std::move(callback)); m_enableDeviceObservers.push_back(spCallback); return spCallback; } void IAudioMainWindow::removeEnableDeviceObserver(const std::shared_ptr& callback) { m_enableDeviceObservers.erase(std::remove_if(m_enableDeviceObservers.begin(), m_enableDeviceObservers.end(), [callback](const std::shared_ptr& existingCallback) { return existingCallback == callback; }), m_enableDeviceObservers.end()); } void IAudioMainWindow::setEnabled(const QString& card, const QString& port, bool enable) { enableDeviceNotify(card, port, enable); } void IAudioMainWindow::startMonitor(uint32_t idx) { if (m_pMonitorStream) std::thread([idx, this] () { m_pMonitorStream->startMonitor(idx); }).detach(); } void IAudioMainWindow::stopMonitor() { if (m_pMonitorStream) m_pMonitorStream->stopMonitor(); } AppManagerMainWidget* IAudioMainWindow::getAppManagerWidget() const { return m_pAppManagerWidget; } void IAudioMainWindow::volumeNotify(int type, int idx, int volume) { for (const auto& callback : m_volumeObservers) { if (callback) { (*callback)(type, idx, volume); } } } void IAudioMainWindow::balanceVolumeNotify(int type, double volume) { for (const auto& callback : m_balanceVolumeObservers) { if (callback) { (*callback)(type, volume); } } } void IAudioMainWindow::muteNotify(int type, int idx) { for (const auto& callback : m_muteObservers) { if (callback) { (*callback)(type, idx); } } } void IAudioMainWindow::defaultDeviceNotify(int type, int idx, const QString& portName, const QString& cardName) { for (const auto& callback : m_deviceObservers) { if (callback) { (*callback)(type, idx, portName, cardName); } } } void IAudioMainWindow::settingsChangedNotify(const QString& key, const QVariant& value) { for (const auto& callback : m_settingsObservers) { if (callback) { (*callback)(key, value); } } } void IAudioMainWindow::detailSettingsClickNotify(int type) { for (const auto& callback : m_detailSettingsObservers) { if (callback) { (*callback)(type); } } } void IAudioMainWindow::enableDeviceNotify(const QString& card, const QString& port, bool enable) { for (const auto& callback : m_enableDeviceObservers) { if (callback) { (*callback)(card, port, enable); } } } ukui-volume-control/audio/ConcreteStrategy.h0000664000175000017500000000614715171074712020214 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 CONCRETESTRATEGY_H #define CONCRETESTRATEGY_H #include "VersionStrategy.h" #include "Ukui5MainWidget.h" #include "Ukui4MainWidget.h" class Ukui2ConcreteStrategy : public VersionStrategy { public: virtual IAudioMainWindow* createMainWindow(std::unordered_map&, std::unordered_map&, std::unordered_map&, std::unordered_map&, std::unordered_map&, std::list&, QWidget* = nullptr) override; }; class Ukui4ConcreteStrategy : public VersionStrategy { public: virtual IAudioMainWindow* createMainWindow(std::unordered_map&, std::unordered_map&, std::unordered_map&, std::unordered_map&, std::unordered_map&, std::list&, QWidget* = nullptr) override; }; class Ukui5ConcreteStrategy : public VersionStrategy { public: virtual IAudioMainWindow* createMainWindow(std::unordered_map&, std::unordered_map&, std::unordered_map&, std::unordered_map&, std::unordered_map&, std::list&, QWidget* = nullptr) override; }; #endif // CONCRETESTRATEGY_H ukui-volume-control/audio/Ukui5MainWidget.h0000664000175000017500000001075115171074712017676 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 UKUI5MAINWIDGET_H #define UKUI5MAINWIDGET_H #include "IAudioMainWidow.h" #include "ISwitchButtonItem.h" #include "IVolumeSliderItem.h" #include "ISelectComboxItem.h" #include "Util.h" #include "DeviceManagerMainWidget.h" #include "ukcc/interface/ukcccommon.h" using namespace UkuiAudioFramwork; enum class FrameType { BALANCE_MONO_FRAME = 0, // 声道平衡及单声道界面Frame LOOP_BACK_FRAME, // 侦听界面子Frame SOUND_THEME_FRAME // 音效主题设置界面子Frame }; class Ukui5MainWidget : public QWidget, public IAudioMainWindow { Q_OBJECT public: Ukui5MainWidget(std::unordered_map&, std::unordered_map&, std::unordered_map&, std::unordered_map&, std::unordered_map&, std::list&, QWidget* = nullptr); public: virtual void initUi() override ; virtual void initSlots() override; virtual void setValue(int, int) override; virtual void setAppValue(const QString&, int) override; virtual void setAppMute(const QString&, bool) override; virtual void setCurrentIndex(int, int) override; virtual void setAppCurrentIndex(const QString&, const NodeType&, int) override; virtual void setAppCbxEnabled(const QString&, const NodeType&, bool) override; virtual void setCurrentAppItem(int) override; virtual void setChecked(int, bool) override; virtual std::list getDevicePortList(int) const override; virtual std::list getDeviceManagetPortList(int) const override; virtual std::list& getAppItemList() override; virtual std::list getAppManagetPortList(int, int) const override; virtual QWidget* getWidget() override; virtual void addDeviceManagerItem(int, const QString&, const QVariant&) override; virtual void removeDeviceManagerItem(int, int) override; virtual void setDeviceManagerItemStatus(int, int, bool) override; virtual void updateUIByButtonStatus(int, bool) override; virtual void updateUIByDevice(int type, bool status) override; virtual void addAppManagerItem(int, int, int, bool, const QString&, const QString&, const QList&, const QList&) override; virtual void removeAppManagerItem(const QString&) override; virtual void addPortToAppItem(const QString&, const NodeType&, const QString&, const QVariant&) override; virtual void removePortFromAppItem(const QString&, const NodeType&, int) override; private: void createdItems(); void setFrameVisible(int index, bool visible); QFrame* addLine(); private: DeviceManagerMainWidget* m_pDevManagerWidget = nullptr; // AppManagerMainWidget* m_pAppManagerWidget = nullptr; DesktopEnvironmentVersion m_type = DesktopEnvironmentVersion::DESKTOP_ENVIRONMENT_VERSION_UKUI2; std::unordered_map& m_titleLabelItemMap; std::unordered_map& m_switchBtnItemMap; std::unordered_map& m_volumeSliderItemMap; std::unordered_map& m_selectComboxItemMap; std::unordered_map& m_detailSettingsItemMap; std::vector m_FrameVector; std::unordered_map m_detailSettingsWidgetMap; QTimer m_pVolumeDebounceTimer; int m_pVolumeType; int m_pVolumeIdx; int m_pVolumeValue; }; #endif // UKUI5MAINWIDGET_H ukui-volume-control/audio/Ukui4MainWidget.h0000664000175000017500000000752615171074712017703 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 UKUI4MAINWIDGET_H #define UKUI4MAINWIDGET_H #include "IAudioMainWidow.h" #include "ISwitchButtonItem.h" #include "IVolumeSliderItem.h" #include "ISelectComboxItem.h" #include "Util.h" using namespace UkuiAudioFramwork; class Ukui4MainWidget : public QWidget, public IAudioMainWindow { public: Ukui4MainWidget(std::unordered_map&, std::unordered_map&, std::unordered_map&, std::unordered_map&, std::unordered_map&, std::list&, QWidget* = nullptr); public: virtual void initUi() override ; virtual void initSlots() override; virtual void setValue(int, int) override; virtual void setAppValue(const QString&, int) override; virtual void setAppMute(const QString&, bool) override; virtual void setCurrentIndex(int, int) override; virtual void setAppCurrentIndex(const QString&, const NodeType&, int) override; virtual void setAppCbxEnabled(const QString&, const NodeType&, bool) override; virtual void setCurrentAppItem(int) override; virtual void setChecked(int, bool) override; virtual std::list getDevicePortList(int) const override; virtual std::list getDeviceManagetPortList(int) const override; virtual std::list& getAppItemList() override; virtual std::list getAppManagetPortList(int, int) const override; virtual QWidget* getWidget() override; virtual void addDeviceManagerItem(int, const QString&, const QVariant&) override; virtual void removeDeviceManagerItem(int, int) override; virtual void setDeviceManagerItemStatus(int, int, bool) override; virtual void updateUIByButtonStatus(int, bool) override; virtual void updateUIByDevice(int, bool) override; virtual void addAppManagerItem(int, int, int, bool, const QString&, const QString&, const QList&, const QList&) override; virtual void removeAppManagerItem(const QString&) override; virtual void addPortToAppItem(const QString&, const NodeType&, const QString&, const QVariant&) override; virtual void removePortFromAppItem(const QString&, const NodeType&, int) override; private: void createdItems(); QFrame* addLine(); private: std::list m_appWidgetList {}; DesktopEnvironmentVersion m_type = DesktopEnvironmentVersion::DESKTOP_ENVIRONMENT_VERSION_UKUI2; std::unordered_map& m_titleLabelItemMap; std::unordered_map& m_switchBtnItemMap; std::unordered_map& m_volumeSliderItemMap; std::unordered_map& m_selectComboxItemMap; std::unordered_map& m_detailSettingsItemMap; }; #endif // UKUI4MAINWIDGET_H ukui-volume-control/audio/IDetailSettingsItem.h0000664000175000017500000000314715171074712020577 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 IDETAILSETTINGSITEM_H #define IDETAILSETTINGSITEM_H #include #include #include /** * @brief 包含DetailSettings子项的类型 */ enum class DetailSettingType { DETAIL_SETTING_DEVMANAGER = 0, // 设备管理子项 DETAIL_SETTING_APPMANAGER, // 应用声音子项 }; class IDetailSettings { public: virtual QWidget* getWidget() = 0; }; class IDetailSettingsItem { public: IDetailSettingsItem(std::shared_ptr, QLabel*); virtual ~IDetailSettingsItem() = default; public: virtual void initUi() = 0; virtual void initSlots() = 0; virtual QWidget* getWidget() = 0; std::shared_ptr getDetailSettings() const; protected: std::shared_ptr m_pDetailSettings = nullptr; QLabel* m_pDisplayLabel = nullptr; }; #endif // IDETAILSETTINGSITEM_H ukui-volume-control/audio/CMakeLists.txt0000664000175000017500000000674715171074712017324 0ustar fengfengcmake_minimum_required(VERSION 3.16) project(audio_U5 LANGUAGES CXX) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_BUILD_TYPE Debug) find_package(PkgConfig) find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Widgets DBus LinguistTools REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Widgets DBus LinguistTools REQUIRED) pkg_check_modules(GLIB2 REQUIRED glib-2.0 gio-2.0) pkg_check_modules(KYSDK REQUIRED kysdk-qtwidgets) pkg_check_modules(PA REQUIRED libpulse libpulse-mainloop-glib) if(QT_VERSION_MAJOR EQUAL 6) pkg_check_modules(QGSETTING REQUIRED gsettings-qt6) else() pkg_check_modules(QGSETTING REQUIRED gsettings-qt) endif() include_directories(${GLIB2_INCLUDE_DIRS}) include_directories(${KYSDK_INCLUDE_DIRS}) include_directories(${QGSETTING_INCLUDE_DIRS}) file(GLOB TS_FILES translations/*.ts) if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) qt6_create_translation(QM_FILES ${CMAKE_CURRENT_SOURCE_DIR} ${TS_FILES} OPTIONS -no-obsolete -no-ui-lines) else() qt5_create_translation(QM_FILES ${CMAKE_CURRENT_SOURCE_DIR} ${TS_FILES} OPTIONS -no-obsolete -no-ui-lines) endif() add_library(audio_U5 SHARED audio.ui main.cpp customstyle.cpp customstyle.h ClientMethod.cpp ClientMethod.h IDetailSettingsItem.cpp IDetailSettingsItem.h IVolumeSliderItem.cpp IVolumeSliderItem.h ISelectComboxItem.cpp ISelectComboxItem.h ISwitchButtonItem.h ISwitchButtonItem.cpp IAudioMainWidow.cpp IAudioMainWidow.h ClientManager.cpp ClientManager.h MonitorStream.cpp MonitorStream.h MainWidget.cpp MainWidget.h VersionStrategy.h ConcreteStrategy.cpp ConcreteStrategy.h TitleLabelItem.cpp TitleLabelItem.h Ukui4MainWidget.cpp Ukui4MainWidget.h ukui4.0/Ukui4SwitchButtonItem.cpp ukui4.0/Ukui4SwitchButtonItem.h ukui4.0/Ukui4VolumeSliderItem.cpp ukui4.0/Ukui4VolumeSliderItem.h ukui4.0/Ukui4SelectComboxItem.cpp ukui4.0/Ukui4SelectComboxItem.h ukui4.0/Audio.cpp ukui4.0/Audio.h Ukcc5DevicePortItem.cpp Ukcc5DevicePortItem.h Ukui5MainWidget.cpp Ukui5MainWidget.h Ukui5DetailSettingsItem.cpp Ukui5DetailSettingsItem.h IDeviceManagerItem.h DeviceManagerMainWidget.cpp DeviceManagerMainWidget.h DeviceManagerWidget.cpp DeviceManagerWidget.h AppManagerMainWidget.cpp AppManagerMainWidget.h AppManagerItemWidget.cpp AppManagerItemWidget.h AppManagerSelectComboxItem.cpp AppManagerSelectComboxItem.h AppManagerSliderItem.cpp AppManagerSliderItem.h include/SwitchButton.cpp include/SwitchButton.h include/interface.h ../common/Ukui4CustomControl.cpp ../common/Ukui4CustomControl.h ../common/DBusClient.cpp ../common/DBusClient.h ${QM_FILES}) target_include_directories(audio_U5 PUBLIC ../backend ../tray ../common) target_link_libraries(audio_U5 ${KYSDK_LIBRARIES} ${GLIB2_LIBRARIES} ${PA_LIBRARIES} ${QGSETTING_LIBRARIES} Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::DBus -lukcc) set(CMAKE_INSTALL_LIBDIR /usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}/ukui-control-center) install(FILES ${TS_FILES} DESTINATION "/usr/share/ukui-media/translations/audio") install(FILES ${QM_FILES} DESTINATION "/usr/share/ukui-media/translations/audio") install(TARGETS ${PROJECT_NAME} DESTINATION "${CMAKE_INSTALL_LIBDIR}") ukui-volume-control/audio/DeviceManagerMainWidget.cpp0000664000175000017500000001130015171074712021710 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "DeviceManagerMainWidget.h" #include #include #include "../backend/Util.h" DeviceManagerMainWidget::DeviceManagerMainWidget(QWidget *parent) : KWidget(parent) { createItems(); initUi(); initSlots(); } void DeviceManagerMainWidget::createItems() { for (const auto& [k, v] : m_keys) { auto item = new QStandardItem(QIcon::fromTheme(v.at(0)), v.at(1)); auto widget = new DeviceManagerWidget(new QLabel(v.at(1)), this); // 设置项的样式 QString itemStyle = "QStandardItem { min-width: 176px; max-width: 176px; min-height: 36px; max-height: 36px; }"; item->setData(itemStyle, Qt::UserRole); item->setData(UkuiAudioFramwork::EnumToInt(k), Qt::UserRole); m_deviceWidgetMap.emplace(k, widget); m_itemMap.emplace(k, item); } } void DeviceManagerMainWidget::initUi() { setIcon(QIcon::fromTheme("ukui-control-center")); setWidgetName(tr("Sound Equipment Control")); setWindowFlags(Qt::Dialog); setWindowModality(Qt::WindowModality::WindowModal); setContentsMargins(0, 0, 0, 0); setFixedSize(760, 520); m_pNavigationBar = new kdk::KNavigationBar(this); for (const auto& [k, v] : m_itemMap) { m_pNavigationBar->addItem(v); } m_pNavigationBar->setFixedSize(200, 478); m_pNavigationBar->setContentsMargins(0, 0, 0, 0); auto vLayout = new QVBoxLayout(this); vLayout->setContentsMargins(6, 20, 0, 0); vLayout->setSpacing(0); vLayout->addWidget(m_pNavigationBar); sideBar()->setLayout(vLayout); // 创建QStackedWidget用于管理不同的界面 auto stackedWidget = new QStackedWidget(this); stackedWidget->setContentsMargins(0, 0, 0, 0); stackedWidget->setFixedSize(560, 478); for (const auto& [k, v] : m_deviceWidgetMap) { stackedWidget->addWidget(v); } auto model = m_pNavigationBar->model(); m_pNavigationBar->listview()->setCurrentIndex(model->item(0, 0)->index()); // 添加右侧设备窗口布局 auto vLayout2 = new QVBoxLayout(this); vLayout2->setContentsMargins(0, 20, 0, 0); vLayout2->setSpacing(0); vLayout2->addWidget(stackedWidget); baseBar()->setLayout(vLayout2); setLayoutType(kdk::HorizontalType); // 获取被点击的item的数据,跳转到对应的页面 connect(m_pNavigationBar->listview(), &QListView::clicked, this, [=](const QModelIndex &index){ auto idx = index.row(); stackedWidget->setCurrentWidget(m_deviceWidgetMap[idx == 0 ? DeviceManagerType::Output : DeviceManagerType::Input]); }); // 键盘方向键 + enter键支持 connect(m_pNavigationBar->listview(), &QListView::activated, this, [=](const QModelIndex &index){ auto idx = index.row(); stackedWidget->setCurrentWidget(m_deviceWidgetMap[idx == 0 ? DeviceManagerType::Output : DeviceManagerType::Input]); }); } void DeviceManagerMainWidget::initSlots() { for (const auto& item : m_deviceWidgetMap) { connect(item.second, &DeviceManagerWidget::enableDeviceSignals, this, [this](const QString& card, const QString& port, bool enable) { Q_EMIT enableDeviceSignals(card, port, enable); }); connect(item.second, &DeviceManagerWidget::closeWindowSignals, this, [this]() { Q_EMIT closeWindowSignals(); }); } } void DeviceManagerMainWidget::addItem(const DeviceManagerType& type, const QString& displayStr, const QString& cardName, const QString& portName) { m_deviceWidgetMap[type]->addItem(displayStr, cardName, portName); } void DeviceManagerMainWidget::removeItem(const DeviceManagerType& type, int idx) { m_deviceWidgetMap[type]->removeItem(idx); } void DeviceManagerMainWidget::setChecked(const DeviceManagerType& type, int idx, bool checked) { m_deviceWidgetMap[type]->setChecked(idx, checked); } DeviceManagerWidget* DeviceManagerMainWidget::getManagerWidgetByType(const DeviceManagerType& type) { return m_deviceWidgetMap[type]; } ukui-volume-control/audio/AppManagerSelectComboxItem.cpp0000664000175000017500000001341015171074712022413 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "AppManagerSelectComboxItem.h" #include #include #include AppManagerSelectComboxItem::AppManagerSelectComboxItem(std::shared_ptr box, QLabel* label, QWidget* parent) : ISelectComboxItem(box, label), QFrame(parent) { initUi(); initSlots(); } void AppManagerSelectComboxItem::initUi() { setFixedSize(512, 60); setFrameShape(QFrame::Shape::Box); m_pDisplayLabel->setFixedSize(120, 36); m_pDisplayLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); m_pSelectBox->getWidget()->setFixedSize(347, 36); auto hLayout = new QHBoxLayout; hLayout->setSpacing(0); hLayout->setContentsMargins(0, 0, 0, 0); hLayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); hLayout->addWidget(m_pDisplayLabel); hLayout->addItem(new QSpacerItem(10, 20, QSizePolicy::Fixed)); hLayout->addWidget(m_pSelectBox->getWidget()); hLayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); setLayout(hLayout); QString theme = "ukui-default"; if (QGSettings::isSchemaInstalled("org.ukui.style")) { QGSettings* gst = new QGSettings("org.ukui.style"); if (gst->keys().contains("styleName")) { theme = gst->get("style-name").toString(); } } auto pal = palette(); if (theme == "ukui-default" || theme == "ukui-light") { pal.setBrush(QPalette::Base, QColor("#eeeeee")); } else if (theme == "ukui-dark") { pal.setBrush(QPalette::Base, QColor("#333333")); } setPalette(pal); } void AppManagerSelectComboxItem::initSlots() { } void AppManagerSelectComboxItem::setEnabled(bool enable) { m_pSelectBox->setEnabled(enable); } QWidget* AppManagerSelectComboxItem::getWidget() { return this; } AppSelectComboxWithDescItem::AppSelectComboxWithDescItem(std::shared_ptr box, QLabel* label, QLabel* hintLabel, QWidget* parent) : ISelectComboxItem(box, label), m_pHintLabel(hintLabel), QFrame(parent) { initUi(); initSlots(); } void AppSelectComboxWithDescItem::initUi() { setFixedSize(512, 90); setFrameShape(QFrame::Shape::Box); m_pDisplayLabel->setFixedSize(120, 36); m_pDisplayLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); m_pSelectBox->getWidget()->setFixedSize(347, 36); m_pSelectBox->setEnabled(false); auto itemHLayout = new QHBoxLayout(); itemHLayout->setSpacing(0); itemHLayout->setContentsMargins(0, 0, 0, 0); itemHLayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); itemHLayout->addWidget(m_pDisplayLabel); itemHLayout->addItem(new QSpacerItem(10, 20, QSizePolicy::Fixed)); itemHLayout->addWidget(m_pSelectBox->getWidget()); itemHLayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); m_pHintIconLabel = new QLabel; m_pHintIconLabel->setPixmap(QIcon::fromTheme("dialog-warning").pixmap(QSize(16, 16))); m_pHintLabel->setFixedSize(325, 28); m_pHintLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); auto hintPl = m_pHintLabel->palette(); QColor color = hintPl.color(QPalette::PlaceholderText); hintPl.setColor(QPalette::Text, color); m_pHintLabel->setPalette(hintPl); m_pHintHLayout = new QHBoxLayout(); m_pHintHLayout->setSpacing(0); m_pHintHLayout->setContentsMargins(0, 0, 0, 0); m_pHintHLayout->addSpacerItem(new QSpacerItem(148, 10, QSizePolicy::Fixed)); m_pHintHLayout->addWidget(m_pHintIconLabel); m_pHintHLayout->addSpacerItem(new QSpacerItem(3, 10, QSizePolicy::Fixed)); m_pHintHLayout->addWidget(m_pHintLabel); m_pHintHLayout->addSpacerItem(new QSpacerItem(16, 10, QSizePolicy::Expanding)); m_pVLayout = new QVBoxLayout(); m_pVLayout->setSpacing(5); m_pVLayout->addLayout(itemHLayout); m_pVLayout->addLayout(m_pHintHLayout); m_pVLayout->setContentsMargins(0, 5, 0, 16); setLayout(m_pVLayout); QString theme = "ukui-default"; if (QGSettings::isSchemaInstalled("org.ukui.style")) { QGSettings* gst = new QGSettings("org.ukui.style"); if (gst->keys().contains("styleName")) { theme = gst->get("style-name").toString(); } } auto pal = palette(); if (theme == "ukui-default" || theme == "ukui-light") { pal.setBrush(QPalette::Base, QColor("#eeeeee")); } else if (theme == "ukui-dark") { pal.setBrush(QPalette::Base, QColor("#333333")); } setPalette(pal); } void AppSelectComboxWithDescItem::initSlots() { } void AppSelectComboxWithDescItem::setEnabled(bool enable) { if (!enable) { m_pVLayout->addItem(m_pHintHLayout); setFixedHeight(90); } else { m_pVLayout->removeItem(m_pHintHLayout); setFixedHeight(60); } m_pSelectBox->setEnabled(enable); m_pHintLabel->setVisible(!enable); m_pHintIconLabel->setVisible(!enable); m_pVLayout->setSpacing(5); m_pVLayout->setContentsMargins(0, 5, 0, 16); update(); qDebug() << QString("Set Appliaction combox enable to %1").arg(enable); } QWidget* AppSelectComboxWithDescItem::getWidget() { return this; } ukui-volume-control/audio/MonitorStream.h0000664000175000017500000000374115171074712017527 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 MONITORSTREAM_H #define MONITORSTREAM_H #include #include #include #include #include #include #include #include #include "IVolumeSliderItem.h" #define DECAY_STEP .04 class MonitorStream { public: MonitorStream(std::shared_ptr); ~MonitorStream(); public: void startMonitor(uint32_t); void stopMonitor(); void wait(int); bool isReady() const; private: void init(); static void contextStateCallback(pa_context*, void*); static void readCallback(pa_stream*, size_t, void*); static void suspended_callback(pa_stream*, void*); void updateVolumeMeter(uint32_t, uint32_t, double); pa_stream* createMonitorStreamForSource(uint32_t, uint32_t, bool); private: std::shared_ptr m_pInputLevelBar = nullptr; pa_context* m_pContext = nullptr; pa_stream* m_pStream = nullptr; pa_mainloop_api* m_pMainLoopApi = nullptr; std::atomic m_bReady {false}; double m_lastPeak = 0.0; std::mutex m_mutex; std::condition_variable m_condition; }; #endif // MONITORSTREAM_H ukui-volume-control/audio/main.cpp0000664000175000017500000000165015171074712016200 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 #include "MainWidget.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWidget w; w.show(); return a.exec(); } ukui-volume-control/audio/ukui4.0/0000775000175000017500000000000015171074712015745 5ustar fengfengukui-volume-control/audio/ukui4.0/Ukui4SelectComboxItem.cpp0000664000175000017500000002116515171074712022606 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "Ukui4SelectComboxItem.h" #include #include #include #include #include "ClientMethod.h" #include "Ukcc5DevicePortItem.h" #include using namespace kdk; Ukui4SelectCombox::Ukui4SelectCombox(QComboBox* box, const QString& objName) : m_pCombox(box) { KDK_EXTEND_ALL_INFO_FORMAT(m_pCombox, "Audio", objName, ""); } void Ukui4SelectCombox::setCurrentIndex(int idx) { // check idx vaild if (idx >= m_pCombox->count()) { std::cout << "invaild index: " << idx << std::endl; return; } m_pCombox->blockSignals(true); m_pCombox->setCurrentIndex(idx); m_pCombox->blockSignals(false); } void Ukui4SelectCombox::setEnabled(bool enable) { m_pCombox->setEnabled(enable); } bool Ukui4SelectCombox::isEnabled() const { return m_pCombox->isEnabled(); } int Ukui4SelectCombox::findData(const QVariant& value, int role) { return m_pCombox->findData(value, role); } int Ukui4SelectCombox::findText(const QString& text) { return m_pCombox->findText(text); } void Ukui4SelectCombox::insertItem(const QString& text, const QVariant& userData) { m_pCombox->blockSignals(true); m_pCombox->insertItem(m_pCombox->count(), text, userData); m_pCombox->blockSignals(false); } void Ukui4SelectCombox::removeItem(int index) { m_pCombox->removeItem(index); } QVariant Ukui4SelectCombox::currentData(int role) const { return m_pCombox->currentData(); } std::list Ukui4SelectCombox::getDataList() const { std::list list; for (int i = 0; i < m_pCombox->count(); ++i) { QList v; auto data = m_pCombox->itemData(i).toStringList(); v.push_back(i); v.push_back(data.at(0)); v.push_back(data.at(1)); list.push_back(QVariant(v)); } return list; } int Ukui4SelectCombox::count() const { return m_pCombox->count(); } void Ukui4SelectCombox::clear() { m_pCombox->clear(); } QWidget* Ukui4SelectCombox::getWidget() { return m_pCombox; } void Ukui4SelectCombox::setParent(QWidget* parent) { m_pCombox->setParent(parent); } Ukui4SelectComboxItem::Ukui4SelectComboxItem(std::shared_ptr box, QLabel* label, SelectComboxType type, QWidget* parent) : ISelectComboxItem(box, label), m_type(type), QFrame(parent) { initUi(); initSlots(); } void Ukui4SelectComboxItem::initUi() { setFixedHeight(60); m_pDisplayLabel->setFixedHeight(28); QHBoxLayout* hLayout = new QHBoxLayout(); hLayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); hLayout->addWidget(m_pDisplayLabel); hLayout->addItem(new QSpacerItem(93, 20, QSizePolicy::Fixed)); hLayout->addWidget(m_pSelectBox->getWidget()); hLayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); hLayout->setSpacing(0); setLayout(hLayout); // setFrameShape(QFrame::Shape::Box); } void Ukui4SelectComboxItem::initSlots() { } QWidget* Ukui4SelectComboxItem::getWidget() { return this; } void Ukui4SelectComboxItem::setEnabled(bool enable) { m_pSelectBox->setEnabled(enable); } Ukui5SelectCombox::Ukui5SelectCombox(QListWidget* box, const QString& objName) : m_pCombox(box) { m_pCombox->setFixedHeight(130); auto pal = m_pCombox->palette(); pal.setBrush(QPalette::Base, QColor(0, 0, 0, 0)); m_pCombox->setPalette(pal); KDK_EXTEND_ALL_INFO_FORMAT(m_pCombox, "Audio", objName, ""); } void Ukui5SelectCombox::setCurrentIndex(int idx) { // check idx vaild if (idx >= m_pCombox->count()) { std::cout << "invaild index: " << idx << std::endl; return; } m_pCombox->blockSignals(true); m_pCombox->setCurrentIndex(m_pCombox->model()->index(idx, 0)); m_pCombox->blockSignals(false); setListWidgetSelectItem(idx); } void Ukui5SelectCombox::setEnabled(bool enable) { m_pCombox->setEnabled(enable); } bool Ukui5SelectCombox::isEnabled() const { return m_pCombox->isEnabled(); } int Ukui5SelectCombox::findData(const QVariant& value, int role) { int idx = -1; for (int i = 0; i < m_pCombox->count(); ++i) { auto item = m_pCombox->item(i); auto wid = (Ukcc5DevicePortItem*) m_pCombox->itemWidget(item); if (2 != value.toStringList().size()) { std::cerr << "Invaild value!" << std::endl; return idx; } if (value.toStringList().at(0) == wid->getCardName() && value.toStringList().at(1) == wid->getPortName()) { idx = i; break; } } return idx; } int Ukui5SelectCombox::findText(const QString& text) { // return m_pCombox->findText(text); return -1; } void Ukui5SelectCombox::insertItem(const QString& text, const QVariant& userData) { qDebug() << "insertItem " << text << " data:" << userData; auto list = userData.toStringList(); auto item = new QListWidgetItem(m_pCombox); item->setSizeHint(QSize(200, 36)); auto widget = new Ukcc5DevicePortItem(new QLabel(text), new QLabel(), list.at(0), list.at(1)); m_pCombox->blockSignals(true); m_pCombox->setItemWidget(item, widget); m_pCombox->insertItem(m_pCombox->count(), item); m_pCombox->blockSignals(false); m_pCombox->update(); } void Ukui5SelectCombox::removeItem(int index) { m_pCombox->blockSignals(true); m_pCombox->removeItemWidget(m_pCombox->takeItem(index)); m_pCombox->blockSignals(false); // auto item = m_pCombox->itemAt(index); // m_pCombox->removeItemWidget(item); } QVariant Ukui5SelectCombox::currentData(int role) const { auto item = m_pCombox->currentItem(); auto wid = (Ukcc5DevicePortItem*) m_pCombox->itemWidget(item); return QVariant(std::initializer_list {wid->getPortName(), wid->getCardName()}); } std::list Ukui5SelectCombox::getDataList() const { std::list list; for (int i = 0; i < m_pCombox->count(); ++i) { auto item = m_pCombox->item(i); auto wid = (Ukcc5DevicePortItem*)m_pCombox->itemWidget(item); QList v; v.push_back(i); v.push_back(wid->getCardName()); v.push_back(wid->getPortName()); list.push_back(QVariant(v)); } return list; } int Ukui5SelectCombox::count() const { return m_pCombox->count(); } void Ukui5SelectCombox::clear() { m_pCombox->blockSignals(true); m_pCombox->clear(); m_pCombox->blockSignals(false); } QWidget* Ukui5SelectCombox::getWidget() { return m_pCombox; } void Ukui5SelectCombox::setParent(QWidget* parent) { m_pCombox->setParent(parent); } void Ukui5SelectCombox::setListWidgetSelectItem(int row) { for (int i = 0; i < m_pCombox->count(); ++i) { auto item = m_pCombox->item(i); auto wid = (Ukcc5DevicePortItem*) m_pCombox->itemWidget(item); if (i == row /*&& !wid->isChecked()*/) { wid->setChecked(true); } else { wid->setChecked(false); } } } Ukui5SelectComboxItem::Ukui5SelectComboxItem(std::shared_ptr box, QLabel* label, SelectComboxType type, QWidget* parent) : ISelectComboxItem(box, label), m_type(type), QFrame(parent) { initUi(); initSlots(); } void Ukui5SelectComboxItem::initUi() { setFixedHeight(190); m_pDisplayLabel->setFixedHeight(28); QVBoxLayout* vLayout = new QVBoxLayout(); vLayout->addItem(new QSpacerItem(16, 10, QSizePolicy::Fixed)); vLayout->addWidget(m_pDisplayLabel); vLayout->addItem(new QSpacerItem(93, 10, QSizePolicy::Fixed)); vLayout->addWidget(m_pSelectBox->getWidget()); vLayout->addItem(new QSpacerItem(16, 10, QSizePolicy::Fixed)); vLayout->setSpacing(0); setLayout(vLayout); vLayout->setContentsMargins(20, 0, 20, 0); // setFrameShape(QFrame::Shape::Box); } void Ukui5SelectComboxItem::initSlots() { } void Ukui5SelectComboxItem::setEnabled(bool enable) { m_pSelectBox->setEnabled(enable); } QWidget* Ukui5SelectComboxItem::getWidget() { return this; } ukui-volume-control/audio/ukui4.0/Ukui4SelectComboxItem.h0000664000175000017500000001026515171074712022252 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 UKUI4SELECTCOMBOXITEM_H #define UKUI4SELECTCOMBOXITEM_H #include #include #include "ISelectComboxItem.h" #include "Ukui4DevicePortSelectItem.h" ///** //* @brief 包含SelectCombox子项的类型 //*/ //enum class SelectComboxType //{ // SELECT_COMBOX_INPUT = 0, // 输入设备子项 // SELECT_COMBOX_OUTPUT, // 输出设备子项 // SELECT_COMBOX_SOUNDEFFECT, // 音效主题子项 // SELECT_COMBOX_VOLUMEADJUST, // 音量调节子项 // SELECT_COMBOX_NOTIFICTION, // 接收通知子项 //}; class Ukui4SelectCombox : public ISelectBox { public: Ukui4SelectCombox(QComboBox*, const QString& = ""); ~Ukui4SelectCombox() = default; public: virtual void setCurrentIndex(int) override; virtual void setEnabled(bool) override; virtual bool isEnabled() const override; virtual int findData(const QVariant&, int = Qt::UserRole) override; virtual int findText(const QString&) override; virtual void insertItem(const QString&, const QVariant& = QVariant()) override; virtual void removeItem(int) override; virtual QVariant currentData(int = Qt::UserRole) const override; virtual std::list getDataList() const override; virtual int count() const override; virtual void clear() override; virtual QWidget* getWidget() override; virtual void setParent(QWidget*) override; private: QComboBox* m_pCombox = nullptr; }; class Ukui4SelectComboxItem : public QFrame, public ISelectComboxItem { Q_OBJECT public: Ukui4SelectComboxItem(std::shared_ptr, QLabel*, SelectComboxType, QWidget* = nullptr); ~Ukui4SelectComboxItem() = default; public: virtual void initUi() override; virtual void initSlots() override; virtual void setEnabled(bool) override; virtual QWidget* getWidget() override; private: SelectComboxType m_type = SelectComboxType::SELECT_COMBOX_INPUT; }; class Ukui5SelectCombox : public ISelectBox { public: Ukui5SelectCombox(QListWidget*, const QString& = ""); ~Ukui5SelectCombox() = default; public: virtual void setCurrentIndex(int) override; virtual void setEnabled(bool) override; virtual bool isEnabled() const override; virtual int findData(const QVariant&, int = Qt::UserRole) override; virtual int findText(const QString&) override; virtual void insertItem(const QString&, const QVariant& = QVariant()) override; virtual void removeItem(int) override; virtual QVariant currentData(int = Qt::UserRole) const override; virtual std::list getDataList() const override; virtual int count() const override; virtual void clear() override; virtual QWidget* getWidget() override; virtual void setParent(QWidget*) override; private: void setListWidgetSelectItem(int); private: QListWidget* m_pCombox = nullptr; std::unordered_map> m_devicePortItemMap{}; }; class Ukui5SelectComboxItem : public QFrame, public ISelectComboxItem { Q_OBJECT public: Ukui5SelectComboxItem(std::shared_ptr, QLabel*, SelectComboxType, QWidget* = nullptr); ~Ukui5SelectComboxItem() = default; public: virtual void initUi() override; virtual void initSlots() override; virtual void setEnabled(bool) override; virtual QWidget* getWidget() override; private: SelectComboxType m_type = SelectComboxType::SELECT_COMBOX_INPUT; }; #endif // UKUI4SELECTCOMBOXITEM_H ukui-volume-control/audio/ukui4.0/Audio.h0000664000175000017500000000707715171074712017172 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 AUDIO_H #define AUDIO_H #include #include #include #include #include #include #include #include "Ukui4SwitchButtonItem.h" #include "Ukui4VolumeSliderItem.h" #include "Ukui4SelectComboxItem.h" #if defined(LIBUKCC_LIBRARY) # define LIBUKCC_EXPORT Q_DECL_EXPORT #else # define LIBUKCC_EXPORT Q_DECL_IMPORT #endif namespace Ui { class Audio; } class Audio : public QObject, CommonInterface { Q_OBJECT Q_PLUGIN_METADATA(IID "org.ukcc.CommonInterface") Q_INTERFACES(CommonInterface) public: Audio(); ~Audio() = default; public: QString plugini18nName() Q_DECL_OVERRIDE; int pluginTypes() Q_DECL_OVERRIDE; QWidget* pluginUi() Q_DECL_OVERRIDE; bool isEnable() const Q_DECL_OVERRIDE; const QString name() const Q_DECL_OVERRIDE; bool isShowOnHomePage() const Q_DECL_OVERRIDE; QIcon icon() const Q_DECL_OVERRIDE; QString translationPath() const Q_DECL_OVERRIDE; private: void initSearchText(); // 搜索翻译 private: QString m_pluginName = ""; int m_pluginType = SYSTEM; QWidget* m_pWidget = nullptr; bool m_firstLoad = true; // std::unordered_map m_switchButtonkeys { // {SwitchButtonType::SWITCH_BUTTON_VOLUMEBOOST, tr("Volume Increase")}, // {SwitchButtonType::SWITCH_BUTTON_MONO, tr("Mono Audio")}, // {SwitchButtonType::SWITCH_BUTTON_ECHOCANCEL, tr("Noise Reduction")}, // {SwitchButtonType::SWITCH_BUTTON_LOOPBACK, tr("Voice Monitor")}, // {SwitchButtonType::SWITCH_BUTTON_STARTUP, tr("Startup Misic")}, // {SwitchButtonType::SWITCH_BUTTON_POWEROFF, tr("Poweroff Music")}, // {SwitchButtonType::SWITCH_BUTTON_LOGOUT, tr("Logout Music")}, // {SwitchButtonType::SWITCH_BUTTON_WAKEUP, tr("Wakeup Music")}, // {SwitchButtonType::SWITCH_BUTTON_ALERT, tr("Beep Switch")} // }; // std::unordered_map m_volumeSliderKeys { // {VolumeSliderType::VOLUME_SLIDER_INPUT, tr("Volume")}, // {VolumeSliderType::VOLUME_SLIDER_OUTPUT, tr("Master Volume")}, // {VolumeSliderType::VOLUME_SLIDER_INPUTLEVEL, tr("Input Level")}, // {VolumeSliderType::VOLUME_SLIDER_BALANCE, tr("Balance")} // }; // std::unordered_map m_selectComboxKeys { // {SelectComboxType::SELECT_COMBOX_INPUT, tr("Input Device")}, // {SelectComboxType::SELECT_COMBOX_OUTPUT, tr("Output Device")}, // {SelectComboxType::SELECT_COMBOX_SOUNDEFFECT, tr("Sound Theme")}, // {SelectComboxType::SELECT_COMBOX_VOLUMEADJUST, tr("Volume Control Sound")}, // {SelectComboxType::SELECT_COMBOX_NOTIFICTION, tr("Notification Sound")} // }; }; #endif // AUDIO_H ukui-volume-control/audio/ukui4.0/Ukui4VolumeSliderItem.h0000664000175000017500000001165315171074712022277 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 UKUI4VOLUMESLIDERITEM_H #define UKUI4VOLUMESLIDERITEM_H #include #include #include #include #include #include #include #include "IVolumeSliderItem.h" ///** //* @brief 包含VolumeSlider子项的类型 //*/ //enum class VolumeSliderType //{ // VOLUME_SLIDER_INPUT = 0, // 输入音量子项 // VOLUME_SLIDER_OUTPUT, // 输出音量子项 // VOLUME_SLIDER_BALANCE, // 平衡音量子项 // VOLUME_SLIDER_INPUTLEVEL // 输入反馈子项 //}; class Ukui4VolumeSlider : public IVolumeSlider { public: Ukui4VolumeSlider(QSlider*, const QString& = ""); ~Ukui4VolumeSlider() = default; public: virtual void setRange(int, int) override; virtual void setValue(int) override; virtual void setEnabled(bool) override; virtual bool isEnabled() const override; virtual uint32_t getValue() const override; virtual QWidget* getWidget() override; private: QSlider* m_pSlider = nullptr; }; class Ukui4VolumeSliderItem : public QFrame, public IVolumeSliderItem { Q_OBJECT public: Ukui4VolumeSliderItem(std::shared_ptr, QLabel*, QLabel*, QPushButton*, VolumeSliderType, QWidget* = nullptr); ~Ukui4VolumeSliderItem() = default; public: virtual void initUi() override; virtual void initSlots() override; virtual QWidget* getWidget() override; virtual void setValue(int) override; virtual void setEnabled(bool) override; virtual QPushButton* getMuteBtn() const override; virtual void setMute(bool) override; private Q_SLOTS: void valueChangedSlots(int); private: void setPercent(int); void setIcon(const QIcon&); void updateIcon(int); void updateLabelWidth(); bool eventFilter(QObject *watched, QEvent *event) override; private: VolumeSliderType m_type = VolumeSliderType::VOLUME_SLIDER_INPUT; QLabel* m_pPercentLabel = nullptr; QPushButton* m_pMuteBtn = nullptr; bool m_muted = false; }; class Ukui4BalanceVolumeSlider : public IVolumeSlider { public: Ukui4BalanceVolumeSlider(QSlider*, const QString& = ""); ~Ukui4BalanceVolumeSlider() = default; public: virtual void setRange(int, int) override; virtual void setValue(int) override; virtual void setEnabled(bool) override; virtual bool isEnabled() const override; virtual uint32_t getValue() const override; virtual QWidget* getWidget() override; private: QSlider* m_pSlider = nullptr; }; class Ukui4BalanceVolumeSliderItem : public QFrame, public IVolumeSliderItem { public: Ukui4BalanceVolumeSliderItem(std::shared_ptr, QLabel*, QLabel*, QLabel*, QWidget* = nullptr); ~Ukui4BalanceVolumeSliderItem() = default; public: virtual void initUi() override; virtual void initSlots() override; virtual QWidget* getWidget() override; virtual void setValue(int) override; virtual void setMute(bool) override; virtual void setEnabled(bool) override; virtual QPushButton* getMuteBtn() const override; private: QLabel* m_pLeftLabel = nullptr; QLabel* m_pRightLabel = nullptr; }; class Ukui4InputLevelVolumeSlider : public IVolumeSlider { public: Ukui4InputLevelVolumeSlider(QProgressBar*); ~Ukui4InputLevelVolumeSlider() = default; public: virtual void setRange(int, int) override; virtual void setValue(int) override; virtual void setEnabled(bool) override; virtual bool isEnabled() const override; virtual uint32_t getValue() const override; virtual QWidget* getWidget() override; private: QProgressBar* m_pProgressBar = nullptr; }; class Ukui4InputLevelVolumeSliderItem : public QFrame, public IVolumeSliderItem { public: Ukui4InputLevelVolumeSliderItem(std::shared_ptr, QLabel*, QWidget* = nullptr); ~Ukui4InputLevelVolumeSliderItem() = default; public: virtual void initUi() override; virtual void initSlots() override; virtual QWidget* getWidget() override; virtual void setValue(int) override; virtual void setMute(bool) override; virtual void setEnabled(bool) override; virtual QPushButton* getMuteBtn() const override; }; #endif // UKUI4VOLUMESLIDERITEM_H ukui-volume-control/audio/ukui4.0/Ukui4SwitchButtonItem.cpp0000664000175000017500000001347215171074712022656 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "Ukui4SwitchButtonItem.h" #include #include #include #include "ClientMethod.h" #include using namespace UkuiAudioFramwork; using namespace kdk; Ukui4SwitchButton::Ukui4SwitchButton(QPushButton *btn, const QString& objName) : m_pSwitchBtn(btn) { KDK_EXTEND_ALL_INFO_FORMAT(m_pSwitchBtn, "Audio", objName, ""); } bool Ukui4SwitchButton::isChecked() const { return m_pSwitchBtn->isChecked(); } void Ukui4SwitchButton::setChecked(bool checked) { m_pSwitchBtn->blockSignals(true); m_pSwitchBtn->setChecked(checked); m_pSwitchBtn->blockSignals(false); } QWidget* Ukui4SwitchButton::getWidget() { return m_pSwitchBtn; } Ukui4SwitchButtonItem::Ukui4SwitchButtonItem(std::shared_ptr btn, QLabel* label, SwitchButtonType type, QWidget* parent) : ISwitchButtonItem(btn, label, type), QFrame(parent) { initUi(); initSlots(); } void Ukui4SwitchButtonItem::initUi() { setFixedHeight(60); m_pDisplayLabel->setFixedHeight(28); QHBoxLayout* hlayout = new QHBoxLayout(this); hlayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); hlayout->addWidget(m_pDisplayLabel); hlayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Expanding)); hlayout->addWidget(m_pSwitchBtn->getWidget()); hlayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); setLayout(hlayout); } void Ukui4SwitchButtonItem::initSlots() { // connect(m_pSwitchBtn->getWidget(), SIGNAL(stateChanged(bool)), this, SLOT(stateChangedSlots(bool))); } QWidget* Ukui4SwitchButtonItem::getWidget() { return this; } void Ukui4SwitchButtonItem::stateChangedSlots(bool state) { QString method = ""; switch (m_type) { case SwitchButtonType::SWITCH_BUTTON_VOLUMEBOOST: method = "setVolumeBoostStatus"; break; case SwitchButtonType::SWITCH_BUTTON_MONO: method = "setMonoStatus"; break; case SwitchButtonType::SWITCH_BUTTON_ECHOCANCEL: method = "setEchoCancelStatus"; break; case SwitchButtonType::SWITCH_BUTTON_LOOPBACK: method = "setLoopbackStatus"; break; case SwitchButtonType::SWITCH_BUTTON_STARTUP: break; case SwitchButtonType::SWITCH_BUTTON_POWEROFF: break; case SwitchButtonType::SWITCH_BUTTON_LOGOUT: break; case SwitchButtonType::SWITCH_BUTTON_WAKEUP: break; case SwitchButtonType::SWITCH_BUTTON_ALERT: method = "setAlertStatus"; break; default: break; } qDebug() << "Ukui4SwitchButtonItem::stateChangedSlots, type: " << EnumToInt(m_type) << ", state: " << state; DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, SETTINGS_INTERFACE, method, state); } Ukui4SwitchButtonWithDescItem::Ukui4SwitchButtonWithDescItem(std::shared_ptr btn, QLabel* disLabel, QLabel* descLabel, SwitchButtonType type, QWidget* parent) : ISwitchButtonItem(btn, disLabel, type), m_pDescLabel(descLabel), QFrame(parent) { initUi(); initSlots(); } void Ukui4SwitchButtonWithDescItem::initUi() { setFixedHeight(90); QPalette palette = m_pDescLabel->palette(); QColor color = qApp->property("kfont-secondary").value().color(); palette.setColor(QPalette::Text, color); m_pDescLabel->setPalette(palette); m_pDisplayLabel->setFixedHeight(28); m_pDescLabel->setFixedHeight(28); QVBoxLayout* vLayout = new QVBoxLayout; vLayout->addStretch(); vLayout->addWidget(m_pDisplayLabel); vLayout->addWidget(m_pDescLabel); vLayout->addStretch(); vLayout->setSpacing(0); QHBoxLayout* hLayout = new QHBoxLayout(this); hLayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); hLayout->addLayout(vLayout); hLayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Expanding)); hLayout->addWidget(m_pSwitchBtn->getWidget()); hLayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); setLayout(hLayout); // setFrameShape(QFrame::Shape::Box); } void Ukui4SwitchButtonWithDescItem::initSlots() { if (QGSettings::isSchemaInstalled("org.ukui.style")) { QGSettings* themeName = new QGSettings("org.ukui.style"); QObject::connect(themeName, &QGSettings::changed, this, [this](const QString& key){ if (key == "styleName" || key == "widgetThemeName" || key == "themeColor" || key == "style-name" || key == "widget-theme-name" || key == "theme-color") { qDebug() << "Ukui4SwitchButtonWithDescItem::initSlots QGSettings::changed key:" << key; QPalette palette = m_pDescLabel->palette(); QColor color = qApp->property("kfont-secondary").value().color(); palette.setColor(QPalette::Text, color); m_pDescLabel->setPalette(palette); } }); } } QWidget* Ukui4SwitchButtonWithDescItem::getWidget() { return this; } ukui-volume-control/audio/ukui4.0/Ukui4VolumeSliderItem.cpp0000664000175000017500000003021215171074712022622 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "Ukui4VolumeSliderItem.h" #include #include #include #include #include "DBusClient.h" #include "customstyle.h" #include "ClientMethod.h" using namespace UkuiAudioFramwork; using namespace kdk; Ukui4VolumeSlider::Ukui4VolumeSlider(QSlider *s, const QString& objName) : m_pSlider(s) { m_pSlider->setOrientation(Qt::Horizontal); KDK_EXTEND_ALL_INFO_FORMAT(m_pSlider, "Audio", objName, ""); } void Ukui4VolumeSlider::setRange(int min, int max) { m_pSlider->setRange(min, max); } void Ukui4VolumeSlider::setValue(int value) { if (m_pSlider) { m_pSlider->blockSignals(true); m_pSlider->setValue(value); m_pSlider->blockSignals(false); } } void Ukui4VolumeSlider::setEnabled(bool enable) { if (m_pSlider) m_pSlider->setEnabled(enable); } bool Ukui4VolumeSlider::isEnabled() const { return m_pSlider->isEnabled(); } uint32_t Ukui4VolumeSlider::getValue() const { return m_pSlider->value(); } QWidget* Ukui4VolumeSlider::getWidget() { return m_pSlider; } Ukui4VolumeSliderItem::Ukui4VolumeSliderItem(std::shared_ptr slider, QLabel* disLabel, QLabel* percLabel, QPushButton* muteBtn, VolumeSliderType type, QWidget* parent) : IVolumeSliderItem(slider, disLabel), m_pPercentLabel(percLabel), m_pMuteBtn(muteBtn), m_type(type), QFrame(parent) { initUi(); initSlots(); } void Ukui4VolumeSliderItem::initUi() { m_pMuteBtn->setFocusPolicy(Qt::NoFocus); m_pMuteBtn->setFixedSize(24, 24); m_pMuteBtn->setFlat(true); m_pMuteBtn->setProperty("setClickPen", QPen(Qt::NoPen)); m_pMuteBtn->setProperty("setHoverPen", QPen(Qt::NoPen)); m_pMuteBtn->setProperty("setClickBrush", QBrush(Qt::transparent)); m_pMuteBtn->setProperty("setHoverBrush", QBrush(Qt::transparent)); switch (m_type) { case VolumeSliderType::VOLUME_SLIDER_OUTPUT: { KDK_EXTEND_ALL_INFO_FORMAT(m_pMuteBtn, "Audio", "volumeSliderOutPut", ""); break; } case VolumeSliderType::VOLUME_SLIDER_INPUT: { KDK_EXTEND_ALL_INFO_FORMAT(m_pMuteBtn, "Audio", "volumeSliderInPut", ""); break; } default: break; } setMinimumSize(550, 60); setMaximumSize(16777215, 60); m_pDisplayLabel->setFixedWidth(300); m_pDisplayLabel->setFixedHeight(28); // m_pPercentLabel->setFixedHeight(28); updateLabelWidth(); // 动态刷新标签宽度 QHBoxLayout* hlayout = new QHBoxLayout(this); hlayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); hlayout->addWidget(m_pDisplayLabel); hlayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); hlayout->addWidget(m_pMuteBtn); // hlayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); hlayout->addWidget(m_pVolumeSlider->getWidget()); // hlayout->addItem(new QSpacerItem(13, 20, QSizePolicy::Maximum)); hlayout->addWidget(m_pPercentLabel); hlayout->addItem(new QSpacerItem(10, 20, QSizePolicy::Maximum)); hlayout->setSpacing(0); setLayout(hlayout); // setFrameShape(QFrame::Shape::Box); qApp->installEventFilter(this); } void Ukui4VolumeSliderItem::initSlots() { // connect(m_pVolumeSlider->getWidget(), SIGNAL(valueChanged(int)), this, SLOT(valueChangedSlots(int))); } QWidget* Ukui4VolumeSliderItem::getWidget() { return this; } void Ukui4VolumeSliderItem::setValue(int value) { if (m_pVolumeSlider->isEnabled()) { m_pVolumeSlider->setValue(value); setPercent(value); updateIcon(value); } } void Ukui4VolumeSliderItem::setEnabled(bool enable) { m_pMuteBtn->setEnabled(enable); m_pVolumeSlider->setEnabled(enable); } QPushButton* Ukui4VolumeSliderItem::getMuteBtn() const { return m_pMuteBtn; } void Ukui4VolumeSliderItem::setMute(bool muted) { m_muted = muted; updateIcon(m_pVolumeSlider->getValue()); } void Ukui4VolumeSliderItem::setPercent(int percent) { m_pPercentLabel->setText(QString("%1").arg(percent) + "%"); } void Ukui4VolumeSliderItem::setIcon(const QIcon& icon) { m_pMuteBtn->setIcon(icon); } void Ukui4VolumeSliderItem::updateIcon(int value) { auto iconStr = "application-x-desktop"; switch (m_type) { case VolumeSliderType::VOLUME_SLIDER_INPUT: { if (m_muted || value <= 0) { iconStr = iconNameInputs[0]; } else if (value > 0 && value <= 33) { iconStr = iconNameInputs[1]; } else if (value >33 && value <= 66) { iconStr = iconNameInputs[2]; } else { iconStr = iconNameInputs[3]; } break; } case VolumeSliderType::VOLUME_SLIDER_OUTPUT: { if (m_muted || value <= 0) { iconStr = iconNameOutputs[0]; } else if (value > 0 && value <= 33) { iconStr = iconNameOutputs[1]; } else if (value >33 && value <= 66) { iconStr = iconNameOutputs[2]; } else { iconStr = iconNameOutputs[3]; } break; } default: break; } setIcon(QIcon::fromTheme(QString(iconStr))); } void Ukui4VolumeSliderItem::updateLabelWidth() { QFontMetrics metrics(qApp->font()); int textWidth = metrics.horizontalAdvance("100%") + 1; // 增加一些边距 m_pPercentLabel->setFixedWidth(textWidth); } bool Ukui4VolumeSliderItem::eventFilter(QObject *watched, QEvent *event) { if (event->type() == QEvent::ApplicationFontChange) { // 系统字体发生变化,更新标签宽度 updateLabelWidth(); // 刷新布局 layout()->invalidate(); layout()->activate(); } return QFrame::eventFilter(watched, event); } void Ukui4VolumeSliderItem::valueChangedSlots(int value) { DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE, AUDIO_OBJECT_PATH, AUDIO_INTERFACE, "setVolume", EnumToInt(m_type), "", value); setPercent(value); } Ukui4BalanceVolumeSlider::Ukui4BalanceVolumeSlider(QSlider *s, const QString& objName) : m_pSlider(s) { KDK_EXTEND_ALL_INFO_FORMAT(m_pSlider, "Audio", objName, ""); // m_pSlider->setRange(-100, 100); // m_pSlider->setSingleStep(100); // m_pSlider->setTickInterval(100); // static_cast(m_pSlider)->setSliderType(kdk::KSliderType::SmoothSlider); // m_pSlider->setOrientation(Qt::Horizontal); // m_pSlider->setFocusPolicy(Qt::StrongFocus); // m_pSlider->setFixedHeight(55); } void Ukui4BalanceVolumeSlider::setRange(int min, int max) { m_pSlider->setRange(min, max); } void Ukui4BalanceVolumeSlider::setValue(int value) { if (m_pSlider) { m_pSlider->blockSignals(true); m_pSlider->setValue(value); m_pSlider->blockSignals(false); } } void Ukui4BalanceVolumeSlider::setEnabled(bool enable) { qDebug() << __func__ << "enable:" << enable; if (m_pSlider) m_pSlider->setEnabled(enable); } bool Ukui4BalanceVolumeSlider::isEnabled() const { return m_pSlider->isEnabled(); } uint32_t Ukui4BalanceVolumeSlider::getValue() const { return m_pSlider->value(); } QWidget* Ukui4BalanceVolumeSlider::getWidget() { return m_pSlider; } Ukui4BalanceVolumeSliderItem::Ukui4BalanceVolumeSliderItem(std::shared_ptr slider, QLabel* disLabel, QLabel* leftLabel, QLabel* rightLabel, QWidget* parent) : IVolumeSliderItem(slider, disLabel), m_pLeftLabel(leftLabel), m_pRightLabel(rightLabel), QFrame(parent) { initUi(); initSlots(); } void Ukui4BalanceVolumeSliderItem::initUi() { setMinimumSize(550, 60); setMaximumSize(16777215, 60); m_pDisplayLabel->setFixedWidth(300); m_pDisplayLabel->setFixedHeight(28); m_pLeftLabel->setFixedHeight(28); m_pRightLabel->setFixedHeight(28); QHBoxLayout* hlayout = new QHBoxLayout(this); hlayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); hlayout->addWidget(m_pDisplayLabel); hlayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); hlayout->addWidget(m_pLeftLabel); // hlayout->addItem(new QSpacerItem(10, 20, QSizePolicy::Fixed)); hlayout->addWidget(m_pVolumeSlider->getWidget()); // hlayout->addItem(new QSpacerItem(10, 20, QSizePolicy::Fixed)); hlayout->addWidget(m_pRightLabel); hlayout->addItem(new QSpacerItem(10, 20, QSizePolicy::Maximum)); hlayout->setSpacing(0); setLayout(hlayout); // setFrameShape(QFrame::Shape::Box); } void Ukui4BalanceVolumeSliderItem::initSlots() { } void Ukui4BalanceVolumeSliderItem::setMute(bool mute) { // TODO } void Ukui4BalanceVolumeSliderItem::setEnabled(bool enable) { qDebug() << __func__ << "enable:" << enable; if (m_pVolumeSlider) m_pVolumeSlider->setEnabled(enable); } void Ukui4BalanceVolumeSliderItem::setValue(int value) { if (m_pVolumeSlider->isEnabled()) m_pVolumeSlider->setValue(value); } QPushButton* Ukui4BalanceVolumeSliderItem::getMuteBtn() const { return nullptr; } QWidget* Ukui4BalanceVolumeSliderItem::getWidget() { return this; } Ukui4InputLevelVolumeSlider::Ukui4InputLevelVolumeSlider(QProgressBar *bar) : m_pProgressBar(bar) { m_pProgressBar->setOrientation(Qt::Horizontal); m_pProgressBar->setStyle(new CustomStyle); m_pProgressBar->setTextVisible(false); } void Ukui4InputLevelVolumeSlider::setRange(int min, int max) { m_pProgressBar->setRange(min, max); } void Ukui4InputLevelVolumeSlider::setValue(int value) { if (m_pProgressBar) m_pProgressBar->setValue(value); } void Ukui4InputLevelVolumeSlider::setEnabled(bool enable) { if (m_pProgressBar) m_pProgressBar->setEnabled(enable); } bool Ukui4InputLevelVolumeSlider::isEnabled() const { return m_pProgressBar->isEnabled(); } uint32_t Ukui4InputLevelVolumeSlider::getValue() const { return m_pProgressBar->value(); } QWidget* Ukui4InputLevelVolumeSlider::getWidget() { return m_pProgressBar; } Ukui4InputLevelVolumeSliderItem::Ukui4InputLevelVolumeSliderItem(std::shared_ptr slider, QLabel* disLabel, QWidget* parent) : IVolumeSliderItem(slider, disLabel), QFrame(parent) { initUi(); initSlots(); } void Ukui4InputLevelVolumeSliderItem::initUi() { setMinimumSize(550, 60); setMaximumSize(16777215, 60); m_pDisplayLabel->setFixedWidth(300); m_pDisplayLabel->setFixedHeight(28); QHBoxLayout* hlayout = new QHBoxLayout(this); hlayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); hlayout->addWidget(m_pDisplayLabel); hlayout->addItem(new QSpacerItem(25, 20, QSizePolicy::Fixed)); hlayout->addWidget(m_pVolumeSlider->getWidget()); // hlayout->addItem(new QSpacerItem(10, 20, QSizePolicy::Maximum)); hlayout->setSpacing(0); setLayout(hlayout); // setFrameShape(QFrame::Shape::Box); } void Ukui4InputLevelVolumeSliderItem::initSlots() { } QWidget* Ukui4InputLevelVolumeSliderItem::getWidget() { return this; } void Ukui4InputLevelVolumeSliderItem::setValue(int value) { if (m_pVolumeSlider->isEnabled()) m_pVolumeSlider->setValue(value); } void Ukui4InputLevelVolumeSliderItem::setMute(bool) { // TODO } void Ukui4InputLevelVolumeSliderItem::setEnabled(bool enable) { if (m_pVolumeSlider) m_pVolumeSlider->setEnabled(enable); } QPushButton* Ukui4InputLevelVolumeSliderItem::getMuteBtn() const { return nullptr; } ukui-volume-control/audio/ukui4.0/Audio.cpp0000664000175000017500000000460115171074712017513 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "Audio.h" #include #include #include #include #include "../MainWidget.h" #include "../Ukui4MainWidget.h" #include "../ui_audio.h" Audio::Audio() : m_firstLoad(true) { #ifndef QT_NO_TRANSLATION QString translatorFileName = QLatin1String("qt_"); translatorFileName += QLocale::system().name(); QTranslator *pTranslator = new QTranslator(); if (pTranslator->load(translatorFileName, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) QApplication::installTranslator(pTranslator); #endif QTranslator *translator = new QTranslator(this); translator->load("/usr/share/ukui-media/translations/audio/audio_" + QLocale::system().name()); QApplication::installTranslator(translator); m_pluginName = tr("Audio"); m_pluginType = SYSTEM; } QString Audio::plugini18nName() { return m_pluginName; } int Audio::pluginTypes() { return m_pluginType; } QWidget* Audio::pluginUi() { if (m_firstLoad) { m_firstLoad = false; m_pWidget = new MainWidget(); } return m_pWidget; } bool Audio::isEnable() const { return true; } const QString Audio::name() const { return QStringLiteral("Audio"); } bool Audio::isShowOnHomePage() const { return true; } QIcon Audio::icon() const { return QIcon::fromTheme("audio-volume-high-symbolic"); } QString Audio::translationPath() const { return "/usr/share/ukui-media/translations/audio/audio_%1.ts"; } void Audio::initSearchText() { //~ contents_path /UkccPlugin/UkccPlugin tr("UkccPlugin"); //~ contents_path /UkccPlugin/ukccplugin test tr("ukccplugin test"); } ukui-volume-control/audio/ukui4.0/Ukui4SwitchButtonItem.h0000664000175000017500000000524715171074712022324 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 UKUI4SWITCHBUTTONITEM_H #define UKUI4SWITCHBUTTONITEM_H #include #include #include "ISwitchButtonItem.h" ///** //* @brief 包含SwitchButton子项的类型 //*/ //enum class SwitchButtonType //{ // SWITCH_BUTTON_VOLUMEBOOST = 0, // 音量增强子项 // SWITCH_BUTTON_MONO, // 单声道子项 // SWITCH_BUTTON_ECHOCANCEL, // 智能降噪子项 // SWITCH_BUTTON_LOOPBACK, // 侦听子项 // SWITCH_BUTTON_STARTUP, // 开机音乐子项 // SWITCH_BUTTON_POWEROFF, // 关机音乐子项 // SWITCH_BUTTON_LOGOUT, // 注销音乐子项 // SWITCH_BUTTON_WAKEUP, // 唤醒音乐子项 // SWITCH_BUTTON_ALERT // 提示音子项 //}; class Ukui4SwitchButton : public ISwitchButton { public: Ukui4SwitchButton(QPushButton*, const QString& = ""); ~Ukui4SwitchButton() = default; public: virtual bool isChecked() const override; virtual void setChecked(bool) override; virtual QWidget* getWidget() override; private: QPushButton* m_pSwitchBtn = nullptr; }; class Ukui4SwitchButtonItem : public QFrame, public ISwitchButtonItem { Q_OBJECT public: Ukui4SwitchButtonItem(std::shared_ptr, QLabel*, SwitchButtonType, QWidget* = nullptr); ~Ukui4SwitchButtonItem() = default; public: virtual void initUi() override; virtual void initSlots() override; virtual QWidget* getWidget() override; private Q_SLOTS: void stateChangedSlots(bool); }; class Ukui4SwitchButtonWithDescItem : public QFrame, public ISwitchButtonItem { Q_OBJECT public: Ukui4SwitchButtonWithDescItem(std::shared_ptr, QLabel*, QLabel*, SwitchButtonType, QWidget* = nullptr); public: virtual void initUi() override; virtual void initSlots() override; virtual QWidget* getWidget() override; private: QLabel* m_pDescLabel; }; #endif // UKUI4SWITCHBUTTONITEM_H ukui-volume-control/audio/MonitorStream.cpp0000664000175000017500000001473415171074712020066 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "MonitorStream.h" #include #include #include #include MonitorStream::MonitorStream(std::shared_ptr s) : m_pInputLevelBar(s) { init(); } void MonitorStream::init() { pa_glib_mainloop* m = pa_glib_mainloop_new(g_main_context_default()); m_pMainLoopApi = pa_glib_mainloop_get_api(m); pa_proplist* proplist = pa_proplist_new(); pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, "Ukui Media Volume Control"); pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, "org.ukui.media.vucontrol"); pa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, "ukui-control-center"); pa_proplist_sets(proplist, PA_PROP_APPLICATION_VERSION, "PACKAGE_VERSION"); m_pContext = pa_context_new_with_proplist(m_pMainLoopApi, nullptr, proplist); g_assert(m_pContext); pa_proplist_free(proplist); pa_context_set_state_callback(m_pContext, contextStateCallback, this); if (pa_context_connect(m_pContext, NULL, PA_CONTEXT_NOFLAGS, nullptr) < 0) { std::cout << "pa_context_connect failed." << std::endl; } } void MonitorStream::contextStateCallback(pa_context* ctx, void* data) { MonitorStream* m = static_cast(data); switch (pa_context_get_state(ctx)) { case PA_CONTEXT_CONNECTING: case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_SETTING_NAME: break; case PA_CONTEXT_READY: { m->m_bReady.store(true, std::memory_order_release); std::atomic_thread_fence(std::memory_order_release); break; } case PA_CONTEXT_TERMINATED: break; case PA_CONTEXT_FAILED: default: m->m_pInputLevelBar->setValue(0); // Anyway, make sure UI is correct first, right qDebug() << "Context error: " << pa_strerror(pa_context_errno(ctx)); } } void MonitorStream::readCallback(pa_stream *s, size_t length, void *userdata) { MonitorStream* m = static_cast(userdata); const void *data; double v; int index; index = pa_stream_get_device_index(s); if (pa_stream_peek(s, &data, &length) < 0) { qDebug() << "Failed to read data from stream"; return; } if (!data) { /* nullptr data means either a hole or empty buffer. * Only drop the stream when there is a hole (length > 0) */ if (length) pa_stream_drop(s); return; } assert(length > 0); assert(length % sizeof(float) == 0); v = ((const float*) data)[length / sizeof(float) -1]; pa_stream_drop(s); if (v < 0) v = 0; if (v > 1) v = 1; m->updateVolumeMeter(index, pa_stream_get_monitor_stream(s), v); } void MonitorStream::suspended_callback(pa_stream* s, void* userdata) { MonitorStream* m = static_cast(userdata); if (pa_stream_is_suspended(s)) m->updateVolumeMeter(pa_stream_get_device_index(s), PA_INVALID_INDEX, -1); } pa_stream* MonitorStream::createMonitorStreamForSource(uint32_t sourceIdx, uint32_t streamIdx = -1, bool suspend = false) { char t[16]; pa_buffer_attr attr; pa_sample_spec ss; pa_stream_flags_t flags; ss.channels = 1; ss.format = PA_SAMPLE_FLOAT32; ss.rate = 25; memset(&attr, 0, sizeof(attr)); attr.fragsize = sizeof(float); attr.maxlength = (uint32_t) -1; snprintf(t, sizeof(t), "%u", sourceIdx); if (!(m_pStream = pa_stream_new(m_pContext, "Peak detect", &ss, nullptr))) { qDebug() << "Create Peak detect failed..."; return nullptr; } if (streamIdx != (uint32_t) -1) pa_stream_set_monitor_stream(m_pStream, streamIdx); pa_stream_set_read_callback(m_pStream, readCallback, this); pa_stream_set_suspended_callback(m_pStream, suspended_callback, this); flags = (pa_stream_flags_t) (PA_STREAM_PEAK_DETECT | PA_STREAM_ADJUST_LATENCY | (suspend ? PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND : PA_STREAM_NOFLAGS)); int ret; if ((ret = pa_stream_connect_record(m_pStream, t, &attr, flags)) < 0) { qDebug() << "Failed to connect monitoring stream." << pa_strerror(pa_context_errno(m_pContext)); pa_stream_unref(m_pStream); return nullptr; } qDebug() << "createMonitorStreamForSource, index:" << sourceIdx << " sreamidx:" << pa_stream_get_index(m_pStream); return m_pStream; } void MonitorStream::updateVolumeMeter(uint32_t index, uint32_t sinkInputIdx, double v) { Q_UNUSED(index); Q_UNUSED(sinkInputIdx); if (m_lastPeak >= DECAY_STEP) if (v < m_lastPeak - DECAY_STEP) v = m_lastPeak - DECAY_STEP; m_lastPeak = v; m_pInputLevelBar->setValue(qRound(v * 100)); } void MonitorStream::startMonitor(uint32_t idx) { while(!m_bReady.load(std::memory_order_acquire)) { std::this_thread::yield(); } if (!m_pStream && m_bReady) std::async(std::launch::async, std::bind(&MonitorStream::createMonitorStreamForSource, this, idx, -1, !!(pa_source_flags{} & PA_SOURCE_NETWORK))); else qDebug() << QString("The Peak detect flow already exists and does not need to be created again."); } void MonitorStream::stopMonitor() { m_pInputLevelBar->setValue(0); // Anyway, make sure UI is correct first, right if (m_pStream) { auto idx = pa_stream_get_index(m_pStream); if (idx != PA_INVALID_INDEX) pa_context_kill_source_output(m_pContext, idx, nullptr, nullptr); pa_stream_unref(m_pStream); m_pStream = nullptr; } } void MonitorStream::wait(int timeout) { std::unique_lock lock(m_mutex); if (!m_bReady) { m_condition.wait_for(lock, std::chrono::milliseconds(timeout)); } } bool MonitorStream::isReady() const { return m_bReady; } MonitorStream::~MonitorStream() { } ukui-volume-control/audio/Ukcc5DevicePortItem.cpp0000664000175000017500000000404415171074712021032 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "Ukcc5DevicePortItem.h" #include #include Ukcc5DevicePortItem::Ukcc5DevicePortItem(QLabel* label, QLabel* iconLabel, const QString& devName, const QString& portName, QWidget* parent) : m_pDisplayLabel(label), m_pIconLabel(iconLabel), IDevicePortItem(devName, portName), QWidget(parent) { initUi(); } void Ukcc5DevicePortItem::initUi() { setFixedHeight(36); this->setContentsMargins(0, 0, 0, 0); m_pDisplayLabel->setFixedHeight(28); auto hLayout = new QHBoxLayout; hLayout->addItem(new QSpacerItem(2, 10, QSizePolicy::Fixed)); hLayout->addWidget(m_pDisplayLabel); hLayout->addStretch(); hLayout->addWidget(m_pIconLabel); hLayout->addItem(new QSpacerItem(16, 90, QSizePolicy::Fixed)); hLayout->setContentsMargins(0, 4, 0, 4); this->setLayout(hLayout); } bool Ukcc5DevicePortItem::isChecked() { return m_bIsChecked; } void Ukcc5DevicePortItem::setChecked(bool checked) { m_bIsChecked = checked; if (checked) { setIcon(QIcon::fromTheme("ukui-selected")); } else { setIcon(QIcon::fromTheme("")); } } void Ukcc5DevicePortItem::setIcon(const QIcon& icon) { m_pIconLabel->setPixmap(icon.pixmap(icon.actualSize(QSize(16, 16)))); } QWidget* Ukcc5DevicePortItem::getWidget() { return this; } ukui-volume-control/audio/translations/0000775000175000017500000000000015171074712017267 5ustar fengfengukui-volume-control/audio/translations/audio_zh_HK.ts0000664000175000017500000003101515171074712022023 0ustar fengfeng AppManagerItemWidget Confirm 確認 Output Volume 輸出音量 Output Device 輸出設備 Input Device 輸入設備 AppManagerMainWidget App Sound Control 應用聲音 None Audio Audio 聲音 UkccPlugin 測試外掛程式 /UkccPlugin/UkccPlugin ukccplugin test 外掛程式測試 /UkccPlugin/ukccplugin test AudioPulginUkui2 声音 DeviceManagerMainWidget Sound Equipment Control 聲音設備管理 Output Device 輸出設備 Input Device 輸入設備 DeviceManagerWidget Confirm 確認 MainWidget Audio 聲音 Unable to obtain system version information, please check the system version! 無法取得系統版本資訊,請檢查系統版本! None Multi Bluetooth Output 多藍牙輸出 Custom 自訂 QObject Output 輸出 /Audio/Output Input 輸入 /Audio/Input Sound Effect 系統音效 /Audio/Sound Effect Advance Settings 高級設置 /Audio/Advance Settings Volume Increase 音量增強 /Audio/Volume Increase Volume above 100% can cause sound distortion and damage your speakers 音量超過100%時可能會導致音效失真並損害你的揚聲器 Mono Audio 單聲道音訊 /Audio/Mono Audio It merges the left and right channels into one channel 會將左聲道和右聲道合併成一個聲道 Noise Reduction 智慧降噪 /Audio/Noise Reduction Voice Monitor 偵聽此設備 /Audio/Voice Monitor You can hear your voice in the output device of your choice 可以在所選輸出設備中聽到自己的聲音 Startup Misic 開機 /Audio/Startup Misic Poweroff Music 關機 /Audio/Poweroff Music Logout Music 註銷 /Audio/Logout Music Wakeup Music 喚醒 /Audio/Wakeup Music Beep Switch 提示音 /Audio/Beep Switch Pause playback when audio device is disconnected 斷開音訊設備時暫停播放 Volume 音量 /Audio/Volume Master Volume 音量 /Audio/Master Volume Input Level 音量反饋 /Audio/Input Level Balance 聲道平衡 /Audio/Balance Left Right Input Device 選擇用於講話或錄製的設備 /Audio/Input Device Output Device 選擇播放聲音的設備 /Audio/Output Device Sound Theme 音效主題 /Audio/Sound Theme Volume Control Sound 音量調節 /Audio/Volume Control Sound Notification Sound 接收通知 /Audio/Notification Sound Sound Equipment Control 聲音設備管理 /Audio/Sound Equipment Control App Sound Control 應用聲音 /Audio/App Sound Control Details 詳情 This case does not support setting the output device 該情況不支援輸出設備 This case does not support setting the input device 該情況不支援輸入設備 System Volume 系統 ukui-volume-control/audio/translations/audio_ky.ts0000664000175000017500000003360015171074712021445 0ustar fengfeng AppManagerItemWidget Confirm جەزىملەشتۈرمەك Output Volume ۅندۉرۉش ۅلچۅمۉ Output Device ۅندۉرۉش جابدۇۇسۇن تانداش Input Device كىيىرگىچ AppManagerMainWidget App Sound Control ئەپ دووش تىزگىندۅۅ جاسوو ،اتقارۇۇ پىروگىرامماسى None جوق Audio Audio دووش UkccPlugin /UkccPlugin/UkccPlugin ukccplugin test ukccplugin سىنىعى /UkccPlugin/ukccplugin test AudioPulginUkui2 声音 DeviceManagerMainWidget Sound Equipment Control دووش ئۈسكۈنىلىرىنى تىزگىندۅۅ جاسوو ،اتقارۇۇ Output Device ۅندۉرۉش جابدۇۇسۇن تانداش Input Device كىيىرگىچ DeviceManagerWidget Confirm جەزىملەشتۈرمەك MainWidget Audio دووش Unable to obtain system version information, please check the system version! ساامالىق باسماسى ۇچۇرۇنا ەە بولعولۇ بولبودۇ، ساامالىق باسماسى نى تەكشەرىپ باعىڭ! None جوق Multi Bluetooth Output كۅپ كۅك تىش ۅندۉرۉش Custom ۅزۉ بەكىتۉۉ QObject Output چىقرىىش /Audio/Output Input كىرگىزۉۉ /Audio/Input Sound Effect دووش ۅنۉمۉ /Audio/Sound Effect Advance Settings الدىن تەڭشۅۅ /Audio/Advance Settings Volume Increase دووشتۇ يۇقىرىلىتىش /Audio/Volume Increase Volume above 100% can cause sound distortion and damage your speakers وبونۇ %100 تىن جوعورۇ دووشتۇ بۇرمالاپ، كەرنەيدى بۇزۇپ قويوت Mono Audio مونو دوبۇش /Audio/Mono Audio It merges the left and right channels into one channel ال وڭ جانا سول قاناللارنى بىر لەشتىرىپ بىر قانالعا الماشتىرات Noise Reduction شاۋقۇننى اپتوماتتىك تۅمۅندۅتۉۉ /Audio/Noise Reduction Voice Monitor دووش كۉزۅتكۉچ /Audio/Voice Monitor You can hear your voice in the output device of your choice سىز ۅزۉڭۉز تاندالعان ۅندۉرۉش اسپابىنان ابازىڭىزدى ۇعا الاسىز Startup Misic مۇزىكانى قوزعوتۇۇ /Audio/Startup Misic Poweroff Music بەكىتىش /Audio/Poweroff Music Logout Music تىزىمدىكىتىن ۅچۉرۉۉ /Audio/Logout Music Wakeup Music ئويغىنىش /Audio/Wakeup Music Beep Switch ەسكەرتۉۉ وبونۇ /Audio/Beep Switch Pause playback when audio device is disconnected دوبۇش جابدۇۇسۇ ۉزۉلگۅندۅ قويۇنۇ توقتوتۇۇ Volume ھەجىمە /Audio/Volume Master Volume دووش /Audio/Master Volume Input Level كىرگىزۉۉ ئنكاسى /Audio/Input Level Balance جارىقتىق تەڭپۇڭلۇقىنى تەڭشۅۅ /Audio/Balance Left سولعو Right وڭ تاراپ Input Device كىيىرگىچ /Audio/Input Device Output Device ۅندۉرۉش جابدۇۇسۇن تانداش /Audio/Output Device Sound Theme دووش تەماسى /Audio/Sound Theme Volume Control Sound دووشتۇ تىزگىندۅۅ جاسوو ،اتقارۇۇ وبونۇ /Audio/Volume Control Sound Notification Sound ۇقتۇرۇۇ وبونۇ /Audio/Notification Sound Sound Equipment Control دووش ئۈسكۈنىلىرىنى تىزگىندۅۅ جاسوو ،اتقارۇۇ /Audio/Sound Equipment Control App Sound Control ئەپ دووش تىزگىندۅۅ جاسوو ،اتقارۇۇ پىروگىرامماسى /Audio/App Sound Control Details ىچكەلەي ، قۇنت قويۇپ ماتىرىيال This case does not support setting the output device بۇل تۉر مۇرۇن .جاعداي ۅندۉرۉش جابدۇۇسۇن تەڭشەشتى قولدوبويت This case does not support setting the input device بۇل تۉر مۇرۇن .جاعداي كىرگىزگىچتى تەڭشەشتى قولدوبويت System Volume ساامالىق وبونۇ ukui-volume-control/audio/translations/audio_bo_CN.ts0000664000175000017500000003700615171074712022006 0ustar fengfeng AppManagerItemWidget Confirm གཏན་འཁེལ་བྱ་དགོས། Output Volume ཕྱིར་གཏོང་གི་སྒྲ་ཚད། Output Device ཕྱིར་གཏོང་སྒྲིག་ཆས་ཀྱི་གདམ་གསེས། Input Device ནང་འཇུག་སྒྲིག་ཆས་ཀྱི་གདམ་གསེས། AppManagerMainWidget App Sound Control ཉེར་སྤྱོད་སྒྲ། None མེད Audio Audio སྒྲ་ཕབ། UkccPlugin ཚོད་ལྟའི་ལྷུ་ལག། /UkccPlugin/UkccPlugin ukccplugin test ལྷུ་ལག་ཡིག་སྒྱུར་ཚོད་ལྟ། /UkccPlugin/ukccplugin test AudioPulginUkui2 声音 DeviceManagerMainWidget Sound Equipment Control སྒྲའི་སྒྲིག་ཆས་དོ་དམ། Output Device ཕྱིར་གཏོང་སྒྲིག་ཆས་ཀྱི་གདམ་གསེས། Input Device ནང་འཇུག་སྒྲིག་ཆས་ཀྱི་གདམ་གསེས། DeviceManagerWidget Confirm གཏན་འཁེལ་བྱ་དགོས། MainWidget Audio སྒྲ་ཕབ། Unable to obtain system version information, please check the system version! མ་ལག་གི་པར་གཞིའི་ཆ་འཕྲིན་ཐོབ་ཐབས་མེད་པས་མ་ལག་གི་པར་གཞི་ལ་ཞིབ་བཤེར་གནང་རོགས། None མེད Multi Bluetooth Output སོ་སྔོན་པོ་མང་པོ་ཕྱིར་གཏོང་། Custom མཚན་ཉིད་རང་འཇོག། QObject Output ཕྱིར་འདྲེན། /Audio/Output Input ནང་འཇུག /Audio/Input Sound Effect རྒྱུད་རིམ་ལྡན་པའི་སྒྲ་གདངས་ཀྱི /Audio/Sound Effect Advance Settings མཐོ་རིམ་གྱི་བཀོད་སྒྲིག་བྱ་དགོས། /Audio/Advance Settings Volume Increase སྒྲ་ཚད་ཇེ་དྲག་ཏུ། /Audio/Volume Increase Volume above 100% can cause sound distortion and damage your speakers སྒྲ་ཚད་བརྒྱ་ཆ་100ལས་བརྒལ་ཚེ་སྒྲ་ནུས་ཉམས་པ་མ་ཟད་ཁྱོད་ཀྱི་སྒྲ་སྐྱེད་ཡོ་བྱད་ལ་གནོད་པ་བཟོ་སྲིད། Mono Audio སྒྲ་ལམ་རྐྱང་པའི་སྒྲ་ཟློས། /Audio/Mono Audio It merges the left and right channels into one channel གཡོན་སྒྲ་དང་གཡས་སྒྲ་གཉིས་ཟླ་སྒྲིལ་བཏང་ནས་སྐད་སྒྲ་ཞིག་ཏུ་འགྱུར་སྲིད། Noise Reduction རིག་ནུས་ཀྱི་འཛེར་སྒྲ་གཅོག་པ། /Audio/Noise Reduction Voice Monitor སྒྲིག་ཆས་འདི་ལ་ཉན་ཞིབ་། /Audio/Voice Monitor You can hear your voice in the output device of your choice འོན་ཀྱང་བདམས་པའི་ཕྱིར་འདོན་སྒྲིག་ཆས་ནང་ནས་རང་ཉིད་ཀྱི་སྐད་ཐོས་། Startup Misic ཁ་ཕྱེ་བ། /Audio/Startup Misic Poweroff Music གླུ་དབྱངས་སྒོ་བརྒྱབ་པ། /Audio/Poweroff Music Logout Music རོལ་དབྱངས་མེད་པར་བཟོ་བ། /Audio/Logout Music Wakeup Music འབོད་སློང་། /Audio/Wakeup Music Beep Switch སྣེ་སྟོན་པའི་སྒྲ། /Audio/Beep Switch Pause playback when audio device is disconnected སྒྲ་དབྱངས་ཀྱི་སྒྲིག་ཆས་མཚམས་བཞག་ནས་གཏོང་མཚམས་འཇོག་དགོས། Volume སྒྲ་ཚད། /Audio/Volume Master Volume སྒྲ་ཚད། /Audio/Master Volume Input Level སྒྲ་ཚད་ཕྱིར་ལན། /Audio/Input Level Balance སྒྲ་ལམ་དོ་མཉམ། /Audio/Balance Left གཡོན་ལོགས་སུ་ཡོད། Right གཡས་ལོགས་སུ་ཡོད། Input Device ནང་འཇུག་སྒྲིག་ཆས་ཀྱི་གདམ་གསེས། /Audio/Input Device Output Device ཕྱིར་གཏོང་སྒྲིག་ཆས་ཀྱི་གདམ་གསེས། /Audio/Output Device Sound Theme སྒྲ་ནུས་ཀྱི་བརྗོད་བྱ། /Audio/Sound Theme Volume Control Sound སྒྲ་ཚད་སྙོམ་སྒྲིག /Audio/Volume Control Sound Notification Sound བསྡུ་ལེན་བརྡ་ཐོ། /Audio/Notification Sound Sound Equipment Control སྒྲའི་སྒྲིག་ཆས་དོ་དམ། /Audio/Sound Equipment Control App Sound Control ཉེར་སྤྱོད་སྒྲ། /Audio/App Sound Control Details ཞིབ་ཕྲའི་གནས་ཚུལ། This case does not support setting the output device གནས་ཚུལ་འདིར་ཕྱིར་གཏོང་སྒྲིག་ཆས་ལ་རྒྱབ་སྐྱོར་མི་བྱེད་པ་། This case does not support setting the input device གནས་ཚུལ་འདིར་ནང་འཇུག་སྒྲིག་ཆས་ལ་རྒྱབ་སྐྱོར་བྱེད་མི་ཐུབ། System Volume མ་ལག་གི་བོངས་ཚད། ukui-volume-control/audio/translations/audio_kk.ts0000664000175000017500000003315615171074712021435 0ustar fengfeng AppManagerItemWidget Confirm كەسٸم جاساۋ Output Volume جاريالاۋ كولەمى Output Device جاريالاۋ اسبابٸن تالداۋ Input Device كىرگىزگىش AppManagerMainWidget App Sound Control جيۋ اۋا مەڭگەرۋ ەتۋ پٸروگٸرامماسٸ None جوق Audio Audio ۇن UkccPlugin /UkccPlugin/UkccPlugin ukccplugin test ukccplugin سىنىعى /UkccPlugin/ukccplugin test AudioPulginUkui2 声音 DeviceManagerMainWidget Sound Equipment Control اۋا جابدىقتارىن مەڭگەرۋ ەتۋ Output Device جاريالاۋ اسبابٸن تالداۋ Input Device كىرگىزگىش DeviceManagerWidget Confirm كەسٸم جاساۋ MainWidget Audio ۇن Unable to obtain system version information, please check the system version! سەستيما باسىلىمى حابارعا يە بولعالٸ بولمادى، سەستيما باسىلىمىنى تەكسەرٸپ كور! None جوق Multi Bluetooth Output كوپ كۆكچىش جاريالاۋ Custom ٶزى بەلگٸلەۋ QObject Output جاريالاۋ /Audio/Output Input كىرگىزگىش /Audio/Input Sound Effect اۋا ٴونٸمٸ /Audio/Sound Effect Advance Settings الدىن تەڭشەۋ /Audio/Advance Settings Volume Increase دٸبٸستٸ جوعارتىلۋ /Audio/Volume Increase Volume above 100% can cause sound distortion and damage your speakers وبونۇ %100 تىن جوعورۇ دووشتۇ بۇرمالاپ، كەرنەيدى بۇزۇپ قويوت Mono Audio مونو ۇن /Audio/Mono Audio It merges the left and right channels into one channel ول وڭ ۋا سول كانالداردى بٸرلەستٸرٸپ بٸر كانالعا ايلاندٸرادٸ Noise Reduction شۋىل اۆتوماتتى تومەندەتۋ /Audio/Noise Reduction Voice Monitor اۋا كۇزەتۋشى /Audio/Voice Monitor You can hear your voice in the output device of your choice ٴسىز ٶزٸڭٸز تالداعان جاريالاۋ اسپابىنان داۋسىڭىزدى ئاڭلىيالايٴسىز Startup Misic مۋزيكانى قوزعالتۋ /Audio/Startup Misic Poweroff Music تاقاۋ /Audio/Poweroff Music Logout Music تٸزٸمدەن ٴوشىرۋ /Audio/Logout Music Wakeup Music ئويغىنىش /Audio/Wakeup Music Beep Switch ەسكەرتپەۋ داۋىسى /Audio/Beep Switch Pause playback when audio device is disconnected ۇن اسپابٸ ٷزٸلگەندە قويۋدى توقتاتۋ Volume يۆگەم /Audio/Volume Master Volume اۋا /Audio/Master Volume Input Level كىرگىزۋ جاۋاب قايتارۋٸ /Audio/Input Level Balance جارىقتىق تەڭپۇڭلۇقىنى تەڭشۅۅ /Audio/Balance Left سولعا Right وڭ جاق Input Device كىرگىزگىش /Audio/Input Device Output Device جاريالاۋ اسبابٸن تالداۋ /Audio/Output Device Sound Theme اۋا تەمەسى /Audio/Sound Theme Volume Control Sound دٸبٸستٸ مەڭگەرۋ ەتۋ داۋىسى /Audio/Volume Control Sound Notification Sound ۇقتٸرۋ داۋىسى /Audio/Notification Sound Sound Equipment Control اۋا جابدىقتارىن مەڭگەرۋ ەتۋ /Audio/Sound Equipment Control App Sound Control جيۋ اۋا مەڭگەرۋ ەتۋ پٸروگٸرامماسٸ /Audio/App Sound Control Details ناقتى مازمۇنى This case does not support setting the output device نۇ ٴتۇر احاۋل جاريالاۋ اسبابٸن تەڭشەۋدٸ قولدامايدى This case does not support setting the input device نۇ ٴتۇر احاۋل كىرگىزۋ اسپابىن تەڭشەۋدٸ قولدامايدى System Volume سەستيما داۋىسى ukui-volume-control/audio/translations/audio_ug.ts0000664000175000017500000003357015171074712021443 0ustar fengfeng AppManagerItemWidget Confirm بەكىتۉۉ Output Volume چىقىرىش مىقدارى Output Device چىقىرىش ئۈسكۈنىسىنى تاللاش Input Device كىرگۈزگۈچ AppManagerMainWidget App Sound Control ئەپ ئاۋاز كونترول قىلىش پروگراممىسى None جوق Audio Audio دووش UkccPlugin قىستۇرما دېتالنى سىناپ باقماق /UkccPlugin/UkccPlugin ukccplugin test ukccplugin سىنىقى /UkccPlugin/ukccplugin test AudioPulginUkui2 声音 DeviceManagerMainWidget Sound Equipment Control ئاۋاز ئۈسكۈنىلىرىنى كونترول قىلىش Output Device چىقىرىش ئۈسكۈنىسىنى تاللاش Input Device كىرگۈزگۈچ DeviceManagerWidget Confirm بەكىتۉۉ MainWidget Audio دووش Unable to obtain system version information, please check the system version! سىستېما نۇسخىسىدىكى ئۇچۇرغا ئېرىشكىلى بولمايدۇ، سىستېما نۇسخىسىنى تەكشۈرۈڭ! None جوق Multi Bluetooth Output كۆپ كۆك چىش چىقىرىش Custom ئۆزى بېكىتىش QObject Output چىقرىىش /Audio/Output Input كىرگۈز /Audio/Input Sound Effect سىستىما ئاۋاز ئۈنۈمى /Audio/Sound Effect Advance Settings ئالىي دەرىجىلىك تەسىس قىلىش /Audio/Advance Settings Volume Increase ئاۋازنى يۇقىرىلىتىش /Audio/Volume Increase Volume above 100% can cause sound distortion and damage your speakers ئاۋاز يۈز پىرسەنتتىن ئاشقاندا ئاۋاز ئۈنۈمىنى يوقىتىش ھەم ئاۋاز ياڭراتقۇڭىزغا زىيان يەتكۈزىدۇ Mono Audio مونو ئۈن /Audio/Mono Audio It merges the left and right channels into one channel سول ئاۋاز يولى بىلەن ئوڭ ئاۋاز يولىنى بىرلەشتۈرۈپ بىر ئاۋاز يولى قىلىدۇ Noise Reduction شاۋقۇننى ئاپتوماتىك تۆۋەنلىتىش /Audio/Noise Reduction Voice Monitor ئاۋاز كۆزەتكۈچ /Audio/Voice Monitor You can hear your voice in the output device of your choice سىز ئۆزىڭىز تاللىغان چىقىرىش ئۈسكۈنىسىدىن ئاۋازىڭىزنى ئاڭلىيالايسىز Startup Misic تېلېفوننى ئېچىڭ /Audio/Startup Misic Poweroff Music تاقاش /Audio/Poweroff Music Logout Music تىزىمدىن ئۆچۈرۈش /Audio/Logout Music Wakeup Music ئويغىنىش /Audio/Wakeup Music Beep Switch ئەسكەرتىش ئاۋازى /Audio/Beep Switch Pause playback when audio device is disconnected ئاۋاز ئۈسكۈنىسىنى ئۈزۈپ قويۇش ۋاقتىنچە توختىتىلدى Volume يۆگەم /Audio/Volume Master Volume ئاۋاز /Audio/Master Volume Input Level كىرگۈزۈش ئىنكاسى /Audio/Input Level Balance يورۇقلۇق تەڭپۇڭلۇقىنى تەڭشەش /Audio/Balance Left سولغا Right ئوڭ Input Device كىرگۈزگۈچ /Audio/Input Device Output Device چىقىرىش ئۈسكۈنىسىنى تاللاش /Audio/Output Device Sound Theme ئاۋاز تېمىسى /Audio/Sound Theme Volume Control Sound ئاۋازنى كونترول قىلىش ئاۋازى /Audio/Volume Control Sound Notification Sound ئۇقتۇرۇش ئاۋازى /Audio/Notification Sound Sound Equipment Control ئاۋاز ئۈسكۈنىلىرىنى كونترول قىلىش /Audio/Sound Equipment Control App Sound Control ئەپ ئاۋاز كونترول قىلىش پروگراممىسى /Audio/App Sound Control Details تەپسىلىي ماتېرىيال This case does not support setting the output device بۇ خىل ئەھۋال چىقىرىش ئۈسكۈنىسىنى تەڭشەشنى قوللىمايدۇ This case does not support setting the input device بۇ خىل ئەھۋال كىرگۈزگۈچنى تەڭشەشنى قوللىمايدۇ System Volume سىستېما ئاۋازى ukui-volume-control/audio/translations/audio_mn.ts0000664000175000017500000004016615171074712021441 0ustar fengfeng AppManagerItemWidget Confirm ᠨᠤᠲᠠᠯᠠᠨ ᠲᠣᠭᠲᠠᠭᠠᠬᠤ ᠃ Output Volume ᠭᠠᠷᠭᠠᠬᠤ ᠳᠠᠭᠤ Output Device ᠳᠠᠭᠤ ᠨᠡᠪᠲᠡᠷᠡᠭᠦᠯᠬᠦ ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ᠎ᠶ᠋ᠢ ᠰᠣᠩᠭᠣᠬᠤ Input Device ᠶᠠᠷᠢᠶᠠᠨ᠎ᠳ᠋ᠤ᠌ ᠬᠡᠷᠡᠭᠯᠡᠬᠦ ᠪᠤᠶᠤ ᠰᠢᠩᠭᠡᠭᠡᠵᠦ ᠦᠢᠯᠡᠳᠬᠦ ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ᠎ᠶ᠋ᠢ ᠰᠣᠩᠭᠣᠬᠤ AppManagerMainWidget App Sound Control ᠬᠡᠷᠡᠭᠯᠡᠭᠡᠨ᠎ᠦ᠌ ᠳᠠᠭᠤ None ᠪᠠᠢᠬᠤ ᠦᠬᠡᠢ Audio Audio ᠠᠦ᠋ᠳᠢᠤ᠋ UkccPlugin Ukccc ᠵᠠᠯᠭᠠᠰᠤ /UkccPlugin/UkccPlugin ukccplugin test ᠤᠭᠯᠠᠭᠤᠷᠭ᠎ᠠ ᠲᠣᠨᠣᠭ᠎ᠤ᠋ᠨ ᠰᠣᠷᠢᠯᠲᠠ /UkccPlugin/ukccplugin test AudioPulginUkui2 声音 DeviceManagerMainWidget Sound Equipment Control ᠳᠠᠭᠤᠨ᠎ᠤ᠋ ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ᠎ᠶ᠋ᠢᠨ ᠬᠠᠮᠢᠶᠠᠷᠤᠯᠲᠠ Output Device ᠳᠠᠭᠤ ᠨᠡᠪᠲᠡᠷᠡᠭᠦᠯᠬᠦ ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ᠎ᠶ᠋ᠢ ᠰᠣᠩᠭᠣᠬᠤ Input Device ᠶᠠᠷᠢᠶᠠᠨ᠎ᠳ᠋ᠤ᠌ ᠬᠡᠷᠡᠭᠯᠡᠬᠦ ᠪᠤᠶᠤ ᠰᠢᠩᠭᠡᠭᠡᠵᠦ ᠦᠢᠯᠡᠳᠬᠦ ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ᠎ᠶ᠋ᠢ ᠰᠣᠩᠭᠣᠬᠤ DeviceManagerWidget Confirm ᠨᠤᠲᠠᠯᠠᠨ ᠲᠣᠭᠲᠠᠭᠠᠬᠤ ᠃ MainWidget Audio ᠠᠦ᠋ᠳᠢᠤ᠋ Unable to obtain system version information, please check the system version! ᠰᠢᠰᠲ᠋ᠧᠮ᠎ᠦ᠋ᠨ ᠬᠡᠪᠯᠡᠯ᠎ᠦ᠋ᠨ ᠵᠠᠩᠭᠢ᠎ᠶ᠋ᠢ ᠣᠯᠬᠤ᠎ᠶ᠋ᠢᠨ ᠠᠷᠭ᠎ᠠ ᠥᠬᠡᠢ ᠂ ᠰᠢᠰᠲ᠋ᠧᠮ᠎ᠦ᠋ᠨ ᠬᠡᠪᠯᠡᠯ᠎ᠢ᠋ ᠪᠠᠢᠴᠠᠭᠠᠷᠠᠢ ! None ᠪᠠᠢᠬᠤ ᠦᠬᠡᠢ Multi Bluetooth Output ᠣᠯᠠᠨ ᠯᠠᠨᠶᠠ ᠭᠠᠷᠭᠠᠯᠳᠠ Custom ᠦᠪᠡᠷᠳᠡᠭᠡᠨ ᠳᠤᠳᠤᠷᠬᠠᠢᠯᠠᠬᠤ QObject Output ᠭᠠᠷᠭᠠᠬᠤ /Audio/Output Input ᠣᠷᠣᠭᠤᠯᠬᠤ /Audio/Input Sound Effect ᠰᠢᠰᠲ᠋ᠧᠮ᠎ᠦ᠋ᠨ ᠳᠠᠭᠤᠨ ᠦᠢᠯᠡᠳᠦᠯ /Audio/Sound Effect Advance Settings ᠳᠡᠭᠡᠳᠦ ᠵᠡᠷᠭᠡ᠎ᠶ᠋ᠢᠨ ᠲᠤᠬᠢᠷᠠᠭᠤᠯᠤᠯ /Audio/Advance Settings Volume Increase ᠳᠠᠭᠤᠨ᠎ᠤ᠋ ᠬᠡᠮᠵᠢᠶ᠎ᠡ ᠴᠢᠩᠭᠠᠷᠠᠬᠤ /Audio/Volume Increase Volume above 100% can cause sound distortion and damage your speakers ᠳᠠᠭᠤᠨ᠎ᠤ᠋ ᠬᠡᠮᠵᠢᠶ᠎ᠡ 100%᠎ᠠ᠋ᠴᠠ ᠬᠡᠲᠦᠷᠡᠬᠦ ᠦᠶ᠎ᠡ᠎ᠳ᠋ᠦ᠍ ᠳᠠᠭᠤ ᠦᠨᠡᠨ ᠪᠠᠢᠳᠠᠯ᠎ᠢ᠋ ᠠᠯᠳᠠᠭᠤᠯᠵᠤ ᠂ ᠴᠢᠨᠦ ᠳᠠᠭᠤ ᠥᠰᠬᠡᠭᠴᠢ᠎ᠶ᠋ᠢ ᠬᠣᠬᠢᠷᠠᠭᠤᠯᠬᠤ ᠪᠣᠯᠤᠨ᠎ᠠ Mono Audio ᠳᠠᠩ ᠳᠠᠭᠤᠨ ᠵᠠᠮ᠎ᠤ᠋ᠨ ᠳᠠᠭᠤᠨ ᠳᠠᠪᠲᠠᠮᠵᠢ /Audio/Mono Audio It merges the left and right channels into one channel ᠵᠡᠭᠦᠨ ᠳᠠᠭᠤᠨ ᠪᠠ ᠪᠠᠷᠠᠭᠤᠨ ᠳᠠᠭᠤᠨ ᠵᠠᠮ᠎ᠢ᠋ ᠨᠢᠭᠡ ᠳᠠᠭᠤᠨ ᠵᠠᠮ ᠪᠣᠯᠭᠠᠨ ᠨᠡᠢᠯᠡᠭᠦᠯᠦᠨ᠎ᠡ Noise Reduction ᠣᠶᠤᠨᠲᠤ ᠎ᠪᠡᠷ ᠱᠤᠤᠭᠢᠶᠠᠨ ᠪᠠᠭᠤᠷᠠᠭᠤᠯᠬᠤ /Audio/Noise Reduction Voice Monitor ᠲᠤᠰ ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ᠎ᠶ᠋ᠢ ᠲᠠᠩᠨᠠᠨ ᠰᠣᠨᠣᠰᠬᠤ /Audio/Voice Monitor You can hear your voice in the output device of your choice ᠰᠣᠩᠭᠣᠵᠤ ᠭᠠᠷᠭᠠᠭᠰᠠᠨ ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ ᠳᠣᠲᠣᠷ᠎ᠠ ᠥᠪᠡᠷ᠎ᠦ᠋ᠨ ᠳᠠᠭᠤ᠎ᠶ᠋ᠢ ᠣᠯᠵᠤ ᠰᠣᠨᠣᠰᠤᠨ᠎ᠠ Startup Misic ᠨᠡᠭᠡᠭᠡᠬᠦ /Audio/Startup Misic Poweroff Music ᠬᠠᠭᠠᠬᠤ /Audio/Poweroff Music Logout Music ᠳᠠᠩᠰᠠ᠎ᠠ᠋ᠴᠠ ᠬᠠᠰᠤᠬᠤ /Audio/Logout Music Wakeup Music ᠳᠠᠭᠤᠳᠠᠨ ᠰᠡᠷᠭᠦᠭᠡᠬᠦ /Audio/Wakeup Music Beep Switch ᠰᠠᠨᠠᠭᠤᠯᠤᠮᠵᠢ /Audio/Beep Switch Pause playback when audio device is disconnected ᠳᠠᠭᠤᠨ ᠳᠠᠪᠲᠠᠮᠵᠢ᠎ᠶ᠋ᠢᠨ ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ᠎ᠶ᠋ᠢ ᠲᠠᠰᠤᠯᠠᠬᠤ ᠦᠶ᠎ᠡ᠎ᠳ᠋ᠦ᠍ ᠨᠡᠪᠲᠡᠷᠡᠭᠦᠯᠬᠦ᠎ᠶ᠋ᠢ ᠲᠦᠷ ᠵᠣᠭᠰᠣᠭᠠᠬᠤ Volume ᠡᠪᠬᠡᠮᠡᠯ /Audio/Volume Master Volume ᠳᠠᠭᠤ /Audio/Master Volume Input Level ᠳᠠᠭᠤᠨ᠎ᠤ᠋ ᠬᠡᠮᠵᠢᠶ᠎ᠡ᠎ᠶ᠋ᠢᠨ ᠪᠤᠴᠠᠯᠲᠠ /Audio/Input Level Balance ᠭᠡᠷᠡᠯᠲᠦᠴᠡ᠎ᠶ᠋ᠢᠨ ᠲᠡᠩᠴᠡᠭᠦᠷᠢ᠎ᠶ᠋ᠢᠨ ᠲᠣᠬᠢᠷᠠᠭᠤᠯᠭ᠎ᠠ /Audio/Balance Left ᠵᠡᠭᠦᠨ Right ᠪᠠᠷᠠᠭᠤᠨ ᠲᠠᠯ᠎ᠠ Input Device ᠶᠠᠷᠢᠶᠠᠨ᠎ᠳ᠋ᠤ᠌ ᠬᠡᠷᠡᠭᠯᠡᠬᠦ ᠪᠤᠶᠤ ᠰᠢᠩᠭᠡᠭᠡᠵᠦ ᠦᠢᠯᠡᠳᠬᠦ ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ᠎ᠶ᠋ᠢ ᠰᠣᠩᠭᠣᠬᠤ /Audio/Input Device Output Device ᠳᠠᠭᠤ ᠨᠡᠪᠲᠡᠷᠡᠭᠦᠯᠬᠦ ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ᠎ᠶ᠋ᠢ ᠰᠣᠩᠭᠣᠬᠤ /Audio/Output Device Sound Theme ᠳᠠᠭᠤᠨ᠎ᠤ᠋ ᠭᠣᠣᠯ ᠰᠡᠳᠦᠪ /Audio/Sound Theme Volume Control Sound ᠳᠠᠭᠤ ᠲᠣᠬᠢᠷᠠᠭᠤᠯᠬᠤ /Audio/Volume Control Sound Notification Sound ᠮᠡᠳᠡᠭᠳᠡᠯ ᠬᠦᠯᠢᠶᠡᠨ ᠠᠪᠬᠤ /Audio/Notification Sound Sound Equipment Control ᠳᠠᠭᠤᠨ᠎ᠤ᠋ ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ᠎ᠶ᠋ᠢᠨ ᠬᠠᠮᠢᠶᠠᠷᠤᠯᠲᠠ /Audio/Sound Equipment Control App Sound Control ᠬᠡᠷᠡᠭᠯᠡᠭᠡᠨ᠎ᠦ᠌ ᠳᠠᠭᠤ /Audio/App Sound Control Details ᠨᠠᠷᠢᠨ ᠰᠤᠷᠠᠭ ᠵᠠᠩᠬᠢ This case does not support setting the output device ᠲᠤᠰ ᠪᠠᠢᠳᠠᠯ ᠨᠢ ᠭᠠᠷᠭᠠᠬᠤ ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ ᠲᠣᠬᠢᠷᠠᠭᠤᠯᠬᠤ᠎ᠶ᠋ᠢ ᠳᠡᠮᠵᠢᠬᠦ ᠦᠭᠡᠢ This case does not support setting the input device ᠲᠤᠰ ᠪᠠᠢᠳᠠᠯ ᠨᠢ ᠣᠷᠣᠭᠤᠯᠬᠤ ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ ᠪᠠᠢᠭᠤᠯᠬᠤ᠎ᠶ᠋ᠢ ᠳᠡᠮᠵᠢᠬᠦ ᠦᠭᠡᠢ System Volume ᠰᠢᠰᠲᠸᠮ ukui-volume-control/audio/translations/audio_zh_CN.ts0000664000175000017500000003100415171074712022017 0ustar fengfeng AppManagerItemWidget Confirm 确认 Output Volume 输出音量 Output Device 输出设备 Input Device 输入设备 AppManagerMainWidget App Sound Control 应用声音 None Audio Audio 声音 UkccPlugin 测试插件 /UkccPlugin/UkccPlugin ukccplugin test 插件测试 /UkccPlugin/ukccplugin test AudioPulginUkui2 声音 DeviceManagerMainWidget Sound Equipment Control 声音设备管理 Output Device 输出设备 Input Device 输入设备 DeviceManagerWidget Confirm 确认 MainWidget Audio 声音 Unable to obtain system version information, please check the system version! 无法获取系统版本信息,请检查系统版本! None Multi Bluetooth Output 多蓝牙输出 Custom 自定义 QObject Output 输出 /Audio/Output Input 输入 /Audio/Input Sound Effect 系统音效 /Audio/Sound Effect Advance Settings 高级设置 /Audio/Advance Settings Volume Increase 音量增强 /Audio/Volume Increase Volume above 100% can cause sound distortion and damage your speakers 音量超过100%时可能会导致音效失真并损害你的扬声器 Mono Audio 单声道音频 /Audio/Mono Audio It merges the left and right channels into one channel 会将左声道和右声道合并成一个声道 Noise Reduction 智能降噪 /Audio/Noise Reduction Voice Monitor 侦听此设备 /Audio/Voice Monitor You can hear your voice in the output device of your choice 可以在所选输出设备中听到自己的声音 Startup Misic 开机 /Audio/Startup Misic Poweroff Music 关机 /Audio/Poweroff Music Logout Music 注销 /Audio/Logout Music Wakeup Music 唤醒 /Audio/Wakeup Music Beep Switch 提示音 /Audio/Beep Switch Pause playback when audio device is disconnected 断开音频设备时暂停播放 Volume 音量 /Audio/Volume Master Volume 音量 /Audio/Master Volume Input Level 音量反馈 /Audio/Input Level Balance 声道平衡 /Audio/Balance Left Right Input Device 选择用于讲话或录制的设备 /Audio/Input Device Output Device 选择播放声音的设备 /Audio/Output Device Sound Theme 音效主题 /Audio/Sound Theme Volume Control Sound 音量调节 /Audio/Volume Control Sound Notification Sound 接收通知 /Audio/Notification Sound Sound Equipment Control 声音设备管理 /Audio/Sound Equipment Control App Sound Control 应用声音 /Audio/App Sound Control Details 详情 This case does not support setting the output device 该情况不支持输出设备 This case does not support setting the input device 该情况不支持输入设备 System Volume 系统 ukui-volume-control/audio/TitleLabelItem.cpp0000664000175000017500000000231015171074712020106 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "TitleLabelItem.h" #include TitleLabelItem::TitleLabelItem(QLabel* label, QWidget* parent) : m_pLabel(label), QWidget(parent) { initUi(); } void TitleLabelItem::initUi() { m_pLabel->setFixedHeight(28); auto hLayout = new QHBoxLayout; hLayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); hLayout->addWidget(m_pLabel); setLayout(hLayout); } QWidget* TitleLabelItem::getWidget() { return this; } ukui-volume-control/audio/ISwitchButtonItem.cpp0000664000175000017500000000225015171074712020636 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "ISwitchButtonItem.h" #include ISwitchButtonItem::ISwitchButtonItem(std::shared_ptr btn, QLabel* label, SwitchButtonType type) : m_pSwitchBtn(btn), m_pDisplayLabel(label), m_type(type) { } void ISwitchButtonItem::setChecked(bool checked) { m_pSwitchBtn->setChecked(checked); } std::shared_ptr ISwitchButtonItem::getSwitchButton() const { return m_pSwitchBtn; } ukui-volume-control/audio/audio.ui0000664000175000017500000000206015171074677016216 0ustar fengfeng Audio 0 0 800 710 0 0 16777215 16777215 Audio 0 0 0 32 48 ukui-volume-control/audio/AppManagerMainWidget.h0000664000175000017500000000473215171074712020711 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 APPMANAGERMAINWIDGET_H #define APPMANAGERMAINWIDGET_H #include #include #include #include #include "AppManagerItemWidget.h" #include "DBusClient.h" class AppManagerMainWidget : public kdk::KWidget { Q_OBJECT public: AppManagerMainWidget(std::list&, QWidget* = nullptr); ~AppManagerMainWidget() = default; public: void addItem(int, int, int, bool, const QString&, const QString&, const QList&, const QList&); void removeItem(const QString&); void setValue(const QString&, int); void setMute(const QString&, bool); void setCurrentItem(int); void setCurrentIndex(const QString&, const NodeType&, int); void addPortToAppItem(const QString&, const NodeType&, const QString&, const QVariant&); void setAppCbxEnabled(const QString&, const NodeType&, bool); void removeProtFromAppItem(const QString&, const NodeType&, int); std::list& getAppItems(); AppManagerItemWidget* getAppWidgetByIndex(int); Q_SIGNALS: void valueChangedSignals(int, int); void muteChangedSignals(int); void currentIndexChangedSignals(const NodeType&, int, const QString&, const QString&); void closeWindowSignals(); private: void initUi(); void initSlots(); private: kdk::KNavigationBar* m_pNavigationBar {}; QStackedWidget* m_pStackedWidget {}; std::map m_itemMap {}; std::list& m_appWidgetList; QStringList m_blackList { "sound-theme-player", "ukui-control-center", "Echo-Cancel Sink Stream", "Echo-Cancel Source Stream" }; }; #endif // APPMANAGERMAINWIDGET_H ukui-volume-control/audio/IAudioMainWidow.h0000664000175000017500000003113315171074712017711 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 IAUDIOMAINWINDOW_H #define IAUDIOMAINWINDOW_H #include #include #include "DBusClient.h" #include "ClientManager.h" #include "MonitorStream.h" #include "AppManagerItemWidget.h" #include "AppManagerMainWidget.h" typedef struct DevicePortInfo { uint32_t index; // 处于控件的位置 QString cardName; // 声卡名称 QString portName; // 设备端口名称 } DevicePortInfo; class IAudioMainWindow { public: IAudioMainWindow(std::list&); virtual ~IAudioMainWindow() = default; public: using VolumeChangedCallback = std::function; using BalanceVolumeChangedCallback = std::function; using MuteChangedCallback = std::function; using DeviceChangedCallback = std::function; using SettingsChangedCallback = std::function; using DetailSettingsClickedCallback = std::function; using EnableDeviceCallback = std::function; std::shared_ptr addVolumeObserver(const VolumeChangedCallback&); void removeVolumeObserver(const std::shared_ptr&); void setVolume(int, int, int); std::shared_ptr addBalanceVolumeObserver(const BalanceVolumeChangedCallback&); void removeBalanceVolumeObserver(const std::shared_ptr&); void setBalance(int, double); std::shared_ptr addMuteObserver(const MuteChangedCallback&); void removeMuteObserver(const std::shared_ptr&); void setMute(int, int); std::shared_ptr addDeviceObserver(const DeviceChangedCallback&); void removeDeviceObserver(const std::shared_ptr&); void setDefaultDevice(int, int, const QString&, const QString&); std::shared_ptr addSettingsObserver(const SettingsChangedCallback&); void removeSettingsObserver(const std::shared_ptr&); void setSettingsValue(const QString&, const QVariant&); std::shared_ptr addDetailSettingsObserver(const DetailSettingsClickedCallback&); void removeDetailSettingsObserver(const std::shared_ptr&); void setDetailSettings(int); std::shared_ptr addEnableDeviceObserver(const EnableDeviceCallback&); void removeEnableDeviceObserver(const std::shared_ptr&); void setEnabled(const QString&, const QString&, bool); void startMonitor(uint32_t); void stopMonitor(); AppManagerMainWidget* getAppManagerWidget() const; public: virtual void initUi() = 0; virtual void initSlots() = 0; virtual void setCurrentIndex(int, int) = 0; virtual void setAppCurrentIndex(const QString&, const NodeType&, int) = 0; virtual void setCurrentAppItem(int) = 0; virtual void setAppCbxEnabled(const QString&, const NodeType&, bool) = 0; virtual void setAppValue(const QString&, int) = 0; virtual void setValue(int, int) = 0; virtual void setAppMute(const QString&, bool) = 0; virtual void setChecked(int, bool) = 0; virtual std::list getDevicePortList(int) const = 0; virtual std::list getDeviceManagetPortList(int) const = 0; virtual std::list& getAppItemList() = 0; virtual std::list getAppManagetPortList(int, int) const = 0; virtual QWidget* getWidget() = 0; virtual void addDeviceManagerItem(int, const QString&, const QVariant&) = 0; virtual void removeDeviceManagerItem(int, int) = 0; virtual void setDeviceManagerItemStatus(int, int, bool) = 0; virtual void updateUIByButtonStatus(int, bool) = 0; virtual void updateUIByDevice(int type, bool status) = 0; virtual void addAppManagerItem(int, int, int, bool, const QString&, const QString&, const QList&, const QList&) = 0; virtual void removeAppManagerItem(const QString&) = 0; virtual void addPortToAppItem(const QString&, const NodeType&, const QString&, const QVariant&) = 0; virtual void removePortFromAppItem(const QString&, const NodeType&, int) = 0; private: void volumeNotify(int, int, int); void balanceVolumeNotify(int, double); void muteNotify(int, int); void defaultDeviceNotify(int, int, const QString&, const QString&); void settingsChangedNotify(const QString&, const QVariant&); void detailSettingsClickNotify(int); void enableDeviceNotify(const QString&, const QString&, bool); protected: std::shared_ptr m_pClientManager {nullptr}; std::shared_ptr m_pMonitorStream {nullptr}; AppManagerMainWidget* m_pAppManagerWidget = nullptr; std::list& m_appWidgetList; std::unordered_map m_switchMethodKeys { {SwitchButtonType::SWITCH_BUTTON_LOOPBACK, "setLoopbackStatus"}, {SwitchButtonType::SWITCH_BUTTON_MONO, "setMonoStatus"}, {SwitchButtonType::SWITCH_BUTTON_ECHOCANCEL, "setEchoCancelStatus"}, {SwitchButtonType::SWITCH_BUTTON_VOLUMEBOOST, "setVolumeBoostStatus"}, {SwitchButtonType::SWITCH_BUTTON_ALERT, "setAlertStatus"}, {SwitchButtonType::SWITCH_BUTTON_STARTUP, "setStartupStatus"}, {SwitchButtonType::SWITCH_BUTTON_POWEROFF, "setPoweroffStatus"}, {SwitchButtonType::SWITCH_BUTTON_LOGOUT, "setLogoutStatus"}, {SwitchButtonType::SWITCH_BUTTON_WAKEUP, "setWakeupStatus"}, {SwitchButtonType::SWITCH_BUTTON_AUTO_PAUSE, "setAutoPauseStatus"}, }; std::unordered_map m_comboxMethodKeys { {SelectComboxType::SELECT_COMBOX_SOUNDEFFECT, "setSoundThemeName"}, {SelectComboxType::SELECT_COMBOX_VOLUMEADJUST, "setVolumeChangedName"}, {SelectComboxType::SELECT_COMBOX_NOTIFICTION, "setNotifyGeneralName"}, }; std::unordered_map m_titleLabelKeys { //~ contents_path /Audio/Output {TitleLabelType::TITLE_LABEL_OUTPUT, QObject::tr("Output")}, //~ contents_path /Audio/Input {TitleLabelType::TITLE_LABEL_INPUT, QObject::tr("Input")}, //~ contents_path /Audio/Sound Effect {TitleLabelType::TITLE_LABEL_SOUNDEFFECT, QObject::tr("Sound Effect")}, //~ contents_path /Audio/Advance Settings {TitleLabelType::TITLE_LABEL_ADVANCESETTINGS, QObject::tr("Advance Settings")}, }; std::unordered_map m_switchButtonkeys { //~ contents_path /Audio/Volume Increase {SwitchButtonType::SWITCH_BUTTON_VOLUMEBOOST, QStringList(std::initializer_list{QObject::tr("Volume Increase"), QObject::tr("Volume above 100% can cause sound distortion and damage your speakers"), QString("VolumeIncrease")})}, //~ contents_path /Audio/Mono Audio {SwitchButtonType::SWITCH_BUTTON_MONO, QStringList(std::initializer_list{QObject::tr("Mono Audio"), QObject::tr("It merges the left and right channels into one channel"), QString("MonoAudio")})}, //~ contents_path /Audio/Noise Reduction {SwitchButtonType::SWITCH_BUTTON_ECHOCANCEL, QStringList(std::initializer_list{QObject::tr("Noise Reduction"), QString("NoiseReduction")})}, //~ contents_path /Audio/Voice Monitor {SwitchButtonType::SWITCH_BUTTON_LOOPBACK, QStringList(std::initializer_list{QObject::tr("Voice Monitor"), QObject::tr("You can hear your voice in the output device of your choice"), QString("VoiceMonitor")})}, //~ contents_path /Audio/Startup Misic {SwitchButtonType::SWITCH_BUTTON_STARTUP, QStringList(std::initializer_list{QObject::tr("Startup Misic"), QString("StartupMisic")})}, //~ contents_path /Audio/Poweroff Music {SwitchButtonType::SWITCH_BUTTON_POWEROFF, QStringList(std::initializer_list{QObject::tr("Poweroff Music"), QString("PoweroffMusic")})}, //~ contents_path /Audio/Logout Music {SwitchButtonType::SWITCH_BUTTON_LOGOUT, QStringList(std::initializer_list{QObject::tr("Logout Music"), QString("LogoutMusic")})}, //~ contents_path /Audio/Wakeup Music {SwitchButtonType::SWITCH_BUTTON_WAKEUP, QStringList(std::initializer_list{QObject::tr("Wakeup Music"), QString("WakeupMusic")})}, //~ contents_path /Audio/Beep Switch {SwitchButtonType::SWITCH_BUTTON_ALERT, QStringList(std::initializer_list{QObject::tr("Beep Switch"), QString("BeepSwitch")})}, {SwitchButtonType::SWITCH_BUTTON_AUTO_PAUSE, QStringList(std::initializer_list{QObject::tr("Pause playback when audio device is disconnected"), QString("PauseDisconnected")})} }; std::unordered_map m_volumeSliderKeys { //~ contents_path /Audio/Volume {VolumeSliderType::VOLUME_SLIDER_INPUT, QStringList(std::initializer_list{QObject::tr("Volume"), QString("Volume")})}, //~ contents_path /Audio/Master Volume {VolumeSliderType::VOLUME_SLIDER_OUTPUT, QStringList(std::initializer_list{QObject::tr("Master Volume"), QString("MasterVolume")})}, //~ contents_path /Audio/Input Level {VolumeSliderType::VOLUME_SLIDER_INPUTLEVEL, QStringList(std::initializer_list{QObject::tr("Input Level"), QString("InputLevel")})}, //~ contents_path /Audio/Balance {VolumeSliderType::VOLUME_SLIDER_BALANCE, QStringList(std::initializer_list{QObject::tr("Balance"), QObject::tr("Left"), QObject::tr("Right"), QString("Balance")})} }; std::unordered_map m_selectComboxKeys { //~ contents_path /Audio/Input Device {SelectComboxType::SELECT_COMBOX_INPUT, QStringList(std::initializer_list{QObject::tr("Input Device"), QString("InputDevice")})}, //~ contents_path /Audio/Output Device {SelectComboxType::SELECT_COMBOX_OUTPUT, QStringList(std::initializer_list{QObject::tr("Output Device"), QString("OutputDevice")})}, //~ contents_path /Audio/Sound Theme {SelectComboxType::SELECT_COMBOX_SOUNDEFFECT, QStringList(std::initializer_list{QObject::tr("Sound Theme"), QString("SoundTheme")})}, //~ contents_path /Audio/Volume Control Sound {SelectComboxType::SELECT_COMBOX_VOLUMEADJUST, QStringList(std::initializer_list{QObject::tr("Volume Control Sound"), QString("VolumeControlSound")})}, //~ contents_path /Audio/Notification Sound {SelectComboxType::SELECT_COMBOX_NOTIFICTION, QStringList(std::initializer_list{QObject::tr("Notification Sound"), QString("NotificationSound")})} }; std::unordered_map m_detailSettingsKeys { //~ contents_path /Audio/Sound Equipment Control {DetailSettingType::DETAIL_SETTING_DEVMANAGER, QStringList(std::initializer_list{QObject::tr("Sound Equipment Control"), QString("SoundEquipmentControl")})}, //~ contents_path /Audio/App Sound Control {DetailSettingType::DETAIL_SETTING_APPMANAGER, QStringList(std::initializer_list{QObject::tr("App Sound Control"), QString("AppSoundControl")})}, }; private: std::vector> m_volumeObservers; std::vector> m_balanceVolumeObservers; std::vector> m_muteObservers; std::vector> m_deviceObservers; std::vector> m_settingsObservers; std::vector> m_detailSettingsObservers; std::vector> m_enableDeviceObservers; }; #endif // IAUDIOMAINWINDOW_H ukui-volume-control/audio/Ukui5DetailSettingsItem.cpp0000664000175000017500000000373315171074712021745 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "Ukui5DetailSettingsItem.h" #include #include using namespace kdk; Ukui5DetailSettings::Ukui5DetailSettings(QPushButton* btn, const QString& objName) : m_pSettingBtn(btn) { KDK_EXTEND_ALL_INFO_FORMAT(m_pSettingBtn, "Audio", objName, ""); } QWidget* Ukui5DetailSettings::getWidget() { return m_pSettingBtn; } Ukui5DetailSettingsItem::Ukui5DetailSettingsItem(std::shared_ptr setting, QLabel* label, QWidget* parent) : IDetailSettingsItem(setting, label), QFrame(parent) { initUi(); } void Ukui5DetailSettingsItem::initUi() { setFixedHeight(60); m_pDisplayLabel->setFixedHeight(28); auto hLayout = new QHBoxLayout(this); hLayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); hLayout->addWidget(m_pDisplayLabel); hLayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Expanding)); hLayout->addWidget(m_pDetailSettings->getWidget()); hLayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); hLayout->setSpacing(0); setLayout(hLayout); setFrameShape(QFrame::Shape::Box); } void Ukui5DetailSettingsItem::initSlots() { } QWidget* Ukui5DetailSettingsItem::getWidget() { return this; } ukui-volume-control/audio/ISelectComboxItem.cpp0000664000175000017500000000370515171074712020576 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2025 KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * 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 "ISelectComboxItem.h" //#include #include ISelectComboxItem::ISelectComboxItem(std::shared_ptr box, QLabel* label) : m_pSelectBox(box), m_pDisplayLabel(label) { } void ISelectComboxItem::setCurrentIndex(int idx) { if (m_pSelectBox->isEnabled()) m_pSelectBox->setCurrentIndex(idx); } int ISelectComboxItem::findData(const QVariant& value, int role) { return m_pSelectBox->findData(value, role); } int ISelectComboxItem::findText(const QString& text) { return m_pSelectBox->findText(text); } void ISelectComboxItem::insertItem(const QString& text, const QVariant& userData) { m_pSelectBox->insertItem(text, userData); } void ISelectComboxItem::removeItem(int index) { m_pSelectBox->removeItem(index); } QVariant ISelectComboxItem::currentData(int role) const { return m_pSelectBox->currentData(); } int ISelectComboxItem::count() const { return m_pSelectBox->count(); } void ISelectComboxItem::clear() { m_pSelectBox->clear(); } std::shared_ptr ISelectComboxItem::getSelectBox() const { return m_pSelectBox; } std::list ISelectComboxItem::getDataList() const { return m_pSelectBox->getDataList(); }