ukui-media/0000775000175000017500000000000015170054730011534 5ustar fengfengukui-media/Makefile.am0000664000175000017500000000155215170052044013567 0ustar fengfengNULL = SUBDIRS = data po man DISTCHECK_CONFIGURE_FLAGS = \ --enable-more-warnings \ --enable-compile-warnings=maximum \ --enable-deprecated DIST_SUBDIRS = \ data \ po \ man \ $(NULL) EXTRA_DIST = \ .version \ autogen.sh \ configure \ ChangeLog \ COPYING \ COPYING-DOCS \ build-aux/git-version-gen \ $(INTLTOOL_BUILT:=.in) \ $(NULL) DISTCLEANFILES = \ po/.intltool-merge-cache \ $(NULL) ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS} # see build-aux/git-version-get for details BUILT_SOURCES = $(top_srcdir)/.version $(top_srcdir)/.version: echo $(VERSION) > $@-t && mv $@-t $@ # Build ChangeLog from GIT history ChangeLog: $(AM_V_GEN) if test -d $(top_srcdir)/.git; then \ GIT_DIR="$(top_srcdir)/.git" git log --stat > $@; \ fi dist: ChangeLog .PHONY: ChangeLog -include $(top_srcdir)/git.mk ukui-media/sound-theme-player/0000775000175000017500000000000015170054730015256 5ustar fengfengukui-media/sound-theme-player/sound-theme-player.pro0000664000175000017500000000244715170054730021531 0ustar fengfeng#------------------------------------------------- # # Project created by QtCreator 2021-01-03T20:04:12 # #------------------------------------------------- QT += core gui dbus xml #greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = sound-theme-player TEMPLATE = app include(dbus-adaptor/dbus-adaptor.pri) # The following define makes your compiler emit warnings if you use # any feature of Qt which as been marked as deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the # deprecated API in order to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS LIBS += -lukui-log4qt # You can also make your code fail to compile if you use deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 target.path = /usr/bin CONFIG += \ c++11 \ no_keywords link_pkgconfig debug\ PKGCONFIG += \ glib-2.0 \ gio-2.0 \ dconf \ gsettings-qt6 \ libxml-2.0 \ libcanberra \ libpulse SOURCES += \ main.cpp \ sound_theme_player.cpp HEADERS += \ sound_theme_player.h INSTALLS += \ target ukui-media/sound-theme-player/dbus-adaptor/0000775000175000017500000000000015170054730017643 5ustar fengfengukui-media/sound-theme-player/dbus-adaptor/org.ukui.soundthemeplayer.xml0000664000175000017500000000055415170054730025523 0ustar fengfeng ukui-media/sound-theme-player/dbus-adaptor/dbus-adaptor.pri0000664000175000017500000000015115170054730022741 0ustar fengfengINCLUDEPATH += $$PWD HEADERS += \ $$PWD/dbus-adaptor.h \ SOURCES += \ $$PWD/dbus-adaptor.cpp \ ukui-media/sound-theme-player/dbus-adaptor/dbus-adaptor.h0000664000175000017500000000251515170054730022404 0ustar fengfeng/* * This file was generated by qdbusxml2cpp version 0.8 * Command line was: qdbusxml2cpp org.ukui.soundthemeplayer.xml -i sound_theme_player.h -a dbus-adaptor * * qdbusxml2cpp is Copyright (C) 2020 The Qt Company Ltd. * * This is an auto-generated file. * This file may have been hand-edited. Look for HAND-EDIT comments * before re-generating it. */ #ifndef DBUS_ADAPTOR_H #define DBUS_ADAPTOR_H #include #include #include "sound_theme_player.h" QT_BEGIN_NAMESPACE class QByteArray; template class QList; template class QMap; class QString; class QVariant; QT_END_NAMESPACE /* * Adaptor class for interface org.ukui.sound.theme.player */ class PlayerAdaptor: public QDBusAbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.ukui.sound.theme.player") Q_CLASSINFO("D-Bus Introspection", "" " \n" " \n" " \n" " \n" " \n" " \n" "") public: PlayerAdaptor(QObject *parent); virtual ~PlayerAdaptor(); public: // PROPERTIES public Q_SLOTS: // METHODS bool playAlertSound(const QString &soundEvent); Q_SIGNALS: // SIGNALS }; #endif ukui-media/sound-theme-player/dbus-adaptor/dbus-adaptor.cpp0000664000175000017500000000204715170054730022737 0ustar fengfeng/* * This file was generated by qdbusxml2cpp version 0.8 * Command line was: qdbusxml2cpp org.ukui.soundthemeplayer.xml -i sound_theme_player.h -a dbus-adaptor * * qdbusxml2cpp is Copyright (C) 2020 The Qt Company Ltd. * * This is an auto-generated file. * Do not edit! All changes made to it will be lost. */ #include "dbus-adaptor.h" #include #include #include #include #include #include #include /* * Implementation of adaptor class PlayerAdaptor */ PlayerAdaptor::PlayerAdaptor(QObject *parent) : QDBusAbstractAdaptor(parent) { // constructor setAutoRelaySignals(true); } PlayerAdaptor::~PlayerAdaptor() { // destructor } bool PlayerAdaptor::playAlertSound(const QString &soundEvent) { // handle method call org.ukui.sound.theme.player.playAlertSound bool out0; QMetaObject::invokeMethod(parent(), "playAlertSound", Q_RETURN_ARG(bool, out0), Q_ARG(QString, soundEvent)); return out0; } ukui-media/sound-theme-player/main.cpp0000664000175000017500000000224515170054730016711 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "sound_theme_player.h" #include #include #include #include "dbus-adaptor.h" #include int main(int argc, char *argv[]) { initUkuiLog4qt("ukui-media"); QCoreApplication a(argc, argv); SoundThemePlayer player; return a.exec(); } ukui-media/sound-theme-player/sound_theme_player.cpp0000664000175000017500000002066415170054730021660 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "sound_theme_player.h" #include #include SoundThemePlayer::SoundThemePlayer() { createCaContext();//注册 UkmediaPlayerDbusRegister(); initGSettings(); addSoundFileList(); } /** * @brief create for the libcanberra context * @return create successfully or not */ bool SoundThemePlayer::createCaContext() { if (nullptr != m_caContext) { ca_context_cancel(m_caContext, 0); ca_context_destroy(m_caContext); } return (ca_context_create(&m_caContext) == 0) ? true : false; } /** * @brief Register for the dbus service */ void SoundThemePlayer::UkmediaPlayerDbusRegister() { QDBusConnection con = QDBusConnection::sessionBus(); if (!con.registerService("org.ukui.sound.theme.player")) qDebug() << "registerService failed" << con.lastError().message(); if (!con.registerObject("/org/ukui/sound/theme/player", this, QDBusConnection::ExportAllSlots | QDBusConnection::ExportAllSignals)) qDebug() << "registerObject failed" << con.lastError().message(); } /** * @brief Play the corresponding sound effects * * @param soundEvent Application sound type * @return Play successfully or not */ bool SoundThemePlayer::playAlertSound(QString soundEvent) { bool soundSwitch; if (m_pSoundSettings->keys().contains("eventSounds")) soundSwitch = m_pSoundSettings->get(EVENT_SOUNDS_KEY).toBool(); if (!soundSwitch) return false; gint retval; QByteArray ba; const gchar *eventId; const gchar *desc = "Sound Theme Player"; if (m_pSoundFileList.contains(soundEvent)) ba = soundEvent.toLatin1(); else { QString theme = m_pSoundSettings->get(SOUND_THEME_KEY).toString(); if (isFileExist(theme, soundEvent)) ba = soundEvent.toLatin1(); else ba = NOTIFICATION_GENERAL; } eventId = ba.data(); if (ba == AUDIO_VOLUME_CHANGE && m_pSoundSettings->get(SOUND_CUSTOM_THEME_KEY).toBool()) { QString soundPath = m_pSoundSettings->get(AUDIO_VOLUME_CHANGE).toString(); retval = playCustomAlertSound(soundPath); return retval; } else if (ba == NOTIFICATION_GENERAL && m_pSoundSettings->get(SOUND_CUSTOM_THEME_KEY).toBool()) { QString soundPath = m_pSoundSettings->get(NOTIFICATION_GENERAL).toString(); retval = playCustomAlertSound(soundPath); return retval; } if (createCaContext()) { retval = ca_context_play(m_caContext, 0, CA_PROP_EVENT_ID, eventId, CA_PROP_EVENT_DESCRIPTION, desc, CA_PROP_CANBERRA_VOLUME, m_dBValue, NULL); } qDebug() << __func__ << soundEvent << eventId << retval; return (retval == 0 ? 1 : 0); } /** * @brief Find the sound file according to the sound type * * @param action Application sound type * @return Sound file */ QString SoundThemePlayer::readJson(QString action) { QFile file("/usr/share/ukui-media/sounds/audio.json"); if (!file.open(QFile::ReadOnly | QFile::Text)) { qDebug() << "can't open error!"; return ""; } // 读取文件的全部内容 QTextStream stream(&file); stream.setEncoding(QStringConverter::Utf8); // 设置读取编码是UTF8 QString str = stream.readAll(); file.close(); // QJsonParseError类用于在JSON解析期间报告错误。 QJsonParseError jsonError; // 将json解析为UTF-8编码的json文档,并从中创建一个QJsonDocument。 // 如果解析成功,返回QJsonDocument对象,否则返回null QJsonDocument doc = QJsonDocument::fromJson(str.toUtf8(), &jsonError); // 判断是否解析失败 if (jsonError.error != QJsonParseError::NoError && !doc.isNull()) { qDebug() << "Json格式错误!" << jsonError.error; return ""; } QJsonObject rootObj = doc.object(); //获取键值对 QJsonValue card = rootObj.value(action); if (card.toString().isEmpty()) return NOTIFICATION_GENERAL; return card.toString(); } bool SoundThemePlayer::playCustomAlertSound(QString soundPath) { int retval = 1; if (createCaContext()) { retval = ca_context_play(m_caContext, 0, CA_PROP_APPLICATION_NAME, "Sound Theme Player", CA_PROP_MEDIA_FILENAME, soundPath.toLatin1().data(), CA_PROP_EVENT_DESCRIPTION, "Testing event sound", CA_PROP_CANBERRA_CACHE_CONTROL, "never", CA_PROP_APPLICATION_ID, "org.ukui.VolumeControl", CA_PROP_CANBERRA_VOLUME, m_dBValue, #ifdef CA_PROP_CANBERRA_ENABLE CA_PROP_CANBERRA_ENABLE, "1", #endif NULL); qDebug() << __func__ << soundPath << retval; } return (retval == 0 ? 1 : 0); } bool SoundThemePlayer::isFileExist(QString theme, QString soundTye) { QString path = SOUND_FILE_PATH; path.replace("xxxTheme", theme); path.replace("xxxFile", soundTye); QFileInfo fileInfo(path); path.replace("ogg", "oga"); QFileInfo fileInfoOga(path); if (fileInfo.isFile()) return true; else if (fileInfoOga.isFile()) return true; else return false; } void SoundThemePlayer::addSoundFileList() { m_pSoundFileList << "audio-volume-change" << "battery-low" << "complete" << "device-added-failed" << "device-added" << "device-removed" << "dialog-error" << "dialog-warning" << "notification-general" << "notification-special" << "operation-file" << "operation-unsupported" << "power-plug" << "screen-capture" << "screen-record" << "start-up" << "trash-empty" ; } void SoundThemePlayer::initGSettings() { if (QGSettings::isSchemaInstalled(KEY_SOUNDS_SCHEMA)) { m_pSoundSettings = new QGSettings(KEY_SOUNDS_SCHEMA); } if (nullptr == m_pSoundSettings) { qDebug() << "m_pSoundSettings create fail"; return; } if (m_pSoundSettings->keys().contains("alertVolume")) { int n_AlertVolume = m_pSoundSettings->get(ALERT_VOLUME).toInt(); m_dBValue = alertVolumeTodB(n_AlertVolume); } connect(m_pSoundSettings, &QGSettings::changed, this, &SoundThemePlayer::onSettingChanged); } void SoundThemePlayer::onSettingChanged(const QString &key) { if (key == "alertVolume") { int n_AlertVolume = m_pSoundSettings->get(ALERT_VOLUME).toInt(); m_dBValue = alertVolumeTodB(n_AlertVolume); qDebug() << "The alert volume is changed, now is" << n_AlertVolume << m_dBValue; } } char *SoundThemePlayer::alertVolumeTodB(int value) { if (value < 0 || value > 100) value = 100; pa_volume_t volume = value / 100.0 * 65536.0; double dBValue = pa_sw_volume_to_dB(volume); QByteArray byteArray = QByteArray::number(dBValue, 'f', 2); if (m_dBValue) { delete[] m_dBValue; m_dBValue = nullptr; } m_dBValue = new char[byteArray.size() + 1]; snprintf(m_dBValue, byteArray.size() + 1, "%s", byteArray.constData()); return m_dBValue; } SoundThemePlayer::~SoundThemePlayer() { } ukui-media/sound-theme-player/sound_theme_player.h0000664000175000017500000000472615170054730021326 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifndef SOUND_THEME_PLAYER_H #define SOUND_THEME_PLAYER_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern "C" { #include #include } #define KEY_SOUNDS_SCHEMA "org.ukui.sound" #define EVENT_SOUNDS_KEY "event-sounds" #define SOUND_THEME_KEY "theme-name" #define SOUND_CUSTOM_THEME_KEY "custom-theme" #define AUDIO_VOLUME_CHANGE "audio-volume-change" #define NOTIFICATION_GENERAL "notification-general" #define ALERT_VOLUME "alert-volume" #define SOUND_FILE_PATH "/usr/share/sounds/xxxTheme/stereo/xxxFile.ogg" class SoundThemePlayer : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface","org.ukui.sound.theme.player") public: SoundThemePlayer(); ~SoundThemePlayer(); bool createCaContext(); void UkmediaPlayerDbusRegister(); QString readJson(QString action); void addSoundFileList(); bool playCustomAlertSound(QString soundPath); bool isFileExist(QString theme, QString soundTye); void initGSettings(); void onSettingChanged(const QString &key); char *alertVolumeTodB(int value); public Q_SLOTS: //METHODS bool playAlertSound(QString soundEvent); private: ca_context *m_caContext = nullptr; QGSettings *m_pSoundSettings = nullptr; QStringList m_pSoundFileList; char *m_dBValue = nullptr; }; #endif // SOUND_THEME_PLAYER_H ukui-media/ukui-login-sound/0000775000175000017500000000000015170054730014745 5ustar fengfengukui-media/ukui-login-sound/main.cpp0000664000175000017500000001305315170054730016377 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include #include #include #include #include #include "ukui_login_sound_user_config.h" #include "ukui_login_sound.h" /* define hw sound card dirvers */ #define HI3XXX_HI6405 "hi3xxx_hi6405" #define HI3XXX_DA_COMBINE_V5 "hi3xxx_DA_combine_v5" #define DA_COMBINE_V5_MACHINE "da_combine_v5_machine" #define HW_AUDIO_SERVICE "/usr/share/hw-audio/hwaudioservice" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); if (QFile(SOUND_EFFECT_JSON).exists()) { syslog(LOG_INFO, "this is not first time longin to the system, so the stratup sound effect will not play."); return 0; } std::shared_ptr info = std::make_shared(); QString autoLoginUser = std::dynamic_pointer_cast(info)->getAutoLoginUser(); QString lastLoginUser = std::dynamic_pointer_cast(info)->getLastLoginUser(); syslog(LOG_DEBUG, "autoLoginUser:%s, lastLoginUser:%s", autoLoginUser.toLatin1().data(), lastLoginUser.toLatin1().data()); // 1. 正常装机或者OEM装机时第一次登录,使用默认设备播放开机音效 if ("oem" == autoLoginUser || ("" == autoLoginUser && "" == lastLoginUser)) { if (QFile::exists(HW_AUDIO_SERVICE)) { system("/usr/share/hw-audio/hwaudioservice -o"); } syslog(LOG_INFO, "when you first enter the system, use the default output device to play startup sound effect."); QString defaultOutputDev = DEFAULT_AUDIO_CARD_VALUE; defaultOutputDev = UkuiLoginSound::getInstance().checkPcm(defaultOutputDev.toLatin1().data()); UkuiLoginSound::getInstance().aplayMain(PLAY_STARTUP_WAV, defaultOutputDev.toLatin1().data()); } else { // 2.当系统设置自动登录时,使用自动登录用户记录的设备信息进行播放,没有设置自动登录用户时,使用上一次登录用户记录的设备信息进行播放 bool status = info->getValueFromJson(info->getJsonByType(std::dynamic_pointer_cast(info)->getJsonMap(), JsonType::JSON_TYPE_USERINFO), AUDIO_STRATUP_SOUNDEFFECT_KEY).toBool(); bool mute = info->getValueFromJson(info->getJsonByType(std::dynamic_pointer_cast(info)->getJsonMap(), JsonType::JSON_TYPE_USERINFO), AUDIO_MUTE_KEY).toBool(); int volume = info->getValueFromJson(info->getJsonByType(std::dynamic_pointer_cast(info)->getJsonMap(), JsonType::JSON_TYPE_USERINFO), AUDIO_VOLUME_KEY).toInt(); /* Do not play the boot sound effect when the user is closed when the boot sound effect, * the mute state, and the volume value are 0 */ if (!status || mute || !volume) { syslog(LOG_DEBUG, "%s user has turned off the system boot sound or it is in mute mode, status: %d, muted: %d, volume: %d.", autoLoginUser == "" ? lastLoginUser.toLatin1().data() : autoLoginUser.toLatin1().data(), status, mute, volume); return 0; } int cardId = info->getValueFromJson(info->getJsonByType(std::dynamic_pointer_cast(info)->getJsonMap(), JsonType::JSON_TYPE_USERINFO), AUDIO_CARDID_KEY).toInt(); QString audioDEV = info->getValueFromJson(info->getJsonByType(std::dynamic_pointer_cast(info)->getJsonMap(), JsonType::JSON_TYPE_USERINFO), AUDIO_CARD_KEY).toString(); /* Check whether the PCM device is valid before playing the boot sound */ audioDEV = UkuiLoginSound::getInstance().checkPcm(audioDEV.toLatin1().data()); bool support = UkuiLoginSound::getInstance().isSupportHardwareVolumeControl(QString("hw:%1").arg(cardId).toLatin1().data()); syslog(LOG_INFO, "play system sound effects on the device:%s at volume:%d.", audioDEV.toLatin1().data(), volume); if (!mute && QFile::exists(HW_AUDIO_SERVICE)) { system("/usr/share/hw-audio/hwaudioservice -o"); } /* When the driver does not support hardware volume control, modify the volume of * the audio file of the boot sound effect through FFMPEG */ if (!support && !UkuiLoginSound::getInstance().wav_convert(volume)) { UkuiLoginSound::getInstance().aplayMain(TMP_STARTUP_WAV_PATH, audioDEV.toLatin1().data()); } else { UkuiLoginSound::getInstance().aplayMain(PLAY_STARTUP_WAV, audioDEV.toLatin1().data()); } } return 0; } ukui-media/ukui-login-sound/ukui-login-sound.pro0000664000175000017500000000264615170054730020710 0ustar fengfengQT += core gui #greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TEMPLATE = app TARGET = ukui-login-sound target.path = /usr/bin CONFIG += \ c++17 \ no_keywords link_pkgconfig debug\ # The following define makes your compiler emit warnings if you use # any Qt feature that has been marked deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the # deprecated API in order to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS # You can also make your code fail to compile if it uses deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += \ ../common/user_config.cpp \ main.cpp \ ukui_login_sound.cpp \ ../common/sound_effect_json.cpp \ ../common/user_info_json.cpp \ ukui_login_sound_user_config.cpp HEADERS += \ formats.h \ ukui_login_sound.h \ ../common/json.h \ ../common/sound_effect_json.h \ ../common/user_info_json.h \ ../common/user_config.h \ ukui_login_sound_user_config.h PKGCONFIG += alsa # Default rules for deployment. #qnx: target.path = /tmp/$${TARGET}/bin #else: unix:!android: target.path = /opt/$${TARGET}/bin #!isEmpty(target.path): INSTALLS += target INSTALLS += target \ ukui-media/ukui-login-sound/ukui_login_sound.h0000664000175000017500000001571515170054730020504 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifndef APLAYPLAY_H #define APLAYPLAY_H //#include "aconfig.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "formats.h" #include #include #include #include #define BUFFER_LEN 1024 //开机音乐播放路径 #define PLAY_STARTUP_WAV "/usr/share/ukui/ukui-session-manager/startup.wav" #define TMP_STARTUP_WAV_PATH "/tmp/startup.wav" #define NORMAL_VOLUME 100.0 #define MIN_VOLUME 0.0 #define ABS(a) (a) < 0 ? -(a) : (a) #ifdef SND_CHMAP_API_VERSION #define CONFIG_SUPPORT_CHMAP 1 #endif #ifndef LLONG_MAX #define LLONG_MAX 9223372036854775807LL #endif #ifndef le16toh #include #define le16toh(x) __le16_to_cpu(x) #define be16toh(x) __be16_to_cpu(x) #define le32toh(x) __le32_to_cpu(x) #define be32toh(x) __be32_to_cpu(x) #endif #define DEFAULT_FORMAT SND_PCM_FORMAT_U8 #define DEFAULT_SPEED 8000 #define FORMAT_DEFAULT -1 #define FORMAT_RAW 0 #define FORMAT_VOC 1 #define FORMAT_WAVE 2 #define FORMAT_AU 3 /* global data */ static snd_pcm_sframes_t (*readi_func)(snd_pcm_t *handle, void *buffer, snd_pcm_uframes_t size); static snd_pcm_sframes_t (*writei_func)(snd_pcm_t *handle, const void *buffer, snd_pcm_uframes_t size); static snd_pcm_sframes_t (*readn_func)(snd_pcm_t *handle, void **bufs, snd_pcm_uframes_t size); static snd_pcm_sframes_t (*writen_func)(snd_pcm_t *handle, void **bufs, snd_pcm_uframes_t size); enum { VUMETER_NONE, VUMETER_MONO, VUMETER_STEREO }; static snd_pcm_t *handle; static struct { snd_pcm_format_t format; unsigned int channels; unsigned int rate; } hwparams, rhwparams; static int timelimit = 0; static int sampleslimit = 0; static int quiet_mode = 0; static int file_type = FORMAT_DEFAULT; static int open_mode = 0; static snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK; static int mmap_flag = 0; static int interleaved = 1; static int nonblock = 0; static volatile sig_atomic_t in_aborting = 0; static u_char *audiobuf = NULL; static snd_pcm_uframes_t chunk_size = 0; static unsigned period_time = 0; static unsigned buffer_time = 0; static snd_pcm_uframes_t period_frames = 0; static snd_pcm_uframes_t buffer_frames = 0; static int avail_min = -1; static int start_delay = 0; static int stop_delay = 0; static int monotonic = 0; static int interactive = 0; static int can_pause = 0; static int fatal_errors = 0; static int verbose = 0; static int vumeter = VUMETER_NONE; static int buffer_pos = 0; static size_t significant_bits_per_sample, bits_per_sample, bits_per_frame; static size_t chunk_bytes; static int test_position = 0; static int test_coef = 8; static int test_nowait = 0; static snd_output_t *snd_log; static long long max_file_size = 0; static int max_file_time = 0; static int use_strftime = 0; volatile static int recycle_capture_file = 0; static long term_c_lflag = -1; static int dump_hw_params = 0; static int fd = -1; static off64_t pbrec_count = LLONG_MAX, fdcount; static int vocmajor, vocminor; static char *pidfile_name = NULL; //FILE *pidf = NULL; static int pidfile_written = 0; #ifdef CONFIG_SUPPORT_CHMAP static snd_pcm_chmap_t *channel_map = NULL; /* chmap to override */ static unsigned int *hw_map = NULL; /* chmap to follow */ #endif //extern "C"{ /* needed prototypes */ class UkuiLoginSound { public: static UkuiLoginSound& getInstance(); int aplayMain(char *filename,char *playdev); char* checkPcm(char *name); bool isSupportHardwareVolumeControl(char *name); int wav_convert(int volume); private: UkuiLoginSound(); UkuiLoginSound(const UkuiLoginSound&) = delete; UkuiLoginSound(UkuiLoginSound&&) = delete; UkuiLoginSound operator=(const UkuiLoginSound&) = delete; UkuiLoginSound operator=(UkuiLoginSound&&) = delete; ~UkuiLoginSound() = default; static void check_wavefile_space(u_char *buffer,u_int len, size_t blimit); static void device_list(); static void pcm_list(void); static void prg_exit(int code); static void signal_handler(int sig); static void signal_handler_recycle (int sig); static ssize_t safe_read(int fd, void *buf, size_t count); static size_t test_wavefile_read(int fd, u_char *buffer, size_t *size, size_t reqsize, int line); static ssize_t test_wavefile(int fd, u_char *_buffer, size_t size); static void show_available_sample_formats(snd_pcm_hw_params_t* params); static int setup_chmap(void); static void set_params(void); static void init_stdin(void); static void done_stdin(void); static void do_pause(void); static void check_stdin(void); static void xrun(void); static void suspend(void); static void print_vu_meter_mono(int perc, int maxperc); static void print_vu_meter_stereo(int *perc, int *maxperc); static void print_vu_meter(signed int *perc, signed int *maxperc); static void compute_max_peak(u_char *data, size_t count); static void do_test_position(void); static u_char *remap_data(u_char *data, size_t count); static u_char **remap_datav(u_char **data, size_t count); static ssize_t pcm_write(u_char *data, size_t count); static ssize_t pcm_writev(u_char **data, unsigned int channels, size_t count); static ssize_t pcm_read(u_char *data, size_t rcount); static ssize_t pcm_readv(u_char **data, unsigned int channels, size_t rcount); static ssize_t voc_pcm_write(u_char *data, size_t count); static void init_raw_data(void); static off64_t calc_count(void); static void header(int rtype, char *name); static void playback_go(int fd, size_t loaded, off64_t count, int rtype, char *name); static int read_header(int *loaded, int header_size); static int playback_wave(char *name, int *loaded); static int playback_raw(char *name, int *loaded); static void playback(char *name); }; #endif // APLAYPLAY_H ukui-media/ukui-login-sound/ukui_login_sound_user_config.cpp0000664000175000017500000000556015170054730023417 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "ukui_login_sound_user_config.h" UkuiLoginSoundUserConfig::UkuiLoginSoundUserConfig() { initSettings(); initJsonMap(); } void UkuiLoginSoundUserConfig::initSettings() { m_pAutoLoginSetting = std::make_unique(LIGHTDM_CONF_FILE, QSettings::IniFormat); m_pLastLoginSetting = std::make_unique(UKUI_GREETER_CONF_FILE, QSettings::IniFormat); } void UkuiLoginSoundUserConfig::initJsonMap() { for (const auto& [k, v] : m_keys) { std::shared_ptr json = nullptr; switch (k) { case JsonType::JSON_TYPE_SOUNDEFFECT: { json = std::make_shared(v()); json->init(); break; } case JsonType::JSON_TYPE_USERINFO: { json = std::make_shared(v()); break; } default: break; } if (json) { m_jsonMap.emplace(k, json); } } } const QString UkuiLoginSoundUserConfig::getAutoLoginUser() const { return m_pAutoLoginSetting->value(LIGHTDM_CONF_KEY).toString(); } const QString UkuiLoginSoundUserConfig::getLastLoginUser() const { return m_pLastLoginSetting->value(UKUI_GREETER_CONF_KEY).toString(); } std::unordered_map> UkuiLoginSoundUserConfig::getJsonMap() const { return m_jsonMap; } QString UkuiLoginSoundUserConfig::getSoundEffectFullPath() { return SOUND_EFFECT_JSON; } QString UkuiLoginSoundUserConfig::getUserInfoFullPath() { QString path = DEFAULT_PATH; QString autoLoginUser = getAutoLoginUser(); QString lastLoginUser = getLastLoginUser(); if ("" == autoLoginUser && "" == lastLoginUser) { path = INVAILD_PATH; } else { autoLoginUser == "" ? path.append(lastLoginUser) : path.append(autoLoginUser); } path == INVAILD_PATH ? path : path.append(AUDIO_JSON); syslog(LOG_DEBUG, "usr info file path: %s.", path.toLatin1().data()); return path; } UkuiLoginSoundUserConfig::~UkuiLoginSoundUserConfig() { } ukui-media/ukui-login-sound/formats.h0000664000175000017500000001076115170054730016576 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifndef FORMATS_H #define FORMATS_H 1 #include #include /* Definitions for .VOC files */ #define VOC_MAGIC_STRING "Creative Voice File\x1A" #define VOC_ACTUAL_VERSION 0x010A #define VOC_SAMPLESIZE 8 #define VOC_MODE_MONO 0 #define VOC_MODE_STEREO 1 #define VOC_DATALEN(bp) ((u_long)(bp->datalen) | \ ((u_long)(bp->datalen_m) << 8) | \ ((u_long)(bp->datalen_h) << 16) ) typedef struct voc_header { u_char magic[20]; /* must be MAGIC_STRING */ u_short headerlen; /* Headerlength, should be 0x1A */ u_short version; /* VOC-file version */ u_short coded_ver; /* 0x1233-version */ } VocHeader; typedef struct voc_blocktype { u_char type; u_char datalen; /* low-byte */ u_char datalen_m; /* medium-byte */ u_char datalen_h; /* high-byte */ } VocBlockType; typedef struct voc_voice_data { u_char tc; u_char pack; } VocVoiceData; typedef struct voc_ext_block { u_short tc; u_char pack; u_char mode; } VocExtBlock; /* Definitions for Microsoft WAVE format */ #if __BYTE_ORDER == __LITTLE_ENDIAN #define COMPOSE_ID(a,b,c,d) ((a) | ((b)<<8) | ((c)<<16) | ((d)<<24)) #define LE_SHORT(v) (v) #define LE_INT(v) (v) #define BE_SHORT(v) bswap_16(v) #define BE_INT(v) bswap_32(v) #elif __BYTE_ORDER == __BIG_ENDIAN #define COMPOSE_ID(a,b,c,d) ((d) | ((c)<<8) | ((b)<<16) | ((a)<<24)) #define LE_SHORT(v) bswap_16(v) #define LE_INT(v) bswap_32(v) #define BE_SHORT(v) (v) #define BE_INT(v) (v) #else #error "Wrong endian" #endif /* Note: the following macros evaluate the parameter v twice */ #define TO_CPU_SHORT(v, be) \ ((be) ? BE_SHORT(v) : LE_SHORT(v)) #define TO_CPU_INT(v, be) \ ((be) ? BE_INT(v) : LE_INT(v)) #define WAV_RIFF COMPOSE_ID('R','I','F','F') #define WAV_RIFX COMPOSE_ID('R','I','F','X') #define WAV_WAVE COMPOSE_ID('W','A','V','E') #define WAV_FMT COMPOSE_ID('f','m','t',' ') #define WAV_DATA COMPOSE_ID('d','a','t','a') /* WAVE fmt block constants from Microsoft mmreg.h header */ #define WAV_FMT_PCM 0x0001 #define WAV_FMT_IEEE_FLOAT 0x0003 #define WAV_FMT_DOLBY_AC3_SPDIF 0x0092 #define WAV_FMT_EXTENSIBLE 0xfffe /* Used with WAV_FMT_EXTENSIBLE format */ #define WAV_GUID_TAG "\x00\x00\x00\x00\x10\x00\x80\x00\x00\xAA\x00\x38\x9B\x71" /* it's in chunks like .voc and AMIGA iff, but my source say there are in only in this combination, so I combined them in one header; it works on all WAVE-file I have */ typedef struct { u_int magic; /* 'RIFF' */ u_int length; /* filelen */ u_int type; /* 'WAVE' */ } WaveHeader; typedef struct { u_short format; /* see WAV_FMT_* */ u_short channels; u_int sample_fq; /* frequence of sample */ u_int byte_p_sec; u_short byte_p_spl; /* samplesize; 1 or 2 bytes */ u_short bit_p_spl; /* 8, 12 or 16 bit */ } WaveFmtBody; typedef struct { WaveFmtBody format; u_short ext_size; u_short bit_p_spl; u_int channel_mask; u_short guid_format; /* WAV_FMT_* */ u_char guid_tag[14]; /* WAV_GUID_TAG */ } WaveFmtExtensibleBody; typedef struct { u_int type; /* 'data' */ u_int length; /* samplecount */ } WaveChunkHeader; /* Definitions for Sparc .au header */ #define AU_MAGIC COMPOSE_ID('.','s','n','d') #define AU_FMT_ULAW 1 #define AU_FMT_LIN8 2 #define AU_FMT_LIN16 3 typedef struct au_header { u_int magic; /* '.snd' */ u_int hdr_size; /* size of header (min 24) */ u_int data_size; /* size of data */ u_int encoding; /* see to AU_FMT_XXXX */ u_int sample_rate; /* sample rate */ u_int channels; /* number of channels (voices) */ } AuHeader; #endif /* FORMATS */ ukui-media/ukui-login-sound/ukui_login_sound.cpp0000664000175000017500000015455415170054730021044 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 = 0) { char name[32]; // sprintf(name, "hw:%d", card); snprintf(name, sizeof(name), "hw:%d", card); if ((err = snd_ctl_open(&handle, name, 0)) < 0) { printf("control open (%i): %s", card, snd_strerror(err)); goto next_card; } if ((err = snd_ctl_card_info(handle, info)) < 0) { printf("control hardware info (%i): %s", card, snd_strerror(err)); snd_ctl_close(handle); goto next_card; } dev = -1; while (1) { unsigned int count; if (snd_ctl_pcm_next_device(handle, &dev)<0) printf("snd_ctl_pcm_next_device"); if (dev < 0) break; snd_pcm_info_set_device(pcminfo, dev); snd_pcm_info_set_subdevice(pcminfo, 0); snd_pcm_info_set_stream(pcminfo, stream); if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) { if (err != -ENOENT) printf("control digital audio info (%i): %s", card, snd_strerror(err)); continue; } printf(("card %i: %s [%s], device %i: %s [%s]\n"), card, snd_ctl_card_info_get_id(info), snd_ctl_card_info_get_name(info), dev, snd_pcm_info_get_id(pcminfo), snd_pcm_info_get_name(pcminfo)); count = snd_pcm_info_get_subdevices_count(pcminfo); printf((" Subdevices: %i/%i\n"), snd_pcm_info_get_subdevices_avail(pcminfo), count); for (idx = 0; idx < (int)count; idx++) { snd_pcm_info_set_subdevice(pcminfo, idx); if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) { printf("control digital audio playback info (%i): %s", card, snd_strerror(err)); } else { printf((" Subdevice #%i: %s\n"), idx, snd_pcm_info_get_subdevice_name(pcminfo)); } } } snd_ctl_close(handle); next_card: if (snd_card_next(&card) < 0) { printf("snd_card_next"); break; } } } void UkuiLoginSound::pcm_list(void) { void **hints, **n; char *name, *descr, *descr1, *io; const char *filter; if (snd_device_name_hint(-1, "pcm", &hints) < 0) return; n = hints; filter = stream == SND_PCM_STREAM_CAPTURE ? "Input" : "Output"; while (*n != NULL) { name = snd_device_name_get_hint(*n, "NAME"); descr = snd_device_name_get_hint(*n, "DESC"); io = snd_device_name_get_hint(*n, "IOID"); if (io != NULL && strcmp(io, filter) != 0) goto __end; printf("%s\n", name); if ((descr1 = descr) != NULL) { printf(" "); while (*descr1) { if (*descr1 == '\n') printf("\n "); else putchar(*descr1); descr1++; } putchar('\n'); } __end: if (name != NULL) free(name); if (descr != NULL) free(descr); if (io != NULL) free(io); n++; } snd_device_name_free_hint(hints); } /* * Subroutine to clean up before exit. */ void UkuiLoginSound::prg_exit(int code) { done_stdin(); if (handle) snd_pcm_close(handle); if (pidfile_written) remove (pidfile_name); exit(code); } void UkuiLoginSound::signal_handler(int sig) { if (in_aborting) return; in_aborting = 1; if (verbose==2) putchar('\n'); if (!quiet_mode) { syslog (LOG_INFO,"sig: %d quiet_mode: %d.", sig, quiet_mode); fprintf(stderr, ("Aborted by signal %s...\n"), strsignal(sig)); } if (handle) snd_pcm_abort(handle); if (sig == SIGABRT) { /* do not call snd_pcm_close() and abort immediately */ handle = NULL; prg_exit(EXIT_FAILURE); } signal(sig, SIG_DFL); } /* call on SIGUSR1 signal. */ void UkuiLoginSound::signal_handler_recycle ([[maybe_unused]] int sig) { /* flag the capture loop to start a new output file */ recycle_capture_file = 1; } int UkuiLoginSound::aplayMain(char *filename, char *playdev) { // int duration_or_sample = 0; // int option_index; // static const char short_options[] = "hnlLD:qt:c:f:r:d:s:MNF:A:R:T:B:vV:IPCi" #ifdef CONFIG_SUPPORT_CHMAP "m:" #endif ; char *pcm_name = playdev; int err; int do_device_list = 0, do_pcm_list = 0; snd_pcm_info_t *info; #ifdef ENABLE_NLS setlocale(LC_ALL, ""); textdomain(PACKAGE); #endif snd_pcm_info_alloca(&info); err = snd_output_stdio_attach(&snd_log, stderr, 0); assert(err >= 0); file_type = FORMAT_DEFAULT; stream = SND_PCM_STREAM_PLAYBACK; chunk_size = -1; rhwparams.format = DEFAULT_FORMAT; rhwparams.rate = DEFAULT_SPEED; rhwparams.channels = 1; if (do_device_list) { if (do_pcm_list) pcm_list(); device_list(); goto __end; } else if (do_pcm_list) { pcm_list(); goto __end; } err = snd_pcm_open(&handle, playdev, stream, open_mode); syslog (LOG_INFO, "start open %s device...", pcm_name); if (err < 0) { printf(("audio open error: %s"), snd_strerror(err)); return 1; } if ((err = snd_pcm_info(handle, info)) < 0) { printf(("info error: %s"), snd_strerror(err)); return 1; } if (nonblock) { err = snd_pcm_nonblock(handle, 1); if (err < 0) { printf(("nonblock setting error: %s"), snd_strerror(err)); return 1; } } chunk_size = 1024; hwparams = rhwparams; audiobuf = (u_char *)malloc(1024); if (audiobuf == NULL) { printf(("not enough memory")); return 1; } if (mmap_flag) { writei_func = snd_pcm_mmap_writei; readi_func = snd_pcm_mmap_readi; writen_func = snd_pcm_mmap_writen; readn_func = snd_pcm_mmap_readn; } else { writei_func = snd_pcm_writei; readi_func = snd_pcm_readi; writen_func = snd_pcm_writen; readn_func = snd_pcm_readn; } if (pidfile_name) { errno = 0; pidf = fopen (pidfile_name, "w"); if (pidf) { (void)fprintf (pidf, "%d\n", getpid()); fclose(pidf); pidfile_written = 1; } else { printf(("Cannot create process ID file %s: %s"), pidfile_name, strerror (errno)); return 1; } } signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); signal(SIGABRT, signal_handler); signal(SIGUSR1, signal_handler_recycle); if (interleaved) { if (stream == SND_PCM_STREAM_PLAYBACK) { playback(filename); } } if (verbose==2) putchar('\n'); snd_pcm_close(handle); handle = NULL; free(audiobuf); __end: snd_output_close(snd_log); snd_config_update_free_global(); prg_exit(EXIT_SUCCESS); /* avoid warning */ return EXIT_SUCCESS; } /* * Safe read (for pipes) */ ssize_t UkuiLoginSound::safe_read(int fd, void *buf, size_t count) { ssize_t result = 0, res; while (count > 0 && !in_aborting) { if ((res = read(fd, buf, count)) == 0) break; if (res < 0) return result > 0 ? result : res; count -= res; result += res; buf = (char *)buf + res; } return result; } /* * helper for test_wavefile */ size_t UkuiLoginSound::test_wavefile_read(int fd, u_char *buffer, size_t *size, size_t reqsize, int line) { if (*size >= reqsize) return *size; if ((size_t)safe_read(fd, buffer + *size, reqsize - *size) != reqsize - *size) { printf(("read error (called from line %i)"), line); prg_exit(EXIT_FAILURE); } return *size = reqsize; } #define check_wavefile_space(buffer, len, blimit) \ if (len > blimit) { \ blimit = len; \ if ((buffer = (u_char *)realloc(buffer, blimit)) == NULL) { \ printf(("not enough memory")); \ prg_exit(EXIT_FAILURE); \ } \ } /* * test, if it's a .WAV file, > 0 if ok (and set the speed, stereo etc.) * == 0 if not * Value returned is bytes to be discarded. */ ssize_t UkuiLoginSound::test_wavefile(int fd, u_char *_buffer, size_t size) { WaveHeader *h = (WaveHeader *)_buffer; u_char *buffer = NULL; size_t blimit = 0; WaveFmtBody *f; WaveChunkHeader *c; u_int type, len; unsigned short format, channels; int big_endian, native_format; if (size < sizeof(WaveHeader)) return -1; if (h->magic == WAV_RIFF) big_endian = 0; else if (h->magic == WAV_RIFX) big_endian = 1; else return -1; if (h->type != WAV_WAVE) return -1; if (size > sizeof(WaveHeader)) { check_wavefile_space(buffer, size - sizeof(WaveHeader), blimit); memcpy(buffer, (_buffer + (sizeof(WaveHeader))), size - sizeof(WaveHeader)); } size -= sizeof(WaveHeader); while (1) { check_wavefile_space(buffer, sizeof(WaveChunkHeader), blimit); test_wavefile_read(fd, buffer, &size, sizeof(WaveChunkHeader), __LINE__); c = (WaveChunkHeader*)buffer; type = c->type; len = TO_CPU_INT(c->length, big_endian); len += len % 2; if (size > sizeof(WaveChunkHeader)) memmove(buffer, buffer + sizeof(WaveChunkHeader), size - sizeof(WaveChunkHeader)); size -= sizeof(WaveChunkHeader); if (type == WAV_FMT) break; check_wavefile_space(buffer, len, blimit); test_wavefile_read(fd, buffer, &size, len, __LINE__); if (size > len) memmove(buffer, buffer + len, size - len); size -= len; } if (len < sizeof(WaveFmtBody)) { printf(("unknown length of 'fmt ' chunk (read %u, should be %u at least)"), len, (u_int)sizeof(WaveFmtBody)); prg_exit(EXIT_FAILURE); } check_wavefile_space(buffer, len, blimit); test_wavefile_read(fd, buffer, &size, len, __LINE__); f = (WaveFmtBody*) buffer; format = TO_CPU_SHORT(f->format, big_endian); if (format == WAV_FMT_EXTENSIBLE) { WaveFmtExtensibleBody *fe = (WaveFmtExtensibleBody*)buffer; if (len < sizeof(WaveFmtExtensibleBody)) { printf(("unknown length of extensible 'fmt ' chunk (read %u, should be %u at least)"), len, (u_int)sizeof(WaveFmtExtensibleBody)); prg_exit(EXIT_FAILURE); } if (memcmp(fe->guid_tag, WAV_GUID_TAG, 14) != 0) { printf(("wrong format tag in extensible 'fmt ' chunk")); prg_exit(EXIT_FAILURE); } format = TO_CPU_SHORT(fe->guid_format, big_endian); } if (format != WAV_FMT_PCM && format != WAV_FMT_IEEE_FLOAT) { printf(("can't play WAVE-file format 0x%04x which is not PCM or FLOAT encoded"), format); prg_exit(EXIT_FAILURE); } channels = TO_CPU_SHORT(f->channels, big_endian); if (channels < 1) { printf(("can't play WAVE-files with %d tracks"), channels); prg_exit(EXIT_FAILURE); } hwparams.channels = channels; switch (TO_CPU_SHORT(f->bit_p_spl, big_endian)) { case 8: if (hwparams.format != DEFAULT_FORMAT && hwparams.format != SND_PCM_FORMAT_U8) fprintf(stderr, ("Warning: format is changed to U8\n")); hwparams.format = SND_PCM_FORMAT_U8; break; case 16: if (big_endian) native_format = SND_PCM_FORMAT_S16_BE; else native_format = SND_PCM_FORMAT_S16_LE; if (hwparams.format != DEFAULT_FORMAT && hwparams.format != native_format) fprintf(stderr, ("Warning: format is changed to %s\n"), snd_pcm_format_name((snd_pcm_format_t)native_format)); hwparams.format = (snd_pcm_format_t)native_format; break; case 24: switch (TO_CPU_SHORT(f->byte_p_spl, big_endian) / hwparams.channels) { case 3: if (big_endian) native_format = SND_PCM_FORMAT_S24_3BE; else native_format = SND_PCM_FORMAT_S24_3LE; if (hwparams.format != DEFAULT_FORMAT && hwparams.format != native_format) fprintf(stderr, ("Warning: format is changed to %s\n"), snd_pcm_format_name((snd_pcm_format_t)native_format)); hwparams.format = (snd_pcm_format_t)native_format; break; case 4: if (big_endian) native_format = SND_PCM_FORMAT_S24_BE; else native_format = SND_PCM_FORMAT_S24_LE; if (hwparams.format != DEFAULT_FORMAT && hwparams.format != native_format) fprintf(stderr, ("Warning: format is changed to %s\n"), snd_pcm_format_name((snd_pcm_format_t)native_format)); hwparams.format = (snd_pcm_format_t)native_format; break; default: printf((" can't play WAVE-files with sample %d bits in %d bytes wide (%d channels)"), TO_CPU_SHORT(f->bit_p_spl, big_endian), TO_CPU_SHORT(f->byte_p_spl, big_endian), hwparams.channels); prg_exit(EXIT_FAILURE); } break; case 32: if (format == WAV_FMT_PCM) { if (big_endian) native_format = SND_PCM_FORMAT_S32_BE; else native_format = SND_PCM_FORMAT_S32_LE; hwparams.format = (snd_pcm_format_t)native_format; } else if (format == WAV_FMT_IEEE_FLOAT) { if (big_endian) native_format = SND_PCM_FORMAT_FLOAT_BE; else native_format = SND_PCM_FORMAT_FLOAT_LE; hwparams.format = (snd_pcm_format_t)native_format; } break; default: printf((" can't play WAVE-files with sample %d bits wide"), TO_CPU_SHORT(f->bit_p_spl, big_endian)); prg_exit(EXIT_FAILURE); } hwparams.rate = TO_CPU_INT(f->sample_fq, big_endian); if (size > len) memmove(buffer, buffer + len, size - len); size -= len; while (1) { u_int type, len; check_wavefile_space(buffer, sizeof(WaveChunkHeader), blimit); test_wavefile_read(fd, buffer, &size, sizeof(WaveChunkHeader), __LINE__); c = (WaveChunkHeader*)buffer; type = c->type; len = TO_CPU_INT(c->length, big_endian); if (size > sizeof(WaveChunkHeader)) memmove(buffer, buffer + sizeof(WaveChunkHeader), size - sizeof(WaveChunkHeader)); size -= sizeof(WaveChunkHeader); if (type == WAV_DATA) { if (len < pbrec_count && len < 0x7ffffffe) pbrec_count = len; if (size > 0) memcpy(_buffer, buffer, size); free(buffer); return size; } len += len % 2; check_wavefile_space(buffer, len, blimit); test_wavefile_read(fd, buffer, &size, len, __LINE__); if (size > len) memmove(buffer, buffer + len, size - len); size -= len; } /* shouldn't be reached */ return -1; } void UkuiLoginSound::show_available_sample_formats(snd_pcm_hw_params_t* params) { // snd_pcm_format_t format; int format; fprintf(stderr, "Available formats:\n"); for (format = 0; format <= SND_PCM_FORMAT_LAST; format++) { if (snd_pcm_hw_params_test_format(handle, params, (snd_pcm_format_t)format) == 0) fprintf(stderr, "- %s\n", snd_pcm_format_name((snd_pcm_format_t)format)); } } #ifdef CONFIG_SUPPORT_CHMAP int UkuiLoginSound::setup_chmap(void) { snd_pcm_chmap_t *chmap = channel_map; char mapped[hwparams.channels]; snd_pcm_chmap_t *hw_chmap; unsigned int ch, i; int err; if (!chmap) return 0; if (chmap->channels != hwparams.channels) { printf(("Channel numbers don't match between hw_params and channel map")); return -1; } err = snd_pcm_set_chmap(handle, chmap); if (!err) return 0; hw_chmap = snd_pcm_get_chmap(handle); if (!hw_chmap) { fprintf(stderr, ("Warning: unable to get channel map\n")); return 0; } if (hw_chmap->channels == chmap->channels && !memcmp(hw_chmap, chmap, 4 * (chmap->channels + 1))) { /* maps are identical, so no need to convert */ free(hw_chmap); return 0; } hw_map = (unsigned int *)calloc(hwparams.channels, sizeof(int)); if (!hw_map) { printf(("not enough memory")); free(hw_chmap); return -1; } memset(mapped, 0, sizeof(mapped)); for (ch = 0; ch < hw_chmap->channels; ch++) { if (chmap->pos[ch] == hw_chmap->pos[ch]) { mapped[ch] = 1; hw_map[ch] = ch; continue; } for (i = 0; i < hw_chmap->channels; i++) { if (!mapped[i] && chmap->pos[ch] == hw_chmap->pos[i]) { mapped[i] = 1; hw_map[ch] = i; break; } } if (i >= hw_chmap->channels) { char buf[256]; printf(("Channel %d doesn't match with hw_parmas"), ch); snd_pcm_chmap_print(hw_chmap, sizeof(buf), buf); fprintf(stderr, "hardware chmap = %s\n", buf); free(hw_chmap); return -1; } } free(hw_chmap); return 0; } #else #define setup_chmap() 0 #endif void UkuiLoginSound::set_params(void) { snd_pcm_hw_params_t *params; snd_pcm_sw_params_t *swparams; snd_pcm_uframes_t buffer_size; int err; size_t n; unsigned int rate; snd_pcm_uframes_t start_threshold, stop_threshold; snd_pcm_hw_params_alloca(¶ms); snd_pcm_sw_params_alloca(&swparams); err = snd_pcm_hw_params_any(handle, params); if (err < 0) { printf(("Broken configuration for this PCM: no configurations available")); prg_exit(EXIT_FAILURE); } if (dump_hw_params) { fprintf(stderr, ("HW Params of device \"%s\":\n"), snd_pcm_name(handle)); fprintf(stderr, "--------------------\n"); snd_pcm_hw_params_dump(params, snd_log); fprintf(stderr, "--------------------\n"); } if (mmap_flag) { snd_pcm_access_mask_t *mask = (snd_pcm_access_mask_t *)alloca(snd_pcm_access_mask_sizeof()); snd_pcm_access_mask_none(mask); snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_INTERLEAVED); snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED); snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_COMPLEX); err = snd_pcm_hw_params_set_access_mask(handle, params, mask); } else if (interleaved) err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); else err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_NONINTERLEAVED); if (err < 0) { printf(("Access type not available")); prg_exit(EXIT_FAILURE); } err = snd_pcm_hw_params_set_format(handle, params, hwparams.format); if (err < 0) { printf(("Sample format non available")); show_available_sample_formats(params); prg_exit(EXIT_FAILURE); } err = snd_pcm_hw_params_set_channels(handle, params, hwparams.channels); if (err < 0) { printf(("Channels count non available")); prg_exit(EXIT_FAILURE); } #if 0 err = snd_pcm_hw_params_set_periods_min(handle, params, 2); assert(err >= 0); #endif rate = hwparams.rate; err = snd_pcm_hw_params_set_rate_near(handle, params, &hwparams.rate, 0); assert(err >= 0); if ((float)rate * 1.05 < hwparams.rate || (float)rate * 0.95 > hwparams.rate) { if (!quiet_mode) { char plugex[64]; const char *pcmname = snd_pcm_name(handle); fprintf(stderr, ("Warning: rate is not accurate (requested = %iHz, got = %iHz)\n"), rate, hwparams.rate); if (! pcmname || strchr(snd_pcm_name(handle), ':')) *plugex = 0; else snprintf(plugex, sizeof(plugex), "(-Dplug:%s)", snd_pcm_name(handle)); fprintf(stderr, (" please, try the plug plugin %s\n"), plugex); } } rate = hwparams.rate; if (buffer_time == 0 && buffer_frames == 0) { err = snd_pcm_hw_params_get_buffer_time_max(params, &buffer_time, 0); assert(err >= 0); if (buffer_time > 500000) buffer_time = 500000; } if (period_time == 0 && period_frames == 0) { if (buffer_time > 0) period_time = buffer_time / 4; else period_frames = buffer_frames / 4; } if (period_time > 0) err = snd_pcm_hw_params_set_period_time_near(handle, params, &period_time, 0); else err = snd_pcm_hw_params_set_period_size_near(handle, params, &period_frames, 0); assert(err >= 0); if (buffer_time > 0) { err = snd_pcm_hw_params_set_buffer_time_near(handle, params, &buffer_time, 0); } else { err = snd_pcm_hw_params_set_buffer_size_near(handle, params, &buffer_frames); } assert(err >= 0); monotonic = snd_pcm_hw_params_is_monotonic(params); can_pause = snd_pcm_hw_params_can_pause(params); err = snd_pcm_hw_params(handle, params); if (err < 0) { printf(("Unable to install hw params:")); snd_pcm_hw_params_dump(params, snd_log); prg_exit(EXIT_FAILURE); } snd_pcm_hw_params_get_period_size(params, &chunk_size, 0); snd_pcm_hw_params_get_buffer_size(params, &buffer_size); if (chunk_size == buffer_size) { printf(("Can't use period equal to buffer size (%lu == %lu)"), chunk_size, buffer_size); prg_exit(EXIT_FAILURE); } err = snd_pcm_sw_params_current(handle, swparams); if (err < 0) { printf(("Unable to get current sw params.")); prg_exit(EXIT_FAILURE); } if (avail_min < 0) n = chunk_size; else n = (double) rate * avail_min / 1000000; err = snd_pcm_sw_params_set_avail_min(handle, swparams, n); /* round up to closest transfer boundary */ n = buffer_size; if (start_delay <= 0) { start_threshold = n + (double) rate * start_delay / 1000000; } else start_threshold = (double) rate * start_delay / 1000000; if (start_threshold < 1) start_threshold = 1; if (start_threshold > n) start_threshold = n; err = snd_pcm_sw_params_set_start_threshold(handle, swparams, start_threshold); assert(err >= 0); if (stop_delay <= 0) stop_threshold = buffer_size + (double) rate * stop_delay / 1000000; else stop_threshold = (double) rate * stop_delay / 1000000; err = snd_pcm_sw_params_set_stop_threshold(handle, swparams, stop_threshold); assert(err >= 0); if (snd_pcm_sw_params(handle, swparams) < 0) { printf(("unable to install sw params:")); snd_pcm_sw_params_dump(swparams, snd_log); prg_exit(EXIT_FAILURE); } if (setup_chmap()) prg_exit(EXIT_FAILURE); if (verbose) snd_pcm_dump(handle, snd_log); bits_per_sample = snd_pcm_format_physical_width(hwparams.format); significant_bits_per_sample = snd_pcm_format_width(hwparams.format); bits_per_frame = bits_per_sample * hwparams.channels; chunk_bytes = chunk_size * bits_per_frame / 8; audiobuf = (u_char *)realloc(audiobuf, chunk_bytes); if (audiobuf == NULL) { printf(("not enough memory")); prg_exit(EXIT_FAILURE); } // fprintf(stderr, "real chunk_size = %i, frags = %i, total = %i\n", chunk_size, setup.buf.block.frags, setup.buf.block.frags * chunk_size); /* stereo VU-meter isn't always available... */ if (vumeter == VUMETER_STEREO) { if (hwparams.channels != 2 || !interleaved || verbose > 2) vumeter = VUMETER_MONO; } /* show mmap buffer arragment */ if (mmap_flag && verbose) { const snd_pcm_channel_area_t *areas; snd_pcm_uframes_t offset, size = chunk_size; int i; err = snd_pcm_mmap_begin(handle, &areas, &offset, &size); if (err < 0) { printf(("snd_pcm_mmap_begin problem: %s"), snd_strerror(err)); prg_exit(EXIT_FAILURE); } for (i = 0; i < hwparams.channels; i++) fprintf(stderr, "mmap_area[%i] = %p,%u,%u (%u)\n", i, areas[i].addr, areas[i].first, areas[i].step, snd_pcm_format_physical_width(hwparams.format)); /* not required, but for sure */ snd_pcm_mmap_commit(handle, offset, 0); } buffer_frames = buffer_size; /* for position test */ } void UkuiLoginSound::init_stdin(void) { struct termios term; long flags; if (!interactive) return; if (!isatty(fileno(stdin))) { interactive = 0; return; } tcgetattr(fileno(stdin), &term); term_c_lflag = term.c_lflag; if (fd == fileno(stdin)) return; flags = fcntl(fileno(stdin), F_GETFL); if (flags < 0 || fcntl(fileno(stdin), F_SETFL, flags|O_NONBLOCK) < 0) fprintf(stderr, ("stdin O_NONBLOCK flag setup failed\n")); term.c_lflag &= ~ICANON; tcsetattr(fileno(stdin), TCSANOW, &term); } void UkuiLoginSound::done_stdin(void) { struct termios term; if (!interactive) return; if (fd == fileno(stdin) || term_c_lflag == -1) return; tcgetattr(fileno(stdin), &term); term.c_lflag = term_c_lflag; tcsetattr(fileno(stdin), TCSANOW, &term); } void UkuiLoginSound::do_pause(void) { int err; unsigned char b; if (!can_pause) { fprintf(stderr, ("\rPAUSE command ignored (no hw support)\n")); return; } if (snd_pcm_state(handle) == SND_PCM_STATE_SUSPENDED) suspend(); err = snd_pcm_pause(handle, 1); if (err < 0) { printf(("pause push error: %s"), snd_strerror(err)); return; } while (1) { while (read(fileno(stdin), &b, 1) != 1); if (b == ' ' || b == '\r') { while (read(fileno(stdin), &b, 1) == 1); if (snd_pcm_state(handle) == SND_PCM_STATE_SUSPENDED) suspend(); err = snd_pcm_pause(handle, 0); if (err < 0) printf(("pause release error: %s"), snd_strerror(err)); return; } } } void UkuiLoginSound::check_stdin(void) { unsigned char b; if (!interactive) return; if (fd != fileno(stdin)) { while (read(fileno(stdin), &b, 1) == 1) { if (b == ' ' || b == '\r') { while (read(fileno(stdin), &b, 1) == 1); fprintf(stderr, ("\r=== PAUSE === ")); fflush(stderr); do_pause(); fprintf(stderr, " \r"); fflush(stderr); } } } } #ifndef timersub #define timersub(a, b, result) \ do { \ (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \ if ((result)->tv_usec < 0) { \ --(result)->tv_sec; \ (result)->tv_usec += 1000000; \ } \ } while (0) #endif #ifndef timermsub #define timermsub(a, b, result) \ do { \ (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ (result)->tv_nsec = (a)->tv_nsec - (b)->tv_nsec; \ if ((result)->tv_nsec < 0) { \ --(result)->tv_sec; \ (result)->tv_nsec += 1000000000L; \ } \ } while (0) #endif /* I/O error handler */ void UkuiLoginSound::xrun(void) { snd_pcm_status_t *status; int res; snd_pcm_status_alloca(&status); if ((res = snd_pcm_status(handle, status))<0) { printf(("status error: %s"), snd_strerror(res)); prg_exit(EXIT_FAILURE); } if (snd_pcm_status_get_state(status) == SND_PCM_STATE_XRUN) { if (fatal_errors) { printf(("fatal %s: %s"), stream == SND_PCM_STREAM_PLAYBACK ? ("underrun") : ("overrun"), snd_strerror(res)); prg_exit(EXIT_FAILURE); } if (monotonic) { #ifdef HAVE_CLOCK_GETTIME struct timespec now, diff, tstamp; clock_gettime(CLOCK_MONOTONIC, &now); snd_pcm_status_get_trigger_htstamp(status, &tstamp); timermsub(&now, &tstamp, &diff); fprintf(stderr, _("%s!!! (at least %.3f ms long)\n"), stream == SND_PCM_STREAM_PLAYBACK ? _("underrun") : _("overrun"), diff.tv_sec * 1000 + diff.tv_nsec / 1000000.0); #else fprintf(stderr, "%s !!!\n", ("underrun")); #endif } else { struct timeval now, diff, tstamp; gettimeofday(&now, 0); snd_pcm_status_get_trigger_tstamp(status, &tstamp); timersub(&now, &tstamp, &diff); fprintf(stderr, ("%s!!! (at least %.3f ms long)\n"), stream == SND_PCM_STREAM_PLAYBACK ? ("underrun") : ("overrun"), diff.tv_sec * 1000 + diff.tv_usec / 1000.0); } if (verbose) { fprintf(stderr, ("Status:\n")); snd_pcm_status_dump(status, snd_log); } if ((res = snd_pcm_prepare(handle))<0) { printf(("xrun: prepare error: %s"), snd_strerror(res)); prg_exit(EXIT_FAILURE); } return; /* ok, data should be accepted again */ } if (snd_pcm_status_get_state(status) == SND_PCM_STATE_DRAINING) { if (verbose) { fprintf(stderr, ("Status(DRAINING):\n")); snd_pcm_status_dump(status, snd_log); } if (stream == SND_PCM_STREAM_CAPTURE) { fprintf(stderr, ("capture stream format change? attempting recover...\n")); if ((res = snd_pcm_prepare(handle))<0) { printf(("xrun(DRAINING): prepare error: %s"), snd_strerror(res)); prg_exit(EXIT_FAILURE); } return; } } if (verbose) { fprintf(stderr, ("Status(R/W):\n")); snd_pcm_status_dump(status, snd_log); } printf(("read/write error, state = %s"), snd_pcm_state_name(snd_pcm_status_get_state(status))); prg_exit(EXIT_FAILURE); } /* I/O suspend handler */ void UkuiLoginSound::suspend(void) { int res; if (!quiet_mode) { fprintf(stderr, ("Suspended. Trying resume. ")); fflush(stderr); } while ((res = snd_pcm_resume(handle)) == -EAGAIN) sleep(1); /* wait until suspend flag is released */ if (res < 0) { if (!quiet_mode) { fprintf(stderr, ("Failed. Restarting stream. ")); fflush(stderr); } if ((res = snd_pcm_prepare(handle)) < 0) { printf(("suspend: prepare error: %s"), snd_strerror(res)); prg_exit(EXIT_FAILURE); } } if (!quiet_mode) fprintf(stderr, ("Done.\n")); } void UkuiLoginSound::print_vu_meter_mono(int perc, int maxperc) { const int bar_length = 50; char line[80]; int val; for (val = 0; val <= perc * bar_length / 100 && val < bar_length; val++) line[val] = '#'; for (; val <= maxperc * bar_length / 100 && val < bar_length; val++) line[val] = ' '; line[val] = '+'; for (++val; val <= bar_length; val++) line[val] = ' '; // if (maxperc > 99) // sprintf(line + val, "| MAX"); // else // sprintf(line + val, "| %02i%%", maxperc); if (maxperc > 99) snprintf(line + val, sizeof(line) - val, "| MAX"); else snprintf(line + val, sizeof(line) - val, "| %02i%%", maxperc); fputs(line, stderr); if (perc > 100) fprintf(stderr, (" !clip ")); } void UkuiLoginSound::print_vu_meter_stereo(int *perc, int *maxperc) { const int bar_length = 35; char line[80]; int c; memset(line, ' ', sizeof(line) - 1); line[bar_length + 3] = '|'; for (c = 0; c < 2; c++) { int p = perc[c] * bar_length / 100; char tmp[4]; if (p > bar_length) p = bar_length; if (c) memset(line + bar_length + 6 + 1, '#', p); else memset(line + bar_length - p - 1, '#', p); p = maxperc[c] * bar_length / 100; if (p > bar_length) p = bar_length; if (c) line[bar_length + 6 + 1 + p] = '+'; else line[bar_length - p - 1] = '+'; // if (ABS(maxperc[c]) > 99) // sprintf(tmp, "MAX"); // else // sprintf(tmp, "%02d%%", maxperc[c]); if (ABS(maxperc[c]) > 99) snprintf(tmp, sizeof(tmp), "MAX"); else snprintf(tmp, sizeof(tmp), "%02d%%", maxperc[c]); if (c) memcpy(line + bar_length + 3 + 1, tmp, 3); else memcpy(line + bar_length, tmp, 3); } line[bar_length * 2 + 6 + 2] = 0; fputs(line, stderr); } void UkuiLoginSound::print_vu_meter(signed int *perc, signed int *maxperc) { if (vumeter == VUMETER_STEREO) print_vu_meter_stereo(perc, maxperc); else print_vu_meter_mono(*perc, *maxperc); } /* peak handler */ void UkuiLoginSound::compute_max_peak(u_char *data, size_t count) { signed int val, max, perc[2], max_peak[2]; static int run = 0; size_t ocount = count; int format_little_endian = snd_pcm_format_little_endian(hwparams.format); int ichans, c; if (vumeter == VUMETER_STEREO) ichans = 2; else ichans = 1; memset(max_peak, 0, sizeof(max_peak)); switch (bits_per_sample) { case 8: { signed char *valp = (signed char *)data; signed char mask = snd_pcm_format_silence(hwparams.format); c = 0; while (count-- > 0) { val = *valp++ ^ mask; val = abs(val); if (max_peak[c] < val) max_peak[c] = val; if (vumeter == VUMETER_STEREO) c = !c; } break; } case 16: { signed short *valp = (signed short *)data; signed short mask = snd_pcm_format_silence_16(hwparams.format); signed short sval; count /= 2; c = 0; while (count-- > 0) { if (format_little_endian) sval = le16toh(*valp); else sval = be16toh(*valp); sval = abs(sval) ^ mask; if (max_peak[c] < sval) max_peak[c] = sval; valp++; if (vumeter == VUMETER_STEREO) c = !c; } break; } case 24: { unsigned char *valp = data; signed int mask = snd_pcm_format_silence_32(hwparams.format); count /= 3; c = 0; while (count-- > 0) { if (format_little_endian) { val = valp[0] | (valp[1]<<8) | (valp[2]<<16); } else { val = (valp[0]<<16) | (valp[1]<<8) | valp[2]; } /* Correct signed bit in 32-bit value */ if (val & (1<<(bits_per_sample-1))) { val |= 0xff<<24; /* Negate upper bits too */ } val = abs(val) ^ mask; if (max_peak[c] < val) max_peak[c] = val; valp += 3; if (vumeter == VUMETER_STEREO) c = !c; } break; } case 32: { signed int *valp = (signed int *)data; signed int mask = snd_pcm_format_silence_32(hwparams.format); count /= 4; c = 0; while (count-- > 0) { if (format_little_endian) val = le32toh(*valp); else val = be32toh(*valp); val = abs(val) ^ mask; if (max_peak[c] < val) max_peak[c] = val; valp++; if (vumeter == VUMETER_STEREO) c = !c; } break; } default: if (run == 0) { fprintf(stderr, ("Unsupported bit size %d.\n"), (int)bits_per_sample); run = 1; } return; } max = 1 << (significant_bits_per_sample-1); if (max <= 0) max = 0x7fffffff; for (c = 0; c < ichans; c++) { if (bits_per_sample > 16) perc[c] = max_peak[c] / (max / 100); else perc[c] = max_peak[c] * 100 / max; } if (interleaved && verbose <= 2) { static int maxperc[2]; static time_t t=0; const time_t tt=time(NULL); if(tt>t) { t=tt; maxperc[0] = 0; maxperc[1] = 0; } for (c = 0; c < ichans; c++) if (perc[c] > maxperc[c]) maxperc[c] = perc[c]; putc('\r', stderr); print_vu_meter(perc, maxperc); fflush(stderr); } else if(verbose==3) { fprintf(stderr, ("Max peak (%li samples): 0x%08x "), (long)ocount, max_peak[0]); for (val = 0; val < 20; val++) if (val <= perc[0] / 5) putc('#', stderr); else putc(' ', stderr); fprintf(stderr, " %i%%\n", perc[0]); fflush(stderr); } } void UkuiLoginSound::do_test_position(void) { static long counter = 0; static time_t tmr = -1; time_t now; static float availsum, delaysum, samples; static snd_pcm_sframes_t maxavail, maxdelay; static snd_pcm_sframes_t minavail, mindelay; static snd_pcm_sframes_t badavail = 0, baddelay = 0; snd_pcm_sframes_t outofrange; snd_pcm_sframes_t avail, delay; int err; err = snd_pcm_avail_delay(handle, &avail, &delay); if (err < 0) return; outofrange = (test_coef * (snd_pcm_sframes_t)buffer_frames) / 2; if (avail > outofrange || avail < -outofrange || delay > outofrange || delay < -outofrange) { badavail = avail; baddelay = delay; availsum = delaysum = samples = 0; maxavail = maxdelay = 0; minavail = mindelay = buffer_frames * 16; fprintf(stderr, ("Suspicious buffer position (%li total): " "avail = %li, delay = %li, buffer = %li\n"), ++counter, (long)avail, (long)delay, (long)buffer_frames); } else if (verbose) { time(&now); if (tmr == (time_t) -1) { tmr = now; availsum = delaysum = samples = 0; maxavail = maxdelay = 0; minavail = mindelay = buffer_frames * 16; } if (avail > maxavail) maxavail = avail; if (delay > maxdelay) maxdelay = delay; if (avail < minavail) minavail = avail; if (delay < mindelay) mindelay = delay; availsum += avail; delaysum += delay; samples++; if (avail != 0 && now != tmr) { fprintf(stderr, "BUFPOS: avg%li/%li " "min%li/%li max%li/%li (%li) (%li:%li/%li)\n", (long)(availsum / samples), (long)(delaysum / samples), (long)minavail, (long)mindelay, (long)maxavail, (long)maxdelay, (long)buffer_frames, counter, badavail, baddelay); tmr = now; } } } /* */ #ifdef CONFIG_SUPPORT_CHMAP u_char * UkuiLoginSound::remap_data(u_char *data, size_t count) { static u_char *tmp, *src, *dst; static size_t tmp_size; size_t sample_bytes = bits_per_sample / 8; size_t step = bits_per_frame / 8; size_t chunk_bytes; unsigned int ch, i; if (!hw_map) return data; chunk_bytes = count * bits_per_frame / 8; if (tmp_size < chunk_bytes) { free(tmp); tmp = (u_char *)malloc(chunk_bytes); if (!tmp) { printf(("not enough memory")); exit(1); } tmp_size = count; } src = data; dst = tmp; for (i = 0; i < count; i++) { for (ch = 0; ch < hwparams.channels; ch++) { memcpy(dst, src + sample_bytes * hw_map[ch], sample_bytes); dst += sample_bytes; } src += step; } return tmp; } u_char ** UkuiLoginSound::remap_datav(u_char **data, [[maybe_unused]] size_t count) { static u_char **tmp; unsigned int ch; if (!hw_map) return data; if (!tmp) { tmp = (u_char **)malloc(sizeof(*tmp) * hwparams.channels); if (!tmp) { printf(("not enough memory")); exit(1); } for (ch = 0; ch < hwparams.channels; ch++) tmp[ch] = data[hw_map[ch]]; } return tmp; } #else #define remap_data(data, count) (data) #define remap_datav(data, count) (data) #endif /* * write function */ ssize_t UkuiLoginSound::pcm_write(u_char *data, size_t count) { ssize_t r; ssize_t result = 0; if (count < chunk_size) { snd_pcm_format_set_silence(hwparams.format, data + count * bits_per_frame / 8, (chunk_size - count) * hwparams.channels); count = chunk_size; } data = remap_data(data, count); while (count > 0 && !in_aborting) { if (test_position) do_test_position(); check_stdin(); r = writei_func(handle, data, count); if (test_position) do_test_position(); if (r == -EAGAIN || (r >= 0 && (size_t)r < count)) { if (!test_nowait) snd_pcm_wait(handle, 100); } else if (r == -EPIPE) { xrun(); } else if (r == -ESTRPIPE) { suspend(); } else if (r < 0) { printf(("write error: %s"), snd_strerror(r)); prg_exit(EXIT_FAILURE); } if (r > 0) { if (vumeter) compute_max_peak(data, r * hwparams.channels); result += r; count -= r; data += r * bits_per_frame / 8; } } return result; } ssize_t UkuiLoginSound::pcm_writev(u_char **data, unsigned int channels, size_t count) { ssize_t r; size_t result = 0; if (count != chunk_size) { unsigned int channel; size_t offset = count; size_t remaining = chunk_size - count; for (channel = 0; channel < channels; channel++) snd_pcm_format_set_silence(hwparams.format, data[channel] + offset * bits_per_sample / 8, remaining); count = chunk_size; } data = remap_datav(data, count); while (count > 0 && !in_aborting) { unsigned int channel; void *bufs[channels]; size_t offset = result; for (channel = 0; channel < channels; channel++) bufs[channel] = data[channel] + offset * bits_per_sample / 8; if (test_position) do_test_position(); check_stdin(); r = writen_func(handle, bufs, count); if (test_position) do_test_position(); if (r == -EAGAIN || (r >= 0 && (size_t)r < count)) { if (!test_nowait) snd_pcm_wait(handle, 100); } else if (r == -EPIPE) { xrun(); } else if (r == -ESTRPIPE) { suspend(); } else if (r < 0) { printf(("writev error: %s"), snd_strerror(r)); prg_exit(EXIT_FAILURE); } if (r > 0) { if (vumeter) { for (channel = 0; channel < channels; channel++) compute_max_peak(data[channel], r); } result += r; count -= r; } } return result; } /* * read function */ ssize_t UkuiLoginSound::pcm_read(u_char *data, size_t rcount) { ssize_t r; size_t result = 0; size_t count = rcount; if (count != chunk_size) { count = chunk_size; } while (count > 0 && !in_aborting) { if (test_position) do_test_position(); check_stdin(); r = readi_func(handle, data, count); if (test_position) do_test_position(); if (r == -EAGAIN || (r >= 0 && (size_t)r < count)) { if (!test_nowait) snd_pcm_wait(handle, 100); } else if (r == -EPIPE) { xrun(); } else if (r == -ESTRPIPE) { suspend(); } else if (r < 0) { printf(("read error: %s"), snd_strerror(r)); prg_exit(EXIT_FAILURE); } if (r > 0) { if (vumeter) compute_max_peak(data, r * hwparams.channels); result += r; count -= r; data += r * bits_per_frame / 8; } } return rcount; } ssize_t UkuiLoginSound::pcm_readv(u_char **data, unsigned int channels, size_t rcount) { ssize_t r; size_t result = 0; size_t count = rcount; if (count != chunk_size) { count = chunk_size; } while (count > 0 && !in_aborting) { unsigned int channel; void *bufs[channels]; size_t offset = result; for (channel = 0; channel < channels; channel++) bufs[channel] = data[channel] + offset * bits_per_sample / 8; if (test_position) do_test_position(); check_stdin(); r = readn_func(handle, bufs, count); if (test_position) do_test_position(); if (r == -EAGAIN || (r >= 0 && (size_t)r < count)) { if (!test_nowait) snd_pcm_wait(handle, 100); } else if (r == -EPIPE) { xrun(); } else if (r == -ESTRPIPE) { suspend(); } else if (r < 0) { printf(("readv error: %s"), snd_strerror(r)); prg_exit(EXIT_FAILURE); } if (r > 0) { if (vumeter) { for (channel = 0; channel < channels; channel++) compute_max_peak(data[channel], r); } result += r; count -= r; } } return rcount; } /* setting the globals for playing raw data */ void UkuiLoginSound::init_raw_data(void) { hwparams = rhwparams; } /* calculate the data count to read from/to dsp */ off64_t UkuiLoginSound::calc_count(void) { off64_t count; if (timelimit == 0) if (sampleslimit == 0) count = pbrec_count; else count = snd_pcm_format_size(hwparams.format, sampleslimit * hwparams.channels); else { count = snd_pcm_format_size(hwparams.format, hwparams.rate * hwparams.channels); count *= (off64_t)timelimit; } return count < pbrec_count ? count : pbrec_count; } void UkuiLoginSound::header(int rtype, char *name) { if (!quiet_mode) { if (! name) name = (char *)((stream == SND_PCM_STREAM_PLAYBACK) ? "stdout" : "stdin"); fprintf(stderr, "%s, ", snd_pcm_format_description(hwparams.format)); fprintf(stderr, ("Rate %d Hz, "), hwparams.rate); if (hwparams.channels == 1) fprintf(stderr, ("Mono")); else if (hwparams.channels == 2) fprintf(stderr, ("Stereo")); else fprintf(stderr, ("Channels %i"), hwparams.channels); fprintf(stderr, "\n"); } } /* playing raw data */ void UkuiLoginSound::playback_go(int fd, size_t loaded, off64_t count, int rtype, char *name) { int l, r; off64_t written = 0; off64_t c; header(rtype, name); set_params(); while (loaded > chunk_bytes && written < count && !in_aborting) { if (pcm_write(audiobuf + written, chunk_size) <= 0) return; written += chunk_bytes; loaded -= chunk_bytes; } if (written > 0 && loaded > 0) memmove(audiobuf, audiobuf + written, loaded); l = loaded; while (written < count && !in_aborting) { do { c = count - written; if (c > chunk_bytes) c = chunk_bytes; /* c < l, there is more data loaded * then we actually need to write */ if (c < l) l = c; c -= l; if (c == 0) break; r = safe_read(fd, audiobuf + l, c); if (r < 0) { perror(name); prg_exit(EXIT_FAILURE); } fdcount += r; if (r == 0) break; l += r; } while ((size_t)l < chunk_bytes); l = l * 8 / bits_per_frame; r = pcm_write(audiobuf, l); if (r != l) break; r = r * bits_per_frame / 8; written += r; l = 0; } snd_pcm_nonblock(handle, 0); snd_pcm_drain(handle); snd_pcm_nonblock(handle, nonblock); } int UkuiLoginSound::read_header(int *loaded, int header_size) { int ret; struct stat buf; ret = fstat(fd, &buf); if (ret < 0) { perror("fstat"); prg_exit(EXIT_FAILURE); } /* don't be adventurous, get out if file size is smaller than * requested header size */ if ((buf.st_mode & S_IFMT) == S_IFREG && buf.st_size < header_size) return -1; if (*loaded < header_size) { header_size -= *loaded; ret = safe_read(fd, audiobuf + *loaded, header_size); if (ret != header_size) { printf(("read error")); prg_exit(EXIT_FAILURE); } *loaded += header_size; } return 0; } int UkuiLoginSound::playback_wave(char *name, int *loaded) { ssize_t dtawave; syslog (LOG_INFO, "start play %s file...", name); if (read_header(loaded, sizeof(WaveHeader)) < 0) return -1; if ((dtawave = test_wavefile(fd, audiobuf, *loaded)) < 0) return -1; pbrec_count = calc_count(); playback_go(fd, dtawave, pbrec_count, FORMAT_WAVE, name); return 0; } int UkuiLoginSound::playback_raw(char *name, int *loaded) { syslog (LOG_INFO, "start play %s file...", name); init_raw_data(); pbrec_count = calc_count(); playback_go(fd, *loaded, pbrec_count, FORMAT_RAW, name); return 0; } /* * let's play or capture it (capture_type says VOC/WAVE/raw) */ void UkuiLoginSound::playback(char *name) { int loaded = 0; pbrec_count = LLONG_MAX; fdcount = 0; if (!name || !strcmp(name, "-")) { fd = fileno(stdin); name = "stdin"; } else { init_stdin(); if ((fd = open(name, O_RDONLY, 0)) == -1) { perror(name); prg_exit(EXIT_FAILURE); } } /* parse the file header */ if (playback_wave(name, &loaded) < 0) playback_raw(name, &loaded); /* should be raw data */ if (fd != fileno(stdin)) close(fd); } UkuiLoginSound::UkuiLoginSound() { } int UkuiLoginSound::wav_convert(int volume) { if (volume > NORMAL_VOLUME) { volume = NORMAL_VOLUME; } float volume_factor = volume/NORMAL_VOLUME; QString cmd = "ffmpeg -i "; cmd += PLAY_STARTUP_WAV; cmd += " "; cmd += QString("-filter:a volume=%1 ").arg(volume_factor); cmd += TMP_STARTUP_WAV_PATH; int err = system(cmd.toLatin1().data()); if(err) { syslog(LOG_ERR, "file convert error, err:%d.", err); return -1; } return 0; } char* UkuiLoginSound::checkPcm(char *pcmName) { void **hints, **n; char *name, *defaultPcmName; defaultPcmName = "default"; if (snd_device_name_hint(-1, "pcm", &hints) < 0) return "default"; n = hints; /* If the configuration file does not specify or does not exist, * the PCM device list is traversed to find the last PCM device * as the output device to play the system sound effect. */ while (*n != NULL) { name = snd_device_name_get_hint(*n, "NAME"); if (strcmp(pcmName, name) == 0) { return pcmName; } else if (strstr(name, "sysdefault")) { defaultPcmName = name; } n++; } snd_device_name_free_hint(hints); if (name != NULL) free(name); syslog(LOG_INFO, "system does not have %s device, so the device from %s changed to %s device", pcmName, pcmName, defaultPcmName); return defaultPcmName; } bool UkuiLoginSound::isSupportHardwareVolumeControl(char *card) { int err; snd_hctl_t* handle; snd_hctl_elem_t* elem; snd_ctl_elem_info_t* info; snd_ctl_elem_id_t* id; snd_ctl_elem_info_alloca(&info); snd_ctl_elem_id_alloca(&id); bool ret = false; if ((err = snd_hctl_open(&handle, card, 0)) < 0) { syslog(LOG_ERR, "control %s open error: %s", card, snd_strerror(err)); return ret; } if ((err = snd_hctl_load(handle)) < 0) { syslog(LOG_ERR, "control %s local error: %s", card, snd_strerror(err)); return ret; } for (elem = snd_hctl_first_elem(handle); elem; elem = snd_hctl_elem_next(elem)) { if ((err = snd_hctl_elem_info(elem, info)) < 0) { syslog(LOG_ERR, "Control %s snd_hctl_elem_info error: %s", card, snd_strerror(err)); return ret; } snd_hctl_elem_get_id(elem, id); char* str = snd_ctl_ascii_elem_id_get(id); if (strstr(str, "Volume")) { ret = true; break; } } syslog(LOG_INFO, "card %s %s hardware volume control.", card, ret ? "support" : "unsupport"); return ret; } ukui-media/ukui-login-sound/ukui_login_sound_user_config.h0000664000175000017500000000400115170054730023051 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifndef UKUILOGINSOUNDUSERCONFIG_H #define UKUILOGINSOUNDUSERCONFIG_H #include #include #include #include #include #include #include #include "../common/user_config.h" #include "../common/user_info_json.h" #include "../common/sound_effect_json.h" class UkuiLoginSoundUserConfig : public UserConfig { public: UkuiLoginSoundUserConfig(); ~UkuiLoginSoundUserConfig(); public: const QString getAutoLoginUser() const; const QString getLastLoginUser() const; std::unordered_map> getJsonMap() const; virtual void initJsonMap() override; private: void initSettings(); QString getSoundEffectFullPath(); QString getUserInfoFullPath(); private: std::unordered_map> m_keys { {JsonType::JSON_TYPE_SOUNDEFFECT, [this](){ return getSoundEffectFullPath();}}, {JsonType::JSON_TYPE_USERINFO, [this](){ return getUserInfoFullPath(); }}, }; std::unique_ptr m_pLastLoginSetting; std::unique_ptr m_pAutoLoginSetting; }; #endif // UKUILOGINSOUNDUSERCONFIG_H ukui-media/AUTHORS0000664000175000017500000000133215170052044012577 0ustar fengfengUKUI: Hao Lee MATE: Perberos Steve Zesch Stefano Karapetsas Michal Ratajsky GNOME Maintainers: Marc-Andre Lureau Bastien Nocera GNOME Authors: Andreas Hyden (grecord) Seth Nickell (gst-mixer) Ronald Bultje (gst-mixer) Thomas Vander Stichele (profiles) William Jon McCann (mate-volume-control) ukui-media/ukui-media-control-led/0000775000175000017500000000000015170054730016006 5ustar fengfengukui-media/ukui-media-control-led/ukui_media_control_led.h0000664000175000017500000000237015170054730022661 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifndef MATE_MEDIA_CONTROL_LED_H #define MATE_MEDIA_CONTROL_LED_H #include #include #include class UkuiMediaControlLed : public QObject { Q_OBJECT public: UkuiMediaControlLed(); ~UkuiMediaControlLed(); private slots: void outputMuteChanged(QString str); void inputMuteChanged(QString str); private: }; #endif // MATE_MEDIA_CONTROL_LED_H ukui-media/ukui-media-control-led/ukui_media_control_led.cpp0000664000175000017500000000501015170054730023206 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #include #include #include #include #include #include #include UkuiMediaControlLed::UkuiMediaControlLed() { QDBusConnection::systemBus().connect(QString(),"/","org.ukui.media","systemOutputVolumeIsMute",this,SLOT(outputMuteChanged(QString))); QDBusConnection::systemBus().connect(QString(),"/","org.ukui.media","systemInputVolumeIsMute",this,SLOT(inputMuteChanged(QString))); } void UkuiMediaControlLed::outputMuteChanged(QString str) { QFile file("/sys/class/leds/platform::mute/brightness"); if(!file.exists()){ qDebug() << " THE DIRECTORY OR FILE IS NOT EXIST "; return; } qDebug() << "sink muted" << str; if (strstr(str.toLatin1().data(),"mute")) { qDebug() << "输出音量静音"; system("echo 1 > /sys/class/leds/platform::mute/brightness"); } else if (strstr(str.toLatin1().data(),"no")) { qDebug() << "输出音量取消静音"; system("echo 0 > /sys/class/leds/platform::mute/brightness"); } } void UkuiMediaControlLed::inputMuteChanged(QString str) { QFile file("/sys/class/leds/platform::micmute/brightness"); if(!file.exists()){ qDebug() << " THE DIRECTORY OR FILE IS NOT EXIST "; return; } qDebug() << "source muted" << str; if (strstr(str.toLatin1().data(),"mute")) { qDebug() << "输入音量静音"; system("echo 1 > /sys/class/leds/platform::micmute/brightness"); } else if (strstr(str.toLatin1().data(),"no")) { qDebug() << "输入音量取消静音"; system("echo 0 > /sys/class/leds/platform::micmute/brightness"); } } UkuiMediaControlLed::~UkuiMediaControlLed() { } ukui-media/ukui-media-control-led/main.cpp0000664000175000017500000000210315170054730017432 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "ukui_media_control_led.h" //#include #include int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); UkuiMediaControlLed control; return a.exec(); } ukui-media/ukui-media-control-led/ukui-media-control-led.pro0000664000175000017500000000206515170052044023001 0ustar fengfeng#------------------------------------------------- # # Project created by QtCreator 2021-01-03T20:04:12 # #------------------------------------------------- QT += core gui dbus #greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = ukui-media-control-led TEMPLATE = app # The following define makes your compiler emit warnings if you use # any feature of Qt which as been marked as deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the # deprecated API in order to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS # You can also make your code fail to compile if you use deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 target.path = /usr/bin SOURCES += \ main.cpp \ ukui_media_control_led.cpp HEADERS += \ ukui_media_control_led.h INSTALLS += \ target ukui-media/man/0000775000175000017500000000000015170054730012307 5ustar fengfengukui-media/man/Makefile.am0000664000175000017500000000017015170052044014335 0ustar fengfengman_MANS = ukui-volume-control.1 ukui-volume-control-applet.1 ukui-volume-control-applet-qt.1 EXTRA_DIST = $(man_MANS) ukui-media/man/ukui-media-control-led.10000664000175000017500000000206115170052044016636 0ustar fengfeng.\" Man Page for ukui-volume-control-applet-qt .TH UKUI-MEDIA-CONTROL-LED 1 "20 February 2019" "UKUI Desktop Environment" .\" Please adjust this date when revising the manpage. .\" .SH "NAME" \fBukui-media-control-led\fR \- The UKUI Volume Control Applet(Qt) .SH "SYNOPSIS" .B ukui-media-control-led [OPTIONS] .SH "DESCRIPTION" The UKUI Volume Control Applet is used for adjusting audio levels from the notification area. .SH "OPTIONS" .TP \fB\-v, \-\-version\fR Output version information and exit. .TP \fB\-d, \-\-debug\fR Enable debugging code. .TP \fB\-\-display=DISPLAY\fR X display to use. .TP \fB\-?, \-h, \-\-help\fR Print standard command line options. .TP \fB\-\-help\-all\fR Print all command line options. .SH "BUGS" .SS Should you encounter any bugs, they may be reported at: http://github.com/ukui/ukui-media/issues .SH "AUTHORS" .SS This Manual Page has been written for the UKUI Desktop Environment by: Adam Erdman (2014) .SH "SEE ALSO" .SS Further information may also be available at: http://ukui.org .BR ukui-media-control-led (1) ukui-media/man/sound-theme-player.10000664000175000017500000000066115170054730016116 0ustar fengfeng.TH sound-theme-player 1 "UKUI Media" .SH NAME sound-theme-player \- 播放 UKUI 声音主题的命令行工具 .SH SYNOPSIS .B sound-theme-player [ .I options ] .SH DESCRIPTION .B sound-theme-player 用于根据 UKUI 声音主题配置播放启动、关机等提示音。 .SH OPTIONS 无特定命令行参数。运行后按默认主题播放指定声音。 .SH SEE ALSO .BR ukui-login-sound (1) .SH AUTHOR Kylin Software Co., Ltd. ukui-media/man/ukui-login-sound.10000664000175000017500000000060415170054730015602 0ustar fengfeng.TH ukui-login-sound 1 "UKUI Media" .SH NAME ukui-login-sound \- 在登录会话时播放提示音 .SH SYNOPSIS .B ukui-login-sound [ .I options ] .SH DESCRIPTION .B ukui-login-sound 在用户登录桌面会话时播放登录提示音,用于桌面环境启动反馈。 .SH OPTIONS 无特定命令行参数。 .SH SEE ALSO .BR sound-theme-player (1) .SH AUTHOR Kylin Software Co., Ltd. ukui-media/man/ukui-volume-control-applet-qt.10000664000175000017500000000207415170052044020235 0ustar fengfeng.\" Man Page for ukui-volume-control-applet-qt .TH UKUI-VOLUME-CONTROL-APPLET-QT 1 "20 February 2019" "UKUI Desktop Environment" .\" Please adjust this date when revising the manpage. .\" .SH "NAME" \fBukui-volume-control-applet-qt\fR \- The UKUI Volume Control Applet(Qt) .SH "SYNOPSIS" .B ukui-volume-control-qt [OPTIONS] .SH "DESCRIPTION" The UKUI Volume Control Applet is used for adjusting audio levels from the notification area. .SH "OPTIONS" .TP \fB\-v, \-\-version\fR Output version information and exit. .TP \fB\-d, \-\-debug\fR Enable debugging code. .TP \fB\-\-display=DISPLAY\fR X display to use. .TP \fB\-?, \-h, \-\-help\fR Print standard command line options. .TP \fB\-\-help\-all\fR Print all command line options. .SH "BUGS" .SS Should you encounter any bugs, they may be reported at: http://github.com/ukui/ukui-media/issues .SH "AUTHORS" .SS This Manual Page has been written for the UKUI Desktop Environment by: Adam Erdman (2014) .SH "SEE ALSO" .SS Further information may also be available at: http://ukui.org .BR ukui-volume-control (1) ukui-media/COPYING0000664000175000017500000004325415170052044012573 0ustar fengfeng GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) 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 this service 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 make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. 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. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the 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 a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE 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. 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 convey 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 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision 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, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This 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. ukui-media/audio/0000775000175000017500000000000015170054730012635 5ustar fengfengukui-media/audio/ukmedia_settings_widget.cpp0000664000175000017500000001210415170054730020241 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "ukmedia_settings_widget.h" #include UkmediaSettingsWidget::UkmediaSettingsWidget(QWidget *parent) : QWidget(parent) { m_pAdvancedSettingsWidget = new QFrame(this); m_pEquipmentControlWidget = new QFrame(m_pAdvancedSettingsWidget); m_pAppSoundCtrlWidget = new QFrame(m_pAdvancedSettingsWidget); m_pAdvancedSettingsWidget->setFrameShape(QFrame::Shape::Box); m_pEquipmentControlWidget->setFrameShape(QFrame::Shape::Box); m_pAppSoundCtrlWidget->setFrameShape(QFrame::Shape::Box); //高级设置标签 //~ contents_path /Audio/Advanced Settings m_pAdvancedSettingsLabel = new TitleLabel(this); m_pAdvancedSettingsLabel->setText(tr("Advanced Settings")); m_pAdvancedSettingsLabel->setContentsMargins(16,0,16,0); m_pAdvancedSettingsLabel->setStyleSheet("QLabel{color: palette(windowText);}"); //声音设备管理标签 //~ contents_path /Audio/Sound Equipment Control m_pEquipmentControlLabel = new QLabel(tr("Sound Equipment Control"), m_pEquipmentControlWidget); m_pDevControlDetailsBtn = new QPushButton(m_pEquipmentControlWidget); m_pDevControlDetailsBtn->setText(tr("Details")); m_pDevControlDetailsBtn->setProperty("useButtonPalette",true); //应用声音标签 //~ contents_path /Audio/App Sound Control m_pAppSoundLabel = new QLabel(tr("App Sound Control"), m_pAppSoundCtrlWidget); m_pAppSoundDetailsBtn = new QPushButton(m_pAppSoundCtrlWidget); m_pAppSoundDetailsBtn->setText(tr("Details")); m_pAppSoundDetailsBtn->setProperty("useButtonPalette", true); //设置大小 m_pAdvancedSettingsWidget->setMinimumSize(550,0); m_pAdvancedSettingsWidget->setMaximumSize(16777215,240); m_pEquipmentControlWidget->setMinimumSize(550,60); m_pEquipmentControlWidget->setMaximumSize(16777215,60); m_pAppSoundCtrlWidget->setMinimumSize(550,60); m_pAppSoundCtrlWidget->setMaximumSize(16777215,60); //声音设备管控 QHBoxLayout *EquipmentControlLayout = new QHBoxLayout(m_pEquipmentControlWidget); EquipmentControlLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); EquipmentControlLayout->addWidget(m_pEquipmentControlLabel); EquipmentControlLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Expanding)); EquipmentControlLayout->addWidget(m_pDevControlDetailsBtn); EquipmentControlLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); EquipmentControlLayout->setSpacing(0); m_pEquipmentControlWidget->setLayout(EquipmentControlLayout); m_pEquipmentControlWidget->layout()->setContentsMargins(0,0,0,0); //应用声音 QHBoxLayout *AppSoundLayout = new QHBoxLayout(m_pAppSoundCtrlWidget); AppSoundLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); AppSoundLayout->addWidget(m_pAppSoundLabel); AppSoundLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Expanding)); AppSoundLayout->addWidget(m_pAppSoundDetailsBtn); AppSoundLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); AppSoundLayout->setSpacing(0); m_pAppSoundCtrlWidget->setLayout(AppSoundLayout); m_pAppSoundCtrlWidget->layout()->setContentsMargins(0,0,0,0); //设备管控和应用声音添加布局 QVBoxLayout *m_pVlayout1 = new QVBoxLayout(m_pAdvancedSettingsWidget); m_pVlayout1->setContentsMargins(0,0,0,0); m_pVlayout1->addWidget(m_pEquipmentControlWidget); m_pVlayout1->addWidget(myLine()); m_pVlayout1->addWidget(m_pAppSoundCtrlWidget); m_pVlayout1->setSpacing(0); m_pAdvancedSettingsWidget->setLayout(m_pVlayout1); m_pAdvancedSettingsWidget->layout()->setContentsMargins(0,0,0,0); //整体布局 QVBoxLayout *vLayout1 = new QVBoxLayout(this); vLayout1->addWidget(m_pAdvancedSettingsLabel); vLayout1->addItem(new QSpacerItem(16,4,QSizePolicy::Fixed)); vLayout1->addWidget(m_pAdvancedSettingsWidget); this->setLayout(vLayout1); this->layout()->setContentsMargins(0,0,0,0); } QFrame* UkmediaSettingsWidget::myLine() { QFrame *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; } UkmediaSettingsWidget::~UkmediaSettingsWidget() { } ukui-media/audio/ukmedia_output_widget.h0000664000175000017500000000535515170054730017420 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifndef UKMEDIAOUTPUTWIDGET_H #define UKMEDIAOUTPUTWIDGET_H #include #include #include #include #include #include #include #include #include #include "ukui_custom_style.h" #include "ukui_list_widget_item.h" #include "customstyle.h" #include #include #include "kswitchbutton.h" #include using namespace kdk; class UkmediaOutputWidget : public QWidget { Q_OBJECT public: explicit UkmediaOutputWidget(QWidget *parent = nullptr); ~UkmediaOutputWidget(); void setVolumeSliderRange(bool status); friend class UkmediaMainWidget; Q_SIGNALS: public Q_SLOTS: void onPaletteChanged(); private: void setLabelAlignment(Qt::Alignment alignment); QFrame* myLine(); private: QFrame *m_pOutputWidget; QFrame *m_pMasterVolumeWidget; QFrame *m_pVolumeIncreaseWidget; QWidget *VolumeIncreaseTipsWidget; QFrame *m_pAudioBlanceWidget; //monoAudio QFrame *m_pMonoAudioWidget; QFrame *m_pChannelBalanceWidget; QFrame *m_pMonoLine; QWidget *MonoAudioTipsWidget; FixLabel *m_pMonoAudioLabel; FixLabel *MonoAudioTipsLabel; KSwitchButton *m_pMonoAudioButton; TitleLabel *m_pOutputLabel; FixLabel *m_pOutputDeviceLabel; FixLabel *m_pOpVolumeLabel; QLabel *m_pOpVolumePercentLabel; FixLabel *m_pOpBalanceLabel; FixLabel *VolumeIncreaseTipsLabel; FixLabel *m_pLeftBalanceLabel; FixLabel *m_pRightBalanceLabel; FixLabel *m_pVolumeIncreaseLabel; QComboBox *m_pDeviceSelectBox; QFrame *m_pOutputSlectWidget; UkuiButtonDrawSvg *m_pOutputIconBtn; AudioSlider *m_pOpVolumeSlider; UkmediaVolumeSlider *m_pOpBalanceSlider; KSwitchButton *m_pVolumeIncreaseButton; QVBoxLayout *m_pVlayout; QString sliderQss; }; #endif // UKMEDIAOUTPUTWIDGET_H ukui-media/audio/audio.cpp0000664000175000017500000000463115170052044014442 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "audio.h" #include "ui_audio.h" #include #include #include Audio::Audio() : mFirstLoad(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/" + QLocale::system().name()); QApplication::installTranslator(translator); pluginName = tr("Audio"); pluginType = SYSTEM; } Audio::~Audio() { } QString Audio::plugini18nName() { return pluginName; } int Audio::pluginTypes() { return pluginType; } QWidget *Audio::pluginUi() { if (mFirstLoad) { mFirstLoad = false; widget = new UkmediaMainWidget; } return widget; } 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/%1.ts"; } void Audio::initSearchText() { //~ contents_path /UkccPlugin/UkccPlugin tr("UkccPlugin"); //~ contents_path /UkccPlugin/ukccplugin test tr("ukccplugin test"); } ukui-media/audio/customstyle.cpp0000664000175000017500000003637515170054730015752 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "customstyle.h" #include #include #include #include CustomStyle::CustomStyle(const QString &proxyStyleName, QObject *parent) : QProxyStyle (proxyStyleName) { Q_UNUSED(parent); m_helpTip = new SliderTipLabelHelper(this); } CustomStyle::~CustomStyle() { } 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; bool vertical = false; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) if (widget) { const QProgressBar *progressBar = qobject_cast(widget); if (progressBar) { vertical = (progressBar->orientation() == Qt::Vertical); } } #else 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); qDebug() << " 点击按钮"; } else { painter->setRenderHint(QPainter::Antialiasing,true); painter->setPen(Qt::NoPen); painter->setBrush(QColor(0xff,0xff,0xff,0x1f)); painter->drawRoundedRect(option->rect,4,4); qDebug() << "悬停按钮"; } } 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"); m_helpTip->registerWidget(widget); widget->installEventFilter(this); } return QProxyStyle::polish(widget); } void CustomStyle::polish(QApplication *application) { return QProxyStyle::polish(application); } // void CustomStyle::polish(QPalette &palette) { // return QProxyStyle::polish(palette); // QProxyStyle::polish(palette); // palette.setBrush(QPalette::Foreground, Qt::black); 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: { qDebug()<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-media/audio/translations/0000775000175000017500000000000015170054730015356 5ustar fengfengukui-media/audio/translations/ky.ts0000664000175000017500000007354615170054730016370 0ustar fengfeng Audio Audio دووش UkccPlugin ukccplugin سىنىعى /UkccPlugin/UkccPlugin ukccplugin test ukccplugin سىنىعى /UkccPlugin/ukccplugin test InputDevWidget Input Devices كىرگىزۉۉ جابدۇۇلار. Confirm البەتتە. OutputDevWidget Output Devices ۅندۉرۉش جابدۇۇلار. Confirm البەتتە. QObject pa_context_get_server_info() failed pa_context_get_server_info() اتقارماق جاسوو ،اتقارۇۇ جەڭىلۉۉ بولدۇ Card callback failure كارتوچكا قايتارۇۇ جەڭىلۉۉ بولدۇ Sink callback failure قايتما ۅندۉرۉش جەڭىلۉۉ بولۇش Source callback failure قايتما كىرگىزۉۉ جەڭىلۉۉ بولدۇ Sink input callback failure چۅگۉپ باراتات. قايتما كىرگىزۉۉ جەڭىلۉۉ بولدۇ Source output callback failure كەلۉۉ جەر جانى ۅندۉرۉش قايتما تەڭشۅۅ جەڭىلۉۉ بولدۇ Client callback failure تىرمىنال قايتما تەڭشەگى جەڭىلۉۉ بولدۇ Server info callback failure مۇلازىمەتىر قايتما تەڭشەگى جەڭىلۉۉ بولدۇ Failed to initialize stream_restore extension: %s stream_restore نى چەچۉۉ جەڭىلۉۉ بولدۇ : ٪s pa_ext_stream_restore_read() failed pa_ext_stream_restore_read() اتقارماق جاسوو ،اتقارۇۇ جەڭىلۉۉ بولدۇ Failed to initialize device manager extension: %s شايمان باشقارعىچتى كەڭەيتۉۉنۉ العاچىنا كەتىرىش جەڭىلۉۉ بولدۇ : ٪s pa_ext_device_manager_read() failed pa_ext_device_manager_read() اتقارماق جاسوو ،اتقارۇۇ جەڭىلۉۉ بولدۇ pa_context_get_sink_info_by_index() failed pa_context_get_sink_info_by_index() اتقارماق جاسوو ،اتقارۇۇ جەڭىلۉۉ بولدۇ pa_context_get_source_info_by_index() failed pa_context_get_source_info_by_index() اتقارماق جاسوو ،اتقارۇۇ جەڭىلۉۉ بولدۇ pa_context_get_sink_input_info() failed pa_context_get_sink_input_info() اتقارماق جاسوو ،اتقارۇۇ جەڭىلۉۉ بولدۇ pa_context_get_client_info() failed pa_context_get_client_info() اتقارماق جاسوو ،اتقارۇۇ جەڭىلۉۉ بولدۇ pa_context_get_card_info_by_index() failed pa_context_get_card_info_by_index() اتقارماق جاسوو ،اتقارۇۇ جەڭىلۉۉ بولدۇ pa_context_subscribe() failed pa_context_subscribe() اتقارماق جاسوو ،اتقارۇۇ جەڭىلۉۉ بولدۇ pa_context_client_info_list() failed pa_context_client_info_list() اتقارماق جاسوو ،اتقارۇۇ جەڭىلۉۉ بولدۇ pa_context_get_card_info_list() failed pa_context_get_card_info_list() اتقارماق جاسوو ،اتقارۇۇ جەڭىلۉۉ بولدۇ pa_context_get_sink_info_list() failed pa_context_get_sink_info_list() اتقارماق جاسوو ،اتقارۇۇ جەڭىلۉۉ بولدۇ pa_context_get_source_info_list() failed pa_context_get_source_info_list() اتقارماق جاسوو ،اتقارۇۇ جەڭىلۉۉ بولدۇ pa_context_get_sink_input_info_list() failed pa_context_get_sink_input_info_list() اتقارماق جاسوو ،اتقارۇۇ جەڭىلۉۉ بولدۇ pa_context_get_source_output_info_list() failed pa_context_get_source_output_info_list() اتقارماق جاسوو ،اتقارۇۇ جەڭىلۉۉ بولدۇ Ukui Media Volume Control Ukui Media دووش كونتىرول جاسوو ،اتقارۇۇ UkmediaAppCtrlWidget App Sound Control تىركەمە دووش تىزگىندۅۅ جاسوو ،اتقارۇۇ پىروگىرامماسى None جوق System Volume ساامالىق وبونۇ UkmediaAppItemWidget Application پراگرامما Output Volume ۅندۉرۉش ۅلچۅمۉ Input Device كىيىرگىچ This case does not support setting the input device بۇل تۉر مۇرۇن .جاعداي كىرگىزگىچتى تەڭشەشتى قولدوبويت Output Device ۅندۉرۉش جابدۇۇسۇن تانداش This case does not support setting the output device بۇل تۉر مۇرۇن .جاعداي ۅندۉرۉش جابدۇۇسۇن تەڭشەشتى قولدوبويت Confirm البەتتە. UkmediaDevControlWidget audio device control 声音设备管理 output device 输出设备 input device 输入设备 Sound Equipment Control دووش جابدۇۇلار. تىزگىندۅۅ جاسوو ،اتقارۇۇ Output Devices ۅندۉرۉش جابدۇۇلار. Input Devices كىرگىزۉۉ جابدۇۇلار. UkmediaInputWidget Input كىرگىز /Audio/Input Input Device كىيىرگىچ /Audio/Input Device Volume كۅلۅمۉ /Audio/Volume Input Level كىرگىزۉۉ ئنكاسى /Audio/Input Level Noise Reduction ىزى-چۇۇ اپتوماتتىك تۅمۅندۅتۉۉ /Audio/Noise Reduction Voice Monitor دووش كۉزۅتكۉچ /audio/Voice Monitor (None Device) (شايمان جوق) You can hear your voice in the output device of your choice سىز ۅزۉڭۉز تاندالعان ۅندۉرۉش اسپابىنان ابازىڭىزدى ۇعا الاسىز UkmediaMainWidget Light-Seeking 寻光 HeYin 和印 Custom ۅزۉ بەكىتۉۉ None جوق UkmediaOutputWidget Output چىعۇۇ /Audio/Output Output Device ۅندۉرۉش جابدۇۇسۇن تانداش /Audio/Output Device Master Volume دووش /Audio/Master Volume Balance جارىقتىق تەڭپۇڭلۇقىنى تەڭشۅۅ /Audio/Balance Left سولعو Right وڭ تاراپ Volume Increase دووشتۇ جوعورۇلاتۇۇ Volume above 100% can cause sound distortion and damage your speakers. وبونۇ ٪100 تىن جوعورۇ دووش بۇرۇلۇش كەلىترىپ چىعارات، قانايعا زىيان جەتگۉزۉلەت. Mono Audio مونو دوبۇش It merges the left and right channels into one channel. ال وڭ-سول قانالدار بىر لەشتىرىپ بىر قانالعا الماشتىرات . UkmediaSettingsWidget Advanced Settings جوعورۇ تەڭشەكتەر Sound Equipment Control دووش جابدۇۇلار. تىزگىندۅۅ جاسوو ،اتقارۇۇ /Audio/Sound Equipment Control Details ىچكەلەي ، قۇنت قويۇپ ماتىرىيال App Sound Control تىركەمە دووش تىزگىندۅۅ جاسوو ،اتقارۇۇ پىروگىرامماسى /Audio/App Sound Control UkmediaSoundEffectsWidget System Sound ساامالىق دووش ۅنۉمۉ Sound Theme دووش تەماسى /Audio/Sound Theme Beep Switch ەسكەرتۉۉ وبونۇ /Audio/Beep Switch Poweroff Music بەكىتىش /Audio/Poweroff Music Startup Music اچۇۇ /Audio/Startup Music Wakeup Music ويعونۇۇ. /Audio/Wakeup Music Notification Sound ۇقتۇرۇۇ وبونۇ /Audio/Notification Sound Volume Control Sound دووشتۇ تىزگىندۅۅ جاسوو ،اتقارۇۇ وبونۇ /Audio/Volume Control Sound Logout Music تىزىمدىكىتىن ۅچۉرۉۉ /Audio/Logout Music UkmediaVolumeControl pa_context_set_sink_volume_by_index() failed pa_context_set_sink_volume_by_index() اتقارماق جاسوو ،اتقارۇۇ جەڭىلۉۉ بولدۇ pa_context_set_sink_mute_by_index() failed pa_context_set_sink_mute_by_index() جەڭىلۉۉ بولدۇ pa_context_set_source_mute_by_index() failed pa_context_set_source_mute_by_index() اتقارماق جاسوو ،اتقارۇۇ جەڭىلۉۉ بولدۇ pa_context_set_source_volume_by_index() failed pa_context_set_source_volume_by_index() اتقارماق جاسوو ،اتقارۇۇ جەڭىلۉۉ بولدۇ pa_context_set_source_output_volume() failed pa_context_set_source_output_volume() اتقارماق جاسوو ،اتقارۇۇ جەڭىلۉۉ بولدۇ pa_context_set_source_output_mute() failed pa_context_set_source_output_mute() اتقارماق جاسوو ،اتقارۇۇ جەڭىلۉۉ بولدۇ pa_context_set_card_profile_by_index() failed pa_context_set_card_profile_by_index() اتقارماق جاسوو ،اتقارۇۇ جەڭىلۉۉ بولدۇ pa_context_set_default_sink() failed pa_context_set_default_sink() اتقارماق جاسوو ،اتقارۇۇ جەڭىلۉۉ بولدۇ pa_context_set_default_source() failed pa_context_set_default_source() اتقارماق جاسوو ،اتقارۇۇ جەڭىلۉۉ بولدۇ pa_context_set_sink_port_by_name() failed pa_context_set_sink_port_by_name() اتقارماق جاسوو ،اتقارۇۇ جەڭىلۉۉ بولدۇ pa_context_set_source_port_by_name() failed pa_context_set_source_port_by_name() اتقارماق جاسوو ،اتقارۇۇ جەڭىلۉۉ بولدۇ (plugged in) ( قاتىشقان) (unavailable) (ىشتەتكەلى بولبويت ) (unplugged) ۇلانبادى Failed to read data from stream اعىمدان ساندۇۇ باياندامالاردى وقۇۇ جەڭىلۉۉ بولدۇ Peak detect چوقۇ نارق تەكشەرىش Failed to create monitoring stream كۉزۅتۉۉ اعىمى قۇرۇۇ جەڭىلۉۉ بولدۇ Failed to connect monitoring stream كۉزۅتۉۉ اعىمدى ۇلوو جەڭىلۉۉ بولدۇ Ignoring sink-input due to it being designated as an event and thus handled by the Event widget چۅگۉپ باراتات. نارق كىرگىزۉۉ نازاردان ساق اتقارىلدى ، انتكەنى ال بىر وقۇيالۇۇ دەپ بەكىتىلدى، وشوعو وقۇيالۇۇنىڭ كىچىك بۅلۅگۉ ووردۇندا بىر تەرەپ اتقارىلدى pa_context_kill_source_output() failed pa_context_kill_source_output() جەڭىلۉۉ بولدۇ Establishing connection to PulseAudio. Please wait... PulseAudio نىڭ ۇلانۇۇسۇ قۇرۇلۇپ جاتات، سەل ساقتاپ تۇرۇڭ ukui-media/audio/translations/zh_CN.ts0000664000175000017500000006236315170054730016741 0ustar fengfeng Audio Audio 声音 UkccPlugin /UkccPlugin/UkccPlugin ukccplugin test /UkccPlugin/ukccplugin test InputDevWidget Input Devices 输入设备 Confirm 确认 OutputDevWidget Output Devices 输出设备 Confirm 确认 QObject pa_context_get_server_info() failed Card callback failure Sink callback failure Source callback failure Sink input callback failure Source output callback failure Client callback failure Server info callback failure Failed to initialize stream_restore extension: %s pa_ext_stream_restore_read() failed Failed to initialize device manager extension: %s pa_ext_device_manager_read() failed pa_context_get_sink_info_by_index() failed pa_context_get_source_info_by_index() failed pa_context_get_sink_input_info() failed pa_context_get_client_info() failed pa_context_get_card_info_by_index() failed pa_context_subscribe() failed pa_context_client_info_list() failed pa_context_get_card_info_list() failed pa_context_get_sink_info_list() failed pa_context_get_source_info_list() failed pa_context_get_sink_input_info_list() failed pa_context_get_source_output_info_list() failed Ukui Media Volume Control UkmediaAppCtrlWidget App Sound Control 应用声音 None System Volume 系统 UkmediaAppItemWidget Application 应用 Output Volume 输出音量 Input Device 输入设备 This case does not support setting the input device 该情况不支持设置输入设备 Output Device 输出设备 This case does not support setting the output device 该情况不支持设置输出设备 Confirm 确认 UkmediaDevControlWidget audio device control 声音设备管理 output device 输出设备 input device 输入设备 Sound Equipment Control 声音设备管理 Output Devices 输出设备 Input Devices 输入设备 UkmediaInputWidget Input 输入 /Audio/Input Input Device 选择输入设备 /Audio/Input Device Volume 音量 /Audio/Volume Input Level 音量反馈 /Audio/Input Level Noise Reduction 智能降噪 /Audio/Noise Reduction Voice Monitor 侦听此设备 /audio/Voice Monitor (None Device) (无设备) You can hear your voice in the output device of your choice 可在所选输出设备中听到自己的声音 UkmediaMainWidget Light-Seeking 寻光 HeYin 和印 Custom 自定义 None UkmediaOutputWidget Output 输出 /Audio/Output Output Device 选择输出设备 /Audio/Output Device Master Volume 音量 /Audio/Master Volume Balance 声道平衡 /Audio/Balance Left Right Volume Increase 音量增强 Volume above 100% can cause sound distortion and damage your speakers. 音量超过100%时可能会导致音效失真并损害你的扬声器。 Mono Audio 单声道音频 It merges the left and right channels into one channel. 会将左声道和右声道合并成一个声道 UkmediaSettingsWidget Advanced Settings 高级设置 Sound Equipment Control 声音设备管理 /Audio/Sound Equipment Control Details 详情 App Sound Control 应用声音 /Audio/App Sound Control UkmediaSoundEffectsWidget System Sound 系统音效 Sound Theme 音效主题 /Audio/Sound Theme Beep Switch 提示音 /Audio/Beep Switch Poweroff Music 关机 /Audio/Poweroff Music Startup Music 开机 /Audio/Startup Music Wakeup Music 唤醒 /Audio/Wakeup Music Notification Sound 接收通知 /Audio/Notification Sound Volume Control Sound 音量调节 /Audio/Volume Control Sound Logout Music 注销 /Audio/Logout Music UkmediaVolumeControl pa_context_set_sink_volume_by_index() failed pa_context_set_sink_mute_by_index() failed pa_context_set_source_mute_by_index() failed pa_context_set_source_volume_by_index() failed pa_context_set_source_output_volume() failed pa_context_set_source_output_mute() failed pa_context_set_card_profile_by_index() failed pa_context_set_default_sink() failed pa_context_set_default_source() failed pa_context_set_sink_port_by_name() failed pa_context_set_source_port_by_name() failed (plugged in) (unavailable) (unplugged) Failed to read data from stream Peak detect Failed to create monitoring stream Failed to connect monitoring stream Ignoring sink-input due to it being designated as an event and thus handled by the Event widget pa_context_kill_source_output() failed Establishing connection to PulseAudio. Please wait... ukui-media/audio/translations/kk.ts0000664000175000017500000007162315170054730016344 0ustar fengfeng Audio Audio ۇن UkccPlugin مىندەتتى ستونى /UkccPlugin/UkccPlugin ukccplugin test ukccplugin سىنىعى /UkccPlugin/ukccplugin test InputDevWidget Input Devices كىرگىزۋ اسباپتارى Confirm تۇراقتاندىرۋ OutputDevWidget Output Devices جاريالاۋ اسباپتارى Confirm تۇراقتاندىرۋ QObject pa_context_get_server_info() failed pa_context_get_server_info() اتقار ەتۋ جەڭىلىپ قالدى Card callback failure قاريتا قايتارىپ ەتۋ جەڭىلىپ قالدى Sink callback failure قايتتما جاريالاۋ جەڭىلىپ بولۋ Source callback failure قايتتما كىرگىزۋ جەڭىلىپ قالدى Sink input callback failure چۆكمە قايتتما كىرگىزۋ جەڭىلىپ قالدى Source output callback failure قاينار جاريالاۋ قايتتما تەڭشەۋ جەڭىلىپ قالدى Client callback failure سوڭعٸ ۇشٸ قايتتما تەڭگەرگٸش جەڭىلىپ قالدى Server info callback failure قىزىمەت وتەۋ اسبابى قايتتما تەڭگەرگٸش جەڭىلىس قالدى Failed to initialize stream_restore extension: %s stream_restore نى جايۋ جەڭىلىس قالدى: ٪s pa_ext_stream_restore_read() failed pa_ext_stream_restore_read() اتقار ەتۋ جەڭىلىپ قالدى Failed to initialize device manager extension: %s اسباب باسقارۋشنى كەڭەيتۋدى قالپىنا قايتارۋ جەڭىلىپ قالدى: ٪s pa_ext_device_manager_read() failed pa_ext_device_manager_read() اتقار ەتۋ جەڭىلىپ قالدى pa_context_get_sink_info_by_index() failed pa_context_get_sink_info_by_index() اتقار ەتۋ جەڭىلىپ قالدى pa_context_get_source_info_by_index() failed pa_context_get_source_info_by_index() اتقار ەتۋ جەڭىلىپ قالدى pa_context_get_sink_input_info() failed pa_context_get_sink_input_info() اتقار ەتۋ جەڭىلىپ قالدى pa_context_get_client_info() failed pa_context_get_client_info() اتقار ەتۋ جەڭىلىپ قالدى pa_context_get_card_info_by_index() failed pa_context_get_card_info_by_index() اتقار ەتۋ جەڭىلىپ قالدى pa_context_subscribe() failed pa_context_subscribe() اتقار ەتۋ جەڭىلىپ قالدى pa_context_client_info_list() failed pa_context_client_info_list() اتقار ەتۋ جەڭىلىپ قالدى pa_context_get_card_info_list() failed pa_context_get_card_info_list() اتقار ەتۋ جەڭىلىپ قالدى pa_context_get_sink_info_list() failed pa_context_get_sink_info_list() اتقار ەتۋ جەڭىلىپ قالدى pa_context_get_source_info_list() failed pa_context_get_source_info_list() اتقار ەتۋ جەڭىلىپ قالدى pa_context_get_sink_input_info_list() failed pa_context_get_sink_input_info_list() اتقار ەتۋ جەڭىلىپ قالدى pa_context_get_source_output_info_list() failed pa_context_get_source_output_info_list() اتقار ەتۋ جەڭىلىپ قالدى Ukui Media Volume Control Ukui Media اۋا كونتىرول ەتۋ UkmediaAppCtrlWidget App Sound Control جيۋ اۋا مەڭگەرۋ ەتۋ پٸروگٸرامماسٸ None جوق System Volume سەستيما داۋىسى UkmediaAppItemWidget Application قولدانعىش Output Volume جاريالاۋ كولەمى Input Device كىرگىزۋ اسپابٸ This case does not support setting the input device نۇ ٴتۇر احاۋل كىرگىزۋ اسپابىن تەڭشەۋدٸ قولدامايدى Output Device جاريالاۋ اسبابٸن تالداۋ This case does not support setting the output device نۇ ٴتۇر احاۋل جاريالاۋ اسبابٸن تەڭشەۋدٸ قولدامايدى Confirm تۇراقتاندىرۋ UkmediaDevControlWidget audio device control 声音设备管理 output device 输出设备 input device 输入设备 Sound Equipment Control اۋا جابدىقتارىن مەڭگەرۋ ەتۋ Output Devices جاريالاۋ اسباپتارى Input Devices كىرگىزۋ اسباپتارى UkmediaInputWidget Input كىرگىزۋ /Audio/Input Input Device كىرگىزۋ اسپابٸ /Audio/Input Device Volume كولەمى /Audio/Volume Input Level كىرگىزۋ جاۋاب قايتارۋٸ /Audio/Input Level Noise Reduction شۋىل اۆتوماتتى تومەندەتۋ /Audio/Noise Reduction Voice Monitor اۋا كۇزەتۋشى /audio/Voice Monitor (None Device) (اسباب جوق) You can hear your voice in the output device of your choice ٴسىز ٶزٸڭٸز تالداعان جاريالاۋ اسپابىنان داۋسىڭىزدى ئاڭلىيالايٴسىز UkmediaMainWidget Light-Seeking 寻光 HeYin 和印 Custom ٶزى بەلگٸلەۋ None جوق UkmediaOutputWidget Output جاريالاۋ /Audio/Output Output Device جاريالاۋ اسبابٸن تالداۋ /Audio/Output Device Master Volume اۋا /Audio/Master Volume Balance جارىقتىق تەپە-تەڭدىك تەڭشەۋ /Audio/Balance Left سول Right وڭ جاق Volume Increase دٸبٸستٸ جوعارتىلۋ Volume above 100% can cause sound distortion and damage your speakers. داۋىسى ٪100 تىن جوعارعى اۋا بۇرىلۋ كەتىرىپ شٸعارادٸ، كانايگا زيان جەتكىزەدى. Mono Audio مونو ۇن It merges the left and right channels into one channel. ول وڭ-سول كانالداردى بٸرلەستٸرٸپ بٸر كانالعا ايلاندٸرادٸ. UkmediaSettingsWidget Advanced Settings جوعارى تەڭشەۋلەر Sound Equipment Control اۋا جابدىقتارىن مەڭگەرۋ ەتۋ /Audio/Sound Equipment Control Details ناقتى مازمۇنى App Sound Control جيۋ اۋا مەڭگەرۋ ەتۋ پٸروگٸرامماسٸ /Audio/App Sound Control UkmediaSoundEffectsWidget System Sound سەستيما اۋا ٶنىمى Sound Theme اۋا تەمەسى /Audio/Sound Theme Beep Switch ەسكەرتپەۋ داۋىسى /Audio/Beep Switch Poweroff Music تاقاۋ /Audio/Poweroff Music Startup Music ٸشٸۋ /Audio/Startup Music Wakeup Music ويانۋ /Audio/Wakeup Music Notification Sound ۇقتٸرۋ داۋىسى /Audio/Notification Sound Volume Control Sound دٸبٸستٸ مەڭگەرۋ ەتۋ داۋىسى /Audio/Volume Control Sound Logout Music تٸزٸمدەن ٴوشىرۋ /Audio/Logout Music UkmediaVolumeControl pa_context_set_sink_volume_by_index() failed pa_context_set_sink_volume_by_index() اتقار ەتۋ جەڭىلىپ قالدى pa_context_set_sink_mute_by_index() failed pa_context_set_sink_mute_by_index() جەڭىلىپ قالدى pa_context_set_source_mute_by_index() failed pa_context_set_source_mute_by_index() اتقار ەتۋ جەڭىلىپ قالدى pa_context_set_source_volume_by_index() failed pa_context_set_source_volume_by_index() اتقار ەتۋ جەڭىلىپ قالدى pa_context_set_source_output_volume() failed pa_context_set_source_output_volume() اتقار ەتۋ جەڭىلىپ قالدى pa_context_set_source_output_mute() failed pa_context_set_source_output_mute() اتقار ەتۋ جەڭىلىپ قالدى pa_context_set_card_profile_by_index() failed pa_context_set_card_profile_by_index() اتقار ەتۋ جەڭىلىپ قالدى pa_context_set_default_sink() failed pa_context_set_default_sink() اتقار ەتۋ جەڭىلىپ قالدى pa_context_set_default_source() failed pa_context_set_default_source() اتقار ەتۋ جەڭىلىپ قالدى pa_context_set_sink_port_by_name() failed pa_context_set_sink_port_by_name() اتقار ەتۋ جەڭىلىپ قالدى pa_context_set_source_port_by_name() failed pa_context_set_source_port_by_name() اتقار ەتۋ جەڭىلىپ قالدى (plugged in) (شارپىلدى) (unavailable) (ٸستەتكەلٸ بولمايدى) (unplugged) جالعاۋنبادٸ Failed to read data from stream اعىمنان ساندىق مالىمەتتەردى وقۋ جەڭىلىپ قالدى Peak detect شوقى ٴمان تەكسەرۋ Failed to create monitoring stream كۇزەتۋگە اعٸمٸ قۇرۋ جەڭىلىپ قالدى Failed to connect monitoring stream كۇزەتۋگە مەنى اۋلاۋ جەڭىلىپ قالدى Ignoring sink-input due to it being designated as an event and thus handled by the Event widget چۆكمە ٴمان كىرگىزۋ نازاردان ساقتانۋ قىلىندى، ۇيتكەنٸ ول بٸر ۋاقىيعالىقتى دوپ بەكٸتٸلدٸ، سوندىقتان ۋاقىيعالىقتىنىڭ كشكەنە بولەك ورٸندا بٸرتەرەپ قىلىندى pa_context_kill_source_output() failed pa_context_kill_source_output() جەڭىلىپ قالدى Establishing connection to PulseAudio. Please wait... PulseAudio نىڭ ورنالاسٸۋٸ جاسالىپ جاتىر، سەل ساقتاپ تۇر ukui-media/audio/translations/ms.ts0000664000175000017500000006272415170054730016360 0ustar fengfeng Audio Audio UkccPlugin /UkccPlugin/UkccPlugin ukccplugin test /UkccPlugin/ukccplugin test InputDevWidget Input Devices Confirm OutputDevWidget Output Devices Confirm QObject pa_context_get_server_info() failed Card callback failure Sink callback failure Source callback failure Sink input callback failure Source output callback failure Client callback failure Server info callback failure Failed to initialize stream_restore extension: %s pa_ext_stream_restore_read() failed Failed to initialize device manager extension: %s pa_ext_device_manager_read() failed pa_context_get_sink_info_by_index() failed pa_context_get_source_info_by_index() failed pa_context_get_sink_input_info() failed pa_context_get_client_info() failed pa_context_get_card_info_by_index() failed pa_context_subscribe() failed pa_context_client_info_list() failed pa_context_get_card_info_list() failed pa_context_get_sink_info_list() failed pa_context_get_source_info_list() failed pa_context_get_sink_input_info_list() failed pa_context_get_source_output_info_list() failed Ukui Media Volume Control UkmediaAppCtrlWidget App Sound Control None System Volume UkmediaAppItemWidget Application Output Volume Input Device This case does not support setting the input device Output Device This case does not support setting the output device Confirm UkmediaDevControlWidget audio device control 声音设备管理 output device 输出设备 input device 输入设备 Sound Equipment Control Output Devices Input Devices UkmediaInputWidget Input /Audio/Input Input Device /Audio/Input Device Volume /Audio/Volume Input Level /Audio/Input Level Noise Reduction /Audio/Noise Reduction Voice Monitor /audio/Voice Monitor (None Device) You can hear your voice in the output device of your choice UkmediaMainWidget Light-Seeking 寻光 HeYin 和印 Custom None UkmediaOutputWidget Output /Audio/Output Output Device /Audio/Output Device Master Volume /Audio/Master Volume Balance /Audio/Balance Left Right Volume Increase Volume above 100% can cause sound distortion and damage your speakers. Mono Audio It merges the left and right channels into one channel. UkmediaSettingsWidget Advanced Settings Sound Equipment Control /Audio/Sound Equipment Control Details App Sound Control /Audio/App Sound Control UkmediaSoundEffectsWidget System Sound Sound Theme /Audio/Sound Theme Beep Switch /Audio/Beep Switch Poweroff Music /Audio/Poweroff Music Startup Music /Audio/Startup Music Wakeup Music /Audio/Wakeup Music Notification Sound /Audio/Notification Sound Volume Control Sound /Audio/Volume Control Sound Logout Music /Audio/Logout Music UkmediaVolumeControl pa_context_set_sink_volume_by_index() failed pa_context_set_sink_mute_by_index() failed pa_context_set_source_mute_by_index() failed pa_context_set_source_volume_by_index() failed pa_context_set_source_output_volume() failed pa_context_set_source_output_mute() failed pa_context_set_card_profile_by_index() failed pa_context_set_default_sink() failed pa_context_set_default_source() failed pa_context_set_sink_port_by_name() failed pa_context_set_source_port_by_name() failed (plugged in) (unavailable) (unplugged) Failed to read data from stream Peak detect Failed to create monitoring stream Failed to connect monitoring stream Ignoring sink-input due to it being designated as an event and thus handled by the Event widget pa_context_kill_source_output() failed Establishing connection to PulseAudio. Please wait... ukui-media/audio/translations/tr.ts0000664000175000017500000006055315170054730016364 0ustar fengfeng Audio Audio UkccPlugin /UkccPlugin/UkccPlugin ukccplugin test /UkccPlugin/ukccplugin test InputDevWidget Input Devices confirm OutputDevWidget Output Devices Confirm QObject pa_context_get_server_info() failed Card callback failure Sink callback failure Source callback failure Sink input callback failure Source output callback failure Client callback failure Server info callback failure Failed to initialize stream_restore extension: %s pa_ext_stream_restore_read() failed Failed to initialize device manager extension: %s pa_ext_device_manager_read() failed pa_context_get_sink_info_by_index() failed pa_context_get_source_info_by_index() failed pa_context_get_sink_input_info() failed pa_context_get_client_info() failed pa_context_get_card_info_by_index() failed pa_context_subscribe() failed pa_context_client_info_list() failed pa_context_get_card_info_list() failed pa_context_get_sink_info_list() failed pa_context_get_source_info_list() failed pa_context_get_sink_input_info_list() failed pa_context_get_source_output_info_list() failed Connection failed, attempting reconnect Ukui Media Volume Control UkmediaAppCtrlWidget App Sound Control None System Volume UkmediaAppItemWidget Application Output Volume Input Device Output Device Confirm UkmediaDevControlWidget Sound Equipment Control Output Devices Input Devices UkmediaInputWidget Input /Audio/Input Input Device /Audio/Input Device Volume /Audio/Volume Input Level /Audio/Input Level Noise Reduction /audio/Noise Voice Monitor /audio/Voice Monitor (None Device) You can hear your voice in the output device of your choice UkmediaMainWidget Custom None UkmediaOutputWidget Output /Audio/Output Output Device /Audio/Output Device Master Volume /Audio/Master Volume Balance /Audio/Balance Left Right Volume Increase Volume above 100% can cause sound distortion and damage your speakers. Mono Audio It merges the left and right channels into one channel. UkmediaSettingsWidget Advanced Settings Sound Equipment Control Details App Sound Control UkmediaSoundEffectsWidget System Sound Sound Theme /Audio/Sound Theme Beep Switch /Audio/Beep Switch Poweroff Music /Audio/Poweroff Music Startup Music /Audio/Startup Music Wakeup Music /Audio/Wakeup Music Logout Music /Audio/Logout Music Notification Sound /Audio/Notification Sound Volume Control Sound /Audio/Volume Control Sound UkmediaVolumeControl pa_context_set_sink_volume_by_index() failed pa_context_set_source_mute_by_index() failed pa_context_set_source_volume_by_index() failed pa_context_set_source_output_volume() failed pa_context_set_source_output_mute() failed pa_context_set_card_profile_by_index() failed pa_context_set_default_sink() failed pa_context_set_default_source() failed pa_context_set_sink_port_by_name() failed pa_context_set_source_port_by_name() failed (plugged in) (unavailable) (unplugged) Failed to read data from stream Peak detect Failed to create monitoring stream Failed to connect monitoring stream Ignoring sink-input due to it being designated as an event and thus handled by the Event widget pa_context_kill_source_output() failed Establishing connection to PulseAudio. Please wait... ukui-media/audio/translations/ug.ts0000664000175000017500000007153415170054730016353 0ustar fengfeng Audio Audio ئۈن ـ سىن UkccPlugin ukccplugin سىنىقى /UkccPlugin/UkccPlugin ukccplugin test ukccplugin سىنىقى /UkccPlugin/ukccplugin test InputDevWidget Input Devices ئۈسكۈنە كىرگۈزۈش Confirm جەزىملەشتۈرۈش OutputDevWidget Output Devices چىقىرىش ئۈسكۈنىلىرى Confirm جەزىملەشتۈرۈش QObject pa_context_get_server_info() failed pa_context_get_server_info() ئىجرا قىلىش مەغلۇپ بولدى Card callback failure كارتا قايتۇرۇۋېتىش مەغلۇپ بولدى Sink callback failure قايتما چىقىرىش مەغلۇپ بولۇش Source callback failure قايتما كىرگۈزۈش مەغلۇپ بولدى Sink input callback failure چۆكمە قايتما كىرگۈزۈش مەغلۇپ بولدى Source output callback failure مەنبە چىقىرىش قايتما تەڭشەش مەغلۇپ بولدى Client callback failure تېرمىنال قايتما تەڭشىكى مەغلۇپ بولدى Server info callback failure مۇلازىمىتېر قايتما تەڭشىكى مەغلۇب بولدى Failed to initialize stream_restore extension: %s stream_restore ئۇزارتىشنى باشلىمىدى: s% pa_ext_stream_restore_read() failed pa_ext_stream_restore_read() ئىجرا قىلىش مەغلۇپ بولدى Failed to initialize device manager extension: %s ئۈسكۈنە باشقۇرغۇچنى ئۇزارتىشنى قوزغىتىش مەغلۇپ بولدى: s% pa_ext_device_manager_read() failed pa_ext_device_manager_read() ئىجرا قىلىش مەغلۇپ بولدى pa_context_get_sink_info_by_index() failed pa_context_get_sink_info_by_index() ئىجرا قىلىش مەغلۇپ بولدى pa_context_get_source_info_by_index() failed pa_context_get_source_info_by_index() ئىجرا قىلىش مەغلۇپ بولدى pa_context_get_sink_input_info() failed pa_context_get_sink_input_info() ئىجرا قىلىش مەغلۇپ بولدى pa_context_get_client_info() failed pa_context_get_client_info() ئىجرا قىلىش مەغلۇپ بولدى pa_context_get_card_info_by_index() failed pa_context_get_card_info_by_index() ئىجرا قىلىش مەغلۇپ بولدى pa_context_subscribe() failed pa_context_subscribe() ئىجرا قىلىش مەغلۇپ بولدى pa_context_client_info_list() failed pa_context_client_info_list() ئىجرا قىلىش مەغلۇپ بولدى pa_context_get_card_info_list() failed pa_context_get_card_info_list() ئىجرا قىلىش مەغلۇپ بولدى pa_context_get_sink_info_list() failed pa_context_get_sink_info_list() ئىجرا قىلىش مەغلۇپ بولدى pa_context_get_source_info_list() failed pa_context_get_source_info_list() ئىجرا قىلىش مەغلۇپ بولدى pa_context_get_sink_input_info_list() failed pa_context_get_sink_input_info_list() ئىجرا قىلىش مەغلۇپ بولدى pa_context_get_source_output_info_list() failed pa_context_get_source_output_info_list() ئىجرا قىلىش مەغلۇپ بولدى Ukui Media Volume Control Ukui Media ئاۋاز كونتىرول قىلىش UkmediaAppCtrlWidget App Sound Control ئەپ ئاۋاز كونتىرول قىلىش None يوق System Volume سىستېما ئاۋازى UkmediaAppItemWidget Application ئەپ Output Volume چىقىرىش ھەجىمى Input Device كىرگۈزۈش ئۈسكۈنىسى This case does not support setting the input device بۇ دېلو كىرگۈزۈش ئۈسكۈنىسىنى تەڭشەشنى قوللىمايدۇ Output Device چىقىرىش ئۈسكۈنىسىنى تاللاش This case does not support setting the output device بۇ دېلودا چىقىرىش ئۈسكۈنىسىنى تەڭشەش قوللىمايدۇ Confirm جەزىملەشتۈرۈش UkmediaDevControlWidget audio device control 声音设备管理 output device 输出设备 input device 输入设备 Sound Equipment Control ئاۋاز ئۈسكۈنىلىرىنى كونتىرول قىلىش Output Devices چىقىرىش ئۈسكۈنىلىرى Input Devices ئۈسكۈنە كىرگۈزۈش UkmediaInputWidget Input كىرگۈزۈش /Audio/Input Input Device كىرگۈزۈش ئۈسكۈنىسى /Audio/Input Device Volume ئاۋاز /Audio/Volume Input Level كىرگۈزۈش ئىنكاسى /Audio/Input Level Noise Reduction شاۋقۇننى ئاپتوماتىك تۆۋەنلىتىش /Audio/Noise Reduction Voice Monitor ئاۋاز كۆزەتكۈچ /audio/Voice Monitor (None Device) (ھېچنىمە ئۈسكۈنىسى) You can hear your voice in the output device of your choice ئاۋازىڭىزنى ئۆزىڭىز تاللىغان چىقىرىش ئۈسكۈنىسىدىن ئاڭلىيالايسىز UkmediaMainWidget Light-Seeking 寻光 HeYin 和印 Custom ئۆرپ-ئادەت None يوق UkmediaOutputWidget Output چىقىرىش /Audio/Output Output Device چىقىرىش ئۈسكۈنىسىنى تاللاش /Audio/Output Device Master Volume ئاۋاز /Audio/Master Volume Balance ئاۋاز يولى تەڭپۇڭلۇقى /Audio/Balance Left سول Right ئوڭ Volume Increase ئاۋازنى يۇقىرىلىتىش Volume above 100% can cause sound distortion and damage your speakers. 100% تىن يۇقىرى ھەجىملىك ئاۋازنىڭ بۇرمىلىنىشىنى كەلتۈرۈپ چىقىرىدۇ ۋە كانايىڭىزنى زەخىملەندۈرىدۇ. Mono Audio Mono Audio It merges the left and right channels into one channel. ئۇ سول ۋە ئوڭ يوللارنى بىر قانالغا بىرلەشتۈرىدۇ. UkmediaSettingsWidget Advanced Settings ئىلغار تەڭشەكلەر Sound Equipment Control ئاۋاز ئۈسكۈنىلىرىنى كونتىرول قىلىش /Audio/Sound Equipment Control Details تەپسىلىي ئۇچۇرى: App Sound Control ئەپ ئاۋاز كونتىرول قىلىش /Audio/App Sound Control UkmediaSoundEffectsWidget System Sound سىستېما ئاۋاز ئۈنىمى Sound Theme ئاۋاز تېمىسى /Audio/Sound Theme Beep Switch ئەسكەرتىش ئاۋازى /Audio/Beep Switch Poweroff Music تاقاش /Audio/Poweroff Music Startup Music ئېچىش /Audio/Startup Music Wakeup Music ئويغىنىش /Audio/Wakeup Music Notification Sound ئۇقتۇرۇش ئاۋازى /Audio/Notification Sound Volume Control Sound ئاۋاز مىقدارىنى كونترول قىلىش ئاۋازى /Audio/Volume Control Sound Logout Music تىزىمدىن ئۆچۈرۈش /Audio/Logout Music UkmediaVolumeControl pa_context_set_sink_volume_by_index() failed pa_context_set_sink_volume_by_index() ئىجرا قىلىش مەغلۇپ بولدى pa_context_set_sink_mute_by_index() failed pa_context_set_source_mute_by_index() failed pa_context_set_source_mute_by_index() ئىجرا قىلىش مەغلۇپ بولدى pa_context_set_source_volume_by_index() failed pa_context_set_source_volume_by_index() ئىجرا قىلىش مەغلۇپ بولدى pa_context_set_source_output_volume() failed pa_context_set_source_output_volume() ئىجرا قىلىش مەغلۇپ بولدى pa_context_set_source_output_mute() failed pa_context_set_source_output_mute() ئىجرا قىلىش مەغلۇپ بولدى pa_context_set_card_profile_by_index() failed pa_context_set_card_profile_by_index() ئىجرا قىلىش مەغلۇپ بولدى pa_context_set_default_sink() failed pa_context_set_default_sink() ئىجرا قىلىش مەغلۇپ بولدى pa_context_set_default_source() failed pa_context_set_default_source() ئىجرا قىلىش مەغلۇپ بولدى pa_context_set_sink_port_by_name() failed pa_context_set_sink_port_by_name() ئىجرا قىلىش مەغلۇپ بولدى pa_context_set_source_port_by_name() failed pa_context_set_source_port_by_name() ئىجرا قىلىش مەغلۇپ بولدى (plugged in) (چېتىلدى) (unavailable) (ئىشلەتكىلى بولمايدۇ) (unplugged) ئۇلانمىدى Failed to read data from stream ئېقىمدىن سانلىق مەلۇماتلارنى ئوقۇش مەغلۇپ بولدى Peak detect چوققا قىممەت تەكشۈرۈش Failed to create monitoring stream كۆزىتىش ئېقىمى قۇرۇش مەغلۇپ بولدى Failed to connect monitoring stream كۆزىتىش ئېقىمىنى ئۇلاش مەغلۇپ بولدى Ignoring sink-input due to it being designated as an event and thus handled by the Event widget چۆكمە قىممەت كىرگۈزۈش نەزەردىن ساقىت قىلىندى، چۈنكى ئۇ بىر ۋەقەلىك دەپ بېكىتىلدى، شۇڭا ۋەقەلىكنىڭ كىچىك قىسمى ئورنىدا بىرتەرەپ قىلىندى pa_context_kill_source_output() failed Establishing connection to PulseAudio. Please wait... PulseAudio نىڭ ئۇلىنىشى قۇرۇلىۋاتىدۇ، سەل ساقلاپ تۇرۇڭ ukui-media/audio/translations/bo_CN.ts0000664000175000017500000007654315170054730016725 0ustar fengfeng Audio Audio སྒྲ་ཕབ། UkccPlugin ཚོད་ལྟའི་ལྷུ་ལག། /UkccPlugin/UkccPlugin ukccplugin test ལྷུ་ལག་ཡིག་སྒྱུར་ཚོད་ལྟ། /UkccPlugin/ukccplugin test InputDevWidget Input Devices ནང་འཇུག་སྒྲིག་ཆས། Confirm ཐག་ཆོད། OutputDevWidget Output Devices ཕྱིར་གཏོང་སྒྲིག་ཆས། Confirm ཐག་ཆོད། QObject pa_context_get_server_info() failed pa_context_get_server_info()ཕམ་ཉེས་བྱུང་བ། Card callback failure བྱང་བུ་ཕྱིར་སློག་བྱེད་མ་ཐུབ་པ། Sink callback failure ཕྱིར་ལྡོག་བྱེད་པར་ཕམ་ཉེས་བྱུང་བ། Source callback failure འབྱུང་ཁུངས་ཕྱིར་ལྡོག་བྱེད་མ་ཐུབ་པ། Sink input callback failure མ་དངུལ་འཇོག་པའི་ཕྱིར་ལྡོག་ལ་ཕམ་ཉེས་བྱུང Source output callback failure འབྱུང་ཁུངས་ཕྱིར་ལྡོག་བྱེད་པར་ཕམ་ཉེས་བྱུང་བ། Client callback failure མངགས་བཅོལ་བྱེད་མཁན་གྱིས་ཕྱིར་སློག་ Server info callback failure ཞབས་ཞུའི་ཡོ་བྱད་ཀྱི་ཆ་འཕྲིན་ཕྱིར་ལྡོག་བྱེད་མ་ཐུབ Failed to initialize stream_restore extension: %s stream_restore་རིང་དུ་གཏོང་བའི་ཐོག་མའི་དུས་ཚོད་ལ་སླེབས་མ་ཐུབ་པ། %s pa_ext_stream_restore_read() failed pa_ext_stream_restore_read()ཕམ་ཁ་བྱུང་བ་རེད། Failed to initialize device manager extension: %s སྒྲིག་ཆས་དོ་དམ་གྱི་དུས་འགྱངས་ལ་ཐོག་མའི་དུས་འགྱངས་བྱེད་མ་ཐུབ་པ། %s pa_ext_device_manager_read() failed pa_ext_device_manager_read()ཕམ་ཉེས་བྱུང་བ། pa_context_get_sink_info_by_index() failed pa_context_get_sink_info_by_index()ཕམ་ཁ་བྱུང་བ་རེད། pa_context_get_source_info_by_index() failed pa_context_get_source_info_by_index()ཕམ་ཁ་བྱུང་བ་རེད། pa_context_get_sink_input_info() failed pa_context_get_sink_input_info()ཕམ་ཁ་བྱུང་བ་རེད། pa_context_get_client_info() failed pa_context_get_client_info()ཕམ་ཉེས་བྱུང་བ། pa_context_get_card_info_by_index() failed pa_context_get_card_info_by_index()ཕམ་ཉེས་བྱུང་བ། pa_context_subscribe() failed pa_context_subscribe()ཕམ་ཁ་བྱུང་བ་རེད། pa_context_client_info_list() failed pa_context_client_info_list()ཕམ་ཉེས་བྱུང་བ། pa_context_get_card_info_list() failed pa_context_get_card_info_list()ཕམ་ཉེས་བྱུང་བ། pa_context_get_sink_info_list() failed pa_context_get_sink_info_list()ཕམ་ཉེས་བྱུང་བ། pa_context_get_source_info_list() failed pa_context_get_source_info_list()ཕམ་ཁ་བྱུང་བ་རེད། pa_context_get_sink_input_info_list() failed pa_context_get_sink_input_info_list()ཕམ་ཉེས་བྱུང་བ། pa_context_get_source_output_info_list() failed pa_context_get_source_output_info_list()ཕམ་ཁ་བྱུང་བ་རེད། Connection failed, attempting reconnect འབྲེལ་མཐུད་བྱེད་མ་ཐུབ་པར་ཡང་བསྐྱར་འབྲེལ་མཐུད་བྱེད་རྩིས་བྱས། Ukui Media Volume Control ཝུའུ་ཁི་ལན་གྱི་སྨྱན་སྦྱོར་གྱི་བོངས་ UkmediaAppCtrlWidget App Sound Control ཉེར་སྤྱོད་སྒྲ། None གཅིག་ཀྱང་མེད། System Volume མ་ལག UkmediaAppItemWidget Application ཉེར་སྤྱོད་བྱ་རིམ། Output Volume ཕྱིར་གཏོང་གི་སྒྲ་ཚད། Input Device ནང་འཇུག་སྒྲིག་ཆས། This case does not support setting the input device གནས་ཚུལ་འདིར་ནང་འཇུག་སྒྲིག་ཆས་ལ་རྒྱབ་སྐྱོར་བྱེད་མི་ཐུབ། Output Device ཕྱིར་གཏོང་སྒྲིག་ཆས། This case does not support setting the output device གནས་ཚུལ་འདིར་ཕྱིར་གཏོང་སྒྲིག་ཆས་ལ་རྒྱབ་སྐྱོར་མི་བྱེད་པ་། Confirm ཐག་ཆོད། UkmediaDevControlWidget Sound Equipment Control སྒྲའི་སྒྲིག་ཆས་དོ་དམ། Output Devices ཕྱིར་གཏོང་སྒྲིག་ཆས། Input Devices ནང་འཇུག་སྒྲིག་ཆས། UkmediaInputWidget Input མ་དངུལ་འཇོག་པ། /Audio/Input Input Device ནང་འཇུག་སྒྲིག་ཆས། /Audio/Input Device Volume སྐད་སྒྲ། /Audio/Volume Input Level སྒྲ་ཚད་ཕྱིར་ལན། /Audio/Input Level Noise Reduction རིག་ནུས་ཀྱི་འཛེར་སྒྲ་གཅོག་པ། /Audio/Noise Reduction Voice Monitor སྒྲིག་ཆས་འདི་ལ་ཉན་ཞིབ་། /audio/Voice Monitor You can hear your voice in the output device of your choice འོན་ཀྱང་བདམས་པའི་ཕྱིར་འདོན་སྒྲིག་ཆས་ནང་ནས་རང་ཉིད་ཀྱི་སྐད་ཐོས་། (None Device) (སྒྲིག་ཆས་མེད་པ་) UkmediaMainWidget Custom མཚན་ཉིད་རང་འཇོག། None གཅིག་ཀྱང་མེད། UkmediaOutputWidget Output ཐོན་ཚད། /Audio/Output Output Device ཕྱིར་གཏོང་སྒྲིག་ཆས། /Audio/Output Device Master Volume སྒྲ་ཚད། /Audio/Master Volume Balance དོ་མཉམ། /Audio/Balance Left གཡོན་ཕྱོགས། Right གཡས་གཡོན། Volume Increase སྒྲ་ཚད་ཇེ་དྲག་ཏུ། Volume above 100% can cause sound distortion and damage your speakers. སྒྲ་ཚད་བརྒྱ་ཆ་༡༠༠ལས་བརྒལ་ན་སྒྲ་ནུས་དངོས་མིན་དང་ཁྱོད་ཀྱི་སྒྲ་སྐྱེད་ཆས་ལ་གནོད་པ་ཐེབས་སྲིད། Mono Audio སྒྲ་ལམ་རྐྱང་པའི་སྒྲ་ཟློས། It merges the left and right channels into one channel. སྒྲ་ལམ་གཡོན་དང་སྒྲ་ལམ་གཡས་ཟླ་སྒྲིལ་བྱས་ནས་སྒྲ་ལམ་གཅིག་ཏུ་གྱུར། UkmediaSettingsWidget Advanced Settings མཐོ་རིམ་སྒྲིག་འགོད། Sound Equipment Control སྒྲའི་སྒྲིག་ཆས་དོ་དམ། /Audio/Sound Equipment Control Details གནས་ཚུལ་ཞིབ་ཕྲ། App Sound Control ཉེར་སྤྱོད་སྒྲ། /Audio/App Sound Control UkmediaSoundEffectsWidget System Sound མ་ལག་སྒྲ་ནུས། Sound Theme སྒྲ་ནུས་ཀྱི་བརྗོད་བྱ། /Audio/Sound Theme Alert Sound ཉེན་བརྡའི་སྒྲ་ /Audio/Alert Sound Beep Switch སྣེ་སྟོན་པའི་སྒྲ། /Audio/Beep Switch Poweroff Music གླུ་དབྱངས་སྒོ་བརྒྱབ་པ། /Audio/Poweroff Music Startup Music རོལ་དབྱངས་ཁ་ཕྱེ་བ། /Audio/Startup Music Wakeup Music འབོད་སློང་། /Audio/Wakeup Music Notification Sound བསྡུ་ལེན་བརྡ་ཐོ། /Audio/Notification Sound Volume Control Sound སྒྲ་ཚད་སྙོམ་སྒྲིག /Audio/Volume Control Sound Volume Change སྒྲ་གདངས་འགྱུར་ལྡོག /Audio/Volume Change Logout Music རོལ་དབྱངས་མེད་པར་བཟོ་བ། /Audio/Logout Music UkmediaVolumeControl pa_context_set_sink_volume_by_index() failed pa_context_set_sink_volume_by_index()ཕམ་ཁ་བྱུང་བ་རེད། pa_context_set_sink_mute_by_index() failed t_sink_mute_by_index() failed pa_context_set_source_mute_by_index() failed pa_context_set_source_mute_by_index()ཕམ་ཁ་བྱུང་བ་རེད། pa_context_set_source_volume_by_index() failed pa_context_set_source_volume_by_index()ཕམ་ཁ་བྱུང་བ་རེད། pa_context_set_source_output_volume() failed pa_context_set_source_output_volume()ཕམ་ཁ་བྱུང་བ་རེད། pa_context_set_source_output_mute() failed pa_context_set_source_output_mute()ཕམ་ཁ་བྱུང་བ་རེད། pa_context_set_card_profile_by_index() failed pa_context_set_card_profile_by_index()ཕམ་ཉེས་བྱུང་བ། pa_context_set_default_sink() failed pa_context_set_default_sink()ཕམ་ཁ་བྱུང་བ་རེད། pa_context_set_default_source() failed pa_context_set_default_source()ཕམ་ཉེས་བྱུང་བ། pa_context_set_sink_port_by_name() failed pa_context_set_sink_port_by_name()ཕམ་ཁ་བྱུང་བ་རེད། pa_context_set_source_port_by_name() failed pa_context_set_source_port_by_name()ཕམ་ཉེས་བྱུང་བ། (plugged in) (ནང་དུ་བཅུག་པ་)། (unavailable) (སྤྱོད་གོ་མི་ཆོད་པ)། (unplugged) (ཁ་ཐོར་ཡ་བྲལ་དུ་སོང་བ་)། Failed to read data from stream ཆུ་ཕྲན་ནས་གཞི་གྲངས་ཀློག་མ་ཐུབ་པ། Peak detect ཡང་རྩེར་ཞིབ་དཔྱད་ཚད་ལེན Failed to create monitoring stream ལྟ་ཞིབ་ཚད་ལེན་གྱི་ཆུ་ཕྲན་གསར་སྐྲུན་བྱེད་མ་ཐུབ་པ Failed to connect monitoring stream ལྟ་ཞིབ་ཚད་ལེན་གྱི་ཆུ་ཕྲན་སྦྲེལ་མཐུད་བྱེད་མ་ཐུབ་པ Ignoring sink-input due to it being designated as an event and thus handled by the Event widget དོན་རྐྱེན་ཞིག་ཏུ་བརྩིས་ནས་དོན་རྐྱེན་ཆུང་ཆུང་ཞིག་ཏུ་བརྩིས་ནས་ཐག་གཅོད་བྱས་པའི་རྐྱེན་གྱིས་ཆུ་ནང་དུ་འཛུལ་བར་སྣང་མེད་བྱས་པ་རེད། pa_context_kill_source_output() failed _output() failed Establishing connection to PulseAudio. Please wait... ཕུའུ་ལུའུ་ཧྲི་ཨོ་ཏོ་དང་འབྲེལ་བ་བཙུགས་པ་རེད། སྐུ་མཁྱེན་སྒུག་རོགས།... ukui-media/audio/translations/vi.ts0000664000175000017500000006716215170054730016360 0ustar fengfeng Audio Audio Thiết bị âm thanh UkccPlugin Plugin Ukcc /UkccPlugin/UkccPlugin ukccplugin test Kiểm tra ukccplugin /UkccPlugin/ukccplugin test InputDevWidget Input Devices Thiết bị đầu vào Confirm Lưu sửa đổi OutputDevWidget Output Devices Thiết bị đầu ra Confirm Lưu sửa đổi QObject pa_context_get_server_info() failed Pa_context_get_server_info() thất bại Card callback failure Lỗi gọi lại thẻ Sink callback failure Lỗi gọi lại Sink thất bại Source callback failure Lỗi gọi lại Nguồn thất bại Sink input callback failure Lỗi callback đầu vào sink Source output callback failure Lỗi phản hồi đầu ra nguồn Client callback failure Lỗi gọi lại từ khách hàng Server info callback failure Lỗi callback thông tin máy chủ Failed to initialize stream_restore extension: %s Khởi tạo phần mở rộng stream_restore thất bại: %s pa_ext_stream_restore_read() failed Đọc pa_ext_stream_restore thất bại Failed to initialize device manager extension: %s Khởi tạo phần mở rộng quản lý thiết bị thất bại: %s pa_ext_device_manager_read() failed Đọc pa_ext_device_manager thất bại pa_context_get_sink_info_by_index() failed Lấy thông tin thiết bị xuất âm bằng chỉ mục trong ngữ cảnh PulseAudio thất bại pa_context_get_source_info_by_index() failed Lấy thông tin về source (đầu vào âm thanh) bằng chỉ mục thất bại pa_context_get_sink_input_info() failed Pa_context_get_sink_input_info() thất bại pa_context_get_client_info() failed Pa context lấy thông tin khách hàng() thất bại pa_context_get_card_info_by_index() failed Lấy thông tin về card âm thanh theo chỉ số thất bại pa_context_subscribe() failed Đăng ký ngữ cảnh PA không thành công pa_context_client_info_list() failed Hàm pa_context_client_info_list() thực thi thất bại pa_context_get_card_info_list() failed Lỗi khi lấy danh sách thông tin card âm thanh của ngữ cảnh PulseAudio pa_context_get_sink_info_list() failed Lỗi khi lấy danh sách thông tin đầu ra âm thanh (sink) từ ngữ cảnh PulseAudio pa_context_get_source_info_list() failed Lỗi khi lấy danh sách thông tin nguồn âm thanh từ ngữ cảnh PulseAudio pa_context_get_sink_input_info_list() failed Hàm pa_context_get_sink_input_info_list() thực thi thất bại pa_context_get_source_output_info_list() failed Hàm pa_context_get_source_output_info_list() thực thi thất bại Ukui Media Volume Control Bộ điều khiển âm lượng đa phương tiện Ukui UkmediaAppCtrlWidget App Sound Control Âm thanh ứng dụng None Sử dùng settings hiện tại của máy in System Volume Âm lượng hệ thống UkmediaAppItemWidget Application Ứng dụng Output Volume Âm lượng đầu ra Input Device Thiết bị đầu vào This case does not support setting the input device Tình huống này không hỗ trợ cài đặt thiết bị đầu vào Output Device Chọn thiết bị đầu ra This case does not support setting the output device Tình huống này không hỗ trợ cài đặt thiết bị đầu ra Confirm Lưu sửa đổi UkmediaDevControlWidget audio device control 声音设备管理 output device 输出设备 input device 输入设备 Sound Equipment Control Quảy lý thiết bị âm thanh Output Devices Thiết bị đầu ra Input Devices Thiết bị đầu vào UkmediaInputWidget Input Đầu vào /Audio/Input Input Device Thiết bị đầu vào /Audio/Input Device Volume Âm thanh /Audio/Volume Input Level Phản hồi âm lượng /Audio/Input Level Noise Reduction Giảm ồn thông minh /Audio/Noise Reduction Voice Monitor Lắng nghe thiết bị này /audio/Voice Monitor (None Device) (Không có thiết bị) You can hear your voice in the output device of your choice Có thể nghe lại giọng nói của mình qua thiết bị đầu ra đã chọn UkmediaMainWidget Light-Seeking 寻光 HeYin 和印 Custom Định dạng khác None Sử dùng settings hiện tại của máy in UkmediaOutputWidget Output Đầu ra /Audio/Output Output Device Chọn thiết bị đầu ra /Audio/Output Device Master Volume Âm lượng /Audio/Master Volume Balance Cân bằng /Audio/Balance Left Có sẵn Right Phía phải Volume Increase Tăng cường âm lượng Volume above 100% can cause sound distortion and damage your speakers. Khi âm lượng vượt quá 100%, có thể gây méo âm và làm hỏng loa của bạn. Mono Audio Âm thanh đơn kênh âm thanh It merges the left and right channels into one channel. Gộp kênh âm thanh trái và phải thành một kênh âm thanh đơn. UkmediaSettingsWidget Advanced Settings Cài đặt cao cấp Sound Equipment Control Quảy lý thiết bị âm thanh /Audio/Sound Equipment Control Details Mô tả chi tiết App Sound Control Âm thanh ứng dụng /Audio/App Sound Control UkmediaSoundEffectsWidget System Sound Hiệu ứng âm thanh hệ thống Sound Theme Chủ đề hiệu ứng âm thanh /Audio/Sound Theme Beep Switch Âm thanh thông báo /Audio/Beep Switch Poweroff Music Tắt máy /Audio/Poweroff Music Startup Music Khởi động /Audio/Startup Music Wakeup Music Đánh thức /Audio/Wakeup Music Notification Sound Nhận thông báo /Audio/Notification Sound Volume Control Sound Điều chỉnh âm lượng /Audio/Volume Control Sound Logout Music Đăng xuất /Audio/Logout Music UkmediaVolumeControl pa_context_set_sink_volume_by_index() failed Hàm pa_context_set_sink_volume_by_index() thực thi thất bại pa_context_set_sink_mute_by_index() failed Hàm pa_context_set_sink_mute_by_index() thực thi thất bại pa_context_set_source_mute_by_index() failed Hàm pa_context_set_source_mute_by_index() thực thi thất bại pa_context_set_source_volume_by_index() failed Hàm pa_context_set_source_volume_by_index() thực thi thất bại pa_context_set_source_output_volume() failed Hàm pa_context_set_source_output_volume() thực thi thất bại pa_context_set_source_output_mute() failed Hàm pa_context_set_source_output_mute() thực thi thất bại pa_context_set_card_profile_by_index() failed Hàm pa_context_set_card_profile_by_index() thực thi thất bại pa_context_set_default_sink() failed Hàm pa_context_set_default_sink() thực thi thất bại pa_context_set_default_source() failed Hàm pa_context_set_default_source() thực thi thất bại pa_context_set_sink_port_by_name() failed Hàm pa_context_set_sink_port_by_name() thực thi thất bại pa_context_set_source_port_by_name() failed Hàm pa_context_set_source_port_by_name() thực thi thất bại (plugged in) (đã cắm) (unavailable) (không khả dụng) (unplugged) (đã ngắt kết nối) Failed to read data from stream Không thể đọc dữ liệu từ luồng Peak detect Phát hiện đỉnh Failed to create monitoring stream Không thể tạo luồng giám sát Failed to connect monitoring stream Không thể kết nối luồng giám sát Ignoring sink-input due to it being designated as an event and thus handled by the Event widget Bỏ qua sink-input do được chỉ định là sự kiện (event) và sẽ được xử lý bởi widget Sự kiện pa_context_kill_source_output() failed pa_context_kill_source_output() không thành công Establishing connection to PulseAudio. Please wait... Đang thiết lập kết nối tới PulseAudio. Vui lòng chờ... ukui-media/audio/translations/ab.ts0000664000175000017500000006116415170054730016320 0ustar fengfeng Audio Audio UkccPlugin /UkccPlugin/UkccPlugin ukccplugin test /UkccPlugin/ukccplugin test InputDevWidget Input Devices Confirm OutputDevWidget Output Devices Confirm QObject pa_context_get_server_info() failed Card callback failure Sink callback failure Source callback failure Sink input callback failure Source output callback failure Client callback failure Server info callback failure Failed to initialize stream_restore extension: %s pa_ext_stream_restore_read() failed Failed to initialize device manager extension: %s pa_ext_device_manager_read() failed pa_context_get_sink_info_by_index() failed pa_context_get_source_info_by_index() failed pa_context_get_sink_input_info() failed pa_context_get_client_info() failed pa_context_get_card_info_by_index() failed pa_context_subscribe() failed pa_context_client_info_list() failed pa_context_get_card_info_list() failed pa_context_get_sink_info_list() failed pa_context_get_source_info_list() failed pa_context_get_sink_input_info_list() failed pa_context_get_source_output_info_list() failed Ukui Media Volume Control UkmediaAppCtrlWidget App Sound Control None System Volume UkmediaAppItemWidget Application Output Volume Input Device This case does not support setting the input device Output Device This case does not support setting the output device Confirm UkmediaDevControlWidget audio device control 声音设备管理 output device 输出设备 input device 输入设备 Sound Equipment Control Output Devices Input Devices UkmediaInputWidget Input /Audio/Input Input Device /Audio/Input Device Volume /Audio/Volume Input Level /Audio/Input Level Noise Reduction /Audio/Noise Reduction Voice Monitor /audio/Voice Monitor (None Device) You can hear your voice in the output device of your choice UkmediaMainWidget Light-Seeking 寻光 HeYin 和印 Custom None UkmediaOutputWidget Output /Audio/Output Output Device /Audio/Output Device Master Volume /Audio/Master Volume Balance /Audio/Balance Left Right Volume Increase Volume above 100% can cause sound distortion and damage your speakers. Mono Audio It merges the left and right channels into one channel. UkmediaSettingsWidget Advanced Settings Sound Equipment Control /Audio/Sound Equipment Control Details App Sound Control /Audio/App Sound Control UkmediaSoundEffectsWidget System Sound Sound Theme /Audio/Sound Theme Beep Switch /Audio/Beep Switch Poweroff Music /Audio/Poweroff Music Startup Music /Audio/Startup Music Wakeup Music /Audio/Wakeup Music Notification Sound /Audio/Notification Sound Volume Control Sound /Audio/Volume Control Sound Logout Music /Audio/Logout Music UkmediaVolumeControl pa_context_set_sink_volume_by_index() failed pa_context_set_sink_mute_by_index() failed pa_context_set_source_mute_by_index() failed pa_context_set_source_volume_by_index() failed pa_context_set_source_output_volume() failed pa_context_set_source_output_mute() failed pa_context_set_card_profile_by_index() failed pa_context_set_default_sink() failed pa_context_set_default_source() failed pa_context_set_sink_port_by_name() failed pa_context_set_source_port_by_name() failed (plugged in) (unavailable) (unplugged) Failed to read data from stream Peak detect Failed to create monitoring stream Failed to connect monitoring stream Ignoring sink-input due to it being designated as an event and thus handled by the Event widget pa_context_kill_source_output() failed Establishing connection to PulseAudio. Please wait... ukui-media/audio/translations/mn_MN.ts0000664000175000017500000010125015170054730016731 0ustar fengfeng Audio Audio ᠠᠦ᠋ᠳᠢᠤ᠋ UkccPlugin Ukccc ᠵᠠᠯᠭᠠᠰᠤ /UkccPlugin/UkccPlugin ukccplugin test ᠤᠭᠯᠠᠭᠤᠷᠭ᠎ᠠ ᠲᠣᠨᠣᠭ᠎ᠤ᠋ᠨ ᠰᠣᠷᠢᠯᠲᠠ /UkccPlugin/ukccplugin test InputDevWidget Input Devices ᠣᠷᠣᠭᠤᠯᠬᠤ ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ Confirm ᠲᠣᠭᠲᠠᠭᠠᠬᠤ OutputDevWidget Output Devices ᠭᠠᠷᠭᠠᠬᠤ ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ Confirm ᠲᠣᠭᠲᠠᠭᠠᠬᠤ QObject pa_context_get_server_info() failed pa_context_get_server_info() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠳᠬᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ Card callback failure ᠺᠠᠷᠲ ᠢ᠋ ᠡᠬᠡᠬᠦᠯᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠦᠬᠡᠢ Sink callback failure ᠡᠬᠡᠬᠦᠯᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ Source callback failure ᠡᠬᠦᠰᠬᠡᠭᠴᠢ ᠵᠢ ᠡᠬᠡᠬᠦᠯᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ Sink input callback failure Sink input ᠢ᠋\ ᠵᠢ ᠡᠬᠡᠬᠦᠯᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ Source output callback failure ᠡᠬᠦᠰᠬᠡᠭᠴᠢ ᠵᠢᠨ ᠭᠠᠷᠭᠠᠯᠳᠠ ᠵᠢ ᠡᠬᠡᠬᠦᠯᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ Client callback failure ᠬᠡᠷᠡᠭᠯᠡᠭᠴᠢ ᠵᠢᠨ ᠦᠵᠦᠬᠦᠷ ᠢ᠋ ᠡᠬᠡᠬᠦᠯᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ Server info callback failure ᠦᠢᠯᠡᠴᠢᠯᠡᠬᠦᠷ ᠢ᠋ ᠡᠬᠡᠬᠦᠯᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ Failed to initialize stream_restore extension: %s stream_restore ᠵᠢ/ ᠢ᠋ ᠠᠨᠭᠬᠠᠵᠢᠭᠤᠯᠵᠤ ᠦᠷᠬᠡᠳᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ᠄%s pa_ext_stream_restore_read() failed pa_ext_stream_restore_read() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠳᠬᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ Failed to initialize device manager extension: %s ᠳᠦᠬᠦᠬᠡᠷᠦᠮᠵᠢ ᠵᠢᠨ ᠬᠠᠮᠢᠶᠠᠷᠤᠯᠳᠠ ᠵᠢᠨ ᠪᠠᠭᠠᠵᠢ ᠵᠢ ᠠᠨᠭᠬᠠᠵᠢᠭᠤᠯᠵᠤ ᠦᠷᠬᠡᠳᠬᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ᠄%s pa_ext_device_manager_read() failed pa_ext_device_manager_read() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠳᠬᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ pa_context_get_sink_info_by_index() failed pa_context_get_sink_info_by_index() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠳᠬᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ pa_context_get_source_info_by_index() failed pa_context_get_source_info_by_index() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠳᠬᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ pa_context_get_sink_input_info() failed pa_context_get_sink_input_info() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠳᠬᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ pa_context_get_client_info() failed pa_context_get_client_info() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠳᠬᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ pa_context_get_card_info_by_index() failed pa_context_get_card_info_by_index() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠳᠬᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ pa_context_subscribe() failed pa_context_subscribe() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠳᠬᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ pa_context_client_info_list() failed pa_context_client_info_list() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠳᠬᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ pa_context_get_card_info_list() failed pa_context_get_card_info_list() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠳᠬᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ pa_context_get_sink_info_list() failed pa_context_get_sink_info_list() ᠵᠢ/ ᠢ᠋ᠬᠦᠢᠴᠡᠳᠬᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ pa_context_get_source_info_list() failed pa_context_get_source_info_list() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠳᠬᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ pa_context_get_sink_input_info_list() failed pa_context_get_sink_input_info_list() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠳᠬᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ pa_context_get_source_output_info_list() failed pa_context_get_source_output_info_list() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠳᠬᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ Connection failed, attempting reconnect ᠴᠦᠷᠬᠡᠯᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠦᠬᠡᠢ᠂ ᠰᠢᠨ᠎ᠡ ᠪᠡᠷ ᠴᠦᠷᠬᠡᠯᠡᠬᠦ ᠵᠢ ᠳᠤᠷᠰᠢᠵᠤ ᠪᠠᠢᠨ᠎ᠠ Ukui Media Volume Control Ukui ᠮᠸᠳᠢᠶ᠎ᠠ ᠵᠢᠨ ᠳᠠᠭᠤ ᠵᠢ ᠡᠵᠡᠮᠳᠡᠬᠦ UkmediaAppCtrlWidget App Sound Control ᠬᠡᠷᠡᠭᠯᠡᠭᠡᠨ᠎ᠦ᠌ ᠳᠠᠭᠤ None ᠦᠬᠡᠢ System Volume ᠰᠢᠰᠲᠸᠮ UkmediaAppItemWidget Application ᠬᠡᠷᠡᠭᠯᠡᠭᠡ Output Volume ᠭᠠᠷᠭᠠᠬᠤ ᠳᠠᠭᠤ Input Device ᠶᠠᠷᠢᠶᠠᠨ᠎ᠳ᠋ᠤ᠌ ᠬᠡᠷᠡᠭᠯᠡᠬᠦ ᠪᠤᠶᠤ ᠰᠢᠩᠭᠡᠭᠡᠵᠦ ᠦᠢᠯᠡᠳᠬᠦ ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ᠎ᠶ᠋ᠢ ᠰᠣᠩᠭᠣᠬᠤ This case does not support setting the input device ᠲᠤᠰ ᠪᠠᠢᠳᠠᠯ ᠨᠢ ᠣᠷᠣᠭᠤᠯᠬᠤ ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ ᠪᠠᠢᠭᠤᠯᠬᠤ᠎ᠶ᠋ᠢ ᠳᠡᠮᠵᠢᠬᠦ ᠦᠭᠡᠢ Output Device ᠳᠠᠭᠤ ᠨᠡᠪᠲᠡᠷᠡᠭᠦᠯᠬᠦ ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ᠎ᠶ᠋ᠢ ᠰᠣᠩᠭᠣᠬᠤ This case does not support setting the output device ᠲᠤᠰ ᠪᠠᠢᠳᠠᠯ ᠨᠢ ᠭᠠᠷᠭᠠᠬᠤ ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ ᠲᠣᠬᠢᠷᠠᠭᠤᠯᠬᠤ᠎ᠶ᠋ᠢ ᠳᠡᠮᠵᠢᠬᠦ ᠦᠭᠡᠢ Confirm ᠲᠣᠭᠲᠠᠭᠠᠬᠤ UkmediaDevControlWidget Sound Equipment Control ᠳᠠᠭᠤᠨ᠎ᠤ᠋ ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ᠎ᠶ᠋ᠢᠨ ᠬᠠᠮᠢᠶᠠᠷᠤᠯᠲᠠ Output Devices ᠭᠠᠷᠭᠠᠬᠤ ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ Input Devices ᠣᠷᠣᠭᠤᠯᠬᠤ ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ UkmediaInputWidget Input ᠣᠷᠣᠭᠤᠯᠬᠤ /Audio/Input Input Device ᠶᠠᠷᠢᠶᠠᠨ᠎ᠳ᠋ᠤ᠌ ᠬᠡᠷᠡᠭᠯᠡᠬᠦ ᠪᠤᠶᠤ ᠰᠢᠩᠭᠡᠭᠡᠵᠦ ᠦᠢᠯᠡᠳᠬᠦ ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ᠎ᠶ᠋ᠢ ᠰᠣᠩᠭᠣᠬᠤ /Audio/Input Device Volume ᠡᠪᠬᠡᠮᠡᠯ /Audio/Volume Input Level ᠳᠠᠭᠤᠨ᠎ᠤ᠋ ᠬᠡᠮᠵᠢᠶ᠎ᠡ᠎ᠶ᠋ᠢᠨ ᠪᠤᠴᠠᠯᠲᠠ /Audio/Input Level Noise Reduction ᠣᠶᠤᠨᠲᠤ ᠎ᠪᠡᠷ ᠱᠤᠤᠭᠢᠶᠠᠨ ᠪᠠᠭᠤᠷᠠᠭᠤᠯᠬᠤ /Audio/Noise Reduction Voice Monitor ᠲᠤᠰ ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ᠎ᠶ᠋ᠢ ᠲᠠᠩᠨᠠᠨ ᠰᠣᠨᠣᠰᠬᠤ /audio/Voice Monitor (None Device) (᠎ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ ᠦᠭᠡᠢ ) You can hear your voice in the output device of your choice ᠰᠣᠩᠭᠣᠵᠤ ᠭᠠᠷᠭᠠᠭᠰᠠᠨ ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ ᠳᠣᠲᠣᠷ᠎ᠠ ᠥᠪᠡᠷ᠎ᠦ᠋ᠨ ᠳᠠᠭᠤ᠎ᠶ᠋ᠢ ᠣᠯᠵᠤ ᠰᠣᠨᠣᠰᠤᠨ᠎ᠠ UkmediaMainWidget Custom ᠦᠪᠡᠷᠳᠡᠬᠡᠨ ᠳᠤᠭᠳᠠᠭᠠᠬᠤ None ᠦᠬᠡᠢ UkmediaOutputWidget Output ᠭᠠᠷᠭᠠᠯᠲᠠ /Audio/Output Output Device ᠳᠠᠭᠤ ᠨᠡᠪᠲᠡᠷᠡᠭᠦᠯᠬᠦ ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ᠎ᠶ᠋ᠢ ᠰᠣᠩᠭᠣᠬᠤ /Audio/Output Device Master Volume ᠳᠠᠭᠤ /Audio/Master Volume Balance ᠭᠡᠷᠡᠯᠲᠦᠴᠡ᠎ᠶ᠋ᠢᠨ ᠲᠡᠩᠴᠡᠭᠦᠷᠢ᠎ᠶ᠋ᠢᠨ ᠲᠣᠬᠢᠷᠠᠭᠤᠯᠭ᠎ᠠ /Audio/Balance Left ᠵᠡᠬᠦᠨ Right ᠪᠠᠷᠠᠭᠤᠨ Volume Increase ᠳᠠᠭᠤᠨ᠎ᠤ᠋ ᠬᠡᠮᠵᠢᠶ᠎ᠡ ᠴᠢᠩᠭᠠᠷᠠᠬᠤ Volume above 100% can cause sound distortion and damage your speakers. ᠳᠠᠭᠤᠨ᠎ᠤ᠋ ᠬᠡᠮᠵᠢᠶ᠎ᠡ 100% ᠶ᠋ᠢ ᠳᠠᠪᠠᠬᠤ ᠦᠶᠡᠰ ᠳᠠᠭᠤᠨ᠎ᠤ᠋ ᠬᠦᠴᠦᠨ ᠰᠣᠯᠢᠭᠳᠠᠵᠤ ᠂ ᠳᠠᠭᠤᠨ᠎ᠤ᠋ ᠰᠦᠷ ᠮᠠᠨᠳᠤᠭᠤᠯᠤᠭᠴᠢ ᠪᠠᠷᠤᠭ ᠬᠣᠬᠢᠷᠠᠯ ᠭᠠᠷᠤᠨ᠎ᠠ. Mono Audio ᠳᠠᠩ ᠳᠠᠭᠤᠨ ᠵᠠᠮ᠎ᠤ᠋ᠨ ᠳᠠᠭᠤᠨ ᠳᠠᠪᠲᠠᠮᠵᠢ It merges the left and right channels into one channel. ᠵᠡᠭᠦᠨ ᠳᠠᠭᠤᠨ ᠪᠠ ᠪᠠᠷᠠᠭᠤᠨ ᠳᠠᠭᠤᠨ ᠵᠠᠮ᠎ᠢ᠋ ᠨᠢᠭᠡ ᠳᠠᠭᠤᠨ ᠵᠠᠮ ᠪᠣᠯᠭᠠᠨ ᠨᠡᠢᠯᠡᠭᠦᠯᠦᠨ᠎ᠡ. UkmediaSettingsWidget Advanced Settings ᠳᠡᠭᠡᠳᠦ ᠵᠡᠷᠭᠡ᠎ᠶ᠋ᠢᠨ ᠲᠣᠬᠢᠷᠠᠭᠤᠯᠤᠯ Sound Equipment Control ᠳᠠᠭᠤᠨ᠎ᠤ᠋ ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ᠎ᠶ᠋ᠢᠨ ᠬᠠᠮᠢᠶᠠᠷᠤᠯᠲᠠ /Audio/Sound Equipment Control Details ᠨᠠᠷᠢᠨ ᠵᠠᠩᠭᠢ App Sound Control ᠬᠡᠷᠡᠭᠯᠡᠭᠡᠨ᠎ᠦ᠌ ᠳᠠᠭᠤ /Audio/App Sound Control UkmediaSoundEffectsWidget System Sound ᠰᠢᠰᠲ᠋ᠧᠮ᠎ᠦ᠋ᠨ ᠳᠠᠭᠤᠨ ᠦᠢᠯᠡᠳᠦᠯ Sound Theme ᠳᠠᠭᠤᠨ᠎ᠤ᠋ ᠭᠣᠣᠯ ᠰᠡᠳᠦᠪ /Audio/Sound Theme Alert Sound ᠮᠡᠳᠡᠭᠳᠡᠯ /Audio/Alert Sound Beep Switch ᠰᠠᠨᠠᠭᠤᠯᠤᠮᠵᠢ /Audio/Beep Switch Poweroff Music ᠬᠠᠭᠠᠬᠤ /Audio/Poweroff Music Startup Music ᠨᠡᠬᠡᠬᠦ /Audio/Startup Music Wakeup Music ᠳᠠᠭᠤᠳᠠᠨ ᠰᠡᠷᠭᠦᠭᠡᠬᠦ /Audio/Wakeup Music Notification Sound ᠮᠡᠳᠡᠭᠳᠡᠯ ᠬᠦᠯᠢᠶᠡᠨ ᠠᠪᠬᠤ /Audio/Notification Sound Volume Control Sound ᠳᠠᠭᠤ ᠲᠣᠬᠢᠷᠠᠭᠤᠯᠬᠤ /Audio/Volume Control Sound Volume Change ᠳᠠᠭᠤᠨ ᠤ᠋ ᠬᠡᠮᠵᠢᠶ᠎ᠡ ᠵᠢ ᠳᠤᠬᠢᠷᠠᠭᠤᠯᠬᠤ /Audio/Volume Change Logout Music ᠳᠠᠩᠰᠠ᠎ᠠ᠋ᠴᠠ ᠬᠠᠰᠤᠬᠤ /Audio/Logout Music UkmediaVolumeControl pa_context_set_sink_volume_by_index() failed pa_context_set_sink_volume_by_index() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠳᠬᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ pa_context_set_sink_mute_by_index() failed pa_context_set_sink_mute_by_index (᠎)᠎ᠢᠯᠠᠭᠳᠠᠵᠠᠢ pa_context_set_source_mute_by_index() failed pa_context_set_source_mute_by_index () ᠢ᠋\ ᠵᠢ ᠬᠦᠢᠴᠡᠳᠬᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ pa_context_set_source_volume_by_index() failed pa_context_set_source_volume_by_index () ᠢ᠋\ ᠵᠢ ᠬᠦᠢᠴᠡᠳᠬᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ pa_context_set_source_output_volume() failed pa_context_set_source_output_volume() ᠢ᠋\ ᠵᠢ ᠬᠦᠢᠴᠡᠳᠬᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ pa_context_set_source_output_mute() failed pa_context_set_source_output_mute() ᠢ᠋\ ᠵᠢ ᠬᠦᠢᠴᠡᠳᠬᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ pa_context_set_card_profile_by_index() failed pa_context_set_card_profile_by_index() ᠢ᠋\ ᠵᠢ ᠬᠦᠢᠴᠡᠳᠬᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ pa_context_set_default_sink() failed pa_context_set_default_sink() ᠢ᠋\ ᠵᠢ ᠬᠦᠢᠴᠡᠳᠬᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ pa_context_set_default_source() failed pa_context_set_default_source() ᠢ᠋\ ᠵᠢ ᠬᠦᠢᠴᠡᠳᠬᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ pa_context_set_sink_port_by_name() failed pa_context_set_sink_port_by_name() ᠢ᠋\ ᠵᠢ ᠬᠦᠢᠴᠡᠳᠬᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ pa_context_set_source_port_by_name() failed pa_context_set_source_port_by_name() ᠢ᠋\ ᠵᠢ ᠬᠦᠢᠴᠡᠳᠬᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠥᠬᠡᠢ (plugged in) ( ᠴᠡᠨᠡᠯᠡᠭᠰᠡᠨ ᠪᠠᠢᠨ᠎ᠠ) (unavailable) ( ᠬᠡᠷᠡᠭᠯᠡᠵᠦ ᠪᠤᠯᠬᠤ ᠦᠬᠡᠢ) (unplugged) ( ᠴᠡᠨᠡᠭᠯᠡᠭᠰᠡᠨ ᠥᠬᠡᠢ) Failed to read data from stream ᠳ᠋ᠠᠢᠲ᠋ᠠ ᠵᠢ ᠤᠩᠰᠢᠬᠤ ᠵᠢᠨ ᠠᠷᠭ᠎ᠠ ᠥᠬᠡᠢ Peak detect ᠣᠷᠭᠢᠯ ᠬᠡᠮᠵᠢᠭᠳᠡᠯ ᠦ᠋ᠨ ᠪᠠᠢᠴᠠᠭᠠᠯᠲᠠ Failed to create monitoring stream ᠬᠢᠨᠠᠨ ᠬᠡᠮᠵᠢᠬᠦ ᠤᠷᠤᠰᠬᠠᠯ ᠢ᠋ ᠪᠠᠢᠭᠤᠯᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠦᠬᠡᠢ Failed to connect monitoring stream ᠬᠢᠨᠠᠨ ᠬᠡᠮᠵᠢᠬᠦ ᠤᠷᠤᠰᠬᠠᠯ ᠢ᠋ ᠴᠦᠷᠬᠡᠯᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠦᠬᠡᠢ Ignoring sink-input due to it being designated as an event and thus handled by the Event widget sink-input ᠢ᠋\ ᠵᠢ ᠤᠮᠳᠤᠭᠠᠢᠯᠠᠨ᠎ᠠ᠂ ᠤᠴᠢᠷ ᠨᠢ ᠳᠡᠬᠦᠨ ᠢ᠋ ᠶᠠᠪᠤᠳᠠᠯ ᠵᠢᠨᠷ ᠳᠤᠭᠳᠠᠭᠠᠭᠰᠠᠨ᠂ ᠡᠢᠮᠤ ᠡᠴᠡ Event widget ᠵᠢᠡᠷ\ ᠪᠡᠷ ᠰᠢᠢᠳᠪᠦᠷᠢᠯᠡᠬᠦ ᠬᠡᠷᠡᠭᠳᠡᠢ pa_context_kill_source_output() failed pa_context_kill_source_output (᠎) ᠢᠯᠠᠭᠳᠠᠬᠤ Establishing connection to PulseAudio. Please wait... PulseAudio ᠳ᠋ᠤ᠌/ ᠲᠤ᠌ ᠴᠦᠷᠬᠡᠯᠡᠵᠦ ᠪᠠᠢᠨ᠎ᠠ᠂ ᠳᠦᠷ ᠬᠦᠯᠢᠶᠡᠬᠡᠷᠡᠢ··· ukui-media/audio/translations/en_US.ts0000664000175000017500000006147215170054730016751 0ustar fengfeng Audio Audio UkccPlugin /UkccPlugin/UkccPlugin ukccplugin test /UkccPlugin/ukccplugin test InputDevWidget Input Devices Confirm OutputDevWidget Output Devices Confirm QObject pa_context_get_server_info() failed Card callback failure Sink callback failure Source callback failure Sink input callback failure Source output callback failure Client callback failure Server info callback failure Failed to initialize stream_restore extension: %s pa_ext_stream_restore_read() failed Failed to initialize device manager extension: %s pa_ext_device_manager_read() failed pa_context_get_sink_info_by_index() failed pa_context_get_source_info_by_index() failed pa_context_get_sink_input_info() failed pa_context_get_client_info() failed pa_context_get_card_info_by_index() failed pa_context_subscribe() failed pa_context_client_info_list() failed pa_context_get_card_info_list() failed pa_context_get_sink_info_list() failed pa_context_get_source_info_list() failed pa_context_get_sink_input_info_list() failed pa_context_get_source_output_info_list() failed Ukui Media Volume Control UkmediaAppCtrlWidget App Sound Control None System Volume UkmediaAppItemWidget Application Output Volume Input Device This case does not support setting the input device Output Device This case does not support setting the output device Confirm UkmediaDevControlWidget Sound Equipment Control Output Devices Input Devices UkmediaInputWidget Input /Audio/Input Input Device /Audio/Input Device Volume /Audio/Volume Input Level /Audio/Input Level Noise Reduction /Audio/Noise Reduction Voice Monitor /audio/Voice Monitor (None Device) You can hear your voice in the output device of your choice UkmediaMainWidget Custom None UkmediaOutputWidget Output /Audio/Output Output Device /Audio/Output Device Master Volume /Audio/Master Volume Balance /Audio/Balance Left Right Volume Increase Volume above 100% can cause sound distortion and damage your speakers. Mono Audio It merges the left and right channels into one channel. UkmediaSettingsWidget Advanced Settings Sound Equipment Control /Audio/Sound Equipment Control Details App Sound Control /Audio/App Sound Control UkmediaSoundEffectsWidget System Sound Sound Theme /Audio/Sound Theme Beep Switch /Audio/Beep Switch Poweroff Music /Audio/Poweroff Music Startup Music /Audio/Startup Music Wakeup Music /Audio/Wakeup Music Logout Music /Audio/Logout Music Notification Sound /Audio/Notification Sound Volume Control Sound /Audio/Volume Control Sound UkmediaVolumeControl pa_context_set_sink_volume_by_index() failed pa_context_set_sink_mute_by_index() failed pa_context_set_source_mute_by_index() failed pa_context_set_source_volume_by_index() failed pa_context_set_source_output_volume() failed pa_context_set_source_output_mute() failed pa_context_set_card_profile_by_index() failed pa_context_set_default_sink() failed pa_context_set_default_source() failed pa_context_set_sink_port_by_name() failed pa_context_set_source_port_by_name() failed (plugged in) (unavailable) (unplugged) Failed to read data from stream Peak detect Failed to create monitoring stream Failed to connect monitoring stream Ignoring sink-input due to it being designated as an event and thus handled by the Event widget pa_context_kill_source_output() failed Establishing connection to PulseAudio. Please wait... ukui-media/audio/translations/zh_HK.ts0000664000175000017500000006433215170054730016741 0ustar fengfeng Audio Audio 聲音 UkccPlugin Ukcc外掛程式 /UkccPlugin/UkccPlugin ukccplugin test ukcc外掛程式 測試 /UkccPlugin/ukccplugin test InputDevWidget Input Devices 輸入裝置 Confirm 確認 OutputDevWidget Output Devices 輸出裝置 Confirm 確認 QObject pa_context_get_server_info() failed pa_context_get_server_info() 執行失敗 Card callback failure 卡片回調失敗 Sink callback failure 輸出回調失敗 Source callback failure 輸入回調失敗 Sink input callback failure 水槽輸入回調失敗 Source output callback failure source-ouput回調失敗 Client callback failure 用戶端回調失敗 Server info callback failure 服務端回調錯誤 Failed to initialize stream_restore extension: %s 初始化stream_restore擴展失敗:%s pa_ext_stream_restore_read() failed pa_ext_stream_restore_read() 執行失敗 Failed to initialize device manager extension: %s 初始化設備管理器擴展失敗:%s pa_ext_device_manager_read() failed pa_ext_device_manager_read() 執行失敗 pa_context_get_sink_info_by_index() failed pa_context_get_sink_info_by_index() 執行失敗 pa_context_get_source_info_by_index() failed pa_context_get_source_info_by_index() 執行失敗 pa_context_get_sink_input_info() failed pa_context_get_sink_input_info() 執行失敗 pa_context_get_client_info() failed pa_context_get_client_info() 執行失敗 pa_context_get_card_info_by_index() failed pa_context_get_card_info_by_index() 執行失敗 pa_context_subscribe() failed pa_context_subscribe() 執行失敗 pa_context_client_info_list() failed pa_context_client_info_list() 執行失敗 pa_context_get_card_info_list() failed pa_context_get_card_info_list() 執行失敗 pa_context_get_sink_info_list() failed pa_context_get_sink_info_list() 執行失敗 pa_context_get_source_info_list() failed pa_context_get_source_info_list() 執行失敗 pa_context_get_sink_input_info_list() failed pa_context_get_sink_input_info_list() 執行失敗 pa_context_get_source_output_info_list() failed pa_context_get_source_output_info_list() 執行失敗 Connection failed, attempting reconnect 連接失敗,嘗試重新連接中 Ukui Media Volume Control UKUI 媒體音量控制 UkmediaAppCtrlWidget App Sound Control 應用程式聲音控制 None System Volume 系統音量 UkmediaAppItemWidget Application 應用 Output Volume 輸出音量 Input Device 選擇輸入設備 This case does not support setting the input device 本案例不支援設定輸入設備 Output Device 選擇輸出設備 This case does not support setting the output device 本案例不支援設定輸出設備 Confirm 確認 UkmediaDevControlWidget Sound Equipment Control 音響設備控制 Output Devices 輸出裝置 Input Devices 輸入裝置 UkmediaInputWidget Input 輸入 /Audio/Input Input Device 選擇輸入設備 /Audio/Input Device Volume 音量 /Audio/Volume Input Level 輸入反饋 /Audio/Input Level Noise Reduction 智慧降噪 /Audio/Noise Reduction Voice Monitor 語音監聽 /audio/Voice Monitor (None Device) (無裝置) You can hear your voice in the output device of your choice 您可以在您選擇的輸出裝置中聽到您的聲音 UkmediaMainWidget Custom 自定義 None UkmediaOutputWidget Output 輸出 /Audio/Output Output Device 選擇輸出設備 /Audio/Output Device Master Volume 音量 /Audio/Master Volume Balance 聲道平衡 /Audio/Balance Left Right Volume Increase 音量增強 Volume above 100% can cause sound distortion and damage your speakers. 音量超過 100% 可能會導致聲音失真並損壞揚聲器。 Mono Audio 單聲道音訊 It merges the left and right channels into one channel. 它將左右通道合併為一個通道。 UkmediaSettingsWidget Advanced Settings 進階設定 Sound Equipment Control 音響設備控制 /Audio/Sound Equipment Control Details 細節 App Sound Control 應用程式聲音控制 /Audio/App Sound Control UkmediaSoundEffectsWidget System Sound 系統音效 Sound Theme 音效主題 /Audio/Sound Theme Alert Sound 通知 /Audio/Alert Sound Beep Switch 提示音 /Audio/Beep Switch Poweroff Music 關機 /Audio/Poweroff Music Startup Music 開機 /Audio/Startup Music Wakeup Music 喚醒 /Audio/Wakeup Music Notification Sound 通知音效 /Audio/Notification Sound Volume Control Sound 音量調整音效 /Audio/Volume Control Sound Volume Change 音量調節 /Audio/Volume Change Logout Music 註銷 /Audio/Logout Music UkmediaVolumeControl pa_context_set_sink_volume_by_index() failed pa_context_set_sink_volume_by_index() 執行失敗 pa_context_set_sink_mute_by_index() failed pa_context_set_source_mute_by_index() failed pa_context_set_source_mute_by_index() 執行失敗 pa_context_set_source_volume_by_index() failed pa_context_set_source_volume_by_index() 執行失敗 pa_context_set_source_output_volume() failed pa_context_set_source_output_volume() 執行失敗 pa_context_set_source_output_mute() failed pa_context_set_source_output_mute() 執行失敗 pa_context_set_card_profile_by_index() failed pa_context_set_card_profile_by_index() 執行失敗 pa_context_set_default_sink() failed pa_context_set_default_sink() 執行失敗 pa_context_set_default_source() failed pa_context_set_default_source() 執行失敗 pa_context_set_sink_port_by_name() failed pa_context_set_sink_port_by_name() 執行失敗 pa_context_set_source_port_by_name() failed pa_context_set_source_port_by_name() 執行失敗 (plugged in) (插上了) (unavailable) ( 不可用 ) (unplugged) 沒有連接 Failed to read data from stream 從流中讀取數據失敗 Peak detect 峰值檢測 Failed to create monitoring stream 創建監控流失敗 Failed to connect monitoring stream 連接監控流失敗 Ignoring sink-input due to it being designated as an event and thus handled by the Event widget 忽略沉降輸入,因為它被指定為事件,因此由事件小部件處理 pa_context_kill_source_output() failed Establishing connection to PulseAudio. Please wait... 正在建立與PulseAudio的連接。 請等待... ukui-media/audio/app-device-control/0000775000175000017500000000000015170054730016330 5ustar fengfengukui-media/audio/app-device-control/ukmedia_app_item_widget.h0000664000175000017500000000533515170054730023347 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifndef UKMEDIA_ITEM_WIDGET_H #define UKMEDIA_ITEM_WIDGET_H #include #include #include #include #include #include #include #include #include #include #include #include #include "kslider.h" #include "../../audio/ukui_custom_style.h" #include "../../common/ukmedia_common.h" using namespace kdk; class UkmediaAppItemWidget : public QWidget { Q_OBJECT public: UkmediaAppItemWidget(QWidget *parent = nullptr); ~UkmediaAppItemWidget(); friend class UkmediaAppCtrlWidget; void initUI(); void addInputCombobox(QString port); void addOutputCombobox(QString port); void setSliderValue(int value); void outputVolumeDarkThemeImage(int value, bool status); void setTitleName(QString name); //设置应用标题名 void setChildObjectName(QString name); //设置控件object name void setInputHintWidgetShow(bool status); void setOutputHintWidgetShow(bool status); private: QFrame* myLine(); // QFrame *line1; // QFrame *line2; QFrame *m_pAppWidget; QFrame *m_pInputWidget; QFrame *m_pOutputWidget; QFrame *m_pVolumeWidget; QFrame *m_pCheckWidget; FixLabel *m_pAppLabel; FixLabel *m_pVolumeLabel; FixLabel *m_pInputDevLabel; FixLabel *m_pOutputDevLabel; FixLabel *m_pVolumeNumLabel; QLabel *m_pInputHintIcon; QLabel *m_pOutputHintIcon; QFrame *m_pInputHintWidget; QFrame *m_pOutputHintWidget; FixLabel *m_pInputHintLabel; FixLabel *m_pOutputHintLabel; KSlider *m_pVolumeSlider; QComboBox *m_pInputCombobox; QComboBox *m_pOutputCombobox; QPushButton *m_pConfirmBtn; UkuiButtonDrawSvg *m_pVolumeBtn; QString mThemeName; void onPaletteChanged(); }; #endif //UKMEDIA_ITEM_WIDGET_H ukui-media/audio/app-device-control/ukmedia_app_device_ctrl.h0000664000175000017500000000663015170054730023330 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifndef UKMEDIA_APP_DEVICE_CTRL_H #define UKMEDIA_APP_DEVICE_CTRL_H #include #include #include #include #include #include #include #include #include #include #include #include #include "kwidget.h" #include "knavigationbar.h" #include "ukmedia_app_item_widget.h" #include "../../common/ukmedia_common.h" using namespace kdk; class UkmediaAppCtrlWidget : public KWidget { Q_OBJECT public: explicit UkmediaAppCtrlWidget(QWidget *parent = nullptr); ~UkmediaAppCtrlWidget(); void initUI(); void initData(); void dealSlot(); void addItem(QString appName); int getSystemVolume(); bool getSystemMuteState(); int getAppVolume(QString app); bool getAppMuteState(QString app); void getAppList(); void getAllPortInfo(); void getRecordAppInfo(); void getPlaybackAppInfo(); QStringList getAllInputPort(); QStringList getAllOutputPort(); QString getSystemInputDevice(); QString getSystemOutputDevice(); QString getAppInputDevice(QString app); QString getAppOutputDevice(QString app); private: KNavigationBar* m_pAppSoundCtrlBar; QStackedWidget* stackWidget; QStringList appList; //获取当前存在的应用名单 QStringList sinkPortList; QStringList sourcePortList; QList recordAppInfoList; QList playbackAppInfoList; QMap portInfoMap; QDBusInterface* interface; bool setSystemVolume(int value); bool setSystemInputDevice(QString portLabel); bool setSystemOutputDevice(QString portLabel); int findAppDirection(QString appName); #ifdef PA_PROP_APPLICATION_MOVE bool checkAppMoveStatus(int appType, QString appName); #endif int indexOfItemCombobox(QString port, QComboBox *box); QString getAppName(QString appName); QString getAppIcon(QString appName); QString AppDesktopFileAdaption(QString appName); private Q_SLOTS: void updatePort(); void updateAppItem(QString appName); void removeAppItem(QString appName); void updateSystemVolume(int value); void updateSystemMuteState(bool state); void updateAppMuteState(QString app, bool state); // void appVolumeChangedSlot(QString app, QString appId, int volume); bool setAppMuteState(); bool setAppVolume(int value); bool setAppInputDevice(QString portLabel); bool setAppOutputDevice(QString portLabel); }; #endif // UKMEDIA_APP_DEVICE_CTRL_H ukui-media/audio/app-device-control/app-device-control.pri0000664000175000017500000000031715170054730022540 0ustar fengfengINCLUDEPATH += $$PWD HEADERS += \ $$PWD/ukmedia_app_item_widget.h \ $$PWD/ukmedia_app_device_ctrl.h \ SOURCES += \ $$PWD/ukmedia_app_item_widget.cpp \ $$PWD/ukmedia_app_device_ctrl.cpp \ ukui-media/audio/app-device-control/ukmedia_app_device_ctrl.cpp0000664000175000017500000010527015170054730023663 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "ukmedia_app_device_ctrl.h" #include UkmediaAppCtrlWidget::UkmediaAppCtrlWidget(QWidget *parent) : KWidget(parent) { initData(); initUI(); dealSlot(); } void UkmediaAppCtrlWidget::initData() { interface = new QDBusInterface(UKUI_MEDIA_SERVICE, UKUI_MEDIA_PATH, UKUI_MEDIA_INTERFACE, QDBusConnection::sessionBus()); if (!interface->isValid()) return; //1.获取app列表 getAppList(); //2.获取所有可用端口信息 getAllPortInfo(); //3.获取可用输出端口 getAllOutputPort(); //4.获取可用输入端口 getAllInputPort(); } void UkmediaAppCtrlWidget::initUI() { setWidgetName(tr("App Sound Control")); setIcon(QIcon::fromTheme("ukui-control-center")); setWindowFlags(Qt::Dialog); stackWidget = new QStackedWidget(this); stackWidget->setFixedSize(560, 520); m_pAppSoundCtrlBar = new KNavigationBar(this); m_pAppSoundCtrlBar->setFixedSize(188,520); QBoxLayout *vLayout = new QVBoxLayout(); vLayout->addWidget(m_pAppSoundCtrlBar); sideBar()->setLayout(vLayout); vLayout = new QVBoxLayout; vLayout->addWidget(stackWidget); baseBar()->setLayout(vLayout); this->setLayoutType(HorizontalType); // 添加应用到导航栏 for (QString appName : appList) addItem(appName); // 初始化UI 默认选中第一项 QStandardItemModel* m_mode = m_pAppSoundCtrlBar->model(); //获取mode if (m_mode->item(0,0)) m_pAppSoundCtrlBar->listview()->setCurrentIndex(m_mode->item(0,0)->index()); } void UkmediaAppCtrlWidget::addItem(QString appName) { // 获取应用 name QString iconName = getAppIcon(appName); QString pAppName = getAppName(appName); // 1.左边导航栏增加标题 QStandardItem *item = new QStandardItem(QIcon::fromTheme(iconName), pAppName); m_pAppSoundCtrlBar->addItem(item); // 2.右边添加窗口 UkmediaAppItemWidget* widget = new UkmediaAppItemWidget(); widget->setTitleName(pAppName); widget->setChildObjectName(appName); widget->setAttribute(Qt::WA_DeleteOnClose); stackWidget->addWidget(widget); // 3.设置app对应音量滑动条值 widget->setSliderValue(getAppVolume(appName)); widget->outputVolumeDarkThemeImage(getAppVolume(appName), getAppMuteState(appName)); // 4.设置app对应输出输入设备 for (QString sinkPortLabel : sinkPortList) widget->addOutputCombobox(sinkPortLabel); for (QString sourcePortLabel : sourcePortList) widget->addInputCombobox(sourcePortLabel); QString defaultInPort = getAppInputDevice(appName); QString defaultOutPort = getAppOutputDevice(appName); widget->m_pOutputCombobox->setCurrentText(defaultOutPort); widget->m_pInputCombobox->setCurrentText(defaultInPort); // 5.slots connect(widget->m_pVolumeSlider, &KSlider::valueChanged, this, &UkmediaAppCtrlWidget::setAppVolume); connect(widget->m_pVolumeBtn, &UkuiButtonDrawSvg::clicked, this, &UkmediaAppCtrlWidget::setAppMuteState); connect(widget->m_pInputCombobox, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) { QComboBox *comboBox = qobject_cast(sender()); if (comboBox) { QString text = comboBox->itemText(index); setAppInputDevice(text); } }); connect(widget->m_pOutputCombobox, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) { QComboBox *comboBox = qobject_cast(sender()); if (comboBox) { QString text = comboBox->itemText(index); setAppOutputDevice(text); } }); connect(widget->m_pConfirmBtn, &QPushButton::clicked, this, [=](){ this->close(); }); //需求31268待2501再合入 #if 1 if (appName.compare(QString(SYSTEM_VOLUME_CTRL)) == 0) { if (!sinkPortList.isEmpty()) { if (QString(sinkPortList.at(0)).compare(QString(tr("None"))) == 0) { widget->m_pVolumeSlider->setEnabled(false); widget->m_pVolumeBtn->setEnabled(false); } else { widget->m_pVolumeSlider->setEnabled(true); widget->m_pVolumeBtn->setEnabled(true); } } } #endif // 6.区分App类型 int appType = findAppDirection(appName); switch (appType) { case PA_STREAM_PLAYBACK: item->setData(PA_STREAM_PLAYBACK); widget->m_pInputCombobox->setDisabled(true); break; case PA_STREAM_RECORD: item->setData(PA_STREAM_RECORD); widget->m_pVolumeWidget->hide(); widget->m_pOutputCombobox->setDisabled(true); break; default: item->setData(PA_STREAM_NODIRECTION); break; } #ifdef PA_PROP_APPLICATION_MOVE widget->setInputHintWidgetShow(checkAppMoveStatus(PA_STREAM_RECORD, appName)); widget->setOutputHintWidgetShow(checkAppMoveStatus(PA_STREAM_PLAYBACK, appName)); #endif qDebug() << __func__ << "Application:" << appName << "Type:" << item->data().toInt(); } void UkmediaAppCtrlWidget::removeAppItem(QString appName) { int index = -1; for (int i = 0; i < appList.count(); i++) { if (appList.at(i) == appName) { index = i; appList.removeAt(i); break; } } if (index == -1) return; QWidget* w = stackWidget->widget(index); stackWidget->removeWidget(w); w->close(); delete w; m_pAppSoundCtrlBar->model()->removeRow(index); stackWidget->setCurrentIndex(m_pAppSoundCtrlBar->listview()->currentIndex().row()); qDebug() << __func__ << appName << "stackWidget:" << stackWidget->children(); } void UkmediaAppCtrlWidget::dealSlot() { // 导航栏点击应用切换对应窗口slot connect(m_pAppSoundCtrlBar->listview(), &QListView::clicked, this, [=](const QModelIndex modelIndex) { int index = m_pAppSoundCtrlBar->listview()->currentIndex().row(); stackWidget->setCurrentIndex(index); });; QDBusConnection::sessionBus().connect(QString(), UKUI_MEDIA_CONTROL_PATH, UKUI_MEDIA_SERVICE, "updatePortSignal", this, SLOT(updatePort())); QDBusConnection::sessionBus().connect(QString(), UKUI_MEDIA_CONTROL_PATH, UKUI_MEDIA_SERVICE, "updateMute", this, SLOT(updateSystemMuteState(bool))); QDBusConnection::sessionBus().connect(QString(), UKUI_MEDIA_CONTROL_PATH, UKUI_MEDIA_SERVICE, "updateVolume", this, SLOT(updateSystemVolume(int))); QDBusConnection::sessionBus().connect(QString(), UKUI_MEDIA_CONTROL_PATH, UKUI_MEDIA_SERVICE, "updateApp", this, SLOT(updateAppItem(QString))); QDBusConnection::sessionBus().connect(QString(), UKUI_MEDIA_CONTROL_PATH, UKUI_MEDIA_SERVICE, "removeSinkInputSignal", this, SLOT(removeAppItem(QString))); // QDBusConnection::sessionBus().connect(QString(), // UKUI_MEDIA_CONTROL_PATH, // UKUI_MEDIA_SERVICE, // "sinkInputVolumeChangedSignal", // this, // SLOT(appVolumeChangedSlot(QString, QString, int))); } // 获取运行应用名单 void UkmediaAppCtrlWidget::getAppList() { // 首先添加系统音量 appList << SYSTEM_VOLUME_CTRL; QDBusReply reply = interface->call("getAppList"); if (!reply.isValid()) { qWarning() << __func__ << "failed"; return; } appList << reply.value(); getPlaybackAppInfo(); getRecordAppInfo(); } void UkmediaAppCtrlWidget::getPlaybackAppInfo() { qRegisterMetaType("appInfo"); qDBusRegisterMetaType(); QDBusReply> reply = interface->call("getPlaybackAppInfo"); QList list = reply.value(); for (int i = 0; i < list.count(); i++) { const QDBusArgument &dbusArgs = list.at(i).value(); while (!dbusArgs.atEnd()) { appInfo info; dbusArgs.beginStructure(); dbusArgs >> info.index; dbusArgs >> info.volume; dbusArgs >> info.channel; dbusArgs >> info.mute; dbusArgs >> info.direction; #ifdef PA_PROP_APPLICATION_MOVE dbusArgs >> info.move; #endif dbusArgs >> info.name; dbusArgs >> info.masterIndex; dbusArgs >> info.masterDevice; playbackAppInfoList.push_back(info); dbusArgs.endStructure(); qDebug() << __func__ << info.index << info.name << info.volume << info.masterDevice; } } } void UkmediaAppCtrlWidget::getRecordAppInfo() { qRegisterMetaType("appInfo"); qDBusRegisterMetaType(); QDBusReply> reply = interface->call("getRecordAppInfo"); QList list = reply.value(); for (int i = 0; i < list.count(); i++) { const QDBusArgument &dbusArgs = list.at(i).value(); while (!dbusArgs.atEnd()) { appInfo info; dbusArgs.beginStructure(); dbusArgs >> info.index; dbusArgs >> info.volume; dbusArgs >> info.channel; dbusArgs >> info.mute; dbusArgs >> info.direction; #ifdef PA_PROP_APPLICATION_MOVE dbusArgs >> info.move; #endif dbusArgs >> info.name; dbusArgs >> info.masterIndex; dbusArgs >> info.masterDevice; recordAppInfoList.push_back(info); dbusArgs.endStructure(); qDebug() << __func__ << info.index << info.name << info.volume << info.masterDevice << info.direction; } } } void UkmediaAppCtrlWidget::getAllPortInfo() { // 注册自定义结构体类型 qRegisterMetaType("pa_device_port_info"); qDBusRegisterMetaType(); QDBusConnection bus = QDBusConnection::sessionBus(); if (!bus.isConnected()) { qDebug() << "Cannot connect to D-Bus."; return ; } QDBusMessage message = QDBusMessage::createMethodCall(PULSEAUDIO_DEVICECONTROL_SERVICE, PULSEAUDIO_DEVICECONTROL_PATH, PULSEAUDIO_DEVICECONTROL_INTERFACE, "GetAllDeviceInfo"); QDBusMessage reply = bus.call(message); const QDBusArgument &dbusArgs = reply.arguments().at(0).value().variant().value(); QList devsInfo; dbusArgs.beginArray(); while (!dbusArgs.atEnd()) { pa_device_port_info info; dbusArgs.beginStructure(); dbusArgs >> info.card; dbusArgs >> info.direction; dbusArgs >> info.available; dbusArgs >> info.plugged_stauts; dbusArgs >> info.name; dbusArgs >> info.description; dbusArgs >> info.device_description; dbusArgs >> info.device_product_name; devsInfo.push_back(info); dbusArgs.endStructure(); } dbusArgs.endArray(); // 添加端口至portInfoMap int count = 0; for (pa_device_port_info &info : devsInfo) { if (info.available == PA_PORT_AVAILABLE_YES || info.available == PA_PORT_AVAILABLE_UNKNOWN) { QString portLabel = info.description + "(" + info.device_description + ")"; info.description = portLabel; portInfoMap.insert(count, info); count++; } } } // 获取所有可用输出端口 QStringList UkmediaAppCtrlWidget::getAllOutputPort() { QMap::iterator it; for (it = portInfoMap.begin(); it != portInfoMap.end(); it++) { if (it.value().direction == PA_DIRECTION_OUTPUT) { sinkPortList << it.value().description; // Usb声卡和蓝牙声卡存在多种配置文件,保留声卡当前选择的配置文件端口 QDBusReply reply = interface->call("isPortHidingNeeded", SoundType::SINK, it.value().card, it.value().description); if (reply.value()) sinkPortList.removeAll(it.value().description); } } if (sinkPortList.isEmpty()) sinkPortList.append(tr("None")); return sinkPortList; } // 获取所有可用输入端口 QStringList UkmediaAppCtrlWidget::getAllInputPort() { QMap::iterator it; for (it = portInfoMap.begin(); it != portInfoMap.end(); it++) { if (it.value().direction == PA_DIRECTION_INPUT) { sourcePortList << it.value().description; QDBusReply reply = interface->call("isPortHidingNeeded", SoundType::SOURCE, it.value().card, it.value().description); if (reply.value()) sourcePortList.removeAll(it.value().description); } } if (sourcePortList.isEmpty()) sourcePortList.append(tr("None")); return sourcePortList; } // 获取系统音量 int UkmediaAppCtrlWidget::getSystemVolume() { QDBusReply reply = interface->call("getDefaultOutputVolume"); if (!reply.isValid()) { qWarning() << __func__ << "failed"; return 0; } return reply.value(); } // 获取应用音量 int UkmediaAppCtrlWidget::getAppVolume(QString app) { if (app == SYSTEM_VOLUME_CTRL) return getSystemVolume(); QDBusReply reply = interface->call("getAppVolume", app); if(!reply.isValid()) { qWarning() << __func__ << "failed"; return 0; } return reply.value(); } bool UkmediaAppCtrlWidget::getSystemMuteState() { QDBusReply reply = interface->call("getDefaultOutputMuteState"); if (!reply.isValid()) { qWarning() << __func__ << "failed"; return false; } return reply.value(); } // 获取应用静音状态 bool UkmediaAppCtrlWidget::getAppMuteState(QString app) { if (app == SYSTEM_VOLUME_CTRL) return getSystemMuteState(); QDBusReply reply = interface->call("getAppMuteState", app); if (!reply.isValid()) { qWarning() << __func__ << "failed"; return false; } return reply.value(); } // 获取系统默认输出设备 QString UkmediaAppCtrlWidget::getSystemOutputDevice() { QDBusReply reply = interface->call("getSystemOutputDevice"); if (!reply.isValid()) { qWarning() << __func__ << "failed"; return ""; } return reply.value(); } // 获取系统默认输入设备 QString UkmediaAppCtrlWidget::getSystemInputDevice() { QDBusReply reply = interface->call("getSystemInputDevice"); if (!reply.isValid()) { qWarning() << __func__ << "failed"; return ""; } return reply.value(); } // 获取应用默认输出设备 QString UkmediaAppCtrlWidget::getAppOutputDevice(QString app) { if (app == SYSTEM_VOLUME_CTRL) return getSystemOutputDevice(); QDBusReply reply = interface->call("getAppOutputDevice", app); if (!reply.isValid()) { qWarning() << __func__ << "failed"; return ""; } return reply.value(); } // 获取应用默认输入设备 QString UkmediaAppCtrlWidget::getAppInputDevice(QString app) { if (app == SYSTEM_VOLUME_CTRL) return getSystemInputDevice(); QDBusReply reply = interface->call("getAppInputDevice", app); if (!reply.isValid()) { qWarning() << __func__ << "failed"; return ""; } return reply.value(); } // 设置系统音量 bool UkmediaAppCtrlWidget::setSystemVolume(int value) { QDBusReply reply = interface->call("setDefaultOutputVolume", value); if (!reply.isValid()) return false; return reply.value(); } // 设置应用音量 bool UkmediaAppCtrlWidget::setAppVolume(int value) { QSlider *slider = qobject_cast(sender()); if (slider == nullptr) return false; QString app = slider->objectName(); UkmediaAppItemWidget *w = stackWidget->findChild(app); w->outputVolumeDarkThemeImage(value, getAppMuteState(app)); if (app == SYSTEM_VOLUME_CTRL) return setSystemVolume(value); QDBusReply reply = interface->call("setAppVolume", app, value); if (!reply.isValid()) { qWarning() << __func__ << "failed"; return false; } return reply.value(); } // 设置应用静音 bool UkmediaAppCtrlWidget::setAppMuteState() { UkuiButtonDrawSvg *btn = qobject_cast(sender()); if (btn == nullptr) return false; QString app = btn->objectName(); // 1.系统静音设置 if (app == SYSTEM_VOLUME_CTRL) { QDBusReply reply1 = interface->call("getDefaultOutputMuteState"); if (!reply1.isValid()) return false; bool state = reply1.value(); QDBusReply reply2 = interface->call("setDefaultOutputMuteState", !state); if (!reply2.isValid()) return false; btn->outputVolumeDarkThemeImage(getAppVolume(app), !state); return reply2.value(); } // 2.应用静音设置 QDBusReply reply1 = interface->call("getAppMuteState", app); if (!reply1.isValid()) return false; bool state = reply1.value(); QDBusReply reply = interface->call("setAppMuteState", app, !state); if (!reply.isValid()) { qWarning() << __func__ << "failed"; return false; } btn->outputVolumeDarkThemeImage(getAppVolume(app), !state); return reply.value(); } // 设置系统输入设备 bool UkmediaAppCtrlWidget::setSystemInputDevice(QString portLabel) { QDBusReply reply = interface->call("setSystemInputDevice", portLabel); if (!reply.isValid()) { qWarning() << __func__ << "failed"; return false; } return reply.value(); } // 设置系统输出设备 bool UkmediaAppCtrlWidget::setSystemOutputDevice(QString portLabel) { QDBusReply reply = interface->call("setSystemOutputDevice", portLabel); if (!reply.isValid()) { qWarning() << __func__ << "failed"; return false; } return reply.value(); } // 设置应用输出设备 bool UkmediaAppCtrlWidget::setAppOutputDevice(QString portLabel) { QComboBox *combobox = qobject_cast(sender()); if (combobox == nullptr) return false; QString app = combobox->objectName().split("-output").at(0); if (app == SYSTEM_VOLUME_CTRL) return setSystemOutputDevice(portLabel); int cardIndex = -1; QString sinkPortName; QMap::iterator it; for (it = portInfoMap.begin(); it != portInfoMap.end(); it++) { pa_device_port_info info = it.value(); if (portLabel == info.description && info.direction == 1) { cardIndex = info.card; sinkPortName = info.name; } } QDBusReply reply = interface->call("setAppOutputDevice", app, cardIndex, sinkPortName); if (!reply.isValid()) { qWarning() << __func__ << "failed"; return false; } if (!reply.value()) { combobox->blockSignals(true); combobox->setCurrentText(getAppOutputDevice(app)); combobox->blockSignals(false); qDebug() << __func__ << "failed"; return false; } return reply.value(); } // 设置应用输入设备 bool UkmediaAppCtrlWidget::setAppInputDevice(QString portLabel) { QComboBox *combobox = qobject_cast(sender()); if (combobox == nullptr) return false; QString app = combobox->objectName().split("-input").at(0); if (app == SYSTEM_VOLUME_CTRL) return setSystemInputDevice(portLabel); int cardIndex = -1; QString sourcePortName; QMap::iterator it; for (it = portInfoMap.begin(); it != portInfoMap.end(); it++) { pa_device_port_info info = it.value(); if (portLabel == info.description && info.direction == 2) { cardIndex = info.card; sourcePortName = info.name; } } QDBusReply reply = interface->call("setAppInputDevice", app, cardIndex, sourcePortName); if (!reply.isValid()) { qWarning() << __func__ << "failed"; return false; } if (!reply.value()) { combobox->blockSignals(true); combobox->setCurrentText(getAppInputDevice(app)); combobox->blockSignals(false); qDebug() << __func__ << "failed"; return false; } return reply.value(); } void UkmediaAppCtrlWidget::updateAppItem(QString appName) { playbackAppInfoList.clear(); recordAppInfoList.clear(); getPlaybackAppInfo(); getRecordAppInfo(); if (!appList.contains(appName)) { appList << appName; addItem(appName); return; } else { UkmediaAppItemWidget *w = stackWidget->findChild(appName); w->setSliderValue(getAppVolume(appName)); w->outputVolumeDarkThemeImage(getAppVolume(appName), getAppMuteState(appName)); int appType = findAppDirection(appName); switch (appType) { case PA_STREAM_PLAYBACK: w->m_pVolumeWidget->show(); w->m_pInputCombobox->setDisabled(true); w->m_pOutputCombobox->setDisabled(false); qDebug() << __func__ << appName << "PA_STREAM_PLAYBACK"; break; case PA_STREAM_RECORD: w->m_pVolumeWidget->hide(); w->m_pInputCombobox->setDisabled(false); w->m_pOutputCombobox->setDisabled(true); qDebug() << __func__ << appName << "PA_STREAM_RECORD"; break; default: w->m_pVolumeWidget->show(); w->m_pInputCombobox->setDisabled(false); w->m_pOutputCombobox->setDisabled(false); qDebug() << __func__ << appName << "PA_STREAM_NODIRECTION"; break; } #ifdef PA_PROP_APPLICATION_MOVE w->setInputHintWidgetShow(checkAppMoveStatus(PA_STREAM_RECORD, appName)); w->setOutputHintWidgetShow(checkAppMoveStatus(PA_STREAM_PLAYBACK, appName)); #endif } } // 同步系统音量 void UkmediaAppCtrlWidget::updateSystemVolume(int value) { UkmediaAppItemWidget *w = stackWidget->findChild(SYSTEM_VOLUME_CTRL); int mediaVolume = UkmediaCommon::getInstance().paVolumeToMediaVolume(value); w->setSliderValue(mediaVolume); w->outputVolumeDarkThemeImage(mediaVolume, getSystemMuteState()); for (int i = 0; i < appList.count(); i++) { QString appName = appList.at(i); UkmediaAppItemWidget *w = stackWidget->findChild(appName); w->m_pOutputCombobox->blockSignals(true); w->m_pOutputCombobox->setCurrentText(getAppOutputDevice(appName)); w->m_pOutputCombobox->blockSignals(false); w->m_pInputCombobox->blockSignals(true); w->m_pInputCombobox->setCurrentText(getAppInputDevice(appName)); w->m_pInputCombobox->blockSignals(false); } } // 同步应用音量 //void UkmediaAppCtrlWidget::appVolumeChangedSlot(QString app, QString appId, int volume) //{ // QSlider *slider = stackWidget->findChild(app); // QLabel *sliderLabel = stackWidget->findChild(app+"-label"); // if (slider == nullptr || sliderLabel == nullptr) // return; // int mediaVolume = UkmediaCommon::getInstance().paVolumeToMediaVolume(volume); // slider->blockSignals(true); // slider->setValue(mediaVolume); // slider->blockSignals(false); // QString percent = QString::number(mediaVolume); // sliderLabel->setText(percent+"%"); // for (QString appName : appList) { // QComboBox *outputBox = stackWidget->findChild(appName + "-output"); // QComboBox *inputBox = stackWidget->findChild(appName + "-output"); // inputBox->blockSignals(true); // outputBox->blockSignals(true); // QString defaultOutPort = getAppOutputDevice(appName); // QString defaultInPort = getAppInputDevice(appName); // inputBox->setCurrentText(defaultInPort); // outputBox->setCurrentText(defaultOutPort); // outputBox->blockSignals(false); // inputBox->blockSignals(false); // } // qDebug() << __func__ << app << volume << stackWidget->children(); //} // 同步系统静音状态 void UkmediaAppCtrlWidget::updateSystemMuteState(bool state) { UkuiButtonDrawSvg *btn = stackWidget->findChild(SYSTEM_VOLUME_CTRL); btn->outputVolumeDarkThemeImage(getSystemVolume(), state); } // 同步应用静音状态 void UkmediaAppCtrlWidget::updateAppMuteState(QString app, bool state) { UkuiButtonDrawSvg *btn = stackWidget->findChild(app); btn->outputVolumeDarkThemeImage(getAppVolume(app), state); } void UkmediaAppCtrlWidget::updatePort() { qDebug() << __func__ << sinkPortList; QStringList currentSinkPortList = sinkPortList; QStringList currentSourcePortList = sourcePortList; portInfoMap.clear(); sinkPortList.clear(); sourcePortList.clear(); getAllPortInfo(); getAllInputPort(); getAllOutputPort(); // 1. 删除不可用输出端口 for (int i = 0; i < currentSinkPortList.count(); i++) { QString port = currentSinkPortList.at(i); if (!sinkPortList.contains(port)) { for (int a = 0; a < appList.count(); a++) { QString appName = appList.at(a); UkmediaAppItemWidget *w = stackWidget->findChild(appName); int index = indexOfItemCombobox(port, w->m_pOutputCombobox); w->m_pOutputCombobox->blockSignals(true); w->m_pOutputCombobox->removeItem(index); w->m_pOutputCombobox->blockSignals(false); } } } // 2. 删除不可用输入端口 for (int i = 0; i < currentSourcePortList.count(); i++) { QString port = currentSourcePortList.at(i); if (!sourcePortList.contains(port)) { for (int a = 0; a < appList.count(); a++) { QString appName = appList.at(a); UkmediaAppItemWidget *w = stackWidget->findChild(appName); int index = indexOfItemCombobox(port, w->m_pInputCombobox); w->m_pInputCombobox->blockSignals(true); w->m_pInputCombobox->removeItem(index); w->m_pInputCombobox->blockSignals(false); } } } // 3. 增加可用输出端口 for (int i = 0; i < sinkPortList.count(); i++) { QString port = sinkPortList.at(i); if (!currentSinkPortList.contains(port)) { for (int a = 0; a < appList.count(); a++) { QString appName = appList.at(a); UkmediaAppItemWidget *w = stackWidget->findChild(appName); w->m_pOutputCombobox->blockSignals(true); w->m_pOutputCombobox->addItem(port); w->m_pOutputCombobox->blockSignals(false); } } } // 4. 增加可用输入端口 for (int i = 0; i < sourcePortList.count(); i++) { QString port = sourcePortList.at(i); if (!currentSourcePortList.contains(port)) { for (int a = 0; a < appList.count(); a++) { QString appName = appList.at(a); UkmediaAppItemWidget *w = stackWidget->findChild(appName); w->m_pInputCombobox->blockSignals(true); w->m_pInputCombobox->addItem(port); w->m_pInputCombobox->blockSignals(false); } } } // 5. 选择默认输出设备 for (int i = 0; i < appList.count(); i++) { QString appName = appList.at(i); UkmediaAppItemWidget *w = stackWidget->findChild(appName); w->m_pOutputCombobox->blockSignals(true); w->m_pOutputCombobox->setCurrentText(getAppOutputDevice(appName)); w->m_pOutputCombobox->blockSignals(false); w->m_pInputCombobox->blockSignals(true); w->m_pInputCombobox->setCurrentText(getAppInputDevice(appName)); w->m_pInputCombobox->blockSignals(false); //需求31268待2501再合入 #if 1 if (appName.compare(QString(SYSTEM_VOLUME_CTRL)) == 0) { if (!sinkPortList.isEmpty()) { if (QString(sinkPortList.at(0)).compare(QString(tr("None"))) == 0) { w->m_pVolumeSlider->setEnabled(false); w->m_pVolumeBtn->setEnabled(false); } else { w->m_pVolumeSlider->setEnabled(true); w->m_pVolumeBtn->setEnabled(true); } } } #endif } } int UkmediaAppCtrlWidget::indexOfItemCombobox(QString port, QComboBox *box) { for (int index = 0; index < box->count(); ++index) { QString textport = box->itemText(index); if (textport == port) { return index; } } return -1; } QString UkmediaAppCtrlWidget::getAppName(QString appName) { GError* error = nullptr; GKeyFileFlags flag = G_KEY_FILE_NONE; GKeyFile* keyfile = g_key_file_new(); appName = AppDesktopFileAdaption(appName); QString path = "/usr/share/applications/"; path.append(appName); path.append(".desktop"); /* Some applications desktop file exist in /etc/xdg/autostart/ path */ QFileInfo file(path); if (!file.exists()) { path = "/etc/xdg/autostart/"; path.append(appName); path.append(".desktop"); } QByteArray fpbyte = path.toLocal8Bit(); char* filepath = fpbyte.data(); if (!g_key_file_load_from_file(keyfile, filepath, flag, &error)) qDebug() << "g_key_file_load_from_file() failed" << error->message; char* name= g_key_file_get_locale_string(keyfile, "Desktop Entry", "Name", nullptr, nullptr); QString namestr=QString::fromLocal8Bit(name); g_key_file_free(keyfile); if (appName == SYSTEM_VOLUME_CTRL) namestr = tr("System Volume"); namestr = (namestr != "") ? namestr : appName; return namestr; } QString UkmediaAppCtrlWidget::getAppIcon(QString appName) { GError** error=nullptr; GKeyFileFlags flags=G_KEY_FILE_NONE; GKeyFile* keyfile=g_key_file_new (); appName = AppDesktopFileAdaption(appName); QString path = "/usr/share/applications/"; path.append(appName); path.append(".desktop"); /* Some applications desktop file exist in /etc/xdg/autostart/ path */ QFileInfo file(path); if (!file.exists()) { path = "/etc/xdg/autostart/"; path.append(appName); path.append(".desktop"); } QByteArray fpbyte=path.toLocal8Bit(); char* filepath=fpbyte.data(); g_key_file_load_from_file(keyfile,filepath,flags,error); char* name=g_key_file_get_locale_string(keyfile,"Desktop Entry","Icon", nullptr, nullptr); QString namestr=QString::fromLocal8Bit(name); g_key_file_free(keyfile); if (appName == SYSTEM_VOLUME_CTRL) namestr = SYSTEM_VOLUME_CTRL; /* If we can't find the app icon * we need to set a default icon for it */ namestr = (namestr != "") ? namestr : "application-x-desktop"; return namestr; } int UkmediaAppCtrlWidget::findAppDirection(QString appName) { int stream = PA_STREAM_NODIRECTION; for (appInfo info : playbackAppInfoList) { if (info.name == appName) { stream = PA_STREAM_PLAYBACK; break; } } for (appInfo info : recordAppInfoList) { if (info.name == appName) { if (stream == PA_STREAM_PLAYBACK) stream = PA_STREAM_NODIRECTION; else stream = PA_STREAM_RECORD; break; } } return stream; } #ifdef PA_PROP_APPLICATION_MOVE bool UkmediaAppCtrlWidget::checkAppMoveStatus(int appType, QString appName) { bool moveStatus = false; if (appName == SYSTEM_VOLUME_CTRL) return true; if (appType == PA_STREAM_PLAYBACK) { //start #227383 if ((appName == "kylin-recorder") && (findAppDirection(appName) == PA_STREAM_NODIRECTION)) { return false; } //end for (appInfo info : playbackAppInfoList) { if (info.name == appName) { moveStatus = (info.move == "yes") ? true : false; break; } } } else { for (appInfo info : recordAppInfoList) { // #214516 Circumvention deals with kylin-recorder if (appName == "kylin-recorder") return false; if (info.name == appName) { moveStatus = (info.move == "yes") ? true : false; break; } } } return moveStatus; } #endif QString UkmediaAppCtrlWidget::AppDesktopFileAdaption(QString appName) { //某些第三方应用获取到appName与它的desktop文件名不一致,需手动适配 if (appName.contains("qaxbrowser")) appName = "qaxbrowser-safe"; return appName; } UkmediaAppCtrlWidget::~UkmediaAppCtrlWidget() { } ukui-media/audio/app-device-control/ukmedia_app_item_widget.cpp0000664000175000017500000003063015170054730023676 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "ukmedia_app_item_widget.h" UkmediaAppItemWidget::UkmediaAppItemWidget(QWidget *parent) : QWidget(parent) { if (QGSettings::isSchemaInstalled("org.ukui.style")) { QGSettings *nThemeSetting = new QGSettings("org.ukui.style"); if (nThemeSetting->keys().contains("styleName")) { mThemeName = nThemeSetting->get("style-name").toString(); } } initUI(); onPaletteChanged(); } void UkmediaAppItemWidget::initUI() { this->setFixedSize(560,477); m_pAppLabel = new FixLabel(tr("Application"), this); m_pAppLabel->setContentsMargins(17,0,0,0); m_pAppWidget = new QFrame(this); m_pInputWidget = new QFrame(m_pAppWidget); m_pOutputWidget = new QFrame(m_pAppWidget); m_pVolumeWidget = new QFrame(m_pAppWidget); m_pCheckWidget = new QFrame(m_pAppWidget); m_pAppWidget->setFrameShape(QFrame::Shape::Box); m_pInputWidget->setFrameShape(QFrame::Shape::Box); m_pOutputWidget->setFrameShape(QFrame::Shape::Box); m_pVolumeWidget->setFrameShape(QFrame::Shape::Box); m_pCheckWidget->setFrameShape(QFrame::Shape::Box); m_pInputWidget->setObjectName("InputWidget"); m_pOutputWidget->setObjectName("OutputWidget"); m_pVolumeWidget->setObjectName("VolumeWidget"); m_pAppWidget->setFixedSize(512, 415); m_pInputWidget->setFixedSize(512, 90); m_pOutputWidget->setFixedSize(512, 90); m_pVolumeWidget->setFixedSize(512, 60); m_pCheckWidget->setFixedSize(512, 36); //应用音量布局 m_pVolumeLabel = new FixLabel(tr("Output Volume"), m_pVolumeWidget); m_pVolumeBtn = new UkuiButtonDrawSvg(m_pVolumeWidget); m_pVolumeSlider = new KSlider(m_pVolumeWidget); m_pVolumeSlider->setRange(0,100); m_pVolumeSlider->setSliderType(SmoothSlider); m_pVolumeSlider->setNodeVisible(false); m_pVolumeNumLabel = new FixLabel("0%", m_pVolumeWidget); m_pVolumeBtn->setFixedSize(24,24); m_pVolumeSlider->setFixedSize(260,55); m_pVolumeLabel->setFixedSize(120,20); m_pVolumeNumLabel->setFixedSize(55,20); m_pVolumeSlider->setOrientation(Qt::Horizontal); m_pVolumeNumLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); QHBoxLayout *volumeLayout = new QHBoxLayout(m_pVolumeWidget); volumeLayout->addItem(new QSpacerItem(17,20,QSizePolicy::Fixed)); volumeLayout->addWidget(m_pVolumeLabel); volumeLayout->addItem(new QSpacerItem(12,20,QSizePolicy::Expanding)); volumeLayout->addWidget(m_pVolumeBtn); volumeLayout->addItem(new QSpacerItem(10,20,QSizePolicy::Fixed)); volumeLayout->addWidget(m_pVolumeSlider); // volumeLayout->addItem(new QSpacerItem(14,20,QSizePolicy::Fixed)); volumeLayout->addWidget(m_pVolumeNumLabel); volumeLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); volumeLayout->setSpacing(0); m_pVolumeWidget->setLayout(volumeLayout); m_pVolumeWidget->layout()->setContentsMargins(0,0,0,0); //输入设备布局 m_pInputDevLabel = new FixLabel(tr("Input Device"), m_pInputWidget); m_pInputCombobox = new QComboBox(m_pInputWidget); m_pInputDevLabel->setFixedSize(120, 36); m_pInputCombobox->setFixedSize(347, 36); m_pInputHintIcon = new QLabel(m_pInputWidget); m_pInputHintIcon->setFixedSize(16, 16); m_pInputHintIcon->setPixmap(QIcon::fromTheme("dialog-warning").pixmap(QSize(16, 16))); m_pInputHintLabel = new FixLabel(tr("This case does not support setting the input device"), m_pInputWidget); m_pInputHintWidget = new QFrame(m_pInputWidget); QHBoxLayout *lay = new QHBoxLayout(m_pInputHintWidget); lay->addWidget(m_pInputHintIcon); lay->addWidget(m_pInputHintLabel); lay->setSpacing(3); m_pInputHintWidget->setLayout(lay); m_pInputHintWidget->layout()->setContentsMargins(0, 0, 0, 0); QGridLayout *gridLayout = new QGridLayout(m_pInputWidget); gridLayout->addWidget(m_pInputDevLabel, 0, 0); gridLayout->addWidget(m_pInputCombobox, 0, 1); gridLayout->addItem(new QSpacerItem(132, 20, QSizePolicy::Fixed), 1, 0); gridLayout->addWidget(m_pInputHintWidget); gridLayout->setSpacing(0); m_pInputWidget->setLayout(gridLayout); m_pInputWidget->layout()->setContentsMargins(17, 11, 16, 0); //输出设备布局 m_pOutputDevLabel = new FixLabel(tr("Output Device"), m_pOutputWidget); m_pOutputCombobox = new QComboBox(m_pOutputWidget); m_pOutputDevLabel->setFixedSize(120, 36); m_pOutputCombobox->setFixedSize(347, 36); m_pOutputHintIcon = new QLabel(m_pOutputWidget); m_pOutputHintIcon->setFixedSize(16, 16); m_pOutputHintIcon->setPixmap(QIcon::fromTheme("dialog-warning").pixmap(QSize(16, 16))); m_pOutputHintLabel = new FixLabel(tr("This case does not support setting the output device"), m_pOutputWidget); m_pOutputHintWidget = new QFrame(m_pOutputWidget); QHBoxLayout *outputLay = new QHBoxLayout(m_pOutputHintWidget); outputLay->addWidget(m_pOutputHintIcon); outputLay->addWidget(m_pOutputHintLabel); outputLay->setSpacing(3); m_pOutputHintWidget->setLayout(outputLay); m_pOutputHintWidget->layout()->setContentsMargins(0, 0, 0, 0); QGridLayout *outputGridLayout = new QGridLayout(m_pOutputWidget); outputGridLayout->addWidget(m_pOutputDevLabel, 0, 0); outputGridLayout->addWidget(m_pOutputCombobox, 0, 1); outputGridLayout->addItem(new QSpacerItem(132, 20, QSizePolicy::Fixed), 1, 0); outputGridLayout->addWidget(m_pOutputHintWidget); outputGridLayout->setSpacing(0); m_pOutputWidget->setLayout(outputGridLayout); m_pOutputWidget->layout()->setContentsMargins(17, 11, 16, 0); // check Btn m_pConfirmBtn = new QPushButton(tr("Confirm"), m_pCheckWidget); m_pConfirmBtn->setFixedSize(96, 36); m_pConfirmBtn->setProperty("isImportant", true); QHBoxLayout *checkLayout = new QHBoxLayout(m_pCheckWidget); checkLayout->addStretch(); checkLayout->addWidget(m_pConfirmBtn); checkLayout->setSpacing(0); m_pCheckWidget->setLayout(checkLayout); m_pCheckWidget->layout()->setContentsMargins(0,0,0,0); // line1 = myLine(); // line2 = myLine(); QVBoxLayout *vlayout = new QVBoxLayout(m_pAppWidget); vlayout->addWidget(m_pVolumeWidget); vlayout->addSpacing(1); vlayout->addWidget(m_pOutputWidget); vlayout->addSpacing(1); vlayout->addWidget(m_pInputWidget); vlayout->addItem(new QSpacerItem(16,197,QSizePolicy::Fixed)); vlayout->addStretch(); vlayout->addWidget(m_pCheckWidget); vlayout->setSpacing(0); vlayout->setContentsMargins(0,0,0,0); //整体布局 QVBoxLayout *appLayout = new QVBoxLayout(this); appLayout->addItem(new QSpacerItem(16,8,QSizePolicy::Fixed)); appLayout->addWidget(m_pAppLabel); appLayout->addItem(new QSpacerItem(16,8,QSizePolicy::Fixed)); appLayout->addWidget(m_pAppWidget); appLayout->addItem(new QSpacerItem(16,24,QSizePolicy::Fixed)); appLayout->addStretch(); appLayout->setSpacing(0); this->setLayout(appLayout); this->layout()->setContentsMargins(25,0,23,0); } void UkmediaAppItemWidget::setInputHintWidgetShow(bool status) { if (status) { m_pInputHintWidget->hide(); m_pInputWidget->setFixedSize(512, 60); m_pInputCombobox->setDisabled(false); } else { m_pInputHintWidget->show(); m_pInputWidget->setFixedSize(512, 90); m_pInputCombobox->setDisabled(true); } } void UkmediaAppItemWidget::setOutputHintWidgetShow(bool status) { if (status) { m_pOutputHintWidget->hide(); m_pOutputWidget->setFixedSize(512, 60); m_pOutputCombobox->setDisabled(false); } else { m_pOutputHintWidget->show(); m_pOutputWidget->setFixedSize(512, 90); m_pOutputCombobox->setDisabled(true); } } // 设置窗口应用标题 void UkmediaAppItemWidget::setTitleName(QString name) { m_pAppLabel->setText(name); } void UkmediaAppItemWidget::setChildObjectName(QString name) { m_pVolumeNumLabel->setObjectName(name+"-label"); m_pVolumeBtn->setObjectName(name); m_pVolumeSlider->setObjectName(name); m_pInputCombobox->setObjectName(name+"-input"); m_pOutputCombobox->setObjectName(name+"-output"); this->setObjectName(name); } void UkmediaAppItemWidget::setSliderValue(int value) { if (m_pVolumeSlider->objectName() == "kylin-settings-system") { if (QGSettings::isSchemaInstalled("org.ukui.sound")) { QGSettings *nThemeSetting = new QGSettings("org.ukui.sound"); if (nThemeSetting->keys().contains("volumeIncrease")) { if (nThemeSetting->get("volumeIncrease").toBool()) m_pVolumeSlider->setRange(0, 125); } } } m_pVolumeSlider->blockSignals(true); m_pVolumeSlider->setValue(value); m_pVolumeSlider->blockSignals(false); QString percent = QString::number(value); m_pVolumeNumLabel->setText(percent+"%"); } void UkmediaAppItemWidget::addInputCombobox(QString port) { m_pInputCombobox->blockSignals(true); m_pInputCombobox->addItem(port); m_pInputCombobox->blockSignals(false); } void UkmediaAppItemWidget::addOutputCombobox(QString port) { m_pOutputCombobox->blockSignals(true); m_pOutputCombobox->addItem(port); m_pOutputCombobox->blockSignals(false); } void UkmediaAppItemWidget::onPaletteChanged() { QPalette palette = m_pInputHintLabel->palette(); QColor color = palette.color(QPalette::PlaceholderText); palette.setColor(QPalette::Text, color); m_pInputHintLabel->setPalette(palette); palette = m_pOutputHintLabel->palette(); color = palette.color(QPalette::PlaceholderText); palette.setColor(QPalette::Text, color); m_pOutputHintLabel->setPalette(palette); if (mThemeName == UKUI_THEME_LIGHT || mThemeName == UKUI_THEME_DEFAULT) { m_pInputWidget->setStyleSheet("#InputWidget{border-radius: 6px; background-color: #F5F5F5;}"); m_pOutputWidget->setStyleSheet("#OutputWidget{border-radius: 6px; background-color: #F5F5F5;}"); m_pVolumeWidget->setStyleSheet("#VolumeWidget{border-radius: 6px; background-color: #F5F5F5;}"); } else if (mThemeName == UKUI_THEME_DARK) { m_pInputWidget->setStyleSheet("#InputWidget{border-radius: 6px; background-color: #333333;}"); m_pOutputWidget->setStyleSheet("#OutputWidget{border-radius: 6px; background-color: #333333;}"); m_pVolumeWidget->setStyleSheet("#VolumeWidget{border-radius: 6px; background-color: #333333;}"); } } // 更新音量图标 void UkmediaAppItemWidget::outputVolumeDarkThemeImage(int value, bool status) { QImage image; QColor color = QColor(0,0,0,216); if (mThemeName == UKUI_THEME_LIGHT || mThemeName == UKUI_THEME_DEFAULT) color = QColor(0,0,0,216); else if (mThemeName == UKUI_THEME_DARK) color = QColor(255,255,255,216); if (status) { image = QIcon::fromTheme("audio-volume-muted-symbolic").pixmap(24,24).toImage(); } else if (value <= 0) { image = QIcon::fromTheme("audio-volume-muted-symbolic").pixmap(24,24).toImage(); } else if (value > 0 && value <= 33) { image = QIcon::fromTheme("audio-volume-low-symbolic").pixmap(24,24).toImage(); } else if (value >33 && value <= 66) { image = QIcon::fromTheme("audio-volume-medium-symbolic").pixmap(24,24).toImage(); } else { image = QIcon::fromTheme("audio-volume-high-symbolic").pixmap(24,24).toImage(); } m_pVolumeBtn->refreshIcon(image, color); } QFrame* UkmediaAppItemWidget::myLine() { QFrame *line = new QFrame(this); line->setFixedSize(512, 1); line->setLineWidth(0); line->setFrameShape(QFrame::HLine); line->setFrameShadow(QFrame::Sunken); return line; } UkmediaAppItemWidget::~UkmediaAppItemWidget() { } ukui-media/audio/titlelabel.cpp0000664000175000017500000000303415170052044015456 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "titlelabel.h" #include #include #include #include TitleLabel::TitleLabel(QWidget *parent): QLabel(parent) { /*初始化字体*/ // QFont font; // QGSettings *m_fontSetting = new QGSettings("org.ukui.style"); // font.setFamily(m_fontSetting->get("systemFont").toString()); // font.setPixelSize(m_fontSetting->get("systemFontSize").toInt() * 18 / 11); //设置的是pt,按照公式计算为px,标题默认字为18px // font.setWeight(QFont::Medium); // this->setFont(font); this ->setContentsMargins(16,0,0,0); // delete m_fontSetting; // m_fontSetting = nullptr; } TitleLabel::~TitleLabel() { } ukui-media/audio/ukui_list_widget_item.h0000664000175000017500000000405415170054730017402 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifndef UKUILISTWIDGETITEM_H #define UKUILISTWIDGETITEM_H #include #include #include #include #include #include #include #include class UkuiListWidget : public QListWidget { Q_OBJECT public: UkuiListWidget(QWidget *parent = nullptr); ~UkuiListWidget(); protected: void paintEvent(QPaintEvent*event) { int i; for (i = 0 ;i < this->count();i++) { QListWidgetItem *item = this->item(i); // item->setTextColor(QColor(0,0,0,0)); delete item; } QListWidget::paintEvent(event); } }; class UkuiListWidgetItem : public QWidget { Q_OBJECT public: UkuiListWidgetItem(QWidget *parent = 0); ~UkuiListWidgetItem(); public: void setLabelText(QString portText,QString deviceLabel); // void setLabelTextIsWhite(bool selected); void setSelected(bool selected); // QString text(); QString portName; QLabel * portLabel; QLabel * deviceLabel; protected: // void paintEvent(QPaintEvent *event); void mousePressEvent(QMouseEvent *ev); private: QWidget * widget; }; #endif // UKUILISTWIDGETITEM_H ukui-media/audio/ukmedia_input_widget.h0000664000175000017500000000461215170054730017212 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifndef UKMEDIAINPUTWIDGET_H #define UKMEDIAINPUTWIDGET_H #include #include #include #include #include #include #include "ukmedia_output_widget.h" #include #include #include #include #include "ukui_custom_style.h" #include "kswitchbutton.h" using namespace kdk; class UkmediaInputWidget : public QWidget { Q_OBJECT public: explicit UkmediaInputWidget(QWidget *parent = nullptr); ~UkmediaInputWidget(); friend class UkmediaMainWidget; Q_SIGNALS: public Q_SLOTS: void onPaletteChanged(); private: void setLabelAlignment(Qt::Alignment alignment); QFrame* myLine(); private: QFrame *m_pInputWidget; QFrame *m_pVolumeWidget; QFrame *m_pInputLevelWidget; QFrame *m_pNoiseReducteWidget; QFrame *m_pLoopBackWidget; QFrame *m_pLoopbackLine; TitleLabel *m_pInputLabel; FixLabel *m_pInputDeviceLabel; FixLabel *m_pIpVolumeLabel; FixLabel *m_pInputLevelLabel; QLabel *m_pIpVolumePercentLabel; FixLabel *m_pNoiseReducteLabel; UkuiButtonDrawSvg *m_pInputIconBtn; AudioSlider *m_pIpVolumeSlider; QProgressBar *m_pInputLevelProgressBar; KSwitchButton *m_pNoiseReducteButton; QString sliderQss; QVBoxLayout *m_pVlayout; QComboBox *m_pInputDeviceSelectBox; QFrame *m_pInputSlectWidget; FixLabel *m_pLoopBackLabel; FixLabel *m_pLoopBackTipsLabel; KSwitchButton *m_pLoopBackButton; }; #endif // UKMEDIAINPUTWIDGET_H ukui-media/audio/ukui_custom_style.h0000664000175000017500000000614715170054730016605 0ustar fengfeng /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifndef UKUICUSTOMSTYLE_H #define UKUICUSTOMSTYLE_H #include #include #include #include #include #include #include "kslider.h" #include #include #include #include using namespace kdk; static QColor symbolic_color = Qt::gray; //文本长自动省略并添加悬浮 class FixLabel : public QLabel { Q_OBJECT public: explicit FixLabel(QWidget *parent = nullptr); explicit FixLabel(QString text , QWidget *parent = nullptr); ~FixLabel(); void setText(const QString &text, bool saveTextFlag = true); QString fullText; private: void paintEvent(QPaintEvent *event); private: }; class AudioSlider : public KSlider { Q_OBJECT public: AudioSlider(QWidget *parent = nullptr); ~AudioSlider(); bool isMouseWheel = false; private: int blueValue = 0; Q_SIGNALS: void blueValueChanged(int value); //针对蓝牙a2dp模式下滑动条跳动,以10为间隔设置音量 protected: void wheelEvent(QWheelEvent *e); void keyReleaseEvent(QKeyEvent *e); void mouseMoveEvent(QMouseEvent *e); void mouseReleaseEvent(QMouseEvent *e); }; class UkmediaVolumeSlider : public KSlider { Q_OBJECT public: UkmediaVolumeSlider(QWidget *parent = nullptr); ~UkmediaVolumeSlider(); void initStyleOption(QStyleOptionSlider *option); void showTooltip(); protected: bool eventFilter(QObject *watched, QEvent *event); }; class UkuiButtonDrawSvg:public QPushButton { Q_OBJECT public: UkuiButtonDrawSvg(QWidget *parent = nullptr); ~UkuiButtonDrawSvg(); QPixmap filledSymbolicColoredPixmap(QImage &source, QColor &baseColor); QRect IconGeometry(); void drawIcon(QPaintEvent* e); //绘制图标 void refreshIcon(QImage image ,QColor color); //刷新图标 void outputVolumeDarkThemeImage(int value, bool status); friend class UkmediaMainWidget; protected: void paintEvent(QPaintEvent *event); bool event(QEvent *e); private: QImage mImage; QColor mColor; bool mousePress = false; }; class TitleLabel : public QLabel { Q_OBJECT public: TitleLabel(QWidget *parent = nullptr); ~TitleLabel(); }; #endif // UKUICUSTOMSTYLE_H ukui-media/audio/ukmedia_volume_control.cpp0000664000175000017500000021663615170054730020125 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "ukmedia_volume_control.h" #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include //pa_sink_info *m_pDefaultSink; /* Used for profile sorting */ struct profile_prio_compare { bool operator() (pa_card_profile_info2 const * const lhs, pa_card_profile_info2 const * const rhs) const { if (lhs->priority == rhs->priority) return strcmp(lhs->name, rhs->name) > 0; return lhs->priority > rhs->priority; } }; struct sink_port_prio_compare { bool operator() (const pa_sink_port_info& lhs, const pa_sink_port_info& rhs) const { if (lhs.priority == rhs.priority) return strcmp(lhs.name, rhs.name) > 0; return lhs.priority > rhs.priority; } }; struct source_port_prio_compare { bool operator() (const pa_source_port_info& lhs, const pa_source_port_info& rhs) const { if (lhs.priority == rhs.priority) return strcmp(lhs.name, rhs.name) > 0; return lhs.priority > rhs.priority; } }; pa_context* UkmediaVolumeControl::context = nullptr; int UkmediaVolumeControl::reconnectTimeout = 3; pa_mainloop_api* UkmediaVolumeControl::api = nullptr; QTimer UkmediaVolumeControl::deviceChangedTimer; UkmediaVolumeControl::UkmediaVolumeControl(): m_connected(false), m_config_filename(nullptr) { profileNameMap.clear(); connectToPulse(this); } /* * 设置输出设备静音 */ void UkmediaVolumeControl::setSinkMute(bool status) { pa_operation* o; if (!(o = pa_context_set_sink_mute_by_index(getContext(), sinkIndex, status, nullptr, nullptr))) { showError(tr("pa_context_set_sink_mute_by_index() failed").toUtf8().constData()); return; } pa_operation_unref(o); } /* * 设置输出设备音量 */ void UkmediaVolumeControl::setSinkVolume(int index, int value) { if (nullptr == m_pDefaultSink) { qWarning() << "m_pDefaultSink is nullptr, set sink volume failed"; return; } pa_cvolume v = m_pDefaultSink->volume; v.channels = channel; for (int i = 0; i < v.channels; i++) v.values[i] = value; if (balance != 0) pa_cvolume_set_balance(&v, &channelMap, balance); /* To set the volume in silent state, unmute the volume first */ if (sinkMuted) setSinkMute(false); pa_operation* o; if (!(o = pa_context_set_sink_volume_by_index(getContext(), index, &v, nullptr, nullptr))) { showError(tr("pa_context_set_sink_volume_by_index() failed").toUtf8().constData()); return; } pa_operation_unref(o); qDebug() << __func__ << "index" << value; } /* * 设置输入设备静音 */ void UkmediaVolumeControl::setSourceMute(bool status) { pa_operation* o; if (!(o = pa_context_set_source_mute_by_index(getContext(), sourceIndex, status, nullptr, nullptr))) { showError(tr("pa_context_set_source_mute_by_index() failed").toUtf8().constData()); return; } pa_operation_unref(o); } /* * 设置输入设备音量 */ void UkmediaVolumeControl::setSourceVolume(int index, int value) { pa_cvolume v = m_pDefaultSource->volume; v.channels = inputChannel; for (int i = 0; i < v.channels; i++) v.values[i] = value; if (sourceMuted) setSourceMute(false); pa_operation* o; if (!(o = pa_context_set_source_volume_by_index(getContext(), index, &v, nullptr, nullptr))) { showError(tr("pa_context_set_source_volume_by_index() failed").toUtf8().constData()); return; } pa_operation_unref(o); qDebug() << __func__ << index << value; } /* * 设置音量平衡值 */ void UkmediaVolumeControl::setBalanceVolume(int index, int value, float b) { if (nullptr == m_pDefaultSink) { qWarning() << "m_pDefaultSink is nullptr, set sink balance failed"; return; } pa_cvolume v = m_pDefaultSink->volume; v.channels = channel; for (int i = 0; i < v.channels; i++) v.values[i] = value; if (b != 0) { balance = b; pa_cvolume_set_balance(&v, &channelMap, balance); } pa_operation* o; if (!(o = pa_context_set_sink_volume_by_index(getContext(), index, &v, nullptr, nullptr))) { showError(tr("pa_context_set_sink_volume_by_index() failed").toUtf8().constData()); return; } pa_operation_unref(o); qDebug() << __func__ << index << value << balance; } /* * 获取输出设备的静音状态 */ bool UkmediaVolumeControl::getSinkMute() { return sinkMuted; } /* * 获取输出设备的音量值 */ int UkmediaVolumeControl::getSinkVolume() { return sinkVolume; } /* * 获取平衡音量 */ float UkmediaVolumeControl::getBalanceVolume() { return balance; } /* * 获取输入设备的静音状态 */ bool UkmediaVolumeControl::getSourceMute() { return sourceMuted; } /* * 获取输入设备的音量值 */ int UkmediaVolumeControl::getSourceVolume() { return sourceVolume; } /* * 获取默认的输出设备名字和输入设备的名字还有此时系统的输出的音量 */ int UkmediaVolumeControl::getDefaultSinkIndex() { pa_operation *o; if (!(o = pa_context_get_server_info(getContext(), serverInfoIndexCb, this))) { showError(QObject::tr("pa_context_get_server_info() failed").toUtf8().constData()); return -1; } pa_operation_unref(o); qDebug() << "getDefaultSinkIndex" << "defalutSinkName&defaultSourceName" << defaultSinkName << sinkVolume << defaultSourceName << sourceVolume; return sinkIndex; } int UkmediaVolumeControl::getDefaultSourceIndex() { return sourceIndex; } /* * 滑动条更改设置sink input 音量值 */ void UkmediaVolumeControl::setSinkInputVolume(int index, int value) { pa_cvolume v = m_pDefaultSink->volume; if (v.channels > 10) v.channels = 2; for (int i = 0; i < v.channels; i++) v.values[i] = value; qDebug() << "set sink input volume " << index << v.channels << value; pa_operation* o; if (!(o = pa_context_set_sink_input_mute(getContext(), index,false, nullptr, nullptr))) { showError(tr("pa_context_set_sink_volume_by_index() failed").toUtf8().constData()); } if (!(o = pa_context_set_sink_input_volume(getContext(), index, &v, nullptr, nullptr))) { showError(tr("pa_context_set_sink_volume_by_index() failed").toUtf8().constData()); } } /* * 滑动条更改设置sink input静音状态 */ void UkmediaVolumeControl::setSinkInputMuted(int index, bool status) { qDebug() << "set sink input muted" << index << status; pa_operation* o; if (!(o = pa_context_set_sink_input_mute(getContext(), index,status, nullptr, nullptr))) { showError(tr("pa_context_set_sink_volume_by_index() failed").toUtf8().constData()); } } /* * 滑动条更改设置source output 音量值 */ void UkmediaVolumeControl::setSourceOutputVolume(int index, int value) { pa_cvolume v = m_pDefaultSink->volume; if (v.channels > 10) v.channels = 2; for (int i=0;isecond); clientNames.erase(i); } } static void updatePorts(UkmediaVolumeControl *d, std::map &ports) { std::map::iterator it; PortInfo p; for (auto & port : d->dPorts) { QByteArray desc; it = ports.find(port.first); if (it == ports.end()) continue; p = it->second; desc = p.description; if (p.available == PA_PORT_AVAILABLE_YES) desc += UkmediaVolumeControl::tr(" (plugged in)").toUtf8().constData(); else if (p.available == PA_PORT_AVAILABLE_NO) { if (p.name == "analog-output-speaker" || p.name == "analog-input-microphone-internal") desc += UkmediaVolumeControl::tr(" (unavailable)").toUtf8().constData(); else desc += UkmediaVolumeControl::tr(" (unplugged)").toUtf8().constData(); } port.second = desc; qDebug() << "updatePorts" << p.name << p.description; } Q_EMIT d->updatePortSignal(); it = ports.find(d->activePort); if (it != ports.end()) { p = it->second; // d->setLatencyOffset(p.latency_offset); } } static void setIconByName(QLabel* label, const char* name) { QIcon icon = QIcon::fromTheme(name); int size = label->style()->pixelMetric(QStyle::PM_ToolBarIconSize); QPixmap pix = icon.pixmap(size, size); label->setPixmap(pix); } void UkmediaVolumeControl::updateCard(UkmediaVolumeControl *c, const pa_card_info &info) { bool insertInputPort = false; bool insertOutputPort = false; bool is_new = false; const char *description; QMultiMap tempInput; QMultiMap tempOutput; QList profileName; QMapportMap; QMultiMap outputPortNameLabelMap; QMultiMapinputPortNameLabelMap; QMap profilePriorityMap; std::set profile_priorities; // 记录声卡配置文件优先级 profile_priorities.clear(); for (pa_card_profile_info2 **p_profile = info.profiles2; *p_profile != nullptr; ++p_profile) { if (((*p_profile)->available == PA_PORT_AVAILABLE_UNKNOWN) || ((*p_profile)->available == PA_PORT_AVAILABLE_YES)) { profile_priorities.insert(*p_profile); profileName.append((*p_profile)->name); profilePriorityMap.insert((*p_profile)->name, (*p_profile)->priority); } } cardProfilePriorityMap.insert(info.index, profilePriorityMap); description = pa_proplist_gets(info.proplist, PA_PROP_DEVICE_DESCRIPTION); c->ports.clear(); for (uint32_t i = 0; i < info.n_ports; ++i) { PortInfo p; p.name = info.ports[i]->name; p.description = info.ports[i]->description; p.priority = info.ports[i]->priority; p.available = info.ports[i]->available; p.direction = info.ports[i]->direction; p.latency_offset = info.ports[i]->latency_offset; if (info.ports[i]->profiles2) for (pa_card_profile_info2 ** p_profile = info.ports[i]->profiles2; *p_profile != nullptr; ++p_profile) { if (((*p_profile)->available == PA_PORT_AVAILABLE_UNKNOWN) || ((*p_profile)->available == PA_PORT_AVAILABLE_YES)) { p.profiles.push_back((*p_profile)->name); } } #ifdef PA_PORT_AVAILABLE_DISABLE if (p.direction == 1 && p.available != PA_PORT_AVAILABLE_NO && p.available != PA_PORT_AVAILABLE_DISABLE) { // portMap.insertMulti(p.name,p.description.data()); insertOutputPort = true; //新增UI设计,combobox显示portname+description QString outputPortName = p.description.data();//端口名(如:扬声器,模拟耳机..) QString outputPortName_and_description = outputPortName + "(" + description + ")"; qDebug() << "add sink port name "<< info.index << p.name << p.description.data() << description ; tempOutput.insertMulti(p.name,outputPortName_and_description); QList portProfileName; for (auto p_profile : p.profiles) { portProfileName.append(p_profile.data()); outputPortNameLabelMap.insertMulti(p.description.data(),p_profile.data()); qDebug() << "ctf profilename map 111 insert -----------" << p.description.data() << p_profile.data(); } profileNameMap.insert(info.index,outputPortNameLabelMap); cardProfileMap.insert(info.index,portProfileName); } else if (p.direction == 2 && p.available != PA_PORT_AVAILABLE_NO && p.available != PA_PORT_AVAILABLE_DISABLE) { insertInputPort = true; //新增UI设计,combobox显示portname+description QString inputPortName = p.description.data();//端口名(如:扬声器,模拟耳机..) QString inputPortName_and_description = inputPortName + "(" + description + ")"; qDebug() << "add source port name "<< info.index << p.name << p.description.data(); tempInput.insertMulti(p.name,inputPortName_and_description); QList portProfileName; for (auto p_profile : p.profiles) { portProfileName.append(p_profile.data()); inputPortNameLabelMap.insertMulti(p.description.data(),p_profile.data()); qDebug() << "ctf profilename map 222 insert -----------" << p.description.data() << p_profile.data(); } inputPortProfileNameMap.insert(info.index,inputPortNameLabelMap); cardProfileMap.insert(info.index,portProfileName); } #else if (p.direction == 1 && p.available != PA_PORT_AVAILABLE_NO) { // portMap.insertMulti(p.name,p.description.data()); insertOutputPort = true; //新增UI设计,combobox显示portname+description QString outputPortName = p.description.data();//端口名(如:扬声器,模拟耳机..) QString outputPortName_and_description = outputPortName + "(" + description + ")"; qDebug() << "add sink port name "<< info.index << p.name << p.description.data() << description ; tempOutput.insert(p.name,outputPortName_and_description); QList portProfileName; for (auto p_profile : p.profiles) { portProfileName.append(p_profile.data()); outputPortNameLabelMap.insert(p.description.data(),p_profile.data()); qDebug() << "ctf profilename map insert -----------" << p.description.data() << p_profile.data(); } profileNameMap.insert(info.index,outputPortNameLabelMap); cardProfileMap.insert(info.index,portProfileName); } else if (p.direction == 2 && p.available != PA_PORT_AVAILABLE_NO) { insertInputPort = true; //新增UI设计,combobox显示portname+description QString inputPortName = p.description.data();//端口名(如:扬声器,模拟耳机..) QString inputPortName_and_description = inputPortName + "(" + description + ")"; qDebug() << "add source port name "<< info.index << p.name << p.description.data(); tempInput.insert(p.name,inputPortName_and_description); QList portProfileName; for (auto p_profile : p.profiles) { portProfileName.append(p_profile.data()); inputPortNameLabelMap.insert(p.description.data(),p_profile.data()); } inputPortProfileNameMap.insert(info.index,inputPortNameLabelMap); cardProfileMap.insert(info.index,portProfileName); } #endif c->ports[p.name] = p; } if (insertInputPort) { inputPortMap.insert(info.index,tempInput); } else { inputPortMap.remove(info.index); } if (insertOutputPort) { outputPortMap.insert(info.index,tempOutput); } else { outputPortMap.remove(info.index); } cardActiveProfileMap.insert(info.index,info.active_profile->name); c->profiles.clear(); for (auto p_profile : profile_priorities) { bool hasNo = false, hasOther = false; std::map::iterator portIt; QByteArray desc = p_profile->description; for (portIt = c->ports.begin(); portIt != c->ports.end(); portIt++) { PortInfo port = portIt->second; if (std::find(port.profiles.begin(), port.profiles.end(), p_profile->name) == port.profiles.end()) continue; if (port.available == PA_PORT_AVAILABLE_NO) hasNo = true; else { hasOther = true; break; } } if (hasNo && !hasOther) desc += tr(" (unplugged)").toUtf8().constData(); if (!p_profile->available) desc += tr(" (unavailable)").toUtf8().constData(); c->profiles.push_back(std::pair(p_profile->name, desc)); if (p_profile->n_sinks == 0 && p_profile->n_sources == 0) c->noInOutProfile = p_profile->name; } c->activeProfile = info.active_profile ? info.active_profile->name : ""; /* Because the port info for sinks and sources is discontinued we need * to update the port info for them here. */ updatePorts(c,c->ports); if (is_new) { updateDeviceVisibility(); } Q_EMIT checkDeviceSelectionSianal(&info); // c->updating = false; } /* * Update output device when the default output device or port is updated */ bool UkmediaVolumeControl::updateSink(UkmediaVolumeControl *w,const pa_sink_info &info) { bool is_new = false; QMultiMaptemp; int volume; if (info.volume.channels >= 2) volume = MAX(info.volume.values[0],info.volume.values[1]); else volume = info.volume.values[0]; //默认的输出音量 if (info.name && strcmp(defaultSinkName.data(),info.name) == 0) { //channel和sinkIndex在此处必须更新 channel = info.volume.channels; sinkIndex= info.index; balance = pa_cvolume_get_balance(&info.volume,&info.channel_map); channelMap = info.channel_map; if (info.active_port) { sinkActivePortMap.insert(info.name,info.active_port->name);//huawei特殊处理 if(strcmp(sinkPortName.toLatin1().data(),info.active_port->name) != 0) { sinkPortName = info.active_port->name; sendDeviceChangedSignal(this); } else { sinkPortName = info.active_port->name; } } defaultOutputCard = info.card; /* bug:96232、95568、95523 解决 hw-panguw 机型,找不到对于sinkPortName导致音量无法同步、静音按钮只能按一次等问题 */ QString temp = ""; if (sinkPortName.contains("histen-algo",Qt::CaseInsensitive)) { sinkPortName = findSinkActivePortName(PANGUW_SINK); temp = "histen-algo"; } if (sinkVolume != volume || sinkMuted != info.mute) { if (temp != "") { sinkPortName = temp; } // 同步音量,同步UI w->refreshVolume(SoundType::SINK, volume, info.mute); } } if (info.ports) { for (pa_sink_port_info ** sinkPort = info.ports; *sinkPort != nullptr; ++sinkPort) { temp.insert(info.name,(*sinkPort)->name); } QList > sinkPortMapList; if(sinkPortMap.isEmpty()) sinkPortMap.insert(info.card,temp); sinkPortMapList.clear(); for (auto it = sinkPortMap.begin(); it != sinkPortMap.end(); ++it) { sinkPortMapList.append(it.value()); } if(!sinkPortMapList.contains(temp)) sinkPortMap.insert(static_cast(info.card),temp); qDebug() << "updateSink" << "defauleSinkName:" << defaultSinkName.data() << "sinkport" << sinkPortName << "sinkVolume" << sinkVolume ; if(strstr(defaultSinkName.data(),".headset_head_unit") || strstr(defaultSourceName.data(),"bt_sco_source")) { Q_EMIT updateMonoAudio(false); qDebug() << "Q_EMIT updateMonoAudio false" << sinkPortName << info.volume.channels; } else if(!strstr(defaultSourceName.data(),"bluez_source.") && !strstr(defaultSourceName.data(),"bt_sco_source")){ Q_EMIT updateMonoAudio(true); qDebug() << "Q_EMIT updateMonoAudio true" << sinkPortName << info.volume.channels; } const char *icon; // std::map::iterator cw; std::set port_priorities; port_priorities.clear(); for (uint32_t i=0; iports.clear(); }else{ qDebug() << " no sink port -> updateSink -> Q_EMIT updateMonoAudio true"; Q_EMIT updateMonoAudio(true); } if (is_new) updateDeviceVisibility(); return is_new; } QString UkmediaVolumeControl::findSinkActivePortName(QString name) { QString portName = ""; QMap::iterator at; for (at=sinkActivePortMap.begin();at!=sinkActivePortMap.end();++at) { if (at.key() == name) { portName = at.value(); break; } } return portName; } /* * stream suspend callback */ static void suspended_callback(pa_stream *s, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (pa_stream_is_suspended(s)) w->updateVolumeMeter(pa_stream_get_device_index(s), PA_INVALID_INDEX, -1); } void UkmediaVolumeControl::readCallback(pa_stream *s, size_t length, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); const void *data; double v; int index; index = pa_stream_get_device_index(s); /* when the default input device is monitor * no input feedback is provided */ if(strstr(w->defaultSourceName, ".monitor")) { Q_EMIT w->peakChangedSignal(0); return; } if (pa_stream_peek(s, &data, &length) < 0) { w->showError(UkmediaVolumeControl::tr("Failed to read data from stream").toUtf8().constData()); 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; w->updateVolumeMeter(index,pa_stream_get_monitor_stream(s),v); } pa_stream* UkmediaVolumeControl::createMonitorStreamForSource(uint32_t source_idx, uint32_t stream_idx = -1, bool suspend = false) { pa_stream *s; 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", source_idx); if (!(s = pa_stream_new(getContext(), tr("Peak detect").toUtf8().constData(), &ss, nullptr))) { showError(tr("Failed to create monitoring stream").toUtf8().constData()); return nullptr; } if (stream_idx != (uint32_t) -1) pa_stream_set_monitor_stream(s, stream_idx); pa_stream_set_read_callback(s, readCallback, this); pa_stream_set_suspended_callback(s, 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)); if (pa_stream_connect_record(s, t, &attr, flags) < 0) { showError(tr("Failed to connect monitoring stream").toUtf8().constData()); pa_stream_unref(s); return nullptr; } return s; } void UkmediaVolumeControl::updateSource(const pa_source_info &info) { int volume = (info.volume.channels >= 2) ? MAX(info.volume.values[0], info.volume.values[1]) : info.volume.values[0]; // Update some variables for the default input if (UKMedia_Equal(defaultSourceName.data(), info.name)) { sourceIndex = info.index; inputChannel = info.volume.channels; if (pa_proplist_gets(info.proplist, PA_PROP_DEVICE_MASTER_DEVICE)) { masterDevice = pa_proplist_gets(info.proplist, PA_PROP_DEVICE_MASTER_DEVICE); sourceInfo s = getSourceInfoByName(masterDevice); defaultInputCard = s.card; sourcePortName = s.active_port_name; qInfo() << "This is a filter source:" << info.name << "master device:" << masterDevice; } else { defaultInputCard = info.card; sourcePortName = (info.active_port) ? info.active_port->name : ""; } //bug#254769/256035 输入设备为内置mic或为空时,需要隐藏侦听功能(控制面板代码实现)并卸载侦听模块 if(!strstr(sourcePortName.toUtf8().constData(), "internal") \ && !strstr(sourcePortName.toUtf8().constData(), "[In] Mic1")) { updateLoopBack(true); qDebug() << "updateSource -> Q_EMIT updateLoopBack true" << sourcePortName; }else{ updateLoopBack(false); qDebug() << "updateSource -> Q_EMIT updateLoopBack false" << sourcePortName; } sendDeviceChangedSignal(this); refreshVolume(SoundType::SOURCE, volume, info.mute); qInfo() << __func__ << "Status1 defaultSource:" << sourceIndex << defaultSourceName << "sourcePort" << sourcePortName; } /* 开启降噪状态下,如果这个source存在多个端口,且不同端口音量相同时,不会触发 noiseReduceSource 回调,导致无法更新 portName */ else if (UKMedia_Equal(masterDevice.toLatin1().data(), info.name) && UKMedia_Equal(defaultSourceName.data(), ("noiseReduceSource"))) { defaultInputCard = info.card; sourcePortName = (info.active_port) ? info.active_port->name : ""; sendDeviceChangedSignal(this); qInfo() << __func__ << "Status2 defaultSource:" << sourceIndex << defaultSourceName << "sourcePort" << sourcePortName; } if (info.ports) { QMultiMaptemp; for (pa_source_port_info **sourcePort = info.ports; *sourcePort != nullptr; ++sourcePort) temp.insert(info.name, (*sourcePort)->name); if(sourcePortMap.isEmpty()) sourcePortMap.insert(static_cast(info.card), temp); QList> sourcePortMapList; sourcePortMapList = sourcePortMap.values(); if(!sourcePortMapList.contains(temp)) sourcePortMap.insert(info.card, temp); } } void UkmediaVolumeControl::updateSinkInput(const pa_sink_input_info &info) { const char *t; if ((t = pa_proplist_gets(info.proplist, "module-stream-restore.id"))) { if (t && strcmp(t, "sink-input-by-media-role:event") == 0) { g_debug("%s", tr("Ignoring sink-input due to it being designated as an event and thus handled by the Event widget").toUtf8().constData()); return; } } const gchar *description = pa_proplist_gets(info.proplist, PA_PROP_APPLICATION_NAME); const gchar *appId = pa_proplist_gets(info.proplist, PA_PROP_APPLICATION_ID); //没制定应用名称的不加入到应用音量中 if (description && !strstr(description,"QtPulseAudio")) { if (!info.corked) { sinkInputMap.insert(description,info.volume.values[0]); if (!sinkInputList.contains(description)) { sinkInputList.append(description); Q_EMIT addSinkInputSignal(description,appId,info.index); } } else { Q_EMIT removeSinkInputSignal(description); sinkInputList.removeAll(description); QMap::iterator it; for(it = sinkInputMap.begin();it!=sinkInputMap.end();) { if(it.key() == description) { sinkInputMap.erase(it); break; } ++it; } } } } void UkmediaVolumeControl::updateSourceOutput(const pa_source_output_info &info) { const char *app; // bool is_new = false; if ((app = pa_proplist_gets(info.proplist, PA_PROP_APPLICATION_ID))) if (app && strcmp(app, "org.PulseAudio.pavucontrol") == 0 || strcmp(app, "org.gnome.VolumeControl") == 0 || strcmp(app, "org.kde.kmixd") == 0) return; const gchar *description = pa_proplist_gets(info.proplist, PA_PROP_APPLICATION_NAME); const gchar *appId = pa_proplist_gets(info.proplist, PA_PROP_APPLICATION_ID); //没制定应用名称的不加入到应用音量中 if (description && !strstr(description,"QtPulseAudio")) { if (appId && !info.corked) { sourceOutputMap.insert(description,info.volume.values[0]); Q_EMIT addSourceOutputSignal(description,appId,info.index); } else { Q_EMIT removeSourceOutputSignal(description); QMap::iterator it; for(it = sourceOutputMap.begin();it!=sourceOutputMap.end();) { if(it.key() == description) { sourceOutputMap.erase(it); break; } ++it; } } } } void UkmediaVolumeControl::updateClient(const pa_client_info &info) { g_free(clientNames[info.index]); clientNames[info.index] = g_strdup(info.name); } void UkmediaVolumeControl::updateServer(const pa_server_info &info) { defaultSourceName = info.default_source_name ? info.default_source_name : ""; defaultSinkName = info.default_sink_name ? info.default_sink_name : ""; qDebug() << "updateServer" << "default_sink:" << info.default_sink_name << "default_source:" << info.default_source_name; if (peak == nullptr && !strstr(defaultSourceName, ".monitor")) { QTimer::singleShot(100, this, [=](){ peak = createMonitorStreamForSource(sourceIndex, -1, !!(sourceFlags & PA_SOURCE_NETWORK)); qDebug() << "Created peak sourceIndex" << sourceIndex; }); } else if (strstr(defaultSourceName, ".monitor")) { peak = nullptr; pa_operation *o; if (!(o = pa_context_kill_source_output(getContext(), peakDetectIndex, nullptr, nullptr))) showError(tr("pa_context_kill_source_output() failed").toUtf8().constData()); } } void UkmediaVolumeControl::updateVolumeMeter(uint32_t index, uint32_t sinkInputIdx, double v) { Q_UNUSED(index); Q_UNUSED(sinkInputIdx); if (lastPeak >= DECAY_STEP) if (v < lastPeak - DECAY_STEP) v = lastPeak - DECAY_STEP; lastPeak = v; Q_EMIT peakChangedSignal(v); } static guint idleSource = 0; gboolean idleCb(gpointer data) { idleSource = 0; return FALSE; } void UkmediaVolumeControl::setConnectionState(gboolean connected) { if (m_connected != connected) { m_connected = connected; if (m_connected) { // connectingLabel->hide(); // notebook->show(); } else { // notebook->hide(); // connectingLabel->show(); } } } void UkmediaVolumeControl::updateDeviceVisibility() { if (idleSource) return; idleSource = g_idle_add(idleCb, this); } void UkmediaVolumeControl::removeCard(uint32_t index) { // if (!cardWidgets.count(index)) // return; // delete cardWidgets[index]; // cardWidgets.erase(index); updateDeviceVisibility(); } //移除对应声卡下的对应sink void UkmediaVolumeControl::removeCardSink(int cardIndex,QString sinkName) { QMultiMap>::iterator it1; QMultiMap::iterator it2; for(it1=sinkPortMap.begin();it1!=sinkPortMap.end();) { //1、找到对应声卡 if(it1.key()==cardIndex) { for(it2=it1.value().begin();it2!=it1.value().end();) { //2、找到对应声卡下的对应sink,进行移除 if(it2.key()==sinkName) { it2 = it1->erase(it2); return; } ++it2; } } ++it1; } } void UkmediaVolumeControl::removeSink(uint32_t sinkIndex) { #if 1 QMap::iterator it; for(it=sinkMap.begin(); it!=sinkMap.end();) { if (it.key() == sinkIndex) { removeCardSink(it.value().card, it.value().name); if(m_pDefaultSink->volume.channels >= 2 && strstr(it.value().name.toLatin1().data(),".a2dp_sink")\ && !strstr(defaultSourceName.data(),"bluez_source.") && !strstr(defaultSourceName.data(),"bt_sco_source")) { Q_EMIT updateMonoAudio(true); qDebug() << "Q_EMIT updateMonoAudio true" << sinkPortName << m_pDefaultSink->volume.channels << it.value().name; } sinkMap.erase(it); break; } ++it; } updateDeviceVisibility(); #else QMap>::iterator it1; QMap::iterator it2; for (it1=sinkMap.begin(); it1!=sinkMap.end();) { if (it1.key() == index) { for(it2=it1.value().begin();it2!=it1.value().end();) { removeCardSink(it2.key(),it2.value().name); if(m_pDefaultSink->volume.channels >= 2 && strstr(it2.value().name.toLatin1().data(),".a2dp_sink")\ && !strstr(defaultSourceName.data(),"bluez_source.") && !strstr(defaultSourceName.data(),"bt_sco_source")) { Q_EMIT updateMonoAudio(true); qDebug() << "Q_EMIT updateMonoAudio true" << sinkPortName << m_pDefaultSink->volume.channels << it2.value().name; } ++it2; } sinkMap.erase(it1); break; } ++it1; } updateDeviceVisibility(); #endif } //移除对应声卡下的对应source void UkmediaVolumeControl::removeCardSource(int cardIndex,QString sourceName) { QMultiMap>::iterator it1; QMultiMap::iterator it2; for(it1=sourcePortMap.begin();it1!=sourcePortMap.end();) { //1、找到对应声卡 if(it1.key()==cardIndex) { for(it2=it1.value().begin();it2!=it1.value().end();) { //2、找到对应声卡下的对应sink,进行移除 if(it2.key()==sourceName) { it2 = it1->erase(it2); return; } ++it2; } } ++it1; } } void UkmediaVolumeControl::removeSource(uint32_t sourceIndex) { #if 1 QMap::iterator it; for (it=sourceMap.begin(); it!=sourceMap.end();) { if (it.key() == sourceIndex) { removeCardSource(it.value().card, it.value().name); sourceMap.erase(it); break; } ++it; } updateDeviceVisibility(); #else QMap>::iterator it1; QMap::iterator it2; for (it1=sourceMap.begin();it1!=sourceMap.end();) { if (it1.key() == index) { for(it2=it1.value().begin();it2!=it1.value().end();) { //it2.key()=cardIndex,it2.value()=sourceName,找到对应声卡下的对应source进行删除 removeCardSource(it2.key(),it2.value().name); ++it2; } sourceMap.erase(it1); break; } ++it1; } updateDeviceVisibility(); #endif } void UkmediaVolumeControl::removeSinkInput(uint32_t index) { updateDeviceVisibility(); } void UkmediaVolumeControl::removeSourceOutput(uint32_t index) { updateDeviceVisibility(); } void UkmediaVolumeControl::removeClient(uint32_t index) { g_free(clientNames[index]); clientNames.erase(index); } void UkmediaVolumeControl::setConnectingMessage(const char *string) { QByteArray markup = ""; if (!string) markup += tr("Establishing connection to PulseAudio. Please wait...").toUtf8().constData(); else markup += string; markup += ""; // connectingLabel->setText(QString::fromUtf8(markup)); } void UkmediaVolumeControl::showError(const char *txt) { char buf[256]; snprintf(buf, sizeof(buf), "%s: %s", txt, pa_strerror(pa_context_errno(context))); qWarning() << QString::fromUtf8(buf); } void UkmediaVolumeControl::decOutstanding(UkmediaVolumeControl *w) { // qDebug() << "decOutstanding---------"; if (n_outstanding <= 0) return; if (--n_outstanding <= 0) { // w->get_window()->set_cursor(); w->setConnectionState(true); } } void UkmediaVolumeControl::cardCb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (eol < 0) { if (pa_context_errno(c) == PA_ERR_NOENTITY) return; w->showError(QObject::tr("Card callback failure").toUtf8().constData()); return; } if (eol > 0) { decOutstanding(w); return; } w->cardMap.insert(i->index,i->name); w->updateCard(w,*i); } void UkmediaVolumeControl::sinkIndexCb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (eol < 0) { if (pa_context_errno(c) == PA_ERR_NOENTITY) return; w->showError(QObject::tr("Sink callback failure").toUtf8().constData()); return; } if (eol > 0) { decOutstanding(w); return; } int volume; if(i->volume.channels >= 2) volume = MAX(i->volume.values[0],i->volume.values[1]); else volume = i->volume.values[0]; w->channel = i->volume.channels; w->defaultOutputCard = i->card; w->sinkIndex = i->index; w->balance = pa_cvolume_get_balance(&i->volume,&i->channel_map); if(i->active_port) w->sinkPortName = i->active_port->name; else w->sinkPortName = ""; // 同步音量,同步UI w->refreshVolume(SoundType::SINK, volume, i->mute); } void UkmediaVolumeControl::sourceIndexCb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (eol < 0) { if (pa_context_errno(c) == PA_ERR_NOENTITY) return; w->showError(QObject::tr("Source callback failure").toUtf8().constData()); return; } if (eol > 0) { decOutstanding(w); return; } int volume; if(i->volume.channels >= 2) volume = MAX(i->volume.values[0],i->volume.values[1]); else volume = i->volume.values[0]; w->inputChannel = i->volume.channels; w->sourceIndex = i->index; if (pa_proplist_gets(i->proplist, PA_PROP_DEVICE_MASTER_DEVICE)) { w->masterDevice = pa_proplist_gets(i->proplist, PA_PROP_DEVICE_MASTER_DEVICE); sourceInfo s = w->getSourceInfoByName(w->masterDevice); w->defaultInputCard = s.card; w->sourcePortName = s.active_port_name; } else { w->defaultInputCard = i->card; w->sourcePortName = (i->active_port) ? i->active_port->name : ""; } w->refreshVolume(SoundType::SOURCE, volume, i->mute); qDebug() << __func__ << "defaultInputCard" << w->defaultInputCard << "sourcePort" << w->sourcePortName; } void UkmediaVolumeControl::sinkCb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (eol < 0) { if (pa_context_errno(c) == PA_ERR_NOENTITY) return; w->showError(QObject::tr("Sink callback failure").toUtf8().constData()); return; } if (eol > 0) { decOutstanding(w); return; } w->m_pDefaultSink = i; qDebug() << "SinkCb" << i->index << i->name; #if 1 w->sinkMap.insert(i->index, w->addSinkInfo(*i)); w->updateSink(w,*i); #else QMap temp; temp.insert(i->card,w->addSinkInfo(*i)); w->sinkMap.insert(i->index,temp); w->updateSink(w,*i); #endif } void UkmediaVolumeControl::sourceCb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (eol < 0) { if (pa_context_errno(c) == PA_ERR_NOENTITY) return; w->showError(QObject::tr("Source callback failure").toUtf8().constData()); return; } if (eol > 0) { decOutstanding(w); return; } w->m_pDefaultSource = i; qInfo() << "sourceCb" << i->index << i->name; w->sourceMap.insert(i->index, w->addSourceInfo(*i)); w->updateSource(*i); } void UkmediaVolumeControl::sinkInputCb(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (eol < 0) { if (pa_context_errno(c) == PA_ERR_NOENTITY) return; w->showError(QObject::tr("Sink input callback failure").toUtf8().constData()); return; } if (eol > 0) { decOutstanding(w); return; } w->updateSinkInput(*i); } void UkmediaVolumeControl::sourceOutputCb(pa_context *c, const pa_source_output_info *i, int eol, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (eol < 0) { if (pa_context_errno(c) == PA_ERR_NOENTITY) return; w->showError(QObject::tr("Source output callback failure").toUtf8().constData()); return; } if (eol > 0) { if (n_outstanding > 0) { /* At this point all notebook pages have been populated, so * let's open one that isn't empty */ // if (default_tab != -1) { // if (default_tab < 1 || default_tab > w->notebook->count()) { // if (!w->sinkInputWidgets.empty()) // w->notebook->setCurrentIndex(0); // else if (!w->sourceOutputWidgets.empty()) // w->notebook->setCurrentIndex(1); // else if (!w->sourceWidgets.empty() && w->sinkWidgets.empty()) // w->notebook->setCurrentIndex(3); // else // w->notebook->setCurrentIndex(2); // } else { // w->notebook->setCurrentIndex(default_tab - 1); // } // default_tab = -1; // } } decOutstanding(w); return; } if (i->name && strstr(i->name, "Peak detect")) w->peakDetectIndex = i->index; qDebug() << __func__ << i->index; w->updateSourceOutput(*i); } void UkmediaVolumeControl::clientCb(pa_context *c, const pa_client_info *i, int eol, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (eol < 0) { if (pa_context_errno(c) == PA_ERR_NOENTITY) return; w->showError(QObject::tr("Client callback failure").toUtf8().constData()); return; } if (eol > 0) { decOutstanding(w); return; } // qDebug() << "clientCb" << i->name; w->updateClient(*i); } void UkmediaVolumeControl::serverInfoIndexCb(pa_context *, const pa_server_info *i, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (!i) { w->showError(QObject::tr("Server info callback failure").toUtf8().constData()); return; } pa_operation *o; qDebug() << "serverInfoIndexCb" << i->default_sink_name << i->default_source_name; w->updateServer(*i); decOutstanding(w); } void UkmediaVolumeControl::serverInfoCb(pa_context *, const pa_server_info *i, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (!i) { w->showError(QObject::tr("Server info callback failure").toUtf8().constData()); return; } pa_operation *o; //默认的输出设备改变时需要获取默认的输出音量 if(!(o = pa_context_get_sink_info_by_name(w->getContext(),i->default_sink_name,sinkIndexCb,w))) { } if(!(o = pa_context_get_source_info_by_name(w->getContext(),i->default_source_name,sourceIndexCb,w))) { } qDebug() << "serverInfoCb" << i->user_name << i->default_sink_name << i->default_source_name; w->updateServer(*i); sendDeviceChangedSignal(w); decOutstanding(w); } void UkmediaVolumeControl::extStreamRestoreReadCb( pa_context *c, const pa_ext_stream_restore_info *i, int eol, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (eol < 0) { decOutstanding(w); g_debug(QObject::tr("Failed to initialize stream_restore extension: %s").toUtf8().constData(), pa_strerror(pa_context_errno(c))); return; } if (eol > 0) { decOutstanding(w); return; } // qDebug() << "extStreamRestoreReadCb" << i->name; // w->updateRole(*i); } void UkmediaVolumeControl::extStreamRestoreSubscribeCb(pa_context *c, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); pa_operation *o; if (!(o = pa_ext_stream_restore_read(c, extStreamRestoreReadCb, w))) { w->showError(QObject::tr("pa_ext_stream_restore_read() failed").toUtf8().constData()); return; } qDebug() << "extStreamRestoreSubscribeCb" ; pa_operation_unref(o); } void UkmediaVolumeControl::extDeviceManagerReadCb( pa_context *c, const pa_ext_device_manager_info *, int eol, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (eol < 0) { decOutstanding(w); g_debug(QObject::tr("Failed to initialize device manager extension: %s").toUtf8().constData(), pa_strerror(pa_context_errno(c))); return; } if (eol > 0) { decOutstanding(w); return; } qDebug() << "extDeviceManagerReadCb"; /* Do something with a widget when this part is written */ } void UkmediaVolumeControl::extDeviceManagerSubscribeCb(pa_context *c, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); pa_operation *o; if (!(o = pa_ext_device_manager_read(c, extDeviceManagerReadCb, w))) { w->showError(QObject::tr("pa_ext_device_manager_read() failed").toUtf8().constData()); return; } qDebug() << "extDeviceManagerSubscribeCb"; pa_operation_unref(o); } void UkmediaVolumeControl::subscribeCb(pa_context *c, pa_subscription_event_type_t t, uint32_t index, void *userdata) { UkmediaVolumeControl *w = 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) w->removeSink(index); else { pa_operation *o; if (!(o = pa_context_get_sink_info_by_index(c, index, sinkCb, w))) { w->showError(QObject::tr("pa_context_get_sink_info_by_index() failed").toUtf8().constData()); return; } pa_operation_unref(o); } break; case PA_SUBSCRIPTION_EVENT_SOURCE: if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) w->removeSource(index); else { pa_operation *o; if (!(o = pa_context_get_source_info_by_index(c, index, sourceCb, w))) { w->showError(QObject::tr("pa_context_get_source_info_by_index() failed").toUtf8().constData()); return; } pa_operation_unref(o); } break; case PA_SUBSCRIPTION_EVENT_SINK_INPUT: if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) w->removeSinkInput(index); else { pa_operation *o; if (!(o = pa_context_get_sink_input_info(c, index, sinkInputCb, w))) { w->showError(QObject::tr("pa_context_get_sink_input_info() failed").toUtf8().constData()); return; } pa_operation_unref(o); } break; case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT: if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) w->removeSourceOutput(index); else { pa_operation *o; if (!(o = pa_context_get_source_output_info(c, index, sourceOutputCb, w))) { w->showError(QObject::tr("pa_context_get_sink_input_info() failed").toUtf8().constData()); 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(c, index, clientCb, w))) { w->showError(QObject::tr("pa_context_get_client_info() failed").toUtf8().constData()); return; } pa_operation_unref(o); } break; case PA_SUBSCRIPTION_EVENT_SERVER: { pa_operation *o; if (!(o = pa_context_get_server_info(c, serverInfoCb, w))) { w->showError(QObject::tr("pa_context_get_server_info() failed").toUtf8().constData()); return; } pa_operation_unref(o); } break; case PA_SUBSCRIPTION_EVENT_CARD: //remove card if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { qDebug() << "remove cards------"; //移除outputPort w->removeSinkPortMap(index); w->removeSourcePortMap(index); w->removeOutputPortMap(index); w->removeInputPortMap(index); Q_EMIT w->updatePortSignal(); w->removeCardMap(index); w->removeCardProfileMap(index); w->removeProfileMap(index); w->removeInputProfile(index); w->removeCard(index); } else { pa_operation *o; if (!(o = pa_context_get_card_info_by_index(c, index, cardCb, w))) { w->showError(QObject::tr("pa_context_get_card_info_by_index() failed").toUtf8().constData()); return; } pa_operation_unref(o); } break; } } void UkmediaVolumeControl::contextStateCallback(pa_context *c, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); g_assert(c); switch (pa_context_get_state(c)) { 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; qDebug() << "pa_context_get_state" << "PA_CONTEXT_READY" << pa_context_get_state(c); reconnectTimeout = 3; pa_context_set_subscribe_callback(c, subscribeCb, w); if (!(o = pa_context_subscribe(c, (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))) { w->showError(QObject::tr("pa_context_subscribe() failed").toUtf8().constData()); return; } pa_operation_unref(o); /* Keep track of the outstanding callbacks for UI tweaks */ n_outstanding = 0; if (!(o = pa_context_get_server_info(c, serverInfoCb, w))) { w->showError(QObject::tr("pa_context_get_server_info() failed").toUtf8().constData()); return; } pa_operation_unref(o); n_outstanding++; if (!(o = pa_context_get_client_info_list(c, clientCb, w))) { w->showError(QObject::tr("pa_context_client_info_list() failed").toUtf8().constData()); return; } pa_operation_unref(o); n_outstanding++; if (!(o = pa_context_get_card_info_list(c, cardCb, w))) { w->showError(QObject::tr("pa_context_get_card_info_list() failed").toUtf8().constData()); return; } pa_operation_unref(o); n_outstanding++; if (!(o = pa_context_get_sink_info_list(c, sinkCb, w))) { w->showError(QObject::tr("pa_context_get_sink_info_list() failed").toUtf8().constData()); return; } pa_operation_unref(o); n_outstanding++; if (!(o = pa_context_get_source_info_list(c, sourceCb, w))) { w->showError(QObject::tr("pa_context_get_source_info_list() failed").toUtf8().constData()); return; } pa_operation_unref(o); n_outstanding++; if (!(o = pa_context_get_sink_input_info_list(c, sinkInputCb, w))) { w->showError(QObject::tr("pa_context_get_sink_input_info_list() failed").toUtf8().constData()); return; } pa_operation_unref(o); n_outstanding++; if (!(o = pa_context_get_source_output_info_list(c, sourceOutputCb, w))) { w->showError(QObject::tr("pa_context_get_source_output_info_list() failed").toUtf8().constData()); return; } pa_operation_unref(o); n_outstanding++; Q_EMIT w->paContextReady(); break; } case PA_CONTEXT_FAILED: w->setConnectionState(false); w->updateDeviceVisibility(); if (w->context != nullptr) { pa_context_unref(w->context); w->context = nullptr; } qWarning("Connection failed, attempting reconnect"); reconnectTimeout--; if (reconnectTimeout <= 0) { qWarning("reconnect pulseaudio Three times failed"); return; } g_timeout_add_seconds(5, connectToPulse, w); return; case PA_CONTEXT_TERMINATED: default: return; } } pa_context* UkmediaVolumeControl::getContext(void) { g_assert(context); return context; } gboolean UkmediaVolumeControl::connectToPulse(gpointer userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (context) { qWarning("pulseAudio is connected"); return false; } pa_glib_mainloop *m = pa_glib_mainloop_new(g_main_context_default()); api = pa_glib_mainloop_get_api(m); pa_proplist *proplist = pa_proplist_new(); pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, QObject::tr("Ukui Media Volume Control").toUtf8().constData()); pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, "org.PulseAudio.pavucontrol"); pa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, "audio-card"); pa_proplist_sets(proplist, PA_PROP_APPLICATION_VERSION, "PACKAGE_VERSION"); context = pa_context_new_with_proplist(api, nullptr, proplist); g_assert(context); pa_proplist_free(proplist); pa_context_set_state_callback(context, contextStateCallback, w); if (pa_context_connect(context, nullptr, PA_CONTEXT_NOFAIL, nullptr) < 0) { if (pa_context_errno(context) == 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());*/ qFatal("connect pulseaudio failed"); } else { reconnectTimeout--; if (reconnectTimeout <= 0) { qWarning("reconnect pulseaudio Three times failed"); return false; } g_timeout_add_seconds(5, connectToPulse, w); } } return false; } /* * 根据名称获取sink input音量 */ int UkmediaVolumeControl::getSinkInputVolume(const gchar *name) { int value = 0; for(auto it = sinkInputMap.begin();it!=sinkInputMap.end();) { if(it.key() == name) { qDebug() << "getSinkInputVolume" << "name:" <>::iterator it; for(it=sinkPortMap.begin();it!=sinkPortMap.end();){ if(it.key() == index) { sinkPortMap.remove(index); break; } ++it; } } void UkmediaVolumeControl::removeSourcePortMap(int index) { for(auto it=sourcePortMap.begin();it!=sourcePortMap.end();){ if(it.key() == index) { sourcePortMap.erase(it); break; } ++it; } } void UkmediaVolumeControl::removeProfileMap(int index) { qDebug() << "removeProfileMap" << index << profileNameMap; for (auto it=profileNameMap.begin();it!=profileNameMap.end();) { if(it.key() == index){ profileNameMap.erase(it); break; } ++it; } } void UkmediaVolumeControl::removeInputProfile(int index) { qDebug() << "removeInputProfile" << index << inputPortProfileNameMap; for (auto it=inputPortProfileNameMap.begin();it!=inputPortProfileNameMap.end();) { if(it.key() == index){ inputPortProfileNameMap.erase(it); break; } ++it; } } sinkInfo UkmediaVolumeControl::addSinkInfo(const pa_sink_info& i) { sinkInfo info; info.name = i.name; info.index = i.index; info.description = i.description; info.volume = i.volume; info.mute = i.mute; info.card = i.card; if (i.active_port) { info.active_port_name = i.active_port->name; info.active_port_description = i.active_port->description; } if (pa_proplist_gets(i.proplist, PA_PROP_DEVICE_MASTER_DEVICE)) info.master_device = pa_proplist_gets(i.proplist, PA_PROP_DEVICE_MASTER_DEVICE); if (i.ports) { for (pa_sink_port_info** sinkPort = i.ports; *sinkPort != nullptr; ++sinkPort) { portInfo pInfo; pInfo.name = (*sinkPort)->name; pInfo.description = (*sinkPort)->description; pInfo.priority = (*sinkPort)->priority; pInfo.available = (*sinkPort)->available; info.sink_port_list.append(pInfo); } } return info; } sourceInfo UkmediaVolumeControl::addSourceInfo(const pa_source_info& i) { sourceInfo info; info.name = i.name; info.index = i.index; info.description = i.description; info.volume = i.volume; info.mute = i.mute; info.card = i.card; if (i.active_port) { info.active_port_name = i.active_port->name; info.active_port_description = i.active_port->description; } if(pa_proplist_gets(i.proplist, PA_PROP_DEVICE_MASTER_DEVICE)) info.master_device = pa_proplist_gets(i.proplist, PA_PROP_DEVICE_MASTER_DEVICE); return info; } /** * @brief UkmediaVolumeControl::refreshVolume * 发送信号刷新音量 */ void UkmediaVolumeControl::refreshVolume(int soundType, int info_Vol, bool info_Mute) { switch (soundType) { case SoundType::SINK: { if (sinkMuted != info_Mute) { //需求31268待2501再合入 #if 1 if (outputPortMap.isEmpty() || defaultSinkName.isEmpty()) { sinkMuted = true; } else { sinkMuted = info_Mute; } #elif 0 sinkMuted = info_Mute; #endif Q_EMIT updateSinkMute(sinkMuted); } if (sinkVolume != info_Vol) { //需求31268待2501再合入 #if 1 if (outputPortMap.isEmpty() || defaultSinkName.isEmpty()) { sinkVolume = 0; sinkMuted = true; } else { sinkVolume = info_Vol; sinkMuted = info_Mute; } #elif 0 sinkVolume = info_Vol; sinkMuted = info_Mute; #endif sendVolumeUpdateSignal(); } } break; case SoundType::SOURCE: { if (sourceMuted != info_Mute) { //需求31268待2501再合入 #if 1 if (inputPortMap.isEmpty() || defaultSourceName.isEmpty()) { sourceMuted = true; } else { sourceMuted = info_Mute; } #elif 0 sourceMuted = info_Mute; #endif Q_EMIT updateSourceMute(sourceMuted); } if (sourceVolume != info_Vol) { //需求31268待2501再合入 #if 1 if (inputPortMap.isEmpty() || defaultSourceName.isEmpty()) { sourceVolume = 0; sourceMuted = true; } else { sourceVolume = info_Vol; sourceMuted = info_Mute; } #elif 0 sourceVolume = info_Vol; sourceMuted = info_Mute; #endif sendSourceVolumeUpdateSignal(); } } break; default: break; } } void UkmediaVolumeControl::sendDeviceChangedSignal(UkmediaVolumeControl* w) { //start 过滤设备端口切换或者设备拔插期间,subscribecb回调反馈的非预期数据 if (nullptr == w) { qDebug() << "w is null"; return; } static UkmediaVolumeControl* control = w; static bool isConnect = false; if (deviceChangedTimer.isActive()) { deviceChangedTimer.stop(); } deviceChangedTimer.setInterval(300); deviceChangedTimer.setSingleShot(true); if (!isConnect) { isConnect = QObject::connect(&deviceChangedTimer, &QTimer::timeout, control, [&control](){ qDebug() << "deviceChangedSignal"; if (control) { Q_EMIT control->deviceChangedSignal(); } }); } deviceChangedTimer.start(); //end } void UkmediaVolumeControl::sendVolumeUpdateSignal() { //start 过滤设备端口切换或者设备拔插期间,subscribecb回调反馈的非预期数据 static bool isConnect = false; if (m_updateVolumeTimer.isActive()) { m_updateVolumeTimer.stop(); } m_updateVolumeTimer.setInterval(150); m_updateVolumeTimer.setSingleShot(true); if (!isConnect) { isConnect = QObject::connect(&m_updateVolumeTimer, &QTimer::timeout, this, [&](){ qDebug() << "refreshVolume sinkVolume:" << sinkVolume; Q_EMIT this->updateVolume(sinkVolume, sinkMuted); }); } m_updateVolumeTimer.start(); //end } void UkmediaVolumeControl::sendSourceVolumeUpdateSignal() { //start 过滤设备端口切换或者设备拔插期间,subscribecb回调反馈的非预期数据 static bool isConnect = false; if (m_updateSourceVolumeTimer.isActive()) { m_updateSourceVolumeTimer.stop(); } m_updateSourceVolumeTimer.setInterval(150); m_updateSourceVolumeTimer.setSingleShot(true); if (!isConnect) { isConnect = QObject::connect(&m_updateSourceVolumeTimer, &QTimer::timeout, this, [&](){ qDebug() << "refreshVolume sourceVolume:" << sourceVolume; Q_EMIT this->updateSourceVolume(sourceVolume, sourceMuted); }); } m_updateSourceVolumeTimer.start(); //end } sinkInfo UkmediaVolumeControl::getSinkInfoByIndex(int index) { QMap::iterator it; for (it = sinkMap.begin(); it != sinkMap.end(); ++it) { if (index == it.key()) return it.value(); } qInfo() << "Can't find sink info by index" << index; sinkInfo nullInfo; return nullInfo; } sinkInfo UkmediaVolumeControl::getSinkInfoByName(QString name) { QMap::iterator it; for (it = sinkMap.begin(); it != sinkMap.end(); ++it) { if (name == it.value().name) return it.value(); } qInfo() << "Can't find sink info by name" << name; sinkInfo nullInfo; return nullInfo; } sourceInfo UkmediaVolumeControl::getSourceInfoByIndex(int index) { QMap::iterator it; for (it = sourceMap.begin(); it != sourceMap.end(); ++it) { if (index == it.key()) return it.value(); } qInfo() << "Can't find source info by index" << index; sourceInfo nullInfo; return nullInfo; } sourceInfo UkmediaVolumeControl::getSourceInfoByName(QString name) { QMap::iterator it; for (it = sourceMap.begin(); it != sourceMap.end(); ++it) { if (name == it.value().name) return it.value(); } qInfo() << "Can't find source info by name" << name; sourceInfo nullInfo; return nullInfo; } ukui-media/audio/audio.pro0000664000175000017500000000425215170054730014463 0ustar fengfeng#------------------------------------------------- # # Project created by QtCreator 2019-05-30T09:45:54 # #------------------------------------------------- #include(../../../env.pri) QT += widgets xml dbus TEMPLATE = lib CONFIG += plugin #include($$PROJECT_COMPONENTSOURCE/label.pri) include(app-device-control/app-device-control.pri) include(audio-device-control/audio-device-control.pri) INCLUDEPATH += ../../.. \ $$PROJECT_COMPONENTSOURCE \ TARGET = $$qtLibraryTarget(audio) DESTDIR = ../ target.path = $$[QT_INSTALL_LIBS]/ukui-control-center CONFIG += c++11 \ no_keywords link_pkgconfig PKGCONFIG += gio-2.0 \ libxml-2.0 \ gsettings-qt6 \ libcanberra \ dconf \ libpulse \ libpulse-mainloop-glib \ kysdk-qtwidgets \ kysdk-sysinfo \ kysdk-hardware #DEFINES += QT_DEPRECATED_WARNINGS LIBS += -lukcc SOURCES += \ audio.cpp \ ukmedia_input_widget.cpp \ ukmedia_main_widget.cpp \ ukmedia_output_widget.cpp \ ukmedia_settings_widget.cpp \ ukmedia_sound_effects_widget.cpp \ ukmedia_volume_control.cpp \ ukui_custom_style.cpp \ customstyle.cpp \ ukmedia_slider_tip_label_helper.cpp \ ukui_list_widget_item.cpp \ ../common/ukmedia_common.cpp HEADERS += \ audio.h \ ukmedia_input_widget.h \ ukmedia_main_widget.h \ ukmedia_output_widget.h \ ukmedia_settings_widget.h \ ukmedia_sound_effects_widget.h \ ukmedia_volume_control.h \ ukui_custom_style.h \ customstyle.h \ ukmedia_slider_tip_label_helper.h \ ukui_list_widget_item.h \ ../common/ukmedia_common.h FORMS += \ audio.ui TRANSLATIONS += \ translations/zh_CN.ts \ translations/tr.ts \ translations/zh_HK.ts \ translations/bo_CN.ts \ translations/en_US.ts \ translations/mn_MN.ts isEmpty(PREFIX) { PREFIX = /usr } qm_files.path = $${PREFIX}/share/ukui-media/translations/audio/ qm_files.files = translations/* CONFIG(release, debug|release) { !system($$PWD/translate_generation.sh): error("Failed to generate translation") } INSTALLS += target \ qm_files \ ukui-media/audio/translate_generation.sh0000775000175000017500000000054315170054730017406 0ustar fengfeng#!/bin/bash ts_list=(`ls translations/*.ts`) source /etc/os-release version=(`echo $ID`) for ts in "${ts_list[@]}" do printf "\nprocess ${ts}\n" if [ "$version" == "fedora" ] || [ "$version" == "opensuse-leap" ] || [ "$version" == "opensuse-tumbleweed" ];then lrelease-qt6 "${ts}" else /usr/lib/qt6/bin/lrelease "${ts}" fi done ukui-media/audio/ukui_list_widget_item.cpp0000664000175000017500000000667115170052044017740 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "ukui_list_widget_item.h" #include #include #include #include //#include #include bool isCheckBluetoothInput; UkuiListWidget::UkuiListWidget(QWidget *parent) : QListWidget(parent) { } UkuiListWidget::~UkuiListWidget() { } UkuiListWidgetItem::UkuiListWidgetItem(QWidget *parent) : QWidget(parent) { this->setFixedSize(500,64); QVBoxLayout *vLayout = new QVBoxLayout; portLabel = new QLabel(this); deviceLabel = new QLabel(this); portLabel->setFixedSize(600,24); deviceLabel->setFixedSize(600,24); vLayout->addWidget(portLabel); vLayout->addWidget(deviceLabel); this->setLayout(vLayout); this->show(); } UkuiListWidgetItem::~UkuiListWidgetItem() { } void UkuiListWidgetItem::setSelected(bool selected){ if (selected) { widget->setStyleSheet("QWidget{background: #3D6BE5; border-radius: 4px;}"); } else { widget->setStyleSheet("QListWidget::Item:hover{background:#FF3D6BE5;border-radius: 4px;}"); } } //设置输出设备的设备名字和名称 void UkuiListWidgetItem::setLabelText(QString portLabel, QString deviceLabel){ this->portLabel->setText(portLabel); this->deviceLabel->setText(deviceLabel); } void UkuiListWidgetItem::mousePressEvent(QMouseEvent *event) { QWidget::mousePressEvent(event); qDebug() << "Mouse Press Event" << this->portLabel->text() << this->deviceLabel->text() << isCheckBluetoothInput; //蓝牙输入去除勾选 if (this->deviceLabel->text().contains("bluez_card")) { if (isCheckBluetoothInput == false) isCheckBluetoothInput = true; else { isCheckBluetoothInput = false; QString cmd = "pactl set-card-profile "+this->deviceLabel->text()+" a2dp_sink"; system(cmd.toLocal8Bit().data()); } } } //void UkuiListWidgetItem::paintEvent(QPaintEvent *event) //{ // QStyleOption opt; // opt.init(this); // QPainter p(this); //// double transparence = transparency * 255; // QColor color = palette().color(QPalette::Base); //// color.setAlpha(transparence); // QBrush brush = QBrush(color); // p.setBrush(brush); // p.setPen(Qt::NoPen); // QPainterPath path; // opt.rect.adjust(0,0,0,0); // path.addRoundedRect(opt.rect,6,6); // p.setRenderHint(QPainter::Antialiasing); // 反锯齿; // p.drawRoundedRect(opt.rect,6,6); // setProperty("blurRegion",QRegion(path.toFillPolygon().toPolygon())); // style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); //// QWidget::paintEvent(event); //} ukui-media/audio/ukmedia_input_widget.cpp0000664000175000017500000002217615170054730017552 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "ukmedia_input_widget.h" #include #include #include UkmediaInputWidget::UkmediaInputWidget(QWidget *parent) : QWidget(parent) { connect(qApp, &QApplication::paletteChanged, this, &UkmediaInputWidget::onPaletteChanged); m_pInputWidget = new QFrame(this); m_pVolumeWidget = new QFrame(m_pInputWidget); m_pInputLevelWidget = new QFrame(m_pInputWidget); m_pInputSlectWidget = new QFrame(m_pInputWidget); m_pNoiseReducteWidget = new QFrame(m_pInputWidget); m_pLoopBackWidget = new QFrame(m_pInputWidget); m_pInputWidget->setFrameShape(QFrame::Shape::Box); m_pVolumeWidget->setFrameShape(QFrame::Shape::Box); m_pInputLevelWidget->setFrameShape(QFrame::Shape::Box); m_pInputSlectWidget->setFrameShape(QFrame::Shape::Box); m_pNoiseReducteWidget->setFrameShape(QFrame::Shape::Box); m_pLoopBackWidget->setFrameShape(QFrame::Shape::Box); //设置大小 m_pVolumeWidget->setMinimumSize(550,60); m_pVolumeWidget->setMaximumSize(16777215,60); m_pInputLevelWidget->setMinimumSize(550,60); m_pInputLevelWidget->setMaximumSize(16777215,60); m_pInputSlectWidget->setMinimumSize(550,60); m_pInputSlectWidget->setMaximumSize(16777215,60); m_pNoiseReducteWidget->setMinimumSize(550,60); m_pNoiseReducteWidget->setMaximumSize(16777215,60); m_pLoopBackWidget->setMinimumSize(550,90); m_pLoopBackWidget->setMaximumSize(16777215,90); m_pInputLabel = new TitleLabel(this); //~ contents_path /Audio/Input m_pInputLabel->setText(tr("Input")); m_pInputLabel->setContentsMargins(16,0,16,0); m_pInputLabel->setStyleSheet("color: palette(windowText);}"); //~ contents_path /Audio/Input Device m_pInputDeviceLabel = new FixLabel(tr("Input Device"),m_pInputSlectWidget); m_pInputDeviceSelectBox = new QComboBox(m_pInputSlectWidget); //~ contents_path /Audio/Volume m_pIpVolumeLabel = new FixLabel(tr("Volume"),m_pVolumeWidget); m_pInputIconBtn = new UkuiButtonDrawSvg(m_pVolumeWidget); m_pIpVolumeSlider = new AudioSlider(); m_pIpVolumePercentLabel = new QLabel(m_pVolumeWidget); //~ contents_path /Audio/Input Level m_pInputLevelLabel = new FixLabel(tr("Input Level"),m_pInputLevelWidget); m_pInputLevelProgressBar = new QProgressBar(m_pInputLevelWidget); m_pInputLevelProgressBar->setStyle(new CustomStyle); m_pInputLevelProgressBar->setTextVisible(false); m_pInputIconBtn->setFocusPolicy(Qt::NoFocus); //~ contents_path /Audio/Noise Reduction m_pNoiseReducteLabel = new FixLabel(tr("Noise Reduction")); m_pNoiseReducteButton = new KSwitchButton(); //~ contents_path /audio/Voice Monitor m_pLoopBackLabel = new FixLabel(tr("Voice Monitor"),m_pLoopBackWidget);//add 侦听功能 m_pLoopBackTipsLabel = new FixLabel(tr("You can hear your voice in the output device of your choice"),m_pLoopBackWidget); m_pLoopBackButton = new KSwitchButton(); QPalette palette = m_pLoopBackTipsLabel->palette(); QColor color = palette.color(QPalette::PlaceholderText); palette.setColor(QPalette::WindowText,color); m_pLoopBackTipsLabel->setPalette(palette); QPalette pe; pe.setColor(QPalette::WindowText,Qt::red); //输入设备添加布局 m_pInputDeviceLabel->setFixedSize(120,40); m_pInputDeviceSelectBox->setFixedHeight(40); QHBoxLayout *inputdeviceSlectLayout = new QHBoxLayout(); inputdeviceSlectLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); inputdeviceSlectLayout->addWidget(m_pInputDeviceLabel); inputdeviceSlectLayout->addItem(new QSpacerItem(113,20,QSizePolicy::Fixed)); inputdeviceSlectLayout->addWidget(m_pInputDeviceSelectBox); inputdeviceSlectLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); inputdeviceSlectLayout->setSpacing(0); m_pInputSlectWidget->setLayout(inputdeviceSlectLayout); m_pInputSlectWidget->layout()->setContentsMargins(0,0,0,0); //主音量添加布局 QHBoxLayout *m_pMasterLayout = new QHBoxLayout(m_pVolumeWidget); m_pIpVolumeLabel->setFixedSize(110,40); m_pInputIconBtn->setFixedSize(24,24); m_pIpVolumeSlider->setFixedHeight(55); m_pIpVolumePercentLabel->setFixedSize(55,20); m_pMasterLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); m_pMasterLayout->addWidget(m_pIpVolumeLabel); m_pMasterLayout->addItem(new QSpacerItem(123,20,QSizePolicy::Fixed)); m_pMasterLayout->addWidget(m_pInputIconBtn); m_pMasterLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); m_pMasterLayout->addWidget(m_pIpVolumeSlider); m_pMasterLayout->addItem(new QSpacerItem(13,20,QSizePolicy::Maximum)); m_pMasterLayout->addWidget(m_pIpVolumePercentLabel); m_pMasterLayout->addItem(new QSpacerItem(10,20,QSizePolicy::Maximum)); m_pMasterLayout->setSpacing(0); m_pVolumeWidget->setLayout(m_pMasterLayout); m_pVolumeWidget->layout()->setContentsMargins(0,0,0,0); //输入反馈添加布局 QHBoxLayout *m_pSoundLayout = new QHBoxLayout(m_pInputLevelWidget); m_pInputLevelLabel->setFixedSize(110,40); m_pSoundLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); m_pSoundLayout->addWidget(m_pInputLevelLabel); m_pSoundLayout->addItem(new QSpacerItem(123,20,QSizePolicy::Fixed)); m_pSoundLayout->addWidget(m_pInputLevelProgressBar); m_pSoundLayout->addItem(new QSpacerItem(5,20,QSizePolicy::Fixed)); m_pSoundLayout->setSpacing(0); m_pInputLevelWidget->setLayout(m_pSoundLayout); m_pInputLevelWidget->layout()->setContentsMargins(0,0,0,0); //智能降噪 QHBoxLayout *noiseReducteLayout = new QHBoxLayout(m_pNoiseReducteWidget); noiseReducteLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); noiseReducteLayout->addWidget(m_pNoiseReducteLabel); noiseReducteLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Expanding)); noiseReducteLayout->addWidget(m_pNoiseReducteButton); noiseReducteLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); noiseReducteLayout->setSpacing(0); m_pNoiseReducteWidget->setLayout(noiseReducteLayout); m_pNoiseReducteWidget->layout()->setContentsMargins(0,0,0,0); //侦听功能添加布局 QGridLayout *gridLayout = new QGridLayout(m_pLoopBackWidget); gridLayout->addWidget(m_pLoopBackLabel,0,0); QHBoxLayout *spacer = new QHBoxLayout(m_pLoopBackWidget); spacer->addStretch(); gridLayout->addLayout(spacer,0,2); gridLayout->addItem(new QSpacerItem(16,4,QSizePolicy::Fixed),1,0); gridLayout->addWidget(m_pLoopBackTipsLabel,2,0,1,3); gridLayout->addWidget(m_pLoopBackButton,0,3,3,1); gridLayout->setSpacing(0); m_pLoopBackWidget->setLayout(gridLayout); m_pLoopBackWidget->layout()->setContentsMargins(16,15,16,15); m_pLoopbackLine = myLine(); //进行整体布局 m_pVlayout = new QVBoxLayout(m_pInputWidget); m_pVlayout->addWidget(m_pInputSlectWidget); m_pVlayout->addWidget(myLine()); m_pVlayout->addWidget(m_pVolumeWidget); m_pVlayout->addWidget(myLine()); m_pVlayout->addWidget(m_pInputLevelWidget); m_pVlayout->addWidget(myLine()); m_pVlayout->addWidget(m_pNoiseReducteWidget); m_pVlayout->addWidget(m_pLoopbackLine); m_pVlayout->addWidget(m_pLoopBackWidget); m_pVlayout->setSpacing(0); m_pInputWidget->setLayout(m_pVlayout); m_pInputWidget->layout()->setContentsMargins(0,0,0,0); QVBoxLayout *m_pVlayout1 = new QVBoxLayout(this); m_pVlayout1->addWidget(m_pInputLabel); m_pVlayout1->addItem(new QSpacerItem(16,4,QSizePolicy::Fixed)); m_pVlayout1->addWidget(m_pInputWidget); this->setLayout(m_pVlayout1); this->layout()->setContentsMargins(0,0,0,0); } void UkmediaInputWidget::setLabelAlignment(Qt::Alignment alignment) { m_pIpVolumePercentLabel->setAlignment(alignment); } QFrame* UkmediaInputWidget::myLine() { QFrame *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; } void UkmediaInputWidget::onPaletteChanged(){ QPalette palette = m_pLoopBackTipsLabel->palette(); QColor color = palette.color(QPalette::PlaceholderText); palette.setColor(QPalette::WindowText,color); m_pLoopBackTipsLabel->setPalette(palette); } UkmediaInputWidget::~UkmediaInputWidget() { } ukui-media/audio/ukmedia_slider_tip_label_helper.cpp0000664000175000017500000001254415170052044021676 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #include #include #include #include #include #include #include #include #include #include #include "ukui_custom_style.h" MediaSliderTipLabel::MediaSliderTipLabel(){ setAttribute(Qt::WA_TranslucentBackground); } MediaSliderTipLabel::~MediaSliderTipLabel(){ } void MediaSliderTipLabel::paintEvent(QPaintEvent *e) { QStyleOptionFrame opt; initStyleOption(&opt); QStylePainter p(this); // p.setBrush(QBrush(QColor(0x1A,0x1A,0x1A,0x4C))); p.setBrush(QBrush(QColor(0xFF,0xFF,0xFF,0x33))); p.setPen(Qt::NoPen); p.drawRoundedRect(this->rect(), 1, 1); QPainterPath path; path.addRoundedRect(opt.rect,6,6); p.setRenderHint(QPainter::Antialiasing); setProperty("blurRegion",QRegion(path.toFillPolygon().toPolygon())); p.drawPrimitive(QStyle::PE_PanelTipLabel, opt); this->setProperty("blurRegion", QRegion(QRect(0, 0, 1, 1))); QLabel::paintEvent(e); } SliderTipLabelHelper::SliderTipLabelHelper(QObject *parent) :QObject(parent) { m_pTiplabel = new MediaSliderTipLabel(); m_pTiplabel->setWindowFlags(Qt::ToolTip); qApp->installEventFilter(new AppEventFilter(this)); m_pTiplabel->setFixedSize(52,30); m_pTiplabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); } void SliderTipLabelHelper::registerWidget(QWidget *w) { w->removeEventFilter(this); w->installEventFilter(this); } void SliderTipLabelHelper::unregisterWidget(QWidget *w) { w->removeEventFilter(this); } bool SliderTipLabelHelper::eventFilter(QObject *obj, QEvent *e) { auto slider = qobject_cast(obj); if (obj == slider) { switch (e->type()) { case QEvent::MouseMove: { QMouseEvent *event = static_cast(e); mouseMoveEvent(obj, event); return false; } case QEvent::MouseButtonRelease: { QMouseEvent *event = static_cast(e); mouseReleaseEvent(obj, event); return false; } case QEvent::MouseButtonPress:{ QMouseEvent *event = static_cast(e); mousePressedEvent(obj,event); } default: return false; } } return QObject::eventFilter(obj,e); } void SliderTipLabelHelper::mouseMoveEvent(QObject *obj, QMouseEvent *e) { Q_UNUSED(e); QRect rect; QStyleOptionSlider m_option; auto slider = qobject_cast(obj); slider->initStyleOption(&m_option); rect = slider->style()->subControlRect(QStyle::CC_Slider, &m_option,QStyle::SC_SliderHandle,slider); QPoint gPos = slider->mapToGlobal(rect.topLeft()); QString percent; percent = QString::number(slider->value()); percent.append("%"); m_pTiplabel->setText(percent); m_pTiplabel->move(gPos.x()-(m_pTiplabel->width()/2)+9,gPos.y()-m_pTiplabel->height()-1); m_pTiplabel->show(); } void SliderTipLabelHelper::mouseReleaseEvent(QObject *obj, QMouseEvent *e) { Q_UNUSED(obj); Q_UNUSED(e); m_pTiplabel->hide(); } void SliderTipLabelHelper::mousePressedEvent(QObject *obj, QMouseEvent *e) { Q_UNUSED(e); QStyleOptionSlider m_option; auto slider = qobject_cast(obj); QRect rect; //获取鼠标的位置,这里并不能直接从ev中取值(因为如果是拖动的话,鼠标开始点击的位置没有意义了) double pos = e->pos().x() / (double)slider->width(); slider->setValue(pos *(slider->maximum() - slider->minimum()) + slider->minimum()); //向父窗口发送自定义事件event type,这样就可以在父窗口中捕获这个事件进行处理 QEvent evEvent(static_cast(QEvent::User + 1)); QCoreApplication::sendEvent(obj, &evEvent); int value = pos *(slider->maximum() - slider->minimum()) + slider->minimum(); slider->initStyleOption(&m_option); rect = slider->style()->subControlRect(QStyle::CC_Slider, &m_option,QStyle::SC_SliderHandle,slider); QPoint gPos = slider->mapToGlobal(rect.topLeft()); QString percent; percent = QString::number(slider->value());//(m_option.sliderValue); percent.append("%"); m_pTiplabel->setText(percent); m_pTiplabel->move(gPos.x()-(m_pTiplabel->width()/2)+9,gPos.y()-m_pTiplabel->height()-1); m_pTiplabel->show(); } // AppEventFilter AppEventFilter::AppEventFilter(SliderTipLabelHelper *parent) : QObject(parent) { m_wm = parent; } bool AppEventFilter::eventFilter(QObject *obj, QEvent *e) { Q_UNUSED(obj); Q_UNUSED(e); return false; } ukui-media/audio/customstyle.h0000664000175000017500000001763215170054730015412 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #include #include "ukmedia_slider_tip_label_helper.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(); /*! * \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; Q_SIGNALS: public Q_SLOTS: private: SliderTipLabelHelper *m_helpTip; }; #endif // CUSTOMSTYLE_H ukui-media/audio/ukmedia_volume_control.h0000664000175000017500000002463415170054730017565 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifndef UKMEDIAVOLUMECONTROL_H #define UKMEDIAVOLUMECONTROL_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ukui_custom_style.h" #include "../common/ukmedia_common.h" #include #define NOISE_REDUCE_SOURCE "noiseReduceSource" #define PANGUW_SINK "alsa_output.platform-raoliang-sndcard.analog-stereo" #define DECAY_STEP .04 static int n_outstanding = 0; class PortInfo { public: QByteArray name; QByteArray description; uint32_t priority; int available; int direction; int64_t latency_offset; std::vector profiles; }; class UkmediaVolumeControl : public QObject { Q_OBJECT public: UkmediaVolumeControl(); friend class UkmediaMainWidget; virtual ~UkmediaVolumeControl(); public: // Connect to pulseaudio pa_context* getContext(void); static gboolean connectToPulse(gpointer userdata); static void contextStateCallback(pa_context *c, void *userdata); static void subscribeCb(pa_context *c, pa_subscription_event_type_t t, uint32_t index, void *userdata); static void clientCb(pa_context *, const pa_client_info *i, int eol, void *userdata); static void serverInfoCb(pa_context *, const pa_server_info *i, void *userdata); static void cardCb(pa_context *, const pa_card_info *i, int eol, void *userdata); static void sinkCb(pa_context *c, const pa_sink_info *i, int eol, void *userdata); static void sourceCb(pa_context *, const pa_source_info *i, int eol, void *userdata); static void sinkInputCb(pa_context *, const pa_sink_input_info *i, int eol, void *userdata); static void sourceOutputCb(pa_context *, const pa_source_output_info *i, int eol, void *userdata); static void readCallback(pa_stream *s, size_t length, void *userdata); // 待优化 void showError(const char *txt); static void decOutstanding(UkmediaVolumeControl *w); static void serverInfoIndexCb(pa_context *, const pa_server_info *i, void *userdata); static void sinkIndexCb(pa_context *c, const pa_sink_info *i, int eol, void *userdata); static void sourceIndexCb(pa_context *c, const pa_source_info *i, int eol, void *userdata); // 暂未使用 void updateDeviceVisibility(); void setConnectionState(gboolean connected); void setConnectingMessage(const char *string = NULL); static void extStreamRestoreReadCb(pa_context *,const pa_ext_stream_restore_info *i,int eol,void *userdata); static void extStreamRestoreSubscribeCb(pa_context *c, void *userdata); // void ext_device_restore_read_cb(pa_context *,const pa_ext_device_restore_info *i,int eol,void *userdata); // static void ext_device_restore_subscribe_cb(pa_context *c, pa_device_type_t type, uint32_t idx, void *userdata); static void extDeviceManagerReadCb(pa_context *,const pa_ext_device_manager_info *,int eol,void *userdata); static void extDeviceManagerSubscribeCb(pa_context *c, void *userdata); // Update audio info void updateClient(const pa_client_info &info); void updateServer(const pa_server_info &info); void updateCard(UkmediaVolumeControl *c,const pa_card_info &info); bool updateSink(UkmediaVolumeControl *c,const pa_sink_info &info); void updateSource(const pa_source_info &info); void updateSinkInput(const pa_sink_input_info &info); void updateSourceOutput(const pa_source_output_info &info); void updateVolumeMeter(uint32_t source_index, uint32_t sink_input_index, double v); void refreshVolume(int soundType, int info_Vol, bool info_Mute); // Sink volume bool getSinkMute(); int getSinkVolume(); void setSinkMute(bool status); void setSinkVolume(int index, int value); void setBalanceVolume(int index, int value, float balance); float getBalanceVolume(); // Source volume bool getSourceMute(); int getSourceVolume(); void setSourceMute(bool status); void setSourceVolume(int index,int value); // Sink-input volume int getSinkInputVolume(const gchar *name); void setSinkInputMuted(int index,bool status); void setSinkInputVolume(int index,int value); // Source-output volume int getSourceOutputVolume(const gchar *name); void setSourceOutputMuted(int index, bool status); void setSourceOutputVolume(int index, int value); // sinkInfo sinkInfo addSinkInfo(const pa_sink_info& i); sinkInfo getSinkInfoByIndex(int index); sinkInfo getSinkInfoByName(QString name); // sourceInfo sourceInfo addSourceInfo(const pa_source_info& i); sourceInfo getSourceInfoByIndex(int index); sourceInfo getSourceInfoByName(QString name); int getDefaultSinkIndex(); int getDefaultSourceIndex(); bool setCardProfile(int index,const gchar *name); bool setDefaultSink(const gchar *name); bool setDefaultSource(const gchar *name); bool setSinkPort(const gchar *sinkName ,const gchar *portName); bool setSourcePort(const gchar *sourceName, const gchar *portName); // Remove info void removeCard(uint32_t index); void removeSink(uint32_t sinkIndex); void removeSource(uint32_t sourceIndex); void removeSinkInput(uint32_t index); void removeSourceOutput(uint32_t index); void removeClient(uint32_t index); // 确认可移除? void removeCardSink(int cardIndex,QString sinkName); void removeCardSource(int cardIndex,QString sourceName); void removeCardMap(int index); //移除指定索引的 card void removeCardProfileMap(int index); //移除声卡profile map void removeInputPortMap(int index); //移除指定索引的input port void removeOutputPortMap(int index); //移除指定索引的output port void removeSinkPortMap(int index); void removeSourcePortMap(int index); void removeProfileMap(int index); void removeInputProfile(int index); QString findSinkActivePortName(QString name); // 输入反馈功能 pa_stream* createMonitorStreamForSource(uint32_t source_idx, uint32_t stream_idx, bool suspend); private: static void sendDeviceChangedSignal(UkmediaVolumeControl* w=nullptr); void sendVolumeUpdateSignal(); void sendSourceVolumeUpdateSignal(); Q_SIGNALS: void paContextReady(); void updateMonoAudio(bool show); void updateLoopBack(bool show); void updateSinkMute(bool isMute); void updateSourceMute(bool isMute); void updateVolume(int value,bool state); void updateSourceVolume(int value,bool state); void addSinkInputSignal(const gchar* name,const gchar *id,int index); void removeSinkInputSignal(const gchar* name); void addSourceOutputSignal(const gchar* name,const gchar *id,int index); void removeSourceOutputSignal(const gchar* name); void checkDeviceSelectionSianal(const pa_card_info *info); void peakChangedSignal(double v); void updatePortSignal(); void updateCboxPortSignal(); void deviceChangedSignal(); public: static pa_context *context; static int reconnectTimeout; static pa_mainloop_api* api; // 输出 int sinkIndex; bool sinkMuted; int sinkVolume = 0; int channel = 2; float balance = 0.0; int defaultOutputCard = -1; QString sinkPortName = ""; QByteArray defaultSinkName; const pa_sink_info *m_pDefaultSink = nullptr; // 输入 int sourceIndex; bool sourceMuted; int sourceVolume = 0; int inputChannel; int defaultInputCard = -1; QString sourcePortName = ""; QString masterDevice = ""; QByteArray defaultSourceName; pa_channel_map channelMap; const pa_source_info *m_pDefaultSource = nullptr; // Card Info QMap cardMap; QMap> cardProfileMap; QMap cardActiveProfileMap; QMap> profileNameMap; //声卡输出配置文件 QMap> cardProfilePriorityMap; //记录声卡优先级配置文件 QMap> inputPortProfileNameMap; // Sink Info QMap sinkMap; QMap sinkActivePortMap; QMultiMap> sinkPortMap; QMap> outputPortMap; // Source Info QMap sourceMap; QMap> inputPortMap; QMultiMap> sourcePortMap; // Sink-Input Info QStringList sinkInputList; QMap sinkInputMap; // Source-Output Info QMap sourceOutputMap; QByteArray activePort; QByteArray activeProfile; QByteArray noInOutProfile; std::map ports; std::map clientNames; std::vector< std::pair > dPorts; std::vector< std::pair > profiles; double lastPeak = 0; int peakDetectIndex = -1; pa_stream *peak = nullptr; pa_source_flags sourceFlags= PA_SOURCE_NOFLAGS; private: gboolean m_connected; gchar* m_config_filename; //start 过滤设备端口切换或者设备拔插期间,subscribecb回调反馈的非预期数据 static QTimer deviceChangedTimer; QTimer m_updateVolumeTimer; QTimer m_updateSourceVolumeTimer; //end }; #endif // UKMEDIAVOLUMECONTROL_H ukui-media/audio/ukmedia_settings_widget.h0000664000175000017500000000342115170054730017710 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifndef UKMEDIA_SETTINGS_WIDGET_H #define UKMEDIA_SETTINGS_WIDGET_H #include #include #include #include #include "ukui_custom_style.h" #include "kswitchbutton.h" using namespace kdk; class UkmediaSettingsWidget : public QWidget { Q_OBJECT public: explicit UkmediaSettingsWidget(QWidget *parent = nullptr); ~UkmediaSettingsWidget(); friend class UkmediaMainWidget; private: QFrame *myLine(); private: QFrame *m_pAdvancedSettingsWidget; //用于存放声音设备管理、应用声音的窗口的窗口 QFrame *m_pEquipmentControlWidget; //声音设备管理窗口 QFrame *m_pAppSoundCtrlWidget; //应用声音窗口 TitleLabel *m_pAdvancedSettingsLabel; QLabel *m_pAppSoundLabel; QLabel *m_pEquipmentControlLabel; QPushButton *m_pAppSoundDetailsBtn; QPushButton *m_pDevControlDetailsBtn; }; #endif // UKMEDIA_SETTINGS_WIDGET_H ukui-media/audio/ukmedia_output_widget.cpp0000664000175000017500000003034715170054730017752 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "ukmedia_output_widget.h" #include "ukui_list_widget_item.h" #include #include UkmediaOutputWidget::UkmediaOutputWidget(QWidget *parent) : QWidget(parent) { connect(qApp, &QApplication::paletteChanged, this, &UkmediaOutputWidget::onPaletteChanged); //加载qss样式文件 QFile QssFile("://combox.qss"); QssFile.open(QFile::ReadOnly); if (QssFile.isOpen()){ sliderQss = QLatin1String(QssFile.readAll()); QssFile.close(); } else { qDebug()<<"combox.qss is not found"; } m_pOutputWidget = new QFrame(this); m_pMasterVolumeWidget = new QFrame(m_pOutputWidget); m_pOutputSlectWidget = new QFrame(m_pOutputWidget); m_pVolumeIncreaseWidget = new QFrame(m_pOutputWidget); m_pAudioBlanceWidget = new QFrame(this); //MonoAudio m_pChannelBalanceWidget = new QFrame(m_pAudioBlanceWidget); m_pMonoAudioWidget = new QFrame(m_pAudioBlanceWidget); m_pOutputWidget->setFrameShape(QFrame::Shape::Box); m_pMasterVolumeWidget->setFrameShape(QFrame::Shape::Box); m_pOutputSlectWidget->setFrameShape(QFrame::Shape::Box); m_pVolumeIncreaseWidget->setFrameShape(QFrame::Shape::Box); m_pAudioBlanceWidget->setFrameShape(QFrame::Shape::Box); //MonoAudio m_pChannelBalanceWidget->setFrameShape(QFrame::Shape::Box); m_pMonoAudioWidget->setFrameShape(QFrame::Shape::Box); //设置大小 m_pOutputWidget->setMinimumSize(550,0); m_pOutputWidget->setMaximumSize(16777215,360); m_pMasterVolumeWidget->setMinimumSize(550,60); m_pMasterVolumeWidget->setMaximumSize(16777215,60); m_pOutputSlectWidget->setMinimumSize(550,60); m_pOutputSlectWidget->setMaximumSize(16777215,60); m_pVolumeIncreaseWidget->setMinimumSize(550,90); m_pVolumeIncreaseWidget->setMaximumSize(16777215,90); m_pChannelBalanceWidget->setMinimumSize(550,60); //MonoAudio m_pChannelBalanceWidget->setMaximumSize(16777215,60); m_pMonoAudioWidget->setMinimumSize(550,90); m_pMonoAudioWidget->setMaximumSize(16777215,90); m_pOutputLabel = new TitleLabel(this); //~ contents_path /Audio/Output m_pOutputLabel->setText(tr("Output")); m_pOutputLabel->setContentsMargins(16,0,16,0); m_pOutputLabel->setStyleSheet("QLabel{color: palette(windowText);}"); //~ contents_path /Audio/Output Device m_pOutputDeviceLabel = new FixLabel(tr("Output Device"),m_pOutputSlectWidget); m_pDeviceSelectBox = new QComboBox(m_pOutputSlectWidget); //~ contents_path /Audio/Master Volume m_pOpVolumeLabel = new FixLabel(tr("Master Volume"),m_pMasterVolumeWidget); m_pOutputIconBtn = new UkuiButtonDrawSvg(m_pMasterVolumeWidget); m_pOpVolumeSlider = new AudioSlider(m_pMasterVolumeWidget); m_pOpVolumePercentLabel = new QLabel(m_pMasterVolumeWidget); //~ contents_path /Audio/Balance m_pOpBalanceLabel = new FixLabel(tr("Balance"),m_pChannelBalanceWidget); m_pLeftBalanceLabel = new FixLabel(tr("Left"),m_pChannelBalanceWidget); m_pOpBalanceSlider = new UkmediaVolumeSlider(m_pChannelBalanceWidget); m_pRightBalanceLabel = new FixLabel(tr("Right"),m_pChannelBalanceWidget); //~ contents_path /Audio/Volume Increase VolumeIncreaseTipsWidget = new QWidget(m_pVolumeIncreaseWidget); m_pVolumeIncreaseLabel = new FixLabel(tr("Volume Increase"),VolumeIncreaseTipsWidget); VolumeIncreaseTipsLabel = new FixLabel(tr("Volume above 100% can cause sound distortion and damage your speakers."),VolumeIncreaseTipsWidget); m_pVolumeIncreaseButton = new KSwitchButton(); QPalette palette = VolumeIncreaseTipsLabel->palette(); QColor color = palette.color(QPalette::PlaceholderText); palette.setColor(QPalette::WindowText,color); VolumeIncreaseTipsLabel->setPalette(palette); QVBoxLayout *vlay = new QVBoxLayout(this); vlay->addStretch(); vlay->addWidget(m_pVolumeIncreaseLabel); vlay->addWidget(VolumeIncreaseTipsLabel); vlay->addStretch(); vlay->setSpacing(0); VolumeIncreaseTipsWidget->setLayout(vlay); VolumeIncreaseTipsWidget->layout()->setContentsMargins(0,0,0,0); m_pOutputIconBtn->setFocusPolicy(Qt::NoFocus); //~ contents_path /Audio/Mono Audio MonoAudioTipsWidget = new QWidget(m_pMonoAudioWidget); m_pMonoAudioLabel = new FixLabel(tr("Mono Audio"),MonoAudioTipsWidget); MonoAudioTipsLabel = new FixLabel(tr("It merges the left and right channels into one channel."),MonoAudioTipsWidget); m_pMonoAudioButton = new KSwitchButton(); QPalette palette1 = MonoAudioTipsLabel->palette(); QColor color1 = palette1.color(QPalette::PlaceholderText); palette1.setColor(QPalette::WindowText,color1); MonoAudioTipsLabel->setPalette(palette1); QVBoxLayout *vlay1 = new QVBoxLayout(this); vlay1->addStretch(); vlay1->addWidget(m_pMonoAudioLabel); vlay1->addWidget(MonoAudioTipsLabel); vlay1->addStretch(); vlay1->setSpacing(0); MonoAudioTipsWidget->setLayout(vlay1); MonoAudioTipsWidget->layout()->setContentsMargins(0,0,0,0); //选择输出设备框添加布局 m_pOutputDeviceLabel->setFixedSize(140,20); m_pDeviceSelectBox->setFixedHeight(40); QHBoxLayout *deviceSlectLayout = new QHBoxLayout(); deviceSlectLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); deviceSlectLayout->addWidget(m_pOutputDeviceLabel); deviceSlectLayout->addItem(new QSpacerItem(93,20,QSizePolicy::Fixed)); deviceSlectLayout->addWidget(m_pDeviceSelectBox); deviceSlectLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); deviceSlectLayout->setSpacing(0); m_pOutputSlectWidget->setLayout(deviceSlectLayout); m_pOutputSlectWidget->layout()->setContentsMargins(0,0,0,0); //主音量添加布局 QHBoxLayout *masterLayout = new QHBoxLayout(m_pMasterVolumeWidget); m_pOpVolumeLabel->setFixedSize(110,40); m_pOutputIconBtn->setFixedSize(24,24); m_pOpVolumeSlider->setFixedHeight(55); m_pOpVolumePercentLabel->setFixedSize(55,20); masterLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); masterLayout->addWidget(m_pOpVolumeLabel); masterLayout->addItem(new QSpacerItem(123,20,QSizePolicy::Fixed)); masterLayout->addWidget(m_pOutputIconBtn); masterLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); masterLayout->addWidget(m_pOpVolumeSlider); masterLayout->addItem(new QSpacerItem(13,20,QSizePolicy::Fixed)); masterLayout->addWidget(m_pOpVolumePercentLabel); masterLayout->addItem(new QSpacerItem(10,20,QSizePolicy::Fixed)); masterLayout->setSpacing(0); m_pMasterVolumeWidget->setLayout(masterLayout); m_pMasterVolumeWidget->layout()->setContentsMargins(0,0,0,0); //声道平衡添加布局 QHBoxLayout *soundLayout = new QHBoxLayout(m_pChannelBalanceWidget); m_pOpBalanceLabel->setFixedSize(110,20); m_pLeftBalanceLabel->setFixedSize(40,30); m_pOpBalanceSlider->setFixedHeight(55); m_pRightBalanceLabel->setFixedSize(55,30); soundLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); soundLayout->addWidget(m_pOpBalanceLabel); soundLayout->addItem(new QSpacerItem(123,20,QSizePolicy::Fixed)); soundLayout->addWidget(m_pLeftBalanceLabel); // soundLayout->addItem(new QSpacerItem(26,20,QSizePolicy::Fixed)); soundLayout->addWidget(m_pOpBalanceSlider); soundLayout->addItem(new QSpacerItem(14,20,QSizePolicy::Fixed)); soundLayout->addWidget(m_pRightBalanceLabel); soundLayout->addItem(new QSpacerItem(10,20,QSizePolicy::Fixed)); soundLayout->setSpacing(0); m_pChannelBalanceWidget->setLayout(soundLayout); m_pChannelBalanceWidget->layout()->setContentsMargins(0,0,0,0); //音量增强 QHBoxLayout *volumeIncreaseLayout = new QHBoxLayout(m_pVolumeIncreaseWidget); volumeIncreaseLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); volumeIncreaseLayout->addWidget(VolumeIncreaseTipsWidget); volumeIncreaseLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Expanding)); volumeIncreaseLayout->addWidget(m_pVolumeIncreaseButton); volumeIncreaseLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); volumeIncreaseLayout->setSpacing(0); m_pVolumeIncreaseWidget->setLayout(volumeIncreaseLayout); m_pVolumeIncreaseWidget->layout()->setContentsMargins(0,0,0,0); // QFrame *outputselectAndVolume = myLine(); // QFrame *volumeAndBalance = myLine(); //单声道音频 QHBoxLayout *monoAudioLayout = new QHBoxLayout(m_pMonoAudioWidget); monoAudioLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); monoAudioLayout->addWidget(MonoAudioTipsWidget); monoAudioLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Expanding)); monoAudioLayout->addWidget(m_pMonoAudioButton); monoAudioLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); monoAudioLayout->setSpacing(0); m_pMonoAudioWidget->setLayout(monoAudioLayout); m_pMonoAudioWidget->layout()->setContentsMargins(0,0,0,0); //进行整体布局 m_pVlayout = new QVBoxLayout(m_pOutputWidget); m_pVlayout->setContentsMargins(0,0,0,0); m_pVlayout->addWidget(m_pOutputSlectWidget); m_pVlayout->addWidget(myLine()); m_pVlayout->addWidget(m_pMasterVolumeWidget); m_pVlayout->addWidget(myLine()); m_pVlayout->addWidget(m_pVolumeIncreaseWidget); m_pVlayout->setSpacing(0); // m_pOutputWidget->setLayout(m_pVlayout); // m_pOutputWidget->layout()->setContentsMargins(0,0,0,0); m_pMonoLine = myLine(); //进行monoAudio布局 QVBoxLayout *m_pVlayout1 = new QVBoxLayout(m_pAudioBlanceWidget); m_pVlayout1->setContentsMargins(0,0,0,0); m_pVlayout1->addWidget(m_pChannelBalanceWidget); m_pVlayout1->addWidget(m_pMonoLine); m_pVlayout1->addWidget(m_pMonoAudioWidget); m_pVlayout1->setSpacing(0); QVBoxLayout *vLayout1 = new QVBoxLayout(this); vLayout1->addWidget(m_pOutputLabel); vLayout1->addItem(new QSpacerItem(16,4,QSizePolicy::Fixed)); vLayout1->addWidget(m_pOutputWidget); vLayout1->addItem(new QSpacerItem(16,4,QSizePolicy::Fixed)); vLayout1->addWidget(m_pAudioBlanceWidget); this->setLayout(vLayout1); this->layout()->setContentsMargins(0,0,0,0); m_pMasterVolumeWidget->setObjectName("masterVolumeWidget"); //设置样式 m_pOutputLabel->setObjectName("m_pOutputLabel"); } void UkmediaOutputWidget::setVolumeSliderRange(bool status) { int maxValue = (status) ? 125 : 100; m_pOpVolumeSlider->blockSignals(true); m_pOpVolumeSlider->setRange(0, maxValue); m_pOpVolumeSlider->blockSignals(false); } void UkmediaOutputWidget::setLabelAlignment(Qt::Alignment alignment) { m_pOpVolumePercentLabel->setAlignment(alignment); m_pRightBalanceLabel->setAlignment(alignment); } QFrame* UkmediaOutputWidget::myLine() { QFrame *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; } void UkmediaOutputWidget::onPaletteChanged(){ QPalette palette = VolumeIncreaseTipsLabel->palette(); QColor color = palette.color(QPalette::PlaceholderText); palette.setColor(QPalette::WindowText,color); VolumeIncreaseTipsLabel->setPalette(palette); palette = MonoAudioTipsLabel->palette(); color = palette.color(QPalette::PlaceholderText); palette.setColor(QPalette::WindowText,color); MonoAudioTipsLabel->setPalette(palette); } UkmediaOutputWidget::~UkmediaOutputWidget() { } ukui-media/audio/ukmedia_sound_effects_widget.h0000664000175000017500000000611515170054730020702 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifndef UKMEDIASOUNDEFFECTSWIDGET_H #define UKMEDIASOUNDEFFECTSWIDGET_H #include #include #include #include #include #include #include "ukui_custom_style.h" #include "kswitchbutton.h" using namespace kdk; class UkuiMessageBox : public QMessageBox { public: explicit UkuiMessageBox(); }; class UkmediaSoundEffectsWidget : public QWidget { Q_OBJECT public: explicit UkmediaSoundEffectsWidget(QWidget *parent = nullptr); ~UkmediaSoundEffectsWidget(); friend class UkmediaMainWidget; void alertSoundWidgetShow(bool status); Q_SIGNALS: public Q_SLOTS: private: QFrame *myLine(); private: QFrame *m_pSystemSoundWidget;//存放注销、唤醒、开机、关机提示音的窗口 QFrame *m_pLagoutWidget;//注销提示音窗口 QFrame *m_pWakeupMusicWidget;//唤醒音乐窗口 QFrame *m_pStartupMusicWidget;//设置开机音乐窗口 QFrame *m_pPoweroffMusicWidget;//关机提示音 QFrame *m_pAlertSoundFrame;//存放注销、唤醒、开机、关机提示音的窗口 QFrame *m_pAlertSoundSwitchWidget;//提示音开关窗口 QFrame *m_pThemeWidget;//提示音下的系统音效主题 QString qss; QStyledItemDelegate *itemDelegate; TitleLabel *m_pSoundEffectLabel; FixLabel *m_pSoundThemeLabel; FixLabel *m_pLagoutLabel; FixLabel *m_pAlertSoundSwitchLabel; FixLabel *m_pWakeupMusicLabel; FixLabel *m_pPoweroffMusicLabel; FixLabel *m_pStartupMusicLabel; QComboBox *m_pSoundThemeCombobox; QVBoxLayout *m_pSoundLayout; KSwitchButton *m_pStartupButton; KSwitchButton *m_pPoweroffButton; KSwitchButton *m_pLogoutButton; KSwitchButton *m_pAlertSoundSwitchButton; KSwitchButton *m_pWakeupMusicButton; FixLabel *m_pVolumeChangeLabel; FixLabel *m_pNotificationLabel; QComboBox *m_pVolumeChangeCombobox; QComboBox *m_pNotificationCombobox; QFrame *m_pVolumeChangeWidget;//提示音下的音量调节 QFrame *m_pNotificationWidget;//提示音下的通知提示 QFrame *line1; QFrame *line2; QFrame *line3; // UkmediaVolumeSlider *m_pAlertSlider; // UkuiButtonDrawSvg *m_pAlertIconBtn; }; #endif // UKMEDIASOUNDEFFECTSWIDGET_H ukui-media/audio/ukmedia_sound_effects_widget.cpp0000664000175000017500000003145515170054730021242 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "ukmedia_sound_effects_widget.h" #include #include #include #include #include UkuiMessageBox::UkuiMessageBox() { } UkmediaSoundEffectsWidget::UkmediaSoundEffectsWidget(QWidget *parent) : QWidget(parent) { m_pSystemSoundWidget = new QFrame(this); m_pStartupMusicWidget = new QFrame(m_pSystemSoundWidget); m_pPoweroffMusicWidget = new QFrame(m_pSystemSoundWidget); m_pLagoutWidget = new QFrame(m_pSystemSoundWidget); m_pWakeupMusicWidget = new QFrame(m_pSystemSoundWidget); m_pAlertSoundFrame = new QFrame(this); m_pAlertSoundSwitchWidget = new QFrame(m_pAlertSoundFrame); m_pThemeWidget = new QFrame(m_pAlertSoundSwitchWidget); m_pNotificationWidget = new QFrame(m_pAlertSoundSwitchWidget); m_pVolumeChangeWidget = new QFrame(m_pAlertSoundSwitchWidget); m_pSystemSoundWidget->setFrameShape(QFrame::Shape::Box); m_pStartupMusicWidget->setFrameShape(QFrame::Shape::Box); m_pPoweroffMusicWidget->setFrameShape(QFrame::Shape::Box); m_pLagoutWidget->setFrameShape(QFrame::Shape::Box); m_pWakeupMusicWidget->setFrameShape(QFrame::Shape::Box); m_pAlertSoundFrame->setFrameShape(QFrame::Shape::Box); m_pAlertSoundSwitchWidget->setFrameShape(QFrame::Shape::Box); m_pThemeWidget->setFrameShape(QFrame::Shape::Box); m_pNotificationWidget->setFrameShape(QFrame::Shape::Box); m_pVolumeChangeWidget->setFrameShape(QFrame::Shape::Box); //~ contents_path /Audio/System Sound 系统音效文本框 m_pSoundEffectLabel = new TitleLabel(this); m_pSoundEffectLabel->setText(tr("System Sound")); m_pSoundEffectLabel->setContentsMargins(16,0,16,0); m_pSoundEffectLabel->setStyleSheet("QLabel{color: palette(windowText);}"); //~ contents_path /Audio/Sound Theme m_pSoundThemeLabel = new FixLabel(tr("Sound Theme"),m_pThemeWidget);//提示音下的系统音效主题 m_pSoundThemeCombobox = new QComboBox(m_pThemeWidget); //~ contents_path /Audio/Beep Switch m_pAlertSoundSwitchLabel = new FixLabel(tr("Beep Switch"),m_pAlertSoundSwitchWidget);//提示音 //~ contents_path /Audio/Poweroff Music m_pPoweroffMusicLabel = new FixLabel(tr("Poweroff Music"),m_pPoweroffMusicWidget);//关机 //~ contents_path /Audio/Startup Music m_pStartupMusicLabel = new FixLabel(tr("Startup Music"),m_pStartupMusicWidget);//开机 //~ contents_path /Audio/Wakeup Music m_pWakeupMusicLabel = new FixLabel(tr("Wakeup Music"),m_pWakeupMusicWidget);//唤醒 //~ contents_path /Audio/Logout Music m_pLagoutLabel = new FixLabel(tr("Logout Music"),m_pLagoutWidget);//注销 //~ contents_path /Audio/Notification Sound m_pNotificationLabel = new FixLabel(tr("Notification Sound"),m_pNotificationWidget);//提示音下的通知提示 m_pNotificationCombobox = new QComboBox(m_pNotificationWidget); m_pNotificationCombobox->setObjectName("notifyCbox"); //~ contents_path /Audio/Volume Control Sound m_pVolumeChangeLabel = new FixLabel(tr("Volume Control Sound"),m_pVolumeChangeWidget);//音量调节 m_pVolumeChangeCombobox = new QComboBox(m_pVolumeChangeWidget); m_pVolumeChangeCombobox->setObjectName("volChangeCbox"); m_pStartupButton = new KSwitchButton(m_pStartupMusicWidget); m_pLogoutButton = new KSwitchButton(m_pLagoutWidget); m_pWakeupMusicButton = new KSwitchButton(m_pWakeupMusicWidget); m_pPoweroffButton = new KSwitchButton(m_pPoweroffMusicWidget); m_pAlertSoundSwitchButton = new KSwitchButton(m_pAlertSoundSwitchWidget); //设置大小 m_pSystemSoundWidget->setMinimumSize(550,0); m_pSystemSoundWidget->setMaximumSize(16777215,240); m_pStartupMusicWidget->setMinimumSize(550,60); m_pStartupMusicWidget->setMaximumSize(16777215,60); m_pPoweroffMusicWidget->setMinimumSize(550,60); m_pPoweroffMusicWidget->setMaximumSize(16777215,60); m_pLagoutWidget->setMinimumSize(550,60); m_pLagoutWidget->setMaximumSize(16777215,60); m_pWakeupMusicWidget->setMinimumSize(550,60); m_pWakeupMusicWidget->setMaximumSize(16777215,60); m_pAlertSoundSwitchWidget->setMinimumSize(550,60); m_pAlertSoundSwitchWidget->setMaximumSize(16777215,60); m_pThemeWidget->setMinimumSize(550,60); m_pThemeWidget->setMaximumSize(16777215,60); m_pSoundEffectLabel->setFixedSize(150,32); m_pSoundThemeLabel->setFixedSize(150,40); m_pPoweroffMusicLabel->setFixedSize(150,32); m_pSoundThemeCombobox->setFixedHeight(40); m_pNotificationWidget->setMinimumSize(550,60); m_pNotificationWidget->setMaximumSize(16777215,60); m_pVolumeChangeWidget->setMinimumSize(550,60); m_pVolumeChangeWidget->setMaximumSize(16777215,60); m_pNotificationCombobox->setFixedHeight(40); m_pVolumeChangeCombobox->setFixedHeight(40); m_pVolumeChangeLabel->setFixedSize(220,40); m_pNotificationLabel->setFixedSize(220,40); //添加布局 QHBoxLayout *themeLayout = new QHBoxLayout(m_pThemeWidget); themeLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); themeLayout->addWidget(m_pSoundThemeLabel); themeLayout->addItem(new QSpacerItem(83,20,QSizePolicy::Fixed)); themeLayout->addWidget(m_pSoundThemeCombobox); themeLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); themeLayout->setSpacing(0); m_pThemeWidget->setLayout(themeLayout); m_pThemeWidget->layout()->setContentsMargins(0,0,0,0); QHBoxLayout *NotificationLayout = new QHBoxLayout(m_pNotificationWidget); NotificationLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); NotificationLayout->addWidget(m_pNotificationLabel); NotificationLayout->addItem(new QSpacerItem(13,20,QSizePolicy::Fixed)); NotificationLayout->addWidget(m_pNotificationCombobox); NotificationLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); NotificationLayout->setSpacing(0); m_pNotificationWidget->setLayout(NotificationLayout); NotificationLayout->layout()->setContentsMargins(0,0,0,0); //开机音乐设置开关 QHBoxLayout *startupLayout = new QHBoxLayout(m_pStartupMusicWidget); startupLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); startupLayout->addWidget(m_pStartupMusicLabel); startupLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Expanding)); startupLayout->addWidget(m_pStartupButton); startupLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); startupLayout->setSpacing(0); m_pStartupMusicWidget->setLayout(startupLayout); m_pStartupMusicWidget->layout()->setContentsMargins(0,0,0,0); //注销提示音布局 QHBoxLayout *lagoutLayout = new QHBoxLayout(m_pLagoutWidget); lagoutLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); lagoutLayout->addWidget(m_pLagoutLabel); lagoutLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Expanding)); lagoutLayout->addWidget(m_pLogoutButton); lagoutLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); lagoutLayout->setSpacing(0); m_pLagoutWidget->setLayout(lagoutLayout); m_pLagoutWidget->layout()->setContentsMargins(0,0,0,0); // m_pLagoutCombobox->setVisible(false); //提示音开关布局 QHBoxLayout *alertSoundLayout = new QHBoxLayout(m_pAlertSoundSwitchWidget); alertSoundLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); alertSoundLayout->addWidget(m_pAlertSoundSwitchLabel); alertSoundLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Expanding)); alertSoundLayout->addWidget(m_pAlertSoundSwitchButton); alertSoundLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); alertSoundLayout->setSpacing(0); m_pAlertSoundSwitchWidget->setLayout(alertSoundLayout); m_pAlertSoundSwitchWidget->layout()->setContentsMargins(0,0,0,0); //窗口关闭提示音 QHBoxLayout *wakeupMusicLayout = new QHBoxLayout(m_pWakeupMusicWidget); wakeupMusicLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); wakeupMusicLayout->addWidget(m_pWakeupMusicLabel); wakeupMusicLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Expanding)); wakeupMusicLayout->addWidget(m_pWakeupMusicButton); wakeupMusicLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); wakeupMusicLayout->setSpacing(0); m_pWakeupMusicWidget->setLayout(wakeupMusicLayout); m_pWakeupMusicWidget->layout()->setContentsMargins(0,0,0,0); //音量改变提示音 QHBoxLayout *volumeChangedLayout = new QHBoxLayout(m_pVolumeChangeWidget); volumeChangedLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); volumeChangedLayout->addWidget(m_pVolumeChangeLabel); volumeChangedLayout->addItem(new QSpacerItem(13,20,QSizePolicy::Fixed)); volumeChangedLayout->addWidget(m_pVolumeChangeCombobox); volumeChangedLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); volumeChangedLayout->setSpacing(0); m_pVolumeChangeWidget->setLayout(volumeChangedLayout); m_pVolumeChangeWidget->layout()->setContentsMargins(0,0,0,0); //关机提示音 QHBoxLayout *poweroffLayout = new QHBoxLayout(m_pPoweroffMusicWidget); poweroffLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); poweroffLayout->addWidget(m_pPoweroffMusicLabel); poweroffLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Expanding)); poweroffLayout->addWidget(m_pPoweroffButton); poweroffLayout->addItem(new QSpacerItem(16,20,QSizePolicy::Fixed)); poweroffLayout->setSpacing(0); m_pPoweroffMusicWidget->setLayout(poweroffLayout); m_pPoweroffMusicWidget->layout()->setContentsMargins(0,0,0,0); QFrame *startupAndPoweroff = myLine(); QFrame *poweroffAndLagout = myLine(); QFrame *lagoutAndWakeup = myLine(); line1 = myLine(); line2 = myLine(); line3 = myLine(); QVBoxLayout *systemSoundLayout = new QVBoxLayout(m_pSystemSoundWidget); systemSoundLayout->setContentsMargins(0,0,0,0); systemSoundLayout->addWidget(m_pStartupMusicWidget); systemSoundLayout->addWidget(startupAndPoweroff); systemSoundLayout->addWidget(m_pPoweroffMusicWidget); systemSoundLayout->addWidget(poweroffAndLagout); systemSoundLayout->addWidget(m_pLagoutWidget); systemSoundLayout->addWidget(lagoutAndWakeup); systemSoundLayout->addWidget(m_pWakeupMusicWidget); systemSoundLayout->setSpacing(0); m_pSystemSoundWidget->setLayout(systemSoundLayout); QVBoxLayout *SoundLayout = new QVBoxLayout(m_pAlertSoundFrame); SoundLayout->setContentsMargins(0,0,0,0); SoundLayout->addWidget(m_pAlertSoundSwitchWidget); SoundLayout->addWidget(line1); SoundLayout->addWidget(m_pThemeWidget); SoundLayout->addWidget(line2); SoundLayout->addWidget(m_pVolumeChangeWidget); SoundLayout->addWidget(line3); SoundLayout->addWidget(m_pNotificationWidget); SoundLayout->setSpacing(0); m_pAlertSoundFrame->setLayout(SoundLayout); //进行整体布局 m_pSoundLayout = new QVBoxLayout(this); m_pSoundLayout->addWidget(m_pSoundEffectLabel); m_pSoundLayout->addItem(new QSpacerItem(16,4,QSizePolicy::Fixed)); m_pSoundLayout->addWidget(m_pSystemSoundWidget); m_pSoundLayout->addItem(new QSpacerItem(16,8,QSizePolicy::Fixed)); m_pSoundLayout->addWidget(m_pAlertSoundFrame); this->setLayout(m_pSoundLayout); m_pSoundLayout->setSpacing(0); this->layout()->setContentsMargins(0,0,0,0); } void UkmediaSoundEffectsWidget::alertSoundWidgetShow(bool status) { if (status) { m_pThemeWidget->show(); m_pVolumeChangeWidget->show(); m_pNotificationWidget->show(); line1->show(); line2->show(); line3->show(); } else { m_pThemeWidget->hide(); m_pVolumeChangeWidget->hide(); m_pNotificationWidget->hide(); line1->hide(); line2->hide(); line3->hide(); } } QFrame* UkmediaSoundEffectsWidget::myLine() { QFrame *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; } UkmediaSoundEffectsWidget::~UkmediaSoundEffectsWidget() { } ukui-media/audio/titlelabel.h0000664000175000017500000000207215170052044015124 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifndef TITLE_LABEL_H #define TITLE_LABEL_H #include class TitleLabel : public QLabel { Q_OBJECT public: TitleLabel(QWidget *parent = nullptr); ~TitleLabel(); }; #endif // TITLE_LABEL_H ukui-media/audio/audio.h0000664000175000017500000000377515170052044014117 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifndef AUDIO_H #define AUDIO_H #include #include #include #include #include "ukmedia_main_widget.h" #include #include #include #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(); 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: QString pluginName; int pluginType; QWidget *widget; bool mFirstLoad; private: void initSearchText(); // 搜索翻译 }; #endif // AUDIO_H ukui-media/audio/ukmedia_main_widget.h0000664000175000017500000002714315170054730017003 0ustar fengfeng /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifndef WIDGET_H #define WIDGET_H #include #include "ukmedia_volume_control.h" #include "ukmedia_output_widget.h" #include "ukmedia_input_widget.h" #include "ukmedia_sound_effects_widget.h" #include "ukui_list_widget_item.h" #include #include #include #include #include #include #include extern "C" { #include #include #include #include #include #include #include #include } #include #include #include #include #include #include #include #include #include #include #include #include "ukcc/interface/ukcccommon.h" #include "audio-device-control/ukmedia_device_control_widget.h" #include "ukmedia_settings_widget.h" #include "app-device-control/ukmedia_app_device_ctrl.h" #define DEFAULT_ALERT_ID "__default" #define CUSTOM_THEME_NAME "__custom" #define NO_SOUNDS_THEME_NAME "__no_sounds" #ifdef __GNUC__ #define CA_CLAMP(x, low, high) \ __extension__ ({ typeof(x) _x = (x); \ typeof(low) _low = (low); \ typeof(high) _high = (high); \ ((_x > _high) ? _high : ((_x < _low) ? _low : _x)); \ }) #else #define CA_CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x))) #endif typedef enum { GVC_LEVEL_SCALE_LINEAR, GVC_LEVEL_SCALE_LOG } LevelScale; class UkmediaMainWidget : public QWidget { Q_OBJECT public: UkmediaMainWidget(QWidget *parent = nullptr); ~UkmediaMainWidget(); private: void initWidget(); //初始化界面 void initGsettings(); //初始化gsetting值 void initLabelAlignment(); //初始化label对齐方式 void initButtonSliderStatus(QString key); void dealSlot(); //处理槽函数 void handleBalanceSlider(float balanceVolume); void themeChangeIcons(); int caProplistMergeAp(ca_proplist *p, va_list ap); int caPlayForWidget(UkmediaMainWidget *w, uint32_t id, ...); int caProplistSetForWidget(ca_proplist *p, UkmediaMainWidget *widget); void inputVolumeDarkThemeImage(int value,bool status); void outputVolumeDarkThemeImage(int value,bool status); int getInputVolume(); int getOutputVolume(); // void comboboxCurrentTextInit(); QList listExistsPath(); QString findFreePath(); void addValue(QString name,QString filename); static void updateTheme (UkmediaMainWidget *w); static void setupThemeSelector (UkmediaMainWidget *w); static void soundThemeInDir (UkmediaMainWidget *w,GHashTable *hash,const char *dir); static char *loadIndexThemeName (const char *index,char **parent); static void setComboxForThemeName (UkmediaMainWidget *w,const char *name); static void updateAlertsFromThemeName (UkmediaMainWidget *w,const gchar *name); static void updateAlert (UkmediaMainWidget *w,const char *alert_id); static int getFileType (const char *sound_name,char **linked_name); static char *customThemeDirPath (const char *child); void initAlertSound(const char *path); void addSoundFileInCombobox(QString path, QString dirName); bool resetCustomSoundEffects(QString theme, QString soundFile); static void populateModelFromDir (UkmediaMainWidget *w,const char *dirname); static void populateModelFromFile (UkmediaMainWidget *w,const char *filename); static void populateModelFromNode (UkmediaMainWidget *w,xmlNodePtr node); static xmlChar *xmlGetAndTrimNames (xmlNodePtr node); void playAlretSoundFromPath (UkmediaMainWidget *w,QString path); static gboolean saveAlertSounds (QComboBox *combox,const char *id); static void deleteOldFiles (const char **sounds); static void deleteOneFile (const char *sound_name, const char *pattern); static void deleteDisabledFiles (const char **sounds); static void addCustomFile (const char **sounds, const char *filename); static gboolean cappletFileDeleteRecursive (GFile *file, GError **error); static gboolean directoryDeleteRecursive (GFile *directory, GError **error); static void createCustomTheme (const char *parent); static void customThemeUpdateTime (void); static gboolean customThemeDirIsEmpty (void); void addNoneItem(int soundType); void removeNoneItem(int soundType); void initOutputComboboxItem();//初始化输出的Combobox选项框 void initInputComboboxItem();//初始化输入的Combobox选项框 void findOutputComboboxItem(QString cardName,QString portLabel); //初始化Combobox output/input list widget的选项 void addComboboxAvailableOutputPort(); void addComboboxOutputListWidgetItem(QString portLabel, QString cardName); void deleteNotAvailableComboboxOutputPort();//删除不可用的端口 int indexOfOutputPortInOutputCombobox(QString portName); bool comboboxOutputPortIsNeedAdd(int index,QString name);//port是否需要在Combobox list中添加 bool comboboxOutputPortIsNeedDelete(int index,QString name);//port是否需要在Combobox list删除 void findInputComboboxItem(QString cardName,QString portLabel); //初始化Combobox output/input list widget的选项 void addComboboxAvailableInputPort(); void addComboboxInputListWidgetItem(QString portLabel, QString cardName); //添加input listwidget item void deleteNotAvailableComboboxInputPort(); int indexOfInputPortInInputCombobox(QString portName);//获取输入combobox当前的选项框的index bool comboboxInputPortIsNeedAdd(int index,QString name);//port是否需要在Combobox list中添加 bool comboboxInputPortIsNeedDelete(int index,QString name);//port是否需要在Combobox list删除 int findCardIndex(QString cardName, QMap cardMap);//查找声卡指定的索引 QString findCardName(int index,QMap cardMap); QString findHighPriorityProfile(int index,QString profile); QString findPortSink(int cardIndex,QString portName); QString findPortSource(int cardIndex,QString portName); bool inputComboboxDeviceContainBluetooth(); QString blueCardNameInCombobox();//记录蓝牙声卡名称 bool exitBluetoochDevice(); QString findOutputPortName(int index,QString portLabel); //找到outputPortLabel对应的portName QString findInputPortName(int index,QString portLabel); //找到inputPortLabel对应的portName QString findOutputPortLabel(int index,QString portName); //查找名为portName对应的portLabel QString findInputPortLabel(int index,QString portName); //查找名为portName对应的portLabel void setCardProfile(QString name,QString profile); //设置声卡的配置文件 void setDefaultOutputPortDevice(QString devName,QString portName); //设置默认的输出端口 void setDefaultInputPortDevice(QString devName,QString portName); //设置默认的输入端口 QString findCardActiveProfile(int index); //查找声卡的active profile private Q_SLOTS: void initVoulmeSlider(); //初始化音量滑动条的值 void themeComboxIndexChangedSlot(int index); //主题下拉框改变 void customSoundEffectsSlot(int index); //自定义音效改变 void outputWidgetSliderChangedSlot(int v); //输出音量改变 void outputWidgetSliderChangedSlotInBlue(int value); //蓝牙模式下调节音量特殊处理 void inputWidgetSliderChangedSlot(int v); //输入滑动条更改 void inputMuteButtonSlot(); //输入音量静音控制 void outputMuteButtonSlot(); //输出音量静音控制 void balanceSliderChangedMono(int balanceSliderValue,int sinkVolume ,int sinkIndex); //开启单声道 void balanceSliderChangedSlot(int balanceSliderValue); //平衡值改变 void peakVolumeChangedSlot(double v); //输入等级 void updateCboxDevicePort(); //更新combobox设备端口 void updateComboboxListWidgetItemSlot(); void timeSliderSlot(); void ukuiThemeChangedSlot(const QString &); void onKeyChanged (const QString &); void globalThemeChangedSlot(const QString &); void startupButtonSwitchChangedSlot(bool status); //开机音乐开关 void poweroffButtonSwitchChangedSlot(bool status); //关机音乐开关 void logoutMusicButtonSwitchChangedSlot(bool status); //注销音乐开关 void wakeButtonSwitchChangedSlot(bool status); //唤醒音乐开关 void alertSoundButtonSwitchChangedSlot(bool status); void noiseReductionButtonSwitchChangedSlot(bool status); //降噪开关 void loopbackButtonSwitchChangedSlot(bool status);//侦听开关 void setAllSinkMono(); //开启单声道,遍历sinkMap,将所有双声道sink设置为单声道输出 void monoAudioBtuuonSwitchChangedSlot(bool status); //单声道开关 void volumeIncreaseBtuuonSwitchChangedSlot(bool status); void bootMusicSettingsChanged(const QString &); void cboxoutputListWidgetCurrentRowChangedSlot();//combobox output list widget选项改变 void cboxinputListWidgetCurrentRowChangedSlot();//combobox input list widget选项改变 private: UkmediaInputWidget *m_pInputWidget; UkmediaOutputWidget *m_pOutputWidget; UkmediaSoundEffectsWidget *m_pSoundWidget; UkmediaVolumeControl *m_pVolumeControl; UkmediaSettingsWidget *m_pSettingsWidget; QStringList m_pSoundList; QStringList m_pSoundNameList; QStringList m_pThemeNameList; QStringList m_soundThemeList; QStringList m_soundThemeDirList; QStringList m_soundThemeXmlNameList; QGSettings *m_pSoundSettings; QGSettings *m_pBootSetting; QGSettings *m_pThemeSetting; QGSettings *m_pGlobalThemeSetting; QString mThemeName; QString m_languageEnvStr; bool m_hasMusic; bool firstEnterSystem = true; const gchar* m_privOutputPortLabel = ""; int callBackCount = 0; bool firstEntry = true; bool cboxfirstEntry = true; QMap currentOutputPortLabelMap; QMap currentInputPortLabelMap; QMultiMap currentCboxOutputPortLabelMap; QMultiMap currentCboxInputPortLabelMap; bool updatePort = true; bool setDefaultstream = true; int reconnectTime; QTimer *time; QTimer *timeSlider; bool mousePress = false; bool mouseReleaseState = false; QTimer *timeSliderBlance; bool mousePressBlance = false; bool mouseReleaseStateBlance = false; ca_context* m_caContext = nullptr; QElapsedTimer m_boxRepeatTime; QTimer m_boxoutputRepeatTimer; int m_boxoutputRow = 0; QTimer m_boxinputRepeatTimer; int m_boxinputRow = 0; SystemVersion m_version; }; #endif // WIDGET_H ukui-media/audio/ukui_custom_style.cpp0000664000175000017500000002165115170054730017135 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "ukui_custom_style.h" #include #include #include #include #include #include #include #include #include FixLabel::FixLabel(QWidget *parent): QLabel(parent) { } FixLabel::FixLabel(QString text , QWidget *parent): QLabel(parent) { this->setText(text); } FixLabel::~FixLabel() { } void FixLabel::paintEvent(QPaintEvent *event) { QFontMetrics fontMetrics(this->font()); #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) int fontSize = fontMetrics.horizontalAdvance(fullText); #else int fontSize = fontMetrics.width(fullText); #endif if (fontSize > this->width()) { this->setText(fontMetrics.elidedText(fullText, Qt::ElideRight, this->width()), false); this->setToolTip(fullText); } else { this->setText(fullText, false); this->setToolTip(""); } QLabel::paintEvent(event); } void FixLabel::setText(const QString & text, bool saveTextFlag) { if (saveTextFlag) fullText = text; QLabel::setText(text); } AudioSlider::AudioSlider(QWidget *parent) : KSlider(parent) { Q_UNUSED(parent); setRange(0,100); setSliderType(SmoothSlider); setOrientation(Qt::Horizontal); setFocusPolicy(Qt::StrongFocus); setNodeVisible(false); } void AudioSlider::wheelEvent(QWheelEvent *e) { if (this->value() - blueValue >= 10 || blueValue - this->value() >= 10) { blueValue = this->value(); Q_EMIT blueValueChanged(blueValue); } return KSlider::wheelEvent(e); } void AudioSlider::keyReleaseEvent(QKeyEvent *e) { if (e->key() == Qt::Key_VolumeMute) { qDebug() << "UkmediaVolumeSlider keyReleaseEvent " << e->key(); // 过滤掉快捷键的操作 e->ignore(); // 忽略该事件 return; } //start 长按左键或者下键调低音量 if (e->isAutoRepeat()) { if (this->value() - blueValue >= 10 || blueValue - this->value() >= 10) { blueValue = this->value(); Q_EMIT blueValueChanged(blueValue); } return; } //end if (this->value() != blueValue) { blueValue = this->value(); Q_EMIT blueValueChanged(blueValue); } return KSlider::keyReleaseEvent(e); } void AudioSlider::mouseMoveEvent(QMouseEvent *e) { isMouseWheel = true; if (this->value() - blueValue >= 10 || blueValue - this->value() >= 10) { blueValue = this->value(); Q_EMIT blueValueChanged(blueValue); } return KSlider::mouseMoveEvent(e); } void AudioSlider::mouseReleaseEvent(QMouseEvent *e) { isMouseWheel = false; if (this->value() != blueValue) { blueValue = this->value(); Q_EMIT blueValueChanged(blueValue); } return KSlider::mouseReleaseEvent(e); } AudioSlider::~AudioSlider() { } UkmediaVolumeSlider::UkmediaVolumeSlider(QWidget *parent) : KSlider(parent) { Q_UNUSED(parent); setRange(-100,100); setSingleStep(100); setTickInterval(100); setSliderType(SmoothSlider); setOrientation(Qt::Horizontal); setFocusPolicy(Qt::StrongFocus); this->installEventFilter(this); } void UkmediaVolumeSlider::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 UkmediaVolumeSlider::eventFilter(QObject *watched, QEvent *event) { if (watched == this) { if (event->type() == QEvent::HoverEnter) showTooltip(); } return KSlider::eventFilter(watched, event); } void UkmediaVolumeSlider::initStyleOption(QStyleOptionSlider *option) { KSlider::initStyleOption(option); } UkmediaVolumeSlider::~UkmediaVolumeSlider() { } /** * @brief UkuiButtonDrawSvg::refreshIcon * 刷新图标 */ void UkuiButtonDrawSvg::refreshIcon(QImage img, QColor color) { mImage = img; mColor = color; this->repaint(); } /** * @brief UkuiButtonDrawSvg::paintEvent * 绘制按钮背景透明 */ void UkuiButtonDrawSvg::paintEvent(QPaintEvent *event) { Q_UNUSED(event) QStyleOption opt; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) opt.initFrom(this); #else opt.init(this); #endif QPainter p(this); p.setBrush(QBrush(QColor(0x13,0x13,0x14,0x00))); p.setPen(Qt::NoPen); QPainterPath path; opt.rect.adjust(0,0,0,0); path.addRoundedRect(opt.rect,6,6); p.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); p.drawRoundedRect(opt.rect,6,6); setProperty("blurRegion",QRegion(path.toFillPolygon().toPolygon())); style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); } QRect UkuiButtonDrawSvg::IconGeometry() { QRect res = QRect(QPoint(0,0),QSize(16,16)); res.moveCenter(QRect(0,0,width(),height()).center()); return res; } /** * @brief UkuiButtonDrawSvg::drawIcon * 绘制按钮图标 */ void UkuiButtonDrawSvg::drawIcon(QPaintEvent* e) { Q_UNUSED(e); QPainter painter(this); QRect iconRect = IconGeometry(); if (mImage.size() != iconRect.size()) { mImage = mImage.scaled(iconRect.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); QRect r = mImage.rect(); r.moveCenter(iconRect.center()); iconRect = r; } this->setProperty("fillIconSymbolicColor", true); filledSymbolicColoredPixmap(mImage,mColor); painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); //抗锯齿 + 高比例模糊处理 painter.drawImage(iconRect, mImage); } // 更新音量图标 void UkuiButtonDrawSvg::outputVolumeDarkThemeImage(int value, bool status) { QImage image; QColor color = QColor(0,0,0,216); if (status) { image = QIcon::fromTheme("audio-volume-muted-symbolic").pixmap(24,24).toImage(); } else if (value <= 0) { image = QIcon::fromTheme("audio-volume-muted-symbolic").pixmap(24,24).toImage(); } else if (value > 0 && value <= 33) { image = QIcon::fromTheme("audio-volume-low-symbolic").pixmap(24,24).toImage(); } else if (value >33 && value <= 66) { image = QIcon::fromTheme("audio-volume-medium-symbolic").pixmap(24,24).toImage(); } else { image = QIcon::fromTheme("audio-volume-high-symbolic").pixmap(24,24).toImage(); } refreshIcon(image, color); } bool UkuiButtonDrawSvg::event(QEvent *event) { switch (event->type()) { case QEvent::Paint: drawIcon(static_cast(event)); break; case QEvent::Move: case QEvent::Resize: { QRect rect = IconGeometry(); } break; case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseButtonDblClick: event->accept(); break; default: break; } return QPushButton::event(event); } UkuiButtonDrawSvg::UkuiButtonDrawSvg(QWidget *parent) { Q_UNUSED(parent); } UkuiButtonDrawSvg::~UkuiButtonDrawSvg() { } QPixmap UkuiButtonDrawSvg::filledSymbolicColoredPixmap(QImage &img, QColor &baseColor) { for (int x = 0; x < img.width(); x++) { for (int y = 0; y < img.height(); y++) { auto color = img.pixelColor(x, y); if (color.alpha() > 0) { int hue = color.hue(); if (!qAbs(hue - symbolic_color.hue()) < 10) { color.setRed(baseColor.red()); color.setGreen(baseColor.green()); color.setBlue(baseColor.blue()); img.setPixelColor(x, y, color); } } } } return QPixmap::fromImage(img); } TitleLabel::TitleLabel(QWidget *parent): QLabel(parent) { this ->setContentsMargins(16,0,0,0); } TitleLabel::~TitleLabel() { } ukui-media/audio/audio.ui0000664000175000017500000000206015170052044014267 0ustar fengfeng Audio 0 0 800 710 0 0 16777215 16777215 Audio 0 0 0 32 48 ukui-media/audio/ukmedia_main_widget.cpp0000664000175000017500000041171215170054730017335 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "ukmedia_main_widget.h" #include #include #include #include #include #include #include #include #include #include #include #define GVC_SOUND_SOUND (xmlChar *) "sound" #define GVC_SOUND_NAME (xmlChar *) "name" #define GVC_SOUND_FILENAME (xmlChar *) "filename" #define KEYBINDINGS_CUSTOM_SCHEMA "org.ukui.media.sound" #define KEYBINDINGS_CUSTOM_DIR "/org/ukui/sound/keybindings/" #define SOUND_THEME_DIR "/usr/share/sounds" #define SOUND_FILE_STARTUP_PATH "/usr/share/ukui-media/sounds/startup.ogg" #define SOUND_FILE_SHUTDOWN_PATH "/usr/share/ukui-media/sounds/shutdown.ogg" #define SOUND_FILE_LOGOUT_PATH "/usr/share/ukui-media/sounds/logout.ogg" #define SOUND_FILE_WEAKUP_PATH "/usr/share/ukui-media/sounds/weakup.ogg" #define MAX_CUSTOM_SHORTCUTS 1000 #define FILENAME_KEY "filename" #define NAME_KEY "name" guint appnum = 0; extern bool isCheckBluetoothInput; enum { SOUND_TYPE_UNSET, SOUND_TYPE_OFF, SOUND_TYPE_DEFAULT_FROM_THEME, SOUND_TYPE_BUILTIN, SOUND_TYPE_CUSTOM }; UkmediaMainWidget::UkmediaMainWidget(QWidget *parent) : QWidget(parent), m_languageEnvStr(getenv("LANGUAGE")), m_version(UkmediaCommon::getInstance().getSystemVersion()) { m_pVolumeControl = new UkmediaVolumeControl; // 获取声音gsettings值 if (QGSettings::isSchemaInstalled(KEY_SOUNDS_SCHEMA)) m_pSoundSettings = new QGSettings(KEY_SOUNDS_SCHEMA); // 初始化界面 initWidget(); initLabelAlignment(); // 初始化系统音效主题 setupThemeSelector(this); updateTheme(this); // 初始化自定义音效 initAlertSound(SOUND_THEME_DIR); // 初始化gsetting值 initGsettings(); time = new QTimer(); // 处理槽函数 dealSlot(); } /* * 初始化界面 */ void UkmediaMainWidget::initWidget() { m_pOutputWidget = new UkmediaOutputWidget(); m_pInputWidget = new UkmediaInputWidget(); m_pSoundWidget = new UkmediaSoundEffectsWidget(); m_pSettingsWidget = new UkmediaSettingsWidget(); cboxfirstEntry = true; mThemeName = UKUI_THEME_LIGHT; QVBoxLayout *m_pvLayout = new QVBoxLayout(); m_pvLayout->addWidget(m_pOutputWidget); m_pvLayout->addWidget(m_pInputWidget); m_pvLayout->addWidget(m_pSoundWidget); m_pvLayout->addWidget(m_pSettingsWidget); m_pvLayout->setSpacing(40); m_pvLayout->addStretch(); this->setLayout(m_pvLayout); this->setMinimumWidth(0); this->setMaximumWidth(16777215); this->layout()->setContentsMargins(0,0,0,0); m_pInputWidget->m_pInputLevelProgressBar->setMaximum(101); /* The following functions are not integrated into openkylin. * Therefore, need to hide the corresponding modules */ if (SystemVersion::SYSTEM_VERSION_OKNILE == m_version \ || SystemVersion::SYSTEM_VERSION_KYLIN_2501_V11 == m_version) { m_pOutputWidget->m_pMonoLine->hide(); m_pOutputWidget->m_pMonoAudioWidget->hide(); m_pInputWidget->m_pLoopbackLine->hide(); m_pInputWidget->m_pLoopBackWidget->hide(); m_pSettingsWidget->hide(); } } QList UkmediaMainWidget::listExistsPath() { char ** childs; int len; DConfClient * client = dconf_client_new(); childs = dconf_client_list (client, KEYBINDINGS_CUSTOM_DIR, &len); g_object_unref (client); QList vals; for (int i = 0; childs[i] != NULL; i++){ if (dconf_is_rel_dir (childs[i], NULL)){ char * val = g_strdup (childs[i]); vals.append(val); } } g_strfreev (childs); return vals; } QString UkmediaMainWidget::findFreePath(){ int i = 0; char * dir; bool found; QList existsdirs; existsdirs = listExistsPath(); for (; i < MAX_CUSTOM_SHORTCUTS; i++){ found = true; dir = QString("custom%1/").arg(i).toLatin1().data(); for (int j = 0; j < existsdirs.count(); j++) if (!g_strcmp0(dir, existsdirs.at(j))){ found = false; break; } if (found) break; } if (i == MAX_CUSTOM_SHORTCUTS){ qDebug() << "Keyboard Shortcuts" << "Too many custom shortcuts"; return ""; } return QString("%1%2").arg(KEYBINDINGS_CUSTOM_DIR).arg(QString(dir)); } void UkmediaMainWidget::addValue(QString name,QString filename) { //在创建setting表时,先判断是否存在该设置,存在时不创建 QList existsPath = listExistsPath(); for (char * path : existsPath) { char * prepath = QString(KEYBINDINGS_CUSTOM_DIR).toLatin1().data(); char * allpath = g_strconcat(prepath, path, nullptr); if (allpath == nullptr) { g_warning("Memory allocation failed for allpath"); continue; } const QByteArray ba(KEYBINDINGS_CUSTOM_SCHEMA); const QByteArray bba(allpath); if(QGSettings::isSchemaInstalled(ba)) { QGSettings * settings = new QGSettings(ba, bba); QString filenameStr = settings->get(FILENAME_KEY).toString(); QString nameStr = settings->get(NAME_KEY).toString(); g_warning("full path: %s", allpath); qDebug() << filenameStr << FILENAME_KEY <set(FILENAME_KEY, filename); settings->set(NAME_KEY, name); } } void UkmediaMainWidget::initLabelAlignment() { if (!UkmediaCommon::getInstance().isHWKLanguageEnv(m_languageEnvStr)) { m_pInputWidget->setLabelAlignment(Qt::AlignCenter); m_pOutputWidget->setLabelAlignment(Qt::AlignCenter); } else { m_pInputWidget->setLabelAlignment(Qt::AlignLeft | Qt::AlignCenter); m_pOutputWidget->setLabelAlignment(Qt::AlignLeft | Qt::AlignCenter); } } /* * 初始化gsetting */ void UkmediaMainWidget::initGsettings() { //获取声音gsettings值 bool status; if (m_pSoundSettings->keys().contains("eventSounds")) { status = m_pSoundSettings->get(EVENT_SOUNDS_KEY).toBool(); m_pSoundWidget->m_pAlertSoundSwitchButton->setChecked(status); m_pSoundWidget->alertSoundWidgetShow(status);//初始化提示音窗口部件是否显示 } if (m_pSoundSettings->keys().contains("dnsNoiseReduction")) { status = m_pSoundSettings->get(DNS_NOISE_REDUCTION).toBool(); m_pInputWidget->m_pNoiseReducteButton->setChecked(status); } if (m_pSoundSettings->keys().contains("loopback")) { status = m_pSoundSettings->get(LOOP_BACK).toBool(); m_pInputWidget->m_pLoopBackButton->setChecked(status); } if (m_pSoundSettings->keys().contains("monoAudio")) { status = m_pSoundSettings->get(MONO_AUDIO).toBool(); m_pOutputWidget->m_pMonoAudioButton->setChecked(status); //需求31268待2501再合入 #if 1 if (m_pVolumeControl->outputPortMap.count() == 0) { m_pOutputWidget->m_pOpBalanceSlider->setValue(0); m_pOutputWidget->m_pOpBalanceSlider->setDisabled(true); } else { if (status) { m_pOutputWidget->m_pOpBalanceSlider->setValue(0); m_pOutputWidget->m_pOpBalanceSlider->setDisabled(true); } else { m_pOutputWidget->m_pOpBalanceSlider->setDisabled(false); } } #elif 0 if (status) { m_pOutputWidget->m_pOpBalanceSlider->setValue(0); m_pOutputWidget->m_pOpBalanceSlider->setDisabled(true); } else { m_pOutputWidget->m_pOpBalanceSlider->setDisabled(false); } #endif } if (m_pSoundSettings->keys().contains("volumeIncrease")) { status = m_pSoundSettings->get(VOLUME_INCREASE).toBool(); m_pOutputWidget->m_pVolumeIncreaseButton->setChecked(status); m_pOutputWidget->setVolumeSliderRange(status); } if(m_pSoundSettings->keys().contains("themeName")) { QString soundThemeStr = m_pSoundSettings->get(SOUND_THEME_KEY).toString(); int index = m_pSoundWidget->m_pSoundThemeCombobox->findData(soundThemeStr); m_pSoundWidget->m_pSoundThemeCombobox->setCurrentIndex(index); } if (m_pSoundSettings->keys().contains("customTheme")) { if (m_pSoundSettings->get(SOUND_CUSTOM_THEME_KEY).toBool()) { m_pSoundWidget->m_pSoundThemeCombobox->blockSignals(true); m_pSoundWidget->m_pSoundThemeCombobox->setCurrentText(tr("Custom")); m_pSoundWidget->m_pSoundThemeCombobox->blockSignals(false); } } if (m_pSoundSettings->keys().contains("notificationGeneral")) { QString notification = m_pSoundSettings->get(NOTIFICATION_GENGERAL).toString(); int index = m_pSoundWidget->m_pNotificationCombobox->findData(notification); m_pSoundWidget->m_pNotificationCombobox->setCurrentIndex(index); } if (m_pSoundSettings->keys().contains("audioVolumeChange")) { QString volumeControl = m_pSoundSettings->get(AUDIO_VOLUME_CHANGE).toString(); int index = m_pSoundWidget->m_pVolumeChangeCombobox->findData(volumeControl); m_pSoundWidget->m_pVolumeChangeCombobox->setCurrentIndex(index); } connect(m_pSoundSettings, SIGNAL(changed(const QString &)),this,SLOT(onKeyChanged(const QString &))); // 检测系统全局主题 if (QGSettings::isSchemaInstalled(UKUI_GLOBALTHEME_SETTINGS)) { m_pGlobalThemeSetting = new QGSettings(UKUI_GLOBALTHEME_SETTINGS); QString globalTheme = m_pGlobalThemeSetting->get(GLOBAL_THEME_NAME).toString(); if (globalTheme != "custom") { m_pSoundSettings->set(SOUND_THEME_KEY, globalTheme); m_pSoundSettings->set(SOUND_CUSTOM_THEME_KEY, false); } connect(m_pGlobalThemeSetting, SIGNAL(changed(const QString &)), this, SLOT(globalThemeChangedSlot(QString))); } //检测系统主题 if (QGSettings::isSchemaInstalled(UKUI_THEME_SETTING)){ m_pThemeSetting = new QGSettings(UKUI_THEME_SETTING); if (m_pThemeSetting->keys().contains("styleName")) { mThemeName = m_pThemeSetting->get(UKUI_THEME_NAME).toString(); } connect(m_pThemeSetting, SIGNAL(changed(const QString &)),this,SLOT(ukuiThemeChangedSlot(const QString &))); } //检测设计开关机音乐 if (QGSettings::isSchemaInstalled(UKUI_SWITCH_SETTING)) { m_pBootSetting = new QGSettings(UKUI_SWITCH_SETTING); // if (QGSettings::isSchemaInstalled(KEY_SOUNDS_SCHEMA)){ // if (m_pSoundSettings->keys().contains("startupMusic")) { // m_pSoundWidget->m_pStartupButton->setChecked(m_pSoundSettings->get(STARTUP_MUSIC).toBool());//开机音乐 // } // } if (m_pBootSetting->keys().contains("startupMusic")) { bool startup = m_pBootSetting->get(UKUI_STARTUP_MUSIC_KEY).toBool();//开机音乐 m_pSoundWidget->m_pStartupButton->setChecked(startup); } if (m_pBootSetting->keys().contains("poweroffMusic")) { bool poweroff = m_pBootSetting->get(UKUI_POWEROFF_MUSIC_KEY).toBool();//关机音乐 m_pSoundWidget->m_pPoweroffButton->setChecked(poweroff); } if (m_pBootSetting->keys().contains("logoutMusic")) { bool logout = m_pBootSetting->get(UKUI_LOGOUT_MUSIC_KEY).toBool();//注销音乐 m_pSoundWidget->m_pLogoutButton->setChecked(logout); } if (m_pBootSetting->keys().contains("weakupMusic")) { bool m_hasMusic = m_pBootSetting->get(UKUI_WAKEUP_MUSIC_KEY).toBool();//休眠音乐 m_pSoundWidget->m_pWakeupMusicButton->setChecked(m_hasMusic); } connect(m_pBootSetting,SIGNAL(changed(const QString &)),this,SLOT(bootMusicSettingsChanged(const QString &))); } } void UkmediaMainWidget::initButtonSliderStatus(QString key) { if (key == "eventSounds") { if (m_pSoundSettings->keys().contains("eventSounds")) m_pSoundWidget->m_pAlertSoundSwitchButton->setChecked(m_pSoundSettings->get(EVENT_SOUNDS_KEY).toBool()); } if (key == "dnsNoiseReduction") { if (m_pSoundSettings->keys().contains("dnsNoiseReduction")) m_pInputWidget->m_pNoiseReducteButton->setChecked(m_pSoundSettings->get(DNS_NOISE_REDUCTION).toBool()); } else if (key == "loopback") { if (m_pSoundSettings->keys().contains("loopback")) m_pInputWidget->m_pLoopBackButton->setChecked(m_pSoundSettings->get(LOOP_BACK).toBool()); } else if (key == "volumeIncrease") { if (m_pSoundSettings->keys().contains("volumeIncrease")) { bool status = m_pSoundSettings->get(VOLUME_INCREASE).toBool(); m_pOutputWidget->m_pVolumeIncreaseButton->setChecked(status); m_pOutputWidget->setVolumeSliderRange(status); } } else if (key == "monoAudio") { if (m_pSoundSettings->keys().contains("monoAudio")) { m_pOutputWidget->m_pMonoAudioButton->setChecked(m_pSoundSettings->get(MONO_AUDIO).toBool()); } } else if (key == "themeName") { int index = m_pSoundWidget->m_pSoundThemeCombobox->findData(m_pSoundSettings->get(SOUND_THEME_KEY).toString()); m_pSoundWidget->m_pSoundThemeCombobox->blockSignals(true); m_pSoundWidget->m_pSoundThemeCombobox->setCurrentIndex(index); m_pSoundWidget->m_pSoundThemeCombobox->blockSignals(false); } else if (key == "customTheme") { if (m_pSoundSettings->get(SOUND_CUSTOM_THEME_KEY).toBool()) m_pSoundWidget->m_pSoundThemeCombobox->setCurrentText(tr("Custom")); } else if (key == "audioVolumeChange") { int index = m_pSoundWidget->m_pVolumeChangeCombobox->findData(m_pSoundSettings->get(AUDIO_VOLUME_CHANGE).toString()); m_pSoundWidget->m_pVolumeChangeCombobox->setCurrentIndex(index); } else if (key == "notificationGeneral") { int index = m_pSoundWidget->m_pNotificationCombobox->findData(m_pSoundSettings->get(NOTIFICATION_GENGERAL).toString()); m_pSoundWidget->m_pNotificationCombobox->setCurrentIndex(index); } } /* * 处理槽函数 */ void UkmediaMainWidget::dealSlot() { QTimer::singleShot(500, this, SLOT(initVoulmeSlider())); m_boxRepeatTime.start(); // 输出模块槽函数 m_boxoutputRepeatTimer.setInterval(1000 * 1); m_boxoutputRepeatTimer.setSingleShot(true); connect(m_pOutputWidget->m_pDeviceSelectBox,QOverload::of(&QComboBox::currentIndexChanged),this,[=](int index){ m_boxoutputRow = index; if (m_boxRepeatTime.elapsed() < (1000 * 1)) { //start 频繁点击重置开启 m_boxoutputRepeatTimer.stop(); m_boxoutputRepeatTimer.start(); //end } else { //start 单点一次直接响应 cboxoutputListWidgetCurrentRowChangedSlot(); //end } m_boxRepeatTime.start(); }); connect(&m_boxoutputRepeatTimer,&QTimer::timeout,this,&UkmediaMainWidget::cboxoutputListWidgetCurrentRowChangedSlot); connect(m_pOutputWidget->m_pOutputIconBtn,SIGNAL(clicked()),this,SLOT(outputMuteButtonSlot())); connect(m_pOutputWidget->m_pOpVolumeSlider,SIGNAL(valueChanged(int)),this,SLOT(outputWidgetSliderChangedSlot(int))); connect(m_pOutputWidget->m_pOpVolumeSlider,SIGNAL(blueValueChanged(int)),this,SLOT(outputWidgetSliderChangedSlotInBlue(int))); connect(m_pOutputWidget->m_pVolumeIncreaseButton,SIGNAL(stateChanged(bool)),this,SLOT(volumeIncreaseBtuuonSwitchChangedSlot(bool))); connect(m_pOutputWidget->m_pOpBalanceSlider,SIGNAL(valueChanged(int)),this,SLOT(balanceSliderChangedSlot(int))); connect(m_pOutputWidget->m_pMonoAudioButton,SIGNAL(stateChanged(bool)),this,SLOT(monoAudioBtuuonSwitchChangedSlot(bool))); // 输入模块槽函数 m_boxinputRepeatTimer.setInterval(1000*1); m_boxinputRepeatTimer.setSingleShot(true); connect(m_pInputWidget->m_pInputDeviceSelectBox,QOverload::of(&QComboBox::currentIndexChanged),this,[=](int index){ m_boxinputRow = index; if (m_boxRepeatTime.elapsed() < (1000 * 1)) { //start 频繁点击重置开启 m_boxinputRepeatTimer.stop(); m_boxinputRepeatTimer.start(); //end } else { //start 单点一次直接响应 cboxinputListWidgetCurrentRowChangedSlot(); //end } m_boxRepeatTime.start(); }); connect(&m_boxinputRepeatTimer,&QTimer::timeout,this,&UkmediaMainWidget::cboxinputListWidgetCurrentRowChangedSlot); connect(m_pInputWidget->m_pInputIconBtn,SIGNAL(clicked()),this,SLOT(inputMuteButtonSlot())); connect(m_pInputWidget->m_pIpVolumeSlider,SIGNAL(valueChanged(int)),this,SLOT(inputWidgetSliderChangedSlot(int))); connect(m_pVolumeControl,SIGNAL(peakChangedSignal(double)),this,SLOT(peakVolumeChangedSlot(double))); // 输入反馈槽函数 connect(m_pInputWidget->m_pNoiseReducteButton,SIGNAL(stateChanged(bool)),this,SLOT(noiseReductionButtonSwitchChangedSlot(bool))); connect(m_pInputWidget->m_pLoopBackButton,SIGNAL(stateChanged(bool)),this,SLOT(loopbackButtonSwitchChangedSlot(bool))); // 系统音效模块槽函数 connect(m_pSoundWidget->m_pStartupButton,SIGNAL(stateChanged(bool)),this,SLOT(startupButtonSwitchChangedSlot(bool))); connect(m_pSoundWidget->m_pPoweroffButton,SIGNAL(stateChanged(bool)),this,SLOT(poweroffButtonSwitchChangedSlot(bool))); connect(m_pSoundWidget->m_pLogoutButton,SIGNAL(stateChanged(bool)),this,SLOT(logoutMusicButtonSwitchChangedSlot(bool))); connect(m_pSoundWidget->m_pWakeupMusicButton,SIGNAL(stateChanged(bool)),this,SLOT(wakeButtonSwitchChangedSlot(bool))); connect(m_pSoundWidget->m_pAlertSoundSwitchButton,SIGNAL(stateChanged(bool)),this,SLOT(alertSoundButtonSwitchChangedSlot(bool))); connect(m_pSoundWidget->m_pSoundThemeCombobox,SIGNAL(currentIndexChanged(int)),this,SLOT(themeComboxIndexChangedSlot(int))); connect(m_pSoundWidget->m_pVolumeChangeCombobox,SIGNAL(currentIndexChanged(int)),this,SLOT(customSoundEffectsSlot(int))); connect(m_pSoundWidget->m_pNotificationCombobox,SIGNAL(currentIndexChanged(int)),this,SLOT(customSoundEffectsSlot(int))); // 更新设备端口 connect(m_pVolumeControl,SIGNAL(updatePortSignal()),this,SLOT(updateCboxDevicePort())); connect(m_pVolumeControl,SIGNAL(deviceChangedSignal()),this,SLOT(updateComboboxListWidgetItemSlot())); timeSlider = new QTimer(this); connect(timeSlider,SIGNAL(timeout()),this,SLOT(timeSliderSlot())); //判断设备是否为单声道来显示/隐藏 单声道模块 connect(m_pVolumeControl,&UkmediaVolumeControl::updateMonoAudio,this,[=](bool show){ if (SystemVersion::SYSTEM_VERSION_OKNILE == m_version \ || SystemVersion::SYSTEM_VERSION_KYLIN_2501_V11 == m_version) { return; } if (show) { m_pOutputWidget->m_pAudioBlanceWidget->show(); m_pOutputWidget->m_pMonoAudioWidget->show(); } else { m_pOutputWidget->m_pAudioBlanceWidget->hide(); m_pOutputWidget->m_pMonoAudioWidget->hide(); } qDebug() << "mono audio show:" << show; }); //bug#254769/256035 输入设备为内置mic或为空时,需要隐藏侦听功能(控制面板代码实现)并卸载侦听模块 connect(m_pVolumeControl,&UkmediaVolumeControl::updateLoopBack,this,[=](bool show){ if (ukcc::UkccCommon::isOpenkylin()) return; if (show) { m_pInputWidget->m_pLoopbackLine->show(); m_pInputWidget->m_pLoopBackWidget->show(); } else { m_pInputWidget->m_pLoopBackButton->setChecked(false); m_pInputWidget->m_pLoopbackLine->hide(); m_pInputWidget->m_pLoopBackWidget->hide(); } qDebug() << "loopback show:" << show; }); connect(m_pVolumeControl,&UkmediaVolumeControl::updateSinkMute,this,[=](bool state) { themeChangeIcons(); }); connect(m_pVolumeControl,&UkmediaVolumeControl::updateSourceMute,this,[=](bool state) { themeChangeIcons(); }); //切换输出设备或者音量改变时需要同步更新音量 connect(m_pVolumeControl,&UkmediaVolumeControl::updateVolume,this,[=](int value, bool state){ qDebug() << "Sink Volume Changed" << value << state; bool isChecked = m_pOutputWidget->m_pVolumeIncreaseButton->isChecked(); if(!isChecked && value > PA_VOLUME_NORMAL){ m_pVolumeControl->setSinkVolume(m_pVolumeControl->sinkIndex,PA_VOLUME_NORMAL); return; } int mediaVolume = UkmediaCommon::getInstance().paVolumeToMediaVolume(value); QString percent = QString::number(mediaVolume); float balanceVolume = m_pVolumeControl->getBalanceVolume(); m_pOutputWidget->m_pOpVolumePercentLabel->setText(percent+"%"); m_pOutputWidget->m_pOpVolumeSlider->blockSignals(true); m_pOutputWidget->m_pOpBalanceSlider->blockSignals(true); handleBalanceSlider(balanceVolume); m_pOutputWidget->m_pOpVolumeSlider->setValue(mediaVolume); m_pOutputWidget->m_pOpVolumeSlider->blockSignals(false); m_pOutputWidget->m_pOpBalanceSlider->blockSignals(false); initOutputComboboxItem(); themeChangeIcons(); }); connect(m_pVolumeControl,&UkmediaVolumeControl::updateSourceVolume,this,[=](int value, bool state){ qDebug() << "Source Volume Changed" << value << state; int mediaVolume = UkmediaCommon::getInstance().paVolumeToMediaVolume(value); QString percent = QString::number(mediaVolume); m_pInputWidget->m_pIpVolumePercentLabel->setText(percent+"%"); m_pInputWidget->m_pIpVolumeSlider->blockSignals(true); m_pInputWidget->m_pIpVolumeSlider->setValue(mediaVolume); m_pInputWidget->m_pIpVolumeSlider->blockSignals(false); //当所有可用的输入设备全部移除,台式机才会出现该情况 if (strstr(m_pVolumeControl->defaultSourceName,"monitor")) { m_pInputWidget->m_pInputLevelProgressBar->setValue(0); } //start 无输入设备时需要自动关闭侦听,此操作要在pa的回调中执行,否则会误关闭 if (m_pVolumeControl->sourcePortName =="") { m_pSoundSettings->set(LOOP_BACK,false); } //end initInputComboboxItem(); themeChangeIcons(); }); //音频设备管控窗口 connect(m_pSettingsWidget->m_pDevControlDetailsBtn, &QPushButton::clicked, this, [=](){ UkmediaDevControlWidget *m_pDevControlWidget = new UkmediaDevControlWidget(this); m_pDevControlWidget->setFixedSize(SETTINGSWIDGET_WIDTH, SETTINGSWIDGET_HEIGHT); m_pDevControlWidget->setAttribute(Qt::WA_DeleteOnClose); m_pDevControlWidget->setWindowModality(Qt::WindowModality::WindowModal); m_pDevControlWidget->show(); }); // 应用声音管控窗口 connect(m_pSettingsWidget->m_pAppSoundDetailsBtn, &QPushButton::clicked, this, [=](){ UkmediaAppCtrlWidget *appCtrlWidget = new UkmediaAppCtrlWidget(this); appCtrlWidget->setFixedSize(SETTINGSWIDGET_WIDTH, SETTINGSWIDGET_HEIGHT); appCtrlWidget->setAttribute(Qt::WA_DeleteOnClose); appCtrlWidget->setWindowModality(Qt::WindowModality::WindowModal); appCtrlWidget->show(); }); } /* * 初始化滑动条的值 */ void UkmediaMainWidget::initVoulmeSlider() { int sinkVolume = UkmediaCommon::getInstance().paVolumeToMediaVolume(m_pVolumeControl->getSinkVolume()); int sourceVolume = UkmediaCommon::getInstance().paVolumeToMediaVolume(m_pVolumeControl->getSourceVolume()); float balanceVolume = m_pVolumeControl->getBalanceVolume(); m_pOutputWidget->m_pOpVolumePercentLabel->setText(QString::number(sinkVolume) + "%"); m_pInputWidget->m_pIpVolumePercentLabel->setText(QString::number(sourceVolume) + "%"); m_pOutputWidget->m_pOpVolumeSlider->blockSignals(true); m_pOutputWidget->m_pOpBalanceSlider->blockSignals(true); m_pInputWidget->m_pIpVolumeSlider->blockSignals(true); handleBalanceSlider(balanceVolume); m_pOutputWidget->m_pOpVolumeSlider->setValue(sinkVolume); m_pInputWidget->m_pIpVolumeSlider->setValue(sourceVolume); m_pOutputWidget->m_pOpVolumeSlider->blockSignals(false); m_pOutputWidget->m_pOpBalanceSlider->blockSignals(false); m_pInputWidget->m_pIpVolumeSlider->blockSignals(false); initOutputComboboxItem(); initInputComboboxItem(); themeChangeIcons(); this->update(); } void UkmediaMainWidget::handleBalanceSlider(float balanceVolume) { bool status; if (m_pSoundSettings->keys().contains("monoAudio")) { status = m_pSoundSettings->get(MONO_AUDIO).toBool(); m_pOutputWidget->m_pMonoAudioButton->blockSignals(true); m_pOutputWidget->m_pMonoAudioButton->setChecked(status); m_pOutputWidget->m_pMonoAudioButton->blockSignals(false); //需求31268待2501再合入 #if 1 if (m_pVolumeControl->outputPortMap.count() == 0) { m_pOutputWidget->m_pOpBalanceSlider->setValue(0); m_pOutputWidget->m_pOpBalanceSlider->setDisabled(true); } else { if (status) { m_pOutputWidget->m_pOpBalanceSlider->setDisabled(false); m_pOutputWidget->m_pOpBalanceSlider->setValue(0); //setAllSinkMono(); #bug232332 m_pOutputWidget->m_pOpBalanceSlider->setDisabled(true); } else { m_pOutputWidget->m_pOpBalanceSlider->setDisabled(false); m_pOutputWidget->m_pOpBalanceSlider->setValue(balanceVolume*100); } } #elif 0 if (status) { m_pOutputWidget->m_pOpBalanceSlider->setDisabled(false); m_pOutputWidget->m_pOpBalanceSlider->setValue(0); //setAllSinkMono(); #bug232332 m_pOutputWidget->m_pOpBalanceSlider->setDisabled(true); } else { m_pOutputWidget->m_pOpBalanceSlider->setDisabled(false); m_pOutputWidget->m_pOpBalanceSlider->setValue(balanceVolume*100); } #endif } } void UkmediaMainWidget::themeChangeIcons() { int nOutputValue = UkmediaCommon::getInstance().paVolumeToMediaVolume(m_pVolumeControl->getSinkVolume()); int nInputValue = UkmediaCommon::getInstance().paVolumeToMediaVolume(m_pVolumeControl->getSourceVolume()); bool outputStatus = m_pVolumeControl->getSinkMute(); bool inputStatus = m_pVolumeControl->getSourceMute(); inputVolumeDarkThemeImage(nInputValue,inputStatus); outputVolumeDarkThemeImage(nOutputValue,outputStatus); } /* * 初始化combobox的值 */ #if 0 void UkmediaMainWidget::comboboxCurrentTextInit() { QList existsPath = listExistsPath(); for (char * path : existsPath) { char * prepath = QString(KEYBINDINGS_CUSTOM_DIR).toLatin1().data(); char * allpath = strcat(prepath, path); const QByteArray ba(KEYBINDINGS_CUSTOM_SCHEMA); const QByteArray bba(allpath); if(QGSettings::isSchemaInstalled(ba)) { QGSettings * settings = new QGSettings(ba, bba); QString filenameStr = settings->get(FILENAME_KEY).toString(); QString nameStr = settings->get(NAME_KEY).toString(); int index = 0; for (int i=0;icount();i++) { QString str = m_pSoundList->at(i); if (str.contains(filenameStr,Qt::CaseSensitive)) { index = i; break; } } if (nameStr == "alert-sound") { QString displayName = m_pSoundNameList->at(index); m_pSoundWidget->m_pAlertSoundCombobox->setCurrentText(displayName); continue; } if (nameStr == "window-close") { QString displayName = m_pSoundNameList->at(index); continue; } else if (nameStr == "volume-changed") { QString displayName = m_pSoundNameList->at(index); m_pSoundWidget->m_pVolumeChangeCombobox->setCurrentText(displayName); continue; } else if (nameStr == "system-setting") { QString displayName = m_pSoundNameList->at(index); continue; } } else { continue; } } } #endif /* * 是否播放开机音乐 */ void UkmediaMainWidget::startupButtonSwitchChangedSlot(bool status) { // if (QGSettings::isSchemaInstalled(KEY_SOUNDS_SCHEMA)){ // if (m_pSoundSettings->keys().contains("startupMusic")) { // m_pSoundSettings->set(STARTUP_MUSIC,status); // } // } bool bBootStatus = true; if (m_pBootSetting->keys().contains("startupMusic")) { bBootStatus = m_pBootSetting->get(UKUI_STARTUP_MUSIC_KEY).toBool(); if (bBootStatus != status) { m_pBootSetting->set(UKUI_STARTUP_MUSIC_KEY,status); } } m_pSoundWidget->m_pStartupButton->setObjectName("m_pSoundWidget->m_pStartupButton"); ukcc::UkccCommon::buriedSettings("Audio", m_pSoundWidget->m_pStartupButton->objectName(), QString("settings"),status?"true":"false"); if (status) { playAlretSoundFromPath(this, SOUND_FILE_STARTUP_PATH); } } /* * 是否播放关机音乐 */ void UkmediaMainWidget::poweroffButtonSwitchChangedSlot(bool status) { bool bBootStatus = true; if (m_pBootSetting->keys().contains("poweroffMusic")) { bBootStatus = m_pBootSetting->get(UKUI_POWEROFF_MUSIC_KEY).toBool(); if (bBootStatus != status) { m_pBootSetting->set(UKUI_POWEROFF_MUSIC_KEY,status); } } m_pSoundWidget->m_pPoweroffButton->setObjectName("m_pSoundWidget->m_pPoweroffButton"); ukcc::UkccCommon::buriedSettings("Audio", m_pSoundWidget->m_pPoweroffButton->objectName(), QString("settings"),status?"true":"false"); if (status) { playAlretSoundFromPath(this, SOUND_FILE_SHUTDOWN_PATH); } } /* * 是否播放注销音乐 */ void UkmediaMainWidget::logoutMusicButtonSwitchChangedSlot(bool status) { bool bBootStatus = true; if (m_pBootSetting->keys().contains("logoutMusic")) { bBootStatus = m_pBootSetting->get(UKUI_LOGOUT_MUSIC_KEY).toBool(); if (bBootStatus != status) { m_pBootSetting->set(UKUI_LOGOUT_MUSIC_KEY,status); } } m_pSoundWidget->m_pLogoutButton->setObjectName("m_pSoundWidget->m_pLogoutButton"); ukcc::UkccCommon::buriedSettings("Audio", m_pSoundWidget->m_pLogoutButton->objectName(), QString("settings"),status?"true":"false"); if (status) { playAlretSoundFromPath(this, SOUND_FILE_LOGOUT_PATH); } } /* * 是否播放唤醒音乐 */ void UkmediaMainWidget::wakeButtonSwitchChangedSlot(bool status) { bool bBootStatus = true; if (m_pBootSetting->keys().contains("weakupMusic")) { bBootStatus = m_pBootSetting->get(UKUI_WAKEUP_MUSIC_KEY).toBool(); if (bBootStatus != status) { m_pBootSetting->set(UKUI_WAKEUP_MUSIC_KEY,status); } } m_pSoundWidget->m_pWakeupMusicButton->setObjectName("m_pSoundWidget->m_pWakeupMusicButton"); ukcc::UkccCommon::buriedSettings("Audio", m_pSoundWidget->m_pWakeupMusicButton->objectName(), QString("settings"),status?"true":"false"); if (status) { playAlretSoundFromPath(this, SOUND_FILE_WEAKUP_PATH); } } /* * 提示音的开关 */ void UkmediaMainWidget::alertSoundButtonSwitchChangedSlot(bool status) { if (QGSettings::isSchemaInstalled(KEY_SOUNDS_SCHEMA)){ if (m_pSoundSettings->keys().contains("eventSounds")) { m_pSoundSettings->set(EVENT_SOUNDS_KEY,status); } } m_pSoundWidget->alertSoundWidgetShow(status); m_pSoundWidget->m_pAlertSoundSwitchButton->setObjectName("m_pSoundWidget->m_pAlertSoundSwitchButton"); ukcc::UkccCommon::buriedSettings("Audio", m_pSoundWidget->m_pAlertSoundSwitchButton->objectName(), QString("settings"),status?"true":"false"); } /* * 开启单声道,遍历sinkMap,将所有双声道sink设置为单声道输出 */ void UkmediaMainWidget::setAllSinkMono() { QMap::iterator it; for(it=m_pVolumeControl->sinkMap.begin(); it!=m_pVolumeControl->sinkMap.end();) { int sinkVolume; if (it.value().volume.channels >= 2) sinkVolume = MAX(it.value().volume.values[0], it.value().volume.values[1]); else sinkVolume = it.value().volume.values[0]; balanceSliderChangedMono(0, sinkVolume, it.key()); ++it; } } void UkmediaMainWidget::monoAudioBtuuonSwitchChangedSlot(bool status) { if (QGSettings::isSchemaInstalled(KEY_SOUNDS_SCHEMA)){ if (m_pSoundSettings->keys().contains("monoAudio")) { m_pSoundSettings->set(MONO_AUDIO,status); //需求31268待2501再合入 #if 1 if (m_pVolumeControl->outputPortMap.count() == 0) { m_pOutputWidget->m_pOpBalanceSlider->setValue(0); m_pOutputWidget->m_pOpBalanceSlider->setDisabled(true); } else { if(status){ m_pOutputWidget->m_pOpBalanceSlider->setValue(0); setAllSinkMono(); m_pOutputWidget->m_pOpBalanceSlider->setDisabled(true); } else{ float balanceVolume = m_pVolumeControl->getBalanceVolume(); m_pOutputWidget->m_pOpBalanceSlider->setDisabled(false); m_pOutputWidget->m_pOpBalanceSlider->setValue(balanceVolume*100); } } #elif 0 if (status) { m_pOutputWidget->m_pOpBalanceSlider->setValue(0); setAllSinkMono(); m_pOutputWidget->m_pOpBalanceSlider->setDisabled(true); } else { float balanceVolume = m_pVolumeControl->getBalanceVolume(); m_pOutputWidget->m_pOpBalanceSlider->setDisabled(false); m_pOutputWidget->m_pOpBalanceSlider->setValue(balanceVolume*100); } #endif } } m_pOutputWidget->m_pMonoAudioButton->setObjectName("m_pOutputWidget->m_pMonoAudioButton"); ukcc::UkccCommon::buriedSettings("Audio",m_pOutputWidget->m_pMonoAudioButton->objectName(), QString("settings"),status?"true":"false"); } void UkmediaMainWidget::noiseReductionButtonSwitchChangedSlot(bool status) { if (QGSettings::isSchemaInstalled(KEY_SOUNDS_SCHEMA)){ if (m_pSoundSettings->keys().contains("dnsNoiseReduction")) { m_pSoundSettings->set(DNS_NOISE_REDUCTION,status); } } m_pInputWidget->m_pNoiseReducteButton->setObjectName("m_pInputWidget->m_pNoiseReducteButton"); ukcc::UkccCommon::buriedSettings("Audio",m_pInputWidget->m_pNoiseReducteButton->objectName(), QString("settings"),status?"true":"false"); } void UkmediaMainWidget::loopbackButtonSwitchChangedSlot(bool status) { if (QGSettings::isSchemaInstalled(KEY_SOUNDS_SCHEMA)){ if (m_pSoundSettings->keys().contains("loopback")) { m_pSoundSettings->set(LOOP_BACK,status); } } m_pInputWidget->m_pLoopBackButton->setObjectName("m_pInputWidget->m_pLoopBackButton"); ukcc::UkccCommon::buriedSettings("Audio",m_pInputWidget->m_pLoopBackButton->objectName(), QString("settings"),status?"true":"false"); } void UkmediaMainWidget::volumeIncreaseBtuuonSwitchChangedSlot(bool status) { // 1. Refresh UI (Tips: UKCC Audio only refresh UI, Volume adjustment is done by ukui-media) m_pOutputWidget->setVolumeSliderRange(status); // 2. Update gsetting value if (QGSettings::isSchemaInstalled(KEY_SOUNDS_SCHEMA) && m_pSoundSettings->keys().contains("volumeIncrease")) m_pSoundSettings->set(VOLUME_INCREASE, status); // 3. Data burial point m_pOutputWidget->m_pVolumeIncreaseButton->setObjectName("m_pOutputWidget->m_pVolumeIncreaseButton"); ukcc::UkccCommon::buriedSettings("Audio", m_pOutputWidget->m_pVolumeIncreaseButton->objectName(), QString("settings"), status ? "true" : "false"); } void UkmediaMainWidget::bootMusicSettingsChanged(const QString &key) { bool bBootStatus = true; if (QGSettings::isSchemaInstalled(UKUI_SWITCH_SETTING)){ if (key == "startupMusic"){ if (m_pBootSetting->keys().contains("startupMusic")) m_pSoundWidget->m_pStartupButton->setChecked(m_pBootSetting->get(UKUI_STARTUP_MUSIC_KEY).toBool()); } else if (key == "poweroffMusic"){ if (m_pBootSetting->keys().contains("poweroffMusic")) m_pSoundWidget->m_pPoweroffButton->setChecked(m_pBootSetting->get(UKUI_POWEROFF_MUSIC_KEY).toBool()); } else if (key == "logoutMusic"){ if (m_pBootSetting->keys().contains("logoutMusic")) m_pSoundWidget->m_pLogoutButton->setChecked(m_pBootSetting->get(UKUI_LOGOUT_MUSIC_KEY).toBool()); } else if (key == "weakupMusic"){ if (m_pBootSetting->keys().contains("weakupMusic")) m_pSoundWidget->m_pWakeupMusicButton->setChecked(m_pBootSetting->get(UKUI_WAKEUP_MUSIC_KEY).toBool()); } } } /* * 系统主题更改 */ void UkmediaMainWidget::ukuiThemeChangedSlot(const QString &themeStr) { if (m_pThemeSetting->keys().contains("styleName")) { mThemeName = m_pThemeSetting->get(UKUI_THEME_NAME).toString(); } int nInputValue = getInputVolume(); int nOutputValue = getOutputVolume(); bool inputStatus = m_pVolumeControl->getSourceMute(); bool outputStatus = m_pVolumeControl->getSinkMute(); inputVolumeDarkThemeImage(nInputValue,inputStatus); outputVolumeDarkThemeImage(nOutputValue,outputStatus); Q_EMIT qApp->paletteChanged(qApp->palette()); this->repaint(); } /* * 获取输入音量值 */ int UkmediaMainWidget::getInputVolume() { return m_pInputWidget->m_pIpVolumeSlider->value(); } /* * 获取输出音量值 */ int UkmediaMainWidget::getOutputVolume() { return m_pOutputWidget->m_pOpVolumeSlider->value(); } /* * 深色主题时输出音量图标 */ void UkmediaMainWidget::outputVolumeDarkThemeImage(int value,bool status) { QImage image; QColor color = QColor(0,0,0,216); if (mThemeName == UKUI_THEME_LIGHT || mThemeName == UKUI_THEME_DEFAULT) { color = QColor(0,0,0,216); } else if (mThemeName == UKUI_THEME_DARK) { color = QColor(255,255,255,216); } if (status) { image = QIcon::fromTheme("audio-volume-muted-symbolic").pixmap(24,24).toImage(); } else if (value <= 0) { image = QIcon::fromTheme("audio-volume-muted-symbolic").pixmap(24,24).toImage(); } else if (value > 0 && value <= 33) { image = QIcon::fromTheme("audio-volume-low-symbolic").pixmap(24,24).toImage(); } else if (value >33 && value <= 66) { image = QIcon::fromTheme("audio-volume-medium-symbolic").pixmap(24,24).toImage(); } else { image = QIcon::fromTheme("audio-volume-high-symbolic").pixmap(24,24).toImage(); } m_pOutputWidget->m_pOutputIconBtn->refreshIcon(image, color); } /* * 输入音量图标 */ void UkmediaMainWidget::inputVolumeDarkThemeImage(int value,bool status) { QImage image; QColor color = QColor(0,0,0,190); if (mThemeName == UKUI_THEME_LIGHT || mThemeName == UKUI_THEME_DEFAULT) { color = QColor(0,0,0,190); } else if (mThemeName == UKUI_THEME_DARK) { color = QColor(255,255,255,190); } if (status) { image = QIcon::fromTheme("microphone-sensitivity-muted-symbolic").pixmap(24,24).toImage(); } else if (value <= 0) { image = QIcon::fromTheme("microphone-sensitivity-muted-symbolic").pixmap(24,24).toImage(); } else if (value > 0 && value <= 33) { image = QIcon::fromTheme("microphone-sensitivity-low-symbolic").pixmap(24,24).toImage(); } else if (value >33 && value <= 66) { image = QIcon::fromTheme("microphone-sensitivity-medium-symbolic").pixmap(24,24).toImage(); } else { image = QIcon::fromTheme("microphone-sensitivity-high-symbolic").pixmap(24,24).toImage(); } m_pInputWidget->m_pInputIconBtn->refreshIcon(image, color); } //void UkmediaMainWidget::onKeyChanged (GSettings *settings,gchar *key,UkmediaMainWidget *m_pWidget) void UkmediaMainWidget::onKeyChanged (const QString &key) { // Q_UNUSED(settings); qDebug() << "onKeyChanged" <get(GLOBAL_THEME_NAME).toString(); if (globalTheme != "custom") { int index = m_pSoundWidget->m_pSoundThemeCombobox->findData(globalTheme); m_pSoundWidget->m_pSoundThemeCombobox->setCurrentIndex(index); } } } /* * 更新主题 */ void UkmediaMainWidget::updateTheme (UkmediaMainWidget *m_pWidget) { g_debug("update theme"); // char *pThemeName; QString pThemeName; gboolean feedBackEnabled; gboolean eventsEnabled; if (QGSettings::isSchemaInstalled(KEY_SOUNDS_SCHEMA)){ if (m_pWidget->m_pSoundSettings->keys().contains("eventSounds")) { eventsEnabled = m_pWidget->m_pSoundSettings->get(EVENT_SOUNDS_KEY).toBool(); } if (eventsEnabled) { if (m_pWidget->m_pSoundSettings->keys().contains("themeName")) { pThemeName = m_pWidget->m_pSoundSettings->get(SOUND_THEME_KEY).toString(); } } else { pThemeName = g_strdup (NO_SOUNDS_THEME_NAME); } } qDebug() << "updateTheme" << pThemeName; //设置combox的主题 setComboxForThemeName (m_pWidget, pThemeName.toLatin1().data()); updateAlertsFromThemeName (m_pWidget, pThemeName.toLatin1().data()); } /* * 设置主题名到combox */ void UkmediaMainWidget::setupThemeSelector (UkmediaMainWidget *m_pWidget) { g_debug("setup theme selector"); GHashTable *hash; const char * const *dataDirs; const char *m_pDataDir; char *dir; guint i; /* 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 (m_pWidget,hash, dir); } m_pDataDir = g_get_user_data_dir (); dir = g_build_filename (m_pDataDir, "sounds", nullptr); soundThemeInDir (m_pWidget,hash, dir); /* If there isn't at least one theme, make everything * insensitive, LAME! */ if (g_hash_table_size (hash) == 0) { g_warning ("Bad setup, install the freedesktop sound theme"); g_hash_table_destroy (hash); return; } /* Add the themes to a combobox */ g_hash_table_destroy (hash); } /* * 主题名所在目录 */ void UkmediaMainWidget::soundThemeInDir (UkmediaMainWidget *m_pWidget,GHashTable *hash,const char *dir) { Q_UNUSED(hash); qDebug() << "sound theme in dir" << dir; GDir *d; const char *m_pName; d = g_dir_open (dir, 0, nullptr); if (d == nullptr) { return; } while ((m_pName = g_dir_read_name (d)) != nullptr) { char *m_pDirName, *m_pIndex, *m_pIndexName; /* Look for directories */ m_pDirName = g_build_filename (dir, m_pName, nullptr); if (g_file_test (m_pDirName, G_FILE_TEST_IS_DIR) == FALSE) { continue; } /* Look for index files */ m_pIndex = g_build_filename (m_pDirName, "index.theme", nullptr); /* Check the name of the theme in the index.theme file */ m_pIndexName = loadIndexThemeName (m_pIndex, nullptr); if (m_pIndexName == nullptr) { continue; } if (QGSettings::isSchemaInstalled(KEY_SOUNDS_SCHEMA)){ QString themeName; if (m_pWidget->m_pSoundSettings->keys().contains("themeName")) { themeName = m_pWidget->m_pSoundSettings->get(SOUND_THEME_KEY).toString(); } qDebug() << "sound theme in dir" << "displayname:" << m_pIndexName << "theme name:" << m_pName << "theme:"<< themeName; if (m_pName && !strstr(m_pName,"ubuntu") && !strstr(m_pName,"freedesktop") && !strstr(m_pName,"custom")) { m_pWidget->m_pThemeNameList.append(m_pName); m_pWidget->m_pSoundWidget->m_pSoundThemeCombobox->addItem(m_pIndexName,m_pName); } } } g_dir_close (d); m_pWidget->m_pSoundWidget->m_pSoundThemeCombobox->blockSignals(true); m_pWidget->m_pSoundWidget->m_pSoundThemeCombobox->addItem(tr("Custom"), "custom"); m_pWidget->m_pSoundWidget->m_pSoundThemeCombobox->blockSignals(false); } /* * 加载下标的主题名 */ char *UkmediaMainWidget::loadIndexThemeName (const char *index,char **parent) { g_debug("load index theme name"); GKeyFile *file; char *indexname = nullptr; gboolean hidden; file = g_key_file_new (); if (g_key_file_load_from_file (file, index, G_KEY_FILE_KEEP_TRANSLATIONS, nullptr) == FALSE) { g_key_file_free (file); return nullptr; } /* 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); /* Save the parent theme, if there's one */ if (parent != nullptr) { *parent = g_key_file_get_string (file,"Sound Theme","Inherits",nullptr); } } g_key_file_free (file); return indexname; } /* * 设置combox的主题名 */ void UkmediaMainWidget::setComboxForThemeName (UkmediaMainWidget *m_pWidget,const char *name) { g_debug("set combox for theme name"); gboolean found; int count = 0; /* If the name is empty, use "freedesktop" */ if (name == nullptr || *name == '\0') { name = "freedesktop"; } QString value; int index = -1; while(!found) { value = m_pWidget->m_pThemeNameList.at(count); found = (value != "" && value == name); count++; if( count >= m_pWidget->m_pThemeNameList.size() || found) { count = 0; break; } } if (m_pWidget->m_pThemeNameList.contains(name)) { index = m_pWidget->m_pThemeNameList.indexOf(name); value = m_pWidget->m_pThemeNameList.at(index); m_pWidget->m_pSoundWidget->m_pSoundThemeCombobox->setCurrentIndex(index); } /* When we can't find the theme we need to set, try to set the default * one "freedesktop" */ if (found) { } else if (strcmp (name, "freedesktop") != 0) {//设置为默认的主题 qDebug() << "设置为默认的主题" << "freedesktop"; g_debug ("not found, falling back to fdo"); setComboxForThemeName (m_pWidget, "freedesktop"); } } /* * 更新报警音 */ void UkmediaMainWidget::updateAlertsFromThemeName (UkmediaMainWidget *m_pWidget,const gchar *m_pName) { g_debug("update alerts from theme name"); if (strcmp (m_pName, CUSTOM_THEME_NAME) != 0) { /* reset alert to default */ updateAlert (m_pWidget, DEFAULT_ALERT_ID); } else { int sound_type; char *linkname; linkname = nullptr; sound_type = getFileType ("bell-terminal", &linkname); g_debug ("Found link: %s", linkname); if (sound_type == SOUND_TYPE_CUSTOM) { updateAlert (m_pWidget, linkname); } } } /* 更新报警声音 */ void UkmediaMainWidget::updateAlert (UkmediaMainWidget *pWidget,const char *alertId) { Q_UNUSED(alertId) g_debug("update alert"); QString themeStr; char *theme; char *parent; gboolean is_custom; gboolean is_default; gboolean add_custom = false; gboolean remove_custom = false; QString nameStr; int index = -1; /* Get the current theme's name, and set the parent */ index = pWidget->m_pSoundWidget->m_pSoundThemeCombobox->currentIndex(); if (index != -1 && pWidget->m_pThemeNameList.count() > 0) { themeStr = pWidget->m_pThemeNameList.at(index); nameStr = pWidget->m_pThemeNameList.at(index); } else { themeStr = "freedesktop"; nameStr = "freedesktop"; } QByteArray ba = themeStr.toLatin1(); theme = ba.data(); QByteArray baParent = nameStr.toLatin1(); parent = baParent.data(); is_custom = strcmp (theme, CUSTOM_THEME_NAME) == 0; is_default = strcmp (alertId, DEFAULT_ALERT_ID) == 0; if (! is_custom && is_default) { /* remove custom just in case */ remove_custom = TRUE; } else if (! is_custom && ! is_default) { createCustomTheme (parent); saveAlertSounds(pWidget->m_pSoundWidget->m_pSoundThemeCombobox, alertId); add_custom = TRUE; } else if (is_custom && is_default) { saveAlertSounds(pWidget->m_pSoundWidget->m_pSoundThemeCombobox, alertId); /* after removing files check if it is empty */ if (customThemeDirIsEmpty ()) { remove_custom = TRUE; } } else if (is_custom && ! is_default) { saveAlertSounds(pWidget->m_pSoundWidget->m_pSoundThemeCombobox, alertId); } if (add_custom) { setComboxForThemeName (pWidget, CUSTOM_THEME_NAME); } else if (remove_custom) { setComboxForThemeName (pWidget, parent); } } /* 获取声音文件类型 */ int UkmediaMainWidget::getFileType (const char *sound_name,char **linked_name) { g_debug("get file type"); char *name, *filename; *linked_name = nullptr; name = g_strdup_printf ("%s.disabled", sound_name); filename = customThemeDirPath (name); if (g_file_test (filename, G_FILE_TEST_IS_REGULAR) != FALSE) { return SOUND_TYPE_OFF; } /* We only check for .ogg files because those are the * only ones we create */ name = g_strdup_printf ("%s.ogg", sound_name); filename = customThemeDirPath (name); g_free (name); if (g_file_test (filename, G_FILE_TEST_IS_SYMLINK) != FALSE) { *linked_name = g_file_read_link (filename, nullptr); g_free (filename); return SOUND_TYPE_CUSTOM; } g_free (filename); return SOUND_TYPE_BUILTIN; } /* 自定义主题路径 */ char *UkmediaMainWidget::customThemeDirPath (const char *child) { g_debug("custom theme dir path"); static char *dir = nullptr; const char *data_dir; if (dir == nullptr) { data_dir = g_get_user_data_dir (); dir = g_build_filename (data_dir, "sounds", CUSTOM_THEME_NAME, nullptr); } if (child == nullptr) return g_strdup (dir); return g_build_filename (dir, child, nullptr); } /* 获取报警声音文件的路径 */ #if 0 void UkmediaMainWidget::populateModelFromDir (UkmediaMainWidget *m_pWidget,const char *dirname)//从目录查找报警声音文件 { g_debug("populate model from dir"); GDir *d; const char *name; char *path; d = g_dir_open (dirname, 0, nullptr); if (d == nullptr) { return; } while ((name = g_dir_read_name (d)) != nullptr) { if (! g_str_has_suffix (name, ".xml")) { continue; } QString themeName = name; QStringList temp = themeName.split("-"); themeName = temp.at(0); if (!m_pWidget->m_soundThemeList.contains(themeName)) { m_pWidget->m_soundThemeList.append(themeName); m_pWidget->m_soundThemeDirList.append(dirname); m_pWidget->m_soundThemeXmlNameList.append(name); } path = g_build_filename (dirname, name, nullptr); } if (QGSettings::isSchemaInstalled(KEY_SOUNDS_SCHEMA)){ QString pThemeName; if (m_pWidget->m_pSoundSettings->keys().contains("themeName")) { pThemeName = m_pWidget->m_pSoundSettings->get(SOUND_THEME_KEY).toString(); } int themeIndex; if(m_pWidget->m_soundThemeList.contains(pThemeName)) { themeIndex = m_pWidget->m_soundThemeList.indexOf(pThemeName); if (themeIndex < 0 ) return; } else { themeIndex = 1; } themeIndex=0; QString dirName = m_pWidget->m_soundThemeDirList.at(themeIndex); QString xmlName = m_pWidget->m_soundThemeXmlNameList.at(themeIndex); path = g_build_filename (dirName.toLatin1().data(), xmlName.toLatin1().data(), nullptr); m_pWidget->m_pSoundWidget->m_pAlertSoundCombobox->blockSignals(true); m_pWidget->m_pSoundWidget->m_pAlertSoundCombobox->clear(); m_pWidget->m_pSoundWidget->m_pAlertSoundCombobox->blockSignals(false); } populateModelFromFile (m_pWidget, path); //初始化声音主题 g_free (path); g_dir_close (d); } /* 获取报警声音文件 */ void UkmediaMainWidget::populateModelFromFile (UkmediaMainWidget *m_pWidget,const char *filename) { g_debug("populate model from file"); xmlDocPtr doc; xmlNodePtr root; xmlNodePtr child; gboolean exists; qDebug() << "populateModelFromFile" <children; child; child = child->next) { if (xmlNodeIsText (child)) { continue; } if (xmlStrcmp (child->name, GVC_SOUND_SOUND) != 0) { continue; } populateModelFromNode (m_pWidget, child); } xmlFreeDoc (doc); } /* 从节点查找声音文件并加载到组合框中 */ void UkmediaMainWidget::populateModelFromNode (UkmediaMainWidget *m_pWidget,xmlNodePtr node) { g_debug("populate model from node"); xmlNodePtr child; xmlChar *filename; xmlChar *name; filename = nullptr; name = xmlGetAndTrimNames (node); for (child = node->children; child; child = child->next) { if (xmlNodeIsText (child)) { continue; } if (xmlStrcmp (child->name, GVC_SOUND_FILENAME) == 0) { filename = xmlNodeGetContent (child); } else if (xmlStrcmp (child->name, GVC_SOUND_NAME) == 0) { /* EH? should have been trimmed */ } } //将找到的声音文件名设置到combox中 if (filename != nullptr && name != nullptr) { m_pWidget->m_pSoundList->append((const char *)filename); m_pWidget->m_pSoundNameList->append((const char *)name); m_pWidget->m_pSoundWidget->m_pAlertSoundCombobox->addItem((char *)name); m_pWidget->m_pSoundWidget->m_pLagoutCombobox->addItem((char *)name); m_pWidget->m_pSoundWidget->m_pVolumeChangeCombobox->addItem((char *)name); } xmlFree (filename); xmlFree (name); } #endif /***************************************************************** * 函数名称: populateModelFromDir * 功能描述: 从目录中查找系统音效文件 * 参数说明: dirname: 目录路径 ******************************************************************/ void UkmediaMainWidget::populateModelFromDir (UkmediaMainWidget *m_pWidget,const char *dirname) { GDir *dir = nullptr; const char *name; char *path; dir = g_dir_open(dirname, 0, nullptr); if (nullptr == dir) { qDebug() << "populateModelFromDir dir is null !"; return; } while ((name = g_dir_read_name(dir)) != nullptr) { if (!g_str_has_suffix (name, ".xml")) continue; QString themeName = name; QStringList temp = themeName.split("."); themeName = temp.at(0); if (!m_pWidget->m_soundThemeList.contains(themeName)) { m_pWidget->m_soundThemeList.append(themeName); m_pWidget->m_soundThemeDirList.append(dirname); m_pWidget->m_soundThemeXmlNameList.append(name); } path = g_build_filename (dirname, name, nullptr); } //初始化声音主题 populateModelFromFile (m_pWidget, path); g_free (path); g_dir_close (dir); } /***************************************************************** * 函数名称: populateModelFromFile * 功能描述: 根据对应主题的xml文件获取音效文件 * 参数说明: filename: 音效主题目录下对应主题xml文件路径 ******************************************************************/ void UkmediaMainWidget::populateModelFromFile (UkmediaMainWidget *m_pWidget,const char *filename) { xmlDocPtr doc; xmlNodePtr root; xmlNodePtr child; if (! g_file_test(filename, G_FILE_TEST_EXISTS)) { qDebug() << "populateModelFromFile filename is not exist !"; return; } doc = xmlParseFile(filename); //载入xml文件 if (nullptr == doc) { qDebug() << "populateModelFromFile xmlParseFile xml failed !"; return; } root = xmlDocGetRootElement(doc); //获得根节点 for (child = root->children; child; child = child->next) { if (xmlNodeIsText(child)) { continue; } if (xmlStrcmp (child->name, GVC_SOUND_SOUND) != 0) { continue; } populateModelFromNode (m_pWidget, child); } xmlFreeDoc (doc); } /***************************************************************** * 函数名称: populateModelFromNode * 功能描述: 从节点查找声音文件并加载到组合框中 * 参数说明: node ******************************************************************/ void UkmediaMainWidget::populateModelFromNode (UkmediaMainWidget *m_pWidget,xmlNodePtr node) { xmlNodePtr child; xmlChar *name = nullptr; xmlChar *filename = nullptr; name = xmlGetAndTrimNames (node); for (child = node->children; child; child = child->next) { if (xmlNodeIsText (child)) { continue; } if (xmlStrcmp (child->name, GVC_SOUND_FILENAME) == 0) { filename = xmlNodeGetContent (child); } else if (xmlStrcmp (child->name, GVC_SOUND_NAME) == 0) { /* EH? should have been trimmed */ } } // 先添加无自定义音效选项 if (m_pWidget->m_pSoundNameList.isEmpty() && m_pWidget->m_pSoundList.isEmpty()) { m_pWidget->m_pSoundList.append(""); m_pWidget->m_pSoundNameList.append(tr("None")); m_pWidget->m_pSoundWidget->m_pVolumeChangeCombobox->blockSignals(true); m_pWidget->m_pSoundWidget->m_pVolumeChangeCombobox->addItem(tr("None"), ""); m_pWidget->m_pSoundWidget->m_pVolumeChangeCombobox->blockSignals(false); m_pWidget->m_pSoundWidget->m_pNotificationCombobox->blockSignals(true); m_pWidget->m_pSoundWidget->m_pNotificationCombobox->addItem(tr("None"), ""); m_pWidget->m_pSoundWidget->m_pNotificationCombobox->blockSignals(false); } // 将找到的声音文件名设置到combox中 if (filename != nullptr && name != nullptr) { m_pWidget->m_pSoundNameList.append((const char *)name); m_pWidget->m_pSoundList.append((const char *)filename); m_pWidget->m_pSoundWidget->m_pVolumeChangeCombobox->blockSignals(true); m_pWidget->m_pSoundWidget->m_pVolumeChangeCombobox->addItem(QString::fromUtf8(name), QString::fromUtf8(filename)); m_pWidget->m_pSoundWidget->m_pVolumeChangeCombobox->blockSignals(false); m_pWidget->m_pSoundWidget->m_pNotificationCombobox->blockSignals(true); m_pWidget->m_pSoundWidget->m_pNotificationCombobox->addItem(QString::fromUtf8(name), QString::fromUtf8(filename)); m_pWidget->m_pSoundWidget->m_pNotificationCombobox->blockSignals(false); } xmlFree (filename); xmlFree (name); } /* Adapted from yelp-toc-pager.c */ xmlChar *UkmediaMainWidget::xmlGetAndTrimNames (xmlNodePtr node) { g_debug("xml get and trim names"); xmlNodePtr cur; xmlChar *keep_lang = nullptr; xmlChar *value; int j, keep_pri = INT_MAX; const gchar * const * langs = g_get_language_names (); value = nullptr; for (cur = node->children; cur; cur = cur->next) { if (! xmlStrcmp (cur->name, GVC_SOUND_NAME)) { xmlChar *cur_lang = nullptr; int cur_pri = INT_MAX; cur_lang = xmlNodeGetLang (cur); if (cur_lang) { for (j = 0; langs[j]; j++) { if (g_str_equal (cur_lang, langs[j])) { cur_pri = j; break; } } } else { cur_pri = INT_MAX - 1; } if (cur_pri <= keep_pri) { if (keep_lang) xmlFree (keep_lang); if (value) xmlFree (value); value = xmlNodeGetContent (cur); keep_lang = cur_lang; keep_pri = cur_pri; } else { if (cur_lang) xmlFree (cur_lang); } } } /* Delete all GVC_SOUND_NAME nodes */ cur = node->children; while (cur) { xmlNodePtr p_this = cur; cur = cur->next; if (! xmlStrcmp (p_this->name, GVC_SOUND_NAME)) { xmlUnlinkNode (p_this); xmlFreeNode (p_this); } } return value; } /* 点击combox播放声音 */ #if 0 void UkmediaMainWidget::comboxIndexChangedSlot(int index) { g_debug("combox index changed slot"); QString sound_name = m_pSoundList->at(index); updateAlert(this,sound_name.toLatin1().data()); playAlretSoundFromPath(this,sound_name); QString fileName = m_pSoundList->at(index); QStringList list = fileName.split("/"); QString soundName = list.at(list.count()-1); QStringList eventIdList = soundName.split("."); QString eventId = eventIdList.at(0); QList existsPath = listExistsPath(); for (char * path : existsPath) { char * prepath = QString(KEYBINDINGS_CUSTOM_DIR).toLatin1().data(); char * allpath = strcat(prepath, path); const QByteArray ba(KEYBINDINGS_CUSTOM_SCHEMA); const QByteArray bba(allpath); if(QGSettings::isSchemaInstalled(ba)) { QGSettings * settings = new QGSettings(ba, bba); // QString filenameStr = settings->get(FILENAME_KEY).toString(); QString nameStr = settings->get(NAME_KEY).toString(); if (nameStr == "alert-sound") { settings->set(FILENAME_KEY,eventId); return; } } else { continue; } } } /* 设置窗口关闭的提示音 */ void UkmediaMainWidget::windowClosedComboboxChangedSlot(int index) { QString fileName = m_pSoundList->at(index); QStringList list = fileName.split("/"); QString soundName = list.at(list.count()-1); QStringList eventIdList = soundName.split("."); QString eventId = eventIdList.at(0); QList existsPath = listExistsPath(); for (char * path : existsPath) { char * prepath = QString(KEYBINDINGS_CUSTOM_DIR).toLatin1().data(); char * allpath = strcat(prepath, path); const QByteArray ba(KEYBINDINGS_CUSTOM_SCHEMA); const QByteArray bba(allpath); if(QGSettings::isSchemaInstalled(ba)) { QGSettings * settings = new QGSettings(ba, bba); // QString filenameStr = settings->get(FILENAME_KEY).toString(); QString nameStr = settings->get(NAME_KEY).toString(); if (nameStr == "window-close") { settings->set(FILENAME_KEY,eventId); return; } } else { continue; } } } /* 设置音量改变的提示声音 */ void UkmediaMainWidget::volumeChangedComboboxChangeSlot(int index) { QString sound_name = m_pSoundList->at(index); // updateAlert(this,sound_name.toLatin1().data()); playAlretSoundFromPath(this,sound_name); QString fileName = m_pSoundList->at(index); QStringList list = fileName.split("/"); QString soundName = list.at(list.count()-1); QStringList eventIdList = soundName.split("."); QString eventId = eventIdList.at(0); QList existsPath = listExistsPath(); for (char * path : existsPath) { char * prepath = QString(KEYBINDINGS_CUSTOM_DIR).toLatin1().data(); char * allpath = strcat(prepath, path); const QByteArray ba(KEYBINDINGS_CUSTOM_SCHEMA); const QByteArray bba(allpath); if(QGSettings::isSchemaInstalled(ba)) { QGSettings * settings = new QGSettings(ba, bba); // QString filenameStr = settings->get(FILENAME_KEY).toString(); QString nameStr = settings->get(NAME_KEY).toString(); if (nameStr == "volume-changed") { settings->set(FILENAME_KEY,eventId); return; } } else { continue; } } } void UkmediaMainWidget::settingMenuComboboxChangedSlot(int index) { QString fileName = m_pSoundList->at(index); QStringList list = fileName.split("/"); QString soundName = list.at(list.count()-1); QStringList eventIdList = soundName.split("."); QString eventId = eventIdList.at(0); QList existsPath = listExistsPath(); for (char * path : existsPath) { char * prepath = QString(KEYBINDINGS_CUSTOM_DIR).toLatin1().data(); char * allpath = strcat(prepath, path); const QByteArray ba(KEYBINDINGS_CUSTOM_SCHEMA); const QByteArray bba(allpath); if(QGSettings::isSchemaInstalled(ba)) { QGSettings * settings = new QGSettings(ba, bba); QString nameStr = settings->get(NAME_KEY).toString(); if (nameStr == "system-setting") { settings->set(FILENAME_KEY,eventId); return; } } else { continue; } } } #endif /* 点击输入音量按钮静音 */ void UkmediaMainWidget::inputMuteButtonSlot() { m_pVolumeControl->setSourceMute(!m_pVolumeControl->sourceMuted); inputVolumeDarkThemeImage(UkmediaCommon::getInstance().paVolumeToMediaVolume(m_pVolumeControl->sourceVolume), !m_pVolumeControl->sourceMuted); ukcc::UkccCommon::buriedSettings("Audio", m_pInputWidget->m_pInputIconBtn->objectName(), QString("settings"), !m_pVolumeControl->sourceMuted? "true" : "false"); } /* 点击输出音量按钮静音 */ void UkmediaMainWidget::outputMuteButtonSlot() { m_pVolumeControl->setSinkMute(!m_pVolumeControl->sinkMuted); outputVolumeDarkThemeImage(UkmediaCommon::getInstance().paVolumeToMediaVolume(m_pVolumeControl->sinkVolume), !m_pVolumeControl->sinkMuted); ukcc::UkccCommon::buriedSettings("Audio", m_pOutputWidget->m_pOutputIconBtn->objectName(), QString("settings"), !m_pVolumeControl->sinkMuted ? "true" : "false"); } /* 点击声音主题实现主题切换 */ void UkmediaMainWidget::themeComboxIndexChangedSlot(int index) { if (-1 == index || nullptr == m_pGlobalThemeSetting) return; QString theme = m_pSoundWidget->m_pSoundThemeCombobox->currentData().toString(); QString globalTheme = m_pGlobalThemeSetting->get(GLOBAL_THEME_NAME).toString(); // 切换主题时,将os全局主题切换成自定义 if (theme != globalTheme) m_pGlobalThemeSetting->set(GLOBAL_THEME_NAME, "custom"); // 切换至自定义音效时,custom-theme开启 if (theme == "custom") { m_pSoundSettings->blockSignals(true); m_pSoundSettings->set(SOUND_CUSTOM_THEME_KEY, true); m_pSoundSettings->blockSignals(true); return; } // 设置系统主题,并重置自定义音效为默认音效 m_pSoundSettings->blockSignals(true); m_pSoundSettings->set(SOUND_THEME_KEY, theme); m_pSoundSettings->set(SOUND_CUSTOM_THEME_KEY, false); m_pSoundSettings->blockSignals(false); if (!resetCustomSoundEffects(theme, AUDIO_VOLUME_CHANGE)) { m_pSoundSettings->set(AUDIO_VOLUME_CHANGE, ""); m_pSoundWidget->m_pVolumeChangeCombobox->blockSignals(true); m_pSoundWidget->m_pVolumeChangeCombobox->setCurrentIndex(0); m_pSoundWidget->m_pVolumeChangeCombobox->blockSignals(false); qWarning("%s resetCustomSoundEffects failed !", AUDIO_VOLUME_CHANGE); } if (!resetCustomSoundEffects(theme, NOTIFICATION_GENGERAL)) { m_pSoundSettings->set(NOTIFICATION_GENGERAL, ""); m_pSoundWidget->m_pNotificationCombobox->blockSignals(true); m_pSoundWidget->m_pNotificationCombobox->setCurrentIndex(0); m_pSoundWidget->m_pNotificationCombobox->blockSignals(false); qWarning("%s resetCustomSoundEffects failed !", NOTIFICATION_GENGERAL); } m_pOutputWidget->m_pDeviceSelectBox->setObjectName("m_pSoundWidget->m_pSoundThemeCombobox"); ukcc::UkccCommon::buriedSettings("Audio", m_pSoundWidget->m_pSoundThemeCombobox->objectName(),"select",m_pThemeNameList.at(index)); } /* 滚动输出音量滑动条 */ void UkmediaMainWidget::outputWidgetSliderChangedSlot(int value) { QString percent; percent = QString::number(value); outputVolumeDarkThemeImage(value, false); m_pOutputWidget->m_pOpVolumePercentLabel->setText(percent + "%"); //蓝牙a2dp模式下滑动条跳动问题,以间隔为10设置音量 if (m_pVolumeControl->defaultSinkName.contains("a2dp_sink")) return; int paVolume = UkmediaCommon::getInstance().mediaVolumeToPaVolume(value); m_pVolumeControl->getDefaultSinkIndex(); m_pVolumeControl->setSinkVolume(m_pVolumeControl->sinkIndex, paVolume); qDebug() << __func__ << value << paVolume; m_pOutputWidget->m_pOpVolumeSlider->setObjectName("m_pOutputWidget->m_pOpVolumeSlider"); ukcc::UkccCommon::buriedSettings("Audio", m_pOutputWidget->m_pOpVolumeSlider->objectName(),"slider",QString::number(value)); } void UkmediaMainWidget::outputWidgetSliderChangedSlotInBlue(int value) { if (!m_pVolumeControl->defaultSinkName.contains("a2dp_sink")) { m_pOutputWidget->m_pOpVolumeSlider->isMouseWheel = false; return; } qDebug() << "Special Handling Adjust volume in Bluetooth a2dp mode" << value; int paVolume = UkmediaCommon::getInstance().mediaVolumeToPaVolume(value); m_pVolumeControl->getDefaultSinkIndex(); m_pVolumeControl->setSinkVolume(m_pVolumeControl->sinkIndex, paVolume); QString percent; percent = QString::number(value); outputVolumeDarkThemeImage(value, false); m_pOutputWidget->m_pOpVolumePercentLabel->setText(percent + "%"); m_pOutputWidget->m_pOpVolumeSlider->setObjectName("m_pOutputWidget->m_pOpVolumeSlider"); ukcc::UkccCommon::buriedSettings("Audio", m_pOutputWidget->m_pOpVolumeSlider->objectName(),"slider",QString::number(value)); } void UkmediaMainWidget::timeSliderSlot() { if(mouseReleaseState){ int value = m_pOutputWidget->m_pOpVolumeSlider->value(); QString percent; bool status = false; percent = QString::number(value); int volume = value*65536/100; if (value <= 0) { status = true; percent = QString::number(0); } else { if (firstEnterSystem) { } else { } } firstEnterSystem = false; outputVolumeDarkThemeImage(value,status); percent.append("%"); m_pOutputWidget->m_pOpVolumePercentLabel->setText(percent); mouseReleaseState = false; mousePress = false; timeSlider->stop(); } else{ timeSlider->start(50); } } /* 滚动输入滑动条 */ void UkmediaMainWidget::inputWidgetSliderChangedSlot(int value) { int paVolume = UkmediaCommon::getInstance().mediaVolumeToPaVolume(value); m_pVolumeControl->getDefaultSinkIndex(); m_pVolumeControl->setSourceVolume(m_pVolumeControl->sourceIndex, paVolume); //输入图标修改成深色主题 inputVolumeDarkThemeImage(value, m_pVolumeControl->sourceMuted); m_pInputWidget->m_pInputIconBtn->repaint(); QString percent = QString::number(value); percent.append("%"); m_pInputWidget->m_pIpVolumePercentLabel->setText(percent); qDebug() << __func__ << value << paVolume; ukcc::UkccCommon::buriedSettings("Audio", m_pInputWidget->m_pIpVolumeSlider->objectName(), "slider", QString::number(value)); } /* * 开启单声道 */ void UkmediaMainWidget::balanceSliderChangedMono(int balanceSliderValue, int sinkVolume, int sinkIndex) { gdouble balanceVolume = balanceSliderValue/100.0; m_pVolumeControl->setBalanceVolume(sinkIndex, sinkVolume, balanceVolume); m_pOutputWidget->m_pOpBalanceSlider->setObjectName("m_pOutputWidget->m_pOpBalanceSlider"); ukcc::UkccCommon::buriedSettings("Audio", m_pOutputWidget->m_pOpBalanceSlider->objectName(),"slider",QString::number(balanceVolume)); } /* * 平衡值改变 */ void UkmediaMainWidget::balanceSliderChangedSlot(int balanceSliderValue) { gdouble balanceVolume = balanceSliderValue/100.0; int paVolume = UkmediaCommon::getInstance().mediaVolumeToPaVolume(m_pOutputWidget->m_pOpVolumeSlider->value()); m_pVolumeControl->setBalanceVolume(m_pVolumeControl->sinkIndex, paVolume, balanceVolume); ukcc::UkccCommon::buriedSettings("Audio", m_pOutputWidget->m_pOpBalanceSlider->objectName(), "slider", QString::number(balanceVolume)); } /* * 输入等级 */ void UkmediaMainWidget::peakVolumeChangedSlot(double v) { if (v >= 0) { m_pInputWidget->m_pInputLevelProgressBar->setEnabled(true); //因为有些电脑MIC增益太大会引起反馈条消失先暂时固定最大值为101 //int value = qRound(v * m_pInputWidget->m_pInputLevelProgressBar->maximum()); int value = qRound(v * 100); m_pInputWidget->m_pInputLevelProgressBar->setValue(value); } else { m_pInputWidget->m_pInputLevelProgressBar->setEnabled(false); m_pInputWidget->m_pInputLevelProgressBar->setValue(0); } } gboolean UkmediaMainWidget::saveAlertSounds (QComboBox *combox,const char *id) { const char *sounds[3] = { "bell-terminal", "bell-window-system", NULL }; char *path; if (strcmp (id, DEFAULT_ALERT_ID) == 0) { deleteOldFiles (sounds); deleteDisabledFiles (sounds); } else { deleteOldFiles (sounds); deleteDisabledFiles (sounds); addCustomFile (sounds, id); } /* And poke the directory so the theme gets updated */ path = customThemeDirPath(NULL); if (utime (path, NULL) != 0) { g_warning ("Failed to update mtime for directory '%s': %s", path, g_strerror (errno)); } g_free (path); return FALSE; } void UkmediaMainWidget::deleteOldFiles (const char **sounds) { guint i; for (i = 0; sounds[i] != NULL; i++) { deleteOneFile (sounds[i], "%s.ogg"); } } void UkmediaMainWidget::deleteOneFile (const char *sound_name, const char *pattern) { GFile *file; char *name, *filename; name = g_strdup_printf (pattern, sound_name); filename = customThemeDirPath(name); g_free (name); file = g_file_new_for_path (filename); g_free (filename); cappletFileDeleteRecursive (file, NULL); g_object_unref (file); } void UkmediaMainWidget::deleteDisabledFiles (const char **sounds) { guint i; for (i = 0; sounds[i] != NULL; i++) { deleteOneFile (sounds[i], "%s.disabled"); } } void UkmediaMainWidget::addCustomFile (const char **sounds, const char *filename) { guint i; for (i = 0; sounds[i] != NULL; i++) { GFile *file; char *name, *path; /* We use *.ogg because it's the first type of file that * libcanberra looks at */ name = g_strdup_printf ("%s.ogg", sounds[i]); path = customThemeDirPath(name); g_free (name); /* In case there's already a link there, delete it */ g_unlink (path); file = g_file_new_for_path (path); g_free (path); /* Create the link */ g_file_make_symbolic_link (file, filename, NULL, NULL); g_object_unref (file); } } /** * A utility routine to delete files and/or directories, * including non-empty directories. **/ gboolean UkmediaMainWidget::cappletFileDeleteRecursive (GFile *file, GError **error) { GFileInfo *info; GFileType type; g_return_val_if_fail (error == NULL || *error == NULL, FALSE); info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_TYPE, G_FILE_QUERY_INFO_NONE, NULL, error); if (info == NULL) { return FALSE; } type = g_file_info_get_file_type (info); g_object_unref (info); if (type == G_FILE_TYPE_DIRECTORY) { return directoryDeleteRecursive (file, error); } else { return g_file_delete (file, NULL, error); } } gboolean UkmediaMainWidget::directoryDeleteRecursive (GFile *directory, GError **error) { GFileEnumerator *enumerator; GFileInfo *info; gboolean success = TRUE; enumerator = g_file_enumerate_children (directory, G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_TYPE, G_FILE_QUERY_INFO_NONE, NULL, error); if (enumerator == NULL) return FALSE; while (success && (info = g_file_enumerator_next_file (enumerator, NULL, NULL))) { GFile *child; child = g_file_get_child (directory, g_file_info_get_name (info)); if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) { success = directoryDeleteRecursive (child, error); } g_object_unref (info); if (success) success = g_file_delete (child, NULL, error); } g_file_enumerator_close (enumerator, NULL, NULL); if (success) success = g_file_delete (directory, NULL, error); return success; } void UkmediaMainWidget::createCustomTheme (const char *parent) { GKeyFile *keyfile; char *data; char *path; /* Create the custom directory */ path = customThemeDirPath(NULL); g_mkdir_with_parents (path, 0755); g_free (path); /* Set the data for index.theme */ keyfile = g_key_file_new (); g_key_file_set_string (keyfile, "Sound Theme", "Name", _("Custom")); g_key_file_set_string (keyfile, "Sound Theme", "Inherits", parent); g_key_file_set_string (keyfile, "Sound Theme", "Directories", "."); data = g_key_file_to_data (keyfile, NULL, NULL); g_key_file_free (keyfile); /* Save the index.theme */ path = customThemeDirPath ("index.theme"); g_file_set_contents (path, data, -1, NULL); g_free (path); g_free (data); customThemeUpdateTime (); } /* This function needs to be called after each individual * changeset to the theme */ void UkmediaMainWidget::customThemeUpdateTime (void) { char *path; path = customThemeDirPath (NULL); utime (path, NULL); g_free (path); } gboolean UkmediaMainWidget::customThemeDirIsEmpty (void) { char *dir; GFile *file; gboolean is_empty; GFileEnumerator *enumerator; GFileInfo *info; GError *error = NULL; dir = customThemeDirPath(NULL); file = g_file_new_for_path (dir); g_free (dir); is_empty = TRUE; enumerator = g_file_enumerate_children (file, G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_TYPE, G_FILE_QUERY_INFO_NONE, NULL, &error); if (enumerator == NULL) { g_warning ("Unable to enumerate files: %s", error->message); g_error_free (error); goto out; } while (is_empty && (info = g_file_enumerator_next_file (enumerator, NULL, NULL))) { if (strcmp ("index.theme", g_file_info_get_name (info)) != 0) { is_empty = FALSE; } g_object_unref (info); } g_file_enumerator_close (enumerator, NULL, NULL); out: g_object_unref (file); return is_empty; } int UkmediaMainWidget::caPlayForWidget(UkmediaMainWidget *w, uint32_t id, ...) { va_list ap; int ret; ca_proplist *p; if ((ret = ca_proplist_create(&p)) < 0) { qDebug() << "ca_proplist_create ret:" << ret; return ret; } if ((ret = caProplistSetForWidget(p, w)) < 0) { qDebug() << "caProplistSetForWidget ret:" << ret; return -1; } va_start(ap, id); ret = caProplistMergeAp(p, ap); va_end(ap); if (ret < 0) { qDebug() << "caProplistMergeAp ret:" << ret; return -1; } if (nullptr != m_caContext) { ca_context_cancel(m_caContext, 0); ca_context_destroy(m_caContext); } ca_context_create(&m_caContext); ret = ca_context_play_full(m_caContext, id, p, NULL, NULL); qDebug() << "ca_context_play_full ret:" << ret; return ret; } int UkmediaMainWidget::caProplistMergeAp(ca_proplist *p, va_list ap) { int ret; for (;;) { const char *key, *value; if (!(key = va_arg(ap, const char*))) break; if (!(value = va_arg(ap, const char*))) return CA_ERROR_INVALID; if ((ret = ca_proplist_sets(p, key, value)) < 0) return ret; } return CA_SUCCESS; } int UkmediaMainWidget::caProplistSetForWidget(ca_proplist *p, UkmediaMainWidget *widget) { int ret; const char *t; QScreen *screen; gint x = -1; gint y = -1; gint width = -1; gint height = -1; gint screen_width = -1; gint screen_height = -1; if ((t = widget->windowTitle().toLatin1().data())) if ((ret = ca_proplist_sets(p, CA_PROP_WINDOW_NAME, t)) < 0) return ret; if (t) if ((ret = ca_proplist_sets(p, CA_PROP_WINDOW_ID, t)) < 0) return ret; if ((t = widget->windowIconText().toLatin1().data())) if ((ret = ca_proplist_sets(p, CA_PROP_WINDOW_ICON_NAME, t)) < 0) return ret; if (screen = qApp->primaryScreen()) { if ((ret = ca_proplist_setf(p, CA_PROP_WINDOW_X11_SCREEN, "%i", 0)) < 0) return ret; } width = widget->size().width(); height = widget->size().height(); if (width > 0) if ((ret = ca_proplist_setf(p, CA_PROP_WINDOW_WIDTH, "%i", width)) < 0) return ret; if (height > 0) if ((ret = ca_proplist_setf(p, CA_PROP_WINDOW_HEIGHT, "%i", height)) < 0) return ret; if (x >= 0 && width > 0) { screen_width = qApp->primaryScreen()->size().width(); x += width/2; x = CA_CLAMP(x, 0, screen_width-1); /* We use these strange format strings here to avoid that libc * applies locale information on the formatting of floating * numbers. */ if ((ret = ca_proplist_setf(p, CA_PROP_WINDOW_HPOS, "%i.%03i", (int) (x/(screen_width-1)), (int) (1000.0*x/(screen_width-1)) % 1000)) < 0) return ret; } if (y >= 0 && height > 0) { screen_height = qApp->primaryScreen()->size().height(); y += height/2; y = CA_CLAMP(y, 0, screen_height-1); if ((ret = ca_proplist_setf(p, CA_PROP_WINDOW_VPOS, "%i.%03i", (int) (y/(screen_height-1)), (int) (1000.0*y/(screen_height-1)) % 1000)) < 0) return ret; } return CA_SUCCESS; } UkmediaMainWidget::~UkmediaMainWidget() { } //查找指定声卡名的索引 int UkmediaMainWidget::findCardIndex(QString cardName, QMap cardMap) { QMap::iterator it; for(it=cardMap.begin();it!=cardMap.end();) { if (it.value() == cardName) { return it.key(); } ++it; } return -1; } /* * 根据声卡索引查找声卡名 */ QString UkmediaMainWidget::findCardName(int index,QMap cardMap) { QMap::iterator it; for(it=cardMap.begin();it!=cardMap.end();) { if (it.key() == index) { return it.value(); } ++it; } return ""; } /* 查找名称为PortLbael 的portName */ QString UkmediaMainWidget::findOutputPortName(int index,QString portLabel) { QMap>::iterator it; QMultiMapportMap; QMultiMap::iterator tempMap; QString portName = ""; for (it = m_pVolumeControl->outputPortMap.begin();it != m_pVolumeControl->outputPortMap.end();) { if (it.key() == index) { portMap = it.value(); for (tempMap = portMap.begin();tempMap!=portMap.end();) { if (tempMap.value() == portLabel) { portName = tempMap.key(); break; } ++tempMap; } } ++it; } return portName; } /* 查找名称为PortName 的portLabel */ QString UkmediaMainWidget::findOutputPortLabel(int index,QString portName) { QMap>::iterator it; QMultiMapportMap; QMultiMap::iterator tempMap; QString portLabel = ""; for (it = m_pVolumeControl->outputPortMap.begin();it != m_pVolumeControl->outputPortMap.end();) { if (it.key() == index) { portMap = it.value(); for (tempMap = portMap.begin();tempMap!=portMap.end();) { qDebug() <<"findOutputPortLabel" <>::iterator it; QMultiMapportMap; QMultiMap::iterator tempMap; QString portName = ""; for (it = m_pVolumeControl->inputPortMap.begin();it != m_pVolumeControl->inputPortMap.end();) { if (it.key() == index) { portMap = it.value(); for (tempMap = portMap.begin();tempMap!=portMap.end();) { if (tempMap.value() == portLabel) { portName = tempMap.key(); break; } ++tempMap; } } ++it; } return portName; } /* 查找名称为PortName 的portLabel */ QString UkmediaMainWidget::findInputPortLabel(int index,QString portName) { QMap>::iterator it; QMultiMapportMap; QMultiMap::iterator tempMap; QString portLabel = ""; for (it = m_pVolumeControl->inputPortMap.begin();it != m_pVolumeControl->inputPortMap.end();) { if (it.key() == index) { portMap = it.value(); for (tempMap = portMap.begin();tempMap!=portMap.end();) { if (tempMap.key() == portName) { portLabel = tempMap.value(); break; } ++tempMap; } } ++it; } return portLabel; } QString UkmediaMainWidget::findHighPriorityProfile(int index, QString profile) { int priority = 0; QString profileName = ""; QMap profileNameMap; QMap::iterator tempMap; QMap>::iterator it; QString profileStr = findCardActiveProfile(index); QStringList list = profileStr.split("+"); QString includeProfile = ""; if (list.count() > 1) { if (profile.contains("output")) includeProfile = list.at(1); else if (profile.contains("input")) includeProfile = list.at(0); } else includeProfile = list.at(0); for (it = m_pVolumeControl->cardProfilePriorityMap.begin(); it != m_pVolumeControl->cardProfilePriorityMap.end(); ++it) { if (index == it.key()) { profileNameMap = it.value(); for (tempMap = profileNameMap.begin(); tempMap != profileNameMap.end(); ++tempMap) { if (!includeProfile.isEmpty() && tempMap.key().contains(includeProfile) && tempMap.key().contains(profile) && !tempMap.key().contains(includeProfile + "-") && !tempMap.key().contains(profile + "-")) { priority = tempMap.value(); profileName = tempMap.key(); qDebug() << "Status1: Find profileName" << profileName << "priority" << priority; } else if (tempMap.key().contains(profile) && tempMap.value() > priority) { priority = tempMap.value(); profileName = tempMap.key(); qDebug() << "Status2: Find profileName" << profileName << "priority" << priority; } } } } qInfo() << __func__ << "Select profile is" << profileName << "index" << index << "includeProfile" << includeProfile; return profileName; } /* * 输入设备中是否包含蓝牙设备 */ bool UkmediaMainWidget::inputComboboxDeviceContainBluetooth() { for (int row=0;rowm_pInputDeviceSelectBox->count();row++) { QString inputComboboxCardName = m_pInputWidget->m_pInputDeviceSelectBox->itemData(row).toString(); if (inputComboboxCardName.contains("bluez")) { return true; } } return false; } QString UkmediaMainWidget::blueCardNameInCombobox() { for (int row=0;rowm_pInputDeviceSelectBox->count();row++) { QString inputComboboxCardName = m_pInputWidget->m_pInputDeviceSelectBox->itemData(row).toString(); if (inputComboboxCardName.contains("bluez")) { return inputComboboxCardName; } } return ""; } /* * 设置声卡的配置文件 */ void UkmediaMainWidget::setCardProfile(QString name, QString profile) { int index = findCardIndex(name,m_pVolumeControl->cardMap); m_pVolumeControl->setCardProfile(index,profile.toLatin1().data()); qDebug() << "set profile" << name << profile << index ; } /* * 设置默认的输出设备端口 */ void UkmediaMainWidget::setDefaultOutputPortDevice(QString devName, QString portName) { int cardIndex = findCardIndex(devName,m_pVolumeControl->cardMap); QString portStr = findOutputPortName(cardIndex,portName); qDebug() << "setDefaultOutputPortDevice" << cardIndex << portStr; QTimer *timer = new QTimer; timer->start(300); connect(timer,&QTimer::timeout,[=](){ QString sinkStr = findPortSink(cardIndex,portStr); qDebug() << "setDefaultOutputPortDevice" << sinkStr; /*默认的stream 和设置的stream相同 需要更新端口*/ if (strcmp(sinkStr.toLatin1().data(),m_pVolumeControl->defaultSinkName) == 0) { m_pVolumeControl->setSinkPort(sinkStr.toLatin1().data(),portStr.toLatin1().data()); } else { m_pVolumeControl->setDefaultSink(sinkStr.toLatin1().data()); m_pVolumeControl->setSinkPort(sinkStr.toLatin1().data(),portStr.toLatin1().data()); } delete timer; }); } /* * 设置默认的输入设备端口 */ void UkmediaMainWidget::setDefaultInputPortDevice(QString devName, QString portName) { int cardIndex = findCardIndex(devName,m_pVolumeControl->cardMap); QString portStr = findInputPortName(cardIndex,portName); QTimer *timer = new QTimer; timer->start(100); connect(timer,&QTimer::timeout,[=](){ QString sourceStr = findPortSource(cardIndex,portStr); /*默认的stream 和设置的stream相同 需要更新端口*/ if (strcmp(sourceStr.toLatin1().data(),m_pVolumeControl->defaultSourceName) == 0) { m_pVolumeControl->setSourcePort(sourceStr.toLatin1().data(),portStr.toLatin1().data()); } else { m_pVolumeControl->setDefaultSource(sourceStr.toLatin1().data()); m_pVolumeControl->setSourcePort(sourceStr.toLatin1().data(),portStr.toLatin1().data()); } qDebug() << "set default input" << portName << cardIndex << portStr << devName <::iterator it; for (it=m_pVolumeControl->cardActiveProfileMap.begin();it!=m_pVolumeControl->cardActiveProfileMap.end();) { if (it.key() == index) { activeProfileName = it.value(); break; } ++it; } return activeProfileName; } /* * Find the corresponding sink according to the port name */ QString UkmediaMainWidget::findPortSink(int cardIndex,QString portName) { QMultiMap>::iterator it; QMultiMap portNameMap; QMultiMap::iterator tempMap; QString sinkStr = ""; for (it=m_pVolumeControl->sinkPortMap.begin();it!=m_pVolumeControl->sinkPortMap.end();) { if (it.key() == cardIndex) { portNameMap = it.value(); for (tempMap=portNameMap.begin();tempMap!=portNameMap.end();) { if (tempMap.value() == portName) { sinkStr = tempMap.key(); qDebug() <<"find port sink" << it.value() << portName<< it.key() <>::iterator it; QMultiMap portNameMap; QMultiMap::iterator tempMap; QString sourceStr = ""; for (it=m_pVolumeControl->sourcePortMap.begin();it!=m_pVolumeControl->sourcePortMap.end();) { if (it.key() == cardIndex) { portNameMap = it.value(); for (tempMap=portNameMap.begin();tempMap!=portNameMap.end();) { if (tempMap.value() == portName) { sourceStr = tempMap.key(); qDebug() << "find port source" <defaultOutputCard == -1 && !m_pOutputWidget->m_pDeviceSelectBox->count()) { addNoneItem(SoundType::SINK); } QString outputCardName = findCardName(m_pVolumeControl->defaultOutputCard, m_pVolumeControl->cardMap); QString outputPortLabel = findOutputPortLabel(m_pVolumeControl->defaultOutputCard, m_pVolumeControl->sinkPortName); findOutputComboboxItem(outputCardName, outputPortLabel); int mediaVolume = UkmediaCommon::getInstance().paVolumeToMediaVolume(m_pVolumeControl->getSinkVolume()); float balanceVolume = m_pVolumeControl->getBalanceVolume(); m_pOutputWidget->m_pOpVolumeSlider->blockSignals(true); m_pOutputWidget->m_pOpBalanceSlider->blockSignals(true); m_pOutputWidget->m_pOpVolumeSlider->setValue(mediaVolume); m_pOutputWidget->m_pOpVolumePercentLabel->setText(QString::number(mediaVolume) + "%"); handleBalanceSlider(balanceVolume); m_pOutputWidget->m_pOpBalanceSlider->blockSignals(false); m_pOutputWidget->m_pOpVolumeSlider->blockSignals(false); qDebug() << "initComboboxItem(Output)" << m_pVolumeControl->defaultOutputCard << outputCardName << m_pVolumeControl->sinkPortName << outputPortLabel; } void UkmediaMainWidget::initInputComboboxItem() { if (m_pVolumeControl->defaultInputCard == -1 && !m_pInputWidget->m_pInputDeviceSelectBox->count()) { addNoneItem(SoundType::SOURCE); } QString inputCardName = findCardName(m_pVolumeControl->defaultInputCard, m_pVolumeControl->cardMap); QString inputPortLabel = findInputPortLabel(m_pVolumeControl->defaultInputCard, m_pVolumeControl->sourcePortName); findInputComboboxItem(inputCardName, inputPortLabel); //当所有可用的输入设备全部移除,台式机才会出现该情况 if (strstr(m_pVolumeControl->defaultSourceName,"monitor")) { m_pInputWidget->m_pIpVolumeSlider->setEnabled(false); m_pInputWidget->m_pIpVolumeSlider->setValue(0); m_pInputWidget->m_pInputLevelProgressBar->setValue(0); }else{ if(!m_pInputWidget->m_pIpVolumeSlider->isEnabled()) m_pInputWidget->m_pIpVolumeSlider->setEnabled(true); } qDebug() <<"initComboboxItem(Input)" << m_pVolumeControl->defaultInputCard << inputCardName << m_pVolumeControl->sourcePortName << inputPortLabel; } void UkmediaMainWidget::findOutputComboboxItem(QString cardName,QString portLabel) { for (int row=0;rowm_pDeviceSelectBox->count();row++) { QString comboboxcardname = m_pOutputWidget->m_pDeviceSelectBox->itemData(row).toString(); QString comboboxportname = m_pOutputWidget->m_pDeviceSelectBox->itemText(row); if (comboboxcardname == cardName && comboboxportname == portLabel) { m_pOutputWidget->m_pDeviceSelectBox->blockSignals(true); m_pOutputWidget->m_pDeviceSelectBox->setCurrentIndex(row); m_pOutputWidget->m_pDeviceSelectBox->blockSignals(false); break; } } m_boxoutputRow = m_pOutputWidget->m_pDeviceSelectBox->currentIndex(); } void UkmediaMainWidget::addNoneItem(int soundType) { int index = -1; switch (soundType) { case SoundType::SINK: //需求31268待2501再合入 #if 1 m_pOutputWidget->m_pOpVolumeSlider->setEnabled(false); m_pOutputWidget->m_pOpBalanceSlider->setDisabled(true); #endif index = m_pOutputWidget->m_pDeviceSelectBox->findText(tr("None")); if (index != -1) return; m_pOutputWidget->m_pDeviceSelectBox->blockSignals(true); m_pOutputWidget->m_pDeviceSelectBox->addItem(tr("None")); m_pOutputWidget->m_pDeviceSelectBox->blockSignals(false); m_pOutputWidget->m_pOpVolumeSlider->blockSignals(true); m_pOutputWidget->m_pOpVolumeSlider->setValue(0); m_pOutputWidget->m_pOpVolumePercentLabel->setText("0%"); m_pVolumeControl->sinkVolume = 0; m_pOutputWidget->m_pOpVolumeSlider->blockSignals(false); break; case SoundType::SOURCE: //需求31268待2501再合入 #if 1 m_pInputWidget->m_pIpVolumeSlider->setEnabled(false); #endif index = m_pInputWidget->m_pInputDeviceSelectBox->findText(tr("None")); if (index != -1) return; m_pInputWidget->m_pInputDeviceSelectBox->blockSignals(true); m_pInputWidget->m_pInputDeviceSelectBox->addItem(tr("None")); m_pInputWidget->m_pInputDeviceSelectBox->blockSignals(false); m_pInputWidget->m_pIpVolumeSlider->blockSignals(true); if(!strstr(m_pVolumeControl->defaultSourceName, "bluez") && !strstr(m_pVolumeControl->defaultSourceName, "bt_sco")){ m_pInputWidget->m_pIpVolumeSlider->setEnabled(false); m_pInputWidget->m_pIpVolumeSlider->setValue(0); m_pInputWidget->m_pIpVolumePercentLabel->setText("0%"); m_pVolumeControl->sourceVolume = 0; } m_pInputWidget->m_pIpVolumeSlider->blockSignals(false); break; default: return; } } void UkmediaMainWidget::removeNoneItem(int soundType) { int index = -1; switch (soundType) { case SoundType::SINK: //需求31268待2501再合入 #if 1 m_pOutputWidget->m_pOpVolumeSlider->setEnabled(true); m_pOutputWidget->m_pOpBalanceSlider->setDisabled(false); #endif index = m_pOutputWidget->m_pDeviceSelectBox->findText(tr("None")); m_pOutputWidget->m_pDeviceSelectBox->blockSignals(true); m_pOutputWidget->m_pDeviceSelectBox->removeItem(index); m_pOutputWidget->m_pDeviceSelectBox->blockSignals(false); break; case SoundType::SOURCE: //需求31268待2501再合入 #if 1 m_pInputWidget->m_pIpVolumeSlider->setEnabled(true); #endif index = m_pInputWidget->m_pInputDeviceSelectBox->findText(tr("None")); m_pInputWidget->m_pInputDeviceSelectBox->blockSignals(true); m_pInputWidget->m_pInputDeviceSelectBox->removeItem(index); m_pInputWidget->m_pInputDeviceSelectBox->blockSignals(false); break; default: return; } } /* * 当前的输出端口是否应该添加到Combobox output list widget上 */ bool UkmediaMainWidget::comboboxOutputPortIsNeedAdd(int index, QString name) { QMultiMap::iterator it; for(it=currentCboxOutputPortLabelMap.begin();it!=currentCboxOutputPortLabelMap.end();) { if (index == it.key() && name == it.value()) { return false; } ++it; } return true; } /* * 当前的输出端口是否应该在output list widget上删除 */ bool UkmediaMainWidget::comboboxOutputPortIsNeedDelete(int index, QString name) { QMap>::iterator it; QMultiMap::iterator at; QMultiMap portMap; for(it = m_pVolumeControl->outputPortMap.begin();it!=m_pVolumeControl->outputPortMap.end();) { if (it.key() == index) { portMap = it.value(); for (at=portMap.begin();at!=portMap.end();) { if (name == at.value()) { return false; } ++at; } } ++it; } if(name == tr("None")){ return false; } return true; } /* * 添加可用的输出端口到ComBoBox output list widget */ void UkmediaMainWidget::addComboboxAvailableOutputPort() { QMap>::iterator at; QMultiMap::iterator it; QMultiMap tempMap; for (at = m_pVolumeControl->outputPortMap.begin(); at != m_pVolumeControl->outputPortMap.end();) { tempMap = at.value(); for (it = tempMap.begin(); it != tempMap.end();) { if (comboboxOutputPortIsNeedAdd(at.key(), it.value())) { qDebug() << "add output combox widget" << at.key()<< it.value() << it.key(); addComboboxOutputListWidgetItem(it.value(), findCardName(at.key(),m_pVolumeControl->cardMap)); currentCboxOutputPortLabelMap.insert(at.key(), it.value()); } ++it; } ++at; } } /* * 添加output port到Combobox output list widget */ void UkmediaMainWidget::addComboboxOutputListWidgetItem(QString portLabel, QString cardName) { int i = m_pOutputWidget->m_pDeviceSelectBox->count(); m_pOutputWidget->m_pDeviceSelectBox->blockSignals(true); m_pOutputWidget->m_pDeviceSelectBox->insertItem(i,portLabel,cardName); m_pOutputWidget->m_pDeviceSelectBox->blockSignals(false); qDebug() << "添加输出设备端口" << portLabel << cardName; } //传进来一个portName用来定位他的位置 int UkmediaMainWidget::indexOfOutputPortInOutputCombobox(QString portName) { for (int row=0;rowm_pDeviceSelectBox->count();row++) { QString text = m_pOutputWidget->m_pDeviceSelectBox->itemText(row); if (text == portName) return row; } return -1; } /* * 移除combobox output list widget上不可用的输出端口 */ void UkmediaMainWidget::deleteNotAvailableComboboxOutputPort() { //删除不可用的输出端口 QMultiMap::iterator it; for(it=currentCboxOutputPortLabelMap.begin();it!=currentCboxOutputPortLabelMap.end();) { //没找到,需要删除 if (comboboxOutputPortIsNeedDelete(it.key(),it.value())) { int index = indexOfOutputPortInOutputCombobox(it.value()); if (index == -1) return; m_pOutputWidget->m_pDeviceSelectBox->blockSignals(true); m_pOutputWidget->m_pDeviceSelectBox->removeItem(index); m_pOutputWidget->m_pDeviceSelectBox->hidePopup(); m_pOutputWidget->m_pDeviceSelectBox->blockSignals(false); qDebug()<< "deleteNotAvailableComboboxOutputPort" <m_pInputDeviceSelectBox->count();row++) { QString comboboxcardname = m_pInputWidget->m_pInputDeviceSelectBox->itemData(row).toString(); QString comboboxportname = m_pInputWidget->m_pInputDeviceSelectBox->itemText(row); if (comboboxcardname == cardName && comboboxportname == portLabel) { m_pInputWidget->m_pInputDeviceSelectBox->blockSignals(true); m_pInputWidget->m_pInputDeviceSelectBox->setCurrentIndex(row); m_pInputWidget->m_pInputDeviceSelectBox->blockSignals(false); if(!strstr(m_pVolumeControl->sourcePortName.toUtf8().constData(), "internal") \ && !strstr(m_pVolumeControl->sourcePortName.toUtf8().constData(), "[In] Mic1")) { Q_EMIT m_pVolumeControl->updateLoopBack(true); qDebug() << "findInputComboboxItem -> Q_EMIT updateLoopBack true" << m_pVolumeControl->sourcePortName; }else{ Q_EMIT m_pVolumeControl->updateLoopBack(false); qDebug() << "findInputComboboxItem -> Q_EMIT updateLoopBack false" << m_pVolumeControl->sourcePortName; } if (comboboxcardname.contains("bluez_card")) { isCheckBluetoothInput = true; } break; } else if (comboboxportname == tr("None")) { m_pInputWidget->m_pInputDeviceSelectBox->blockSignals(true); m_pInputWidget->m_pInputDeviceSelectBox->setCurrentIndex(row); m_pInputWidget->m_pInputDeviceSelectBox->blockSignals(false); Q_EMIT m_pVolumeControl->updateLoopBack(false); qDebug() << "findInputComboboxItem else -> Q_EMIT updateLoopBack false" << m_pVolumeControl->sourcePortName; } } } /* * 在input combobox list widget删除不可用的端口 */ void UkmediaMainWidget::deleteNotAvailableComboboxInputPort() { //删除不可用的输入端口 QMultiMap::iterator it; for(it=currentCboxInputPortLabelMap.begin();it!=currentCboxInputPortLabelMap.end();) { //没找到,需要删除 if (comboboxInputPortIsNeedDelete(it.key(),it.value())) { int index = indexOfInputPortInInputCombobox(it.value()); if (index == -1) return; m_pInputWidget->m_pInputDeviceSelectBox->blockSignals(true); m_pInputWidget->m_pInputDeviceSelectBox->removeItem(index); m_pInputWidget->m_pInputDeviceSelectBox->hidePopup(); m_pInputWidget->m_pInputDeviceSelectBox->blockSignals(false); it = currentCboxInputPortLabelMap.erase(it); continue; } ++it; } } //传进来input portName用来定位他的位置 int UkmediaMainWidget::indexOfInputPortInInputCombobox(QString portName) { for (int index=0;indexm_pInputDeviceSelectBox->count();index++) { QString textport = m_pInputWidget->m_pInputDeviceSelectBox->itemText(index); // QString textcard = m_pInputWidget->m_pInputDeviceSelectBox->itemData(index).toString(); if (textport == portName ) { return index; } } return -1; } /* * 添加可用的输入端口到input list widget */ void UkmediaMainWidget::addComboboxAvailableInputPort() { QMap>::iterator at; QMultiMap::iterator it; QMultiMap tempMap; qDebug() << "addComboboxAvailableInputPort"<< m_pVolumeControl->inputPortMap.count() << m_pInputWidget->m_pInputDeviceSelectBox->currentText(); //增加端口 for(at=m_pVolumeControl->inputPortMap.begin();at!=m_pVolumeControl->inputPortMap.end();) { tempMap = at.value(); for (it=tempMap.begin();it!=tempMap.end();){ if (comboboxInputPortIsNeedAdd(at.key(),it.value())){ addComboboxInputListWidgetItem(it.value(), findCardName(at.key(),m_pVolumeControl->cardMap)); currentCboxInputPortLabelMap.insert(at.key(), it.value()); } ++it; } ++at; } } /* * 添加输入端口到input combobox widget */ void UkmediaMainWidget::addComboboxInputListWidgetItem(QString portLabel, QString cardName) { int i = m_pInputWidget->m_pInputDeviceSelectBox->count(); m_pInputWidget->m_pInputDeviceSelectBox->blockSignals(true); m_pInputWidget->m_pInputDeviceSelectBox->insertItem(i,portLabel,cardName); m_pInputWidget->m_pInputDeviceSelectBox->blockSignals(false); qDebug() << "添加输入设备端口" << portLabel << cardName; } /* * 当前的输入端口是否应该添加到Combobox input list widget上 */ bool UkmediaMainWidget::comboboxInputPortIsNeedAdd(int index, QString name) { QMultiMap::iterator it; for(it=currentCboxInputPortLabelMap.begin();it!=currentCboxInputPortLabelMap.end();) { if (index == it.key() && name == it.value()) return false; ++it; } return true; } /* * 当前的输入端口是否应该在Combobox list上删除 */ bool UkmediaMainWidget::comboboxInputPortIsNeedDelete(int index, QString name) { QMap>::iterator it; QMultiMap::iterator at; QMultiMap portMap; for(it = m_pVolumeControl->inputPortMap.begin();it!=m_pVolumeControl->inputPortMap.end();) { if (it.key() == index) { portMap = it.value(); for (at=portMap.begin();at!=portMap.end();) { if (name == at.value()) return false; ++at; } } ++it; } if(name == tr("None")){ return false; } return true; } /* * 更新设备端口 */ void UkmediaMainWidget::updateCboxDevicePort() { QMap>::iterator it; QMultiMap::iterator at; QMultiMap temp; currentCboxInputPortLabelMap.clear(); currentCboxOutputPortLabelMap.clear(); qDebug() << __func__ << "output" << m_pVolumeControl->outputPortMap.count() << "input" << m_pVolumeControl->inputPortMap.count(); // 1. update sink int count = m_pVolumeControl->outputPortMap.count(); if (count == 0) { addNoneItem(SoundType::SINK); } else { removeNoneItem(SoundType::SINK); } // 2.update input count = m_pVolumeControl->inputPortMap.count(); // 2.1 无可用输入设备,需添加None端口 if (count == 0) { addNoneItem(SoundType::SOURCE); } // 2.2 存在两个及以上可用输入设备,需移除None端口 else if (count > 1) { removeNoneItem(SoundType::SOURCE); } // 2.3 存在一个可用输入设备时,判断是否为蓝牙(蓝牙默认a2dp,需保留None端口) else if (count == 1) { int cardIndex = m_pVolumeControl->inputPortMap.firstKey(); QString cardName = findCardName(cardIndex, m_pVolumeControl->cardMap); if (cardName.contains("bluez") ) addNoneItem(SoundType::SOURCE); else removeNoneItem(SoundType::SOURCE); } // 3.初始化设备端口 if (cboxfirstEntry) { for(it = m_pVolumeControl->outputPortMap.begin(); it != m_pVolumeControl->outputPortMap.end(); ++it) { temp = it.value(); for (at = temp.begin(); at != temp.end(); ++at) { QString cardName = findCardName(it.key(), m_pVolumeControl->cardMap); addComboboxOutputListWidgetItem(at.value(), cardName); } } for(it = m_pVolumeControl->inputPortMap.begin(); it != m_pVolumeControl->inputPortMap.end(); ++it) { temp = it.value(); for (at = temp.begin(); at != temp.end(); ++at) { QString cardName = findCardName(it.key(), m_pVolumeControl->cardMap); addComboboxInputListWidgetItem(at.value(), cardName); } } cboxfirstEntry = false; } else {// 4.更新设备端口 for (int i = 0; i < m_pOutputWidget->m_pDeviceSelectBox->count(); i++) { int index = -1; QMap::iterator at; QString portname = m_pOutputWidget->m_pDeviceSelectBox->itemText(i); QString cardname = m_pOutputWidget->m_pDeviceSelectBox->itemData(i).toString(); for (at = m_pVolumeControl->cardMap.begin(); at != m_pVolumeControl->cardMap.end(); ++at) { if (cardname == at.value()) { index = at.key(); break; } } currentCboxOutputPortLabelMap.insert(index, portname); } for(int i = 0; i < m_pInputWidget->m_pInputDeviceSelectBox->count(); i++) { int index = -1; QMap::iterator at; QString portname = m_pInputWidget->m_pInputDeviceSelectBox->itemText(i); QString cardname = m_pInputWidget->m_pInputDeviceSelectBox->itemData(i).toString(); for (at = m_pVolumeControl->cardMap.begin(); at != m_pVolumeControl->cardMap.end(); ++at) { if (cardname == at.value()) { index = at.key(); break; } } currentCboxInputPortLabelMap.insert(index,portname); } m_pInputWidget->m_pInputDeviceSelectBox->blockSignals(true); m_pOutputWidget->m_pDeviceSelectBox->blockSignals(true); deleteNotAvailableComboboxOutputPort(); addComboboxAvailableOutputPort(); initOutputComboboxItem(); deleteNotAvailableComboboxInputPort(); addComboboxAvailableInputPort(); m_pOutputWidget->m_pDeviceSelectBox->blockSignals(false); m_pInputWidget->m_pInputDeviceSelectBox->blockSignals(false); } } /* * Combobox选项改变,设置对应的输出设备 */ void UkmediaMainWidget::cboxoutputListWidgetCurrentRowChangedSlot() { //当所有可用的输出设备全部移除,台式机才会出现该情况 if (m_boxoutputRow == -1) return; QString outputComboboxPortName = m_pOutputWidget->m_pDeviceSelectBox->itemText(m_boxoutputRow); QString outputComboboxCardName = m_pOutputWidget->m_pDeviceSelectBox->itemData(m_boxoutputRow).toString(); QString inputComboboxPortName = m_pInputWidget->m_pInputDeviceSelectBox->currentText(); QString inputComboboxCardName = m_pInputWidget->m_pInputDeviceSelectBox->currentData().toString(); bool isContainBlue = inputComboboxDeviceContainBluetooth(); //当输出设备从蓝牙切换到其他设备时,需将蓝牙声卡的配置文件切换为a2dp-sink if (isContainBlue && (strstr(m_pVolumeControl->defaultSourceName,\ "headset_head_unit") || strstr(m_pVolumeControl->defaultSourceName,"bt_sco_source"))){ QString cardName = blueCardNameInCombobox(); setCardProfile(cardName,"a2dp_sink"); } QMap>::iterator outputProfileMap; QMap>::iterator inputProfileMap; QMultiMap tempMap; QMultiMap::iterator at; QString endOutputProfile = ""; QString endInputProfile = ""; int count,i; int currentCardIndex = findCardIndex(outputComboboxCardName,m_pVolumeControl->cardMap); QStringList outputComboboxPortNameList = outputComboboxPortName.split("(");//新增设计combobox需要显示 端口名+(description); QStringList inputComboboxPortNameList = inputComboboxPortName.split("("); for (outputProfileMap=m_pVolumeControl->profileNameMap.begin();outputProfileMap!= m_pVolumeControl->profileNameMap.end();) { if(currentCardIndex == outputProfileMap.key()){ tempMap = outputProfileMap.value(); for(at=tempMap.begin(),i=0;at!= tempMap.end();++i){ if (at.key() == outputComboboxPortNameList.at(0)) { count = i; endOutputProfile = at.value(); } ++at; } } ++outputProfileMap; } if (m_pInputWidget->m_pInputDeviceSelectBox->currentText().size()!=0) { QMultiMap ::iterator it; QMultiMap temp; int index = findCardIndex(inputComboboxCardName,m_pVolumeControl->cardMap); for (inputProfileMap=m_pVolumeControl->inputPortProfileNameMap.begin(),count=0;inputProfileMap!= m_pVolumeControl->inputPortProfileNameMap.end();count++) { if (inputProfileMap.key() == index) { temp = inputProfileMap.value(); for(it = temp.begin(); it != temp.end();){ if(it.key() == inputComboboxPortNameList.at(0)){ endInputProfile = it.value(); } ++it; } } ++inputProfileMap; } } qDebug() << "outputListWidgetCurrentRowChangedSlot" << m_boxoutputRow << outputComboboxPortName << endOutputProfile <m_pInputDeviceSelectBox->currentText().size()!=0 && outputComboboxCardName == inputComboboxCardName ) || \ outputComboboxCardName == "alsa_card.platform-sound_DA_combine_v5" && inputComboboxCardName == "3a.algo") { QString setProfile = endOutputProfile; if (!endOutputProfile.contains("input:analog-stereo") || !endOutputProfile.contains("HiFi")) { setProfile += "+"; setProfile +=endInputProfile; } if (endOutputProfile.contains("a2dp-sink") && endInputProfile.contains("headset-head-unit")) setProfile = endOutputProfile; setCardProfile(outputComboboxCardName,setProfile); setDefaultOutputPortDevice(outputComboboxCardName,outputComboboxPortName); } //如果选择的输入输出设备不是同一块声卡,需要设置一个优先级高的配置文件 else { int index = findCardIndex(outputComboboxCardName,m_pVolumeControl->cardMap); QMap >::iterator it; QString profileName; for(it=m_pVolumeControl->cardProfileMap.begin();it!=m_pVolumeControl->cardProfileMap.end();) { if (it.key() == index) { if (strstr(endOutputProfile.toLatin1().data(),"headset_head_unit")) endOutputProfile = "a2dp_sink"; profileName = findHighPriorityProfile(index,endOutputProfile); } ++it; } QString setProfile = profileName; setCardProfile(outputComboboxCardName,setProfile); setDefaultOutputPortDevice(outputComboboxCardName,outputComboboxPortName); } m_pOutputWidget->m_pDeviceSelectBox->setObjectName("m_pOutputWidget->m_pDeviceSelectBox"); ukcc::UkccCommon::buriedSettings("Audio", m_pOutputWidget->m_pDeviceSelectBox->objectName(),"select",m_pOutputWidget->m_pDeviceSelectBox->itemText(m_boxoutputRow)); qDebug() << "active combobox output port:" << outputComboboxPortName << outputComboboxCardName; } void UkmediaMainWidget::cboxinputListWidgetCurrentRowChangedSlot() { if (m_boxinputRow == -1) { return; } QString inputComboboxPortName = m_pInputWidget->m_pInputDeviceSelectBox->itemText(m_boxinputRow); QString inputComboboxCardName = m_pInputWidget->m_pInputDeviceSelectBox->itemData(m_boxinputRow).toString(); QString outputComboboxPortName = m_pOutputWidget->m_pDeviceSelectBox->currentText(); QString outputComboboxCardName = m_pOutputWidget->m_pDeviceSelectBox->currentData().toString(); bool isContainBlue = inputComboboxDeviceContainBluetooth(); //当输出设备从蓝牙切换到其他设备时,需将蓝牙声卡的配置文件切换为a2dp-sink if (isContainBlue && (strstr(m_pVolumeControl->defaultSinkName,"headset_head_unit") || strstr(m_pVolumeControl->defaultSourceName,"bt_sco_source"))) { QString cardName = blueCardNameInCombobox(); setCardProfile(cardName,"a2dp_sink"); } if(inputComboboxCardName.contains("bluez_card")) { isCheckBluetoothInput = true; } else { isCheckBluetoothInput = false; if(inputComboboxPortName == tr("None")){ qDebug() << "come Back Bluetooth A2dp Model"; return ; } } QMap>::iterator inputProfileMap; QMap>::iterator outputProfileMap; QMultiMap temp; QMultiMap::iterator at; QString endOutputProfile = ""; QString endInputProfile = ""; int index = findCardIndex(inputComboboxCardName,m_pVolumeControl->cardMap); QStringList outputComboboxPortNameList = outputComboboxPortName.split("(");//新增设计combobox需要显示 端口名+(description); QStringList inputComboboxPortNameList = inputComboboxPortName.split("("); for (inputProfileMap=m_pVolumeControl->inputPortProfileNameMap.begin();inputProfileMap!= m_pVolumeControl->inputPortProfileNameMap.end();) { if (inputProfileMap.key() == index) { temp = inputProfileMap.value(); for(at=temp.begin();at!=temp.end();){ if(at.key() == inputComboboxPortNameList.at(0)){ endInputProfile = at.value(); } ++at; } } ++inputProfileMap; } if (m_pOutputWidget->m_pDeviceSelectBox->currentText().size()!=0) { QMultiMap ::iterator it; QMultiMap temp; int index = findCardIndex(outputComboboxCardName,m_pVolumeControl->cardMap); for (outputProfileMap=m_pVolumeControl->profileNameMap.begin();outputProfileMap!= m_pVolumeControl->profileNameMap.end();) { if (outputProfileMap.key() == index) { temp = outputProfileMap.value(); for(it=temp.begin();it!=temp.end();){ if(it.key() == outputComboboxPortNameList.at(0)){ endOutputProfile = it.value(); } ++it; } } ++outputProfileMap; } } //如果选择的输入输出设备为同一个声卡,则追加指定输入输出端口属于的配置文件 if (m_pOutputWidget->m_pDeviceSelectBox->currentText().size()!=0 && inputComboboxCardName == outputComboboxCardName) { QString setProfile; //有些声卡的配置文件默认只有输入/输出设备或者配置文件包含了输出输入设备,因此只需要取其中一个配置文件即可 if (endOutputProfile == "a2dp-sink" || endInputProfile == "headset_head_unit" || endOutputProfile == "HiFi" ) { setProfile += endInputProfile; } else { setProfile += endOutputProfile; setProfile += "+"; setProfile +=endInputProfile; } setCardProfile(inputComboboxCardName,setProfile); setDefaultInputPortDevice(inputComboboxCardName,inputComboboxPortName); } //如果选择的输入输出设备不是同一块声卡,需要设置一个优先级高的配置文件 else { int index = findCardIndex(inputComboboxCardName,m_pVolumeControl->cardMap); QMap >::iterator it; QString profileName; for(it=m_pVolumeControl->cardProfileMap.begin();it!=m_pVolumeControl->cardProfileMap.end();) { if (it.key() == index) { QStringList list= it.value(); profileName = findHighPriorityProfile(index,endInputProfile); if (list.contains(endOutputProfile)) { } } ++it; } QString setProfile = profileName; setCardProfile(inputComboboxCardName,setProfile); setDefaultInputPortDevice(inputComboboxCardName,inputComboboxPortName); } m_pInputWidget->m_pInputDeviceSelectBox->setObjectName("m_pInputWidget->m_pInputDeviceSelectBox"); ukcc::UkccCommon::buriedSettings("Audio",m_pInputWidget->m_pInputDeviceSelectBox->objectName(),"select",m_pInputWidget->m_pInputDeviceSelectBox->itemText(m_boxinputRow)); qDebug() << "active combobox input port:" << inputComboboxPortName << inputComboboxCardName<< isCheckBluetoothInput; } void UkmediaMainWidget::updateComboboxListWidgetItemSlot() { qDebug() << "updateComboboxListWidgetItemSlot"; initOutputComboboxItem(); initInputComboboxItem(); themeChangeIcons(); } /***************************************************************** * 函数名称: initAlertSound * 功能描述: 初始化音效提示音 * 参数说明: 系统音效主题路径 /usr/share/sounds ******************************************************************/ void UkmediaMainWidget::initAlertSound(const char* path) { QDir dir(path); if (!dir.exists()) return; // 获取音效主题文件夹 dir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); QStringList nameList = dir.entryList(); QFileInfoList pathList = dir.entryInfoList(); // 忽略freeDesktop if (nameList.contains("freedesktop")) { nameList.removeOne("freedesktop"); pathList.removeOne(QFileInfo("/usr/share/sounds/freedesktop")); } for (int i = 0; i < pathList.size(); i++) { QFileInfo fileInfo = pathList.at(i); if (fileInfo.isDir()) addSoundFileInCombobox(fileInfo.filePath(), fileInfo.fileName()); } } /***************************************************************** * 函数名称: addSoundFileInCombobox * 功能描述: 添加音效文件到自定义音效下拉框 * 参数说明: path: 音效主题路径 dirName: 音效主题名 ******************************************************************/ void UkmediaMainWidget::addSoundFileInCombobox(QString path, QString dirName) { QDir dir(path); if (!dir.exists()) return; dir.setSorting(QDir::DirsFirst); dir.setFilter(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot); dir.setSorting(QDir::DirsLast); QStringList nameList = dir.entryList(); QFileInfoList pathList = dir.entryInfoList(); QString xmlName = dirName.append(".xml"); // 添加kylin规范音效主题 if (nameList.contains(xmlName) && nameList.contains("stereo")) { for(int i = 0; i < pathList.size(); i++) { QFileInfo fileInfo = pathList.at(i); if (fileInfo.fileName().contains(xmlName)) populateModelFromFile(this, fileInfo.filePath().toLatin1().data()); } } // 兼容社区音效主题 else if (! nameList.contains(xmlName) && nameList.contains("stereo")) { for(int i = 0; i < pathList.size(); i++) { QFileInfo fileInfo = pathList.at(i); if (fileInfo.fileName() == "stereo" && fileInfo.isDir()) { QDir d(fileInfo.filePath()); d.setFilter(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot); QFileInfoList dList = d.entryInfoList(); for(int i = 0; i < dList.size(); i++) { QFileInfo fileInfo = dList.at(i); QString name = fileInfo.fileName().append("("); name.append(dir.dirName()); name.append(")"); m_pSoundList.append(fileInfo.filePath()); m_pSoundNameList.append(name); m_pSoundWidget->m_pVolumeChangeCombobox->blockSignals(true); m_pSoundWidget->m_pVolumeChangeCombobox->addItem(name, fileInfo.filePath()); m_pSoundWidget->m_pVolumeChangeCombobox->blockSignals(false); m_pSoundWidget->m_pNotificationCombobox->blockSignals(true); m_pSoundWidget->m_pNotificationCombobox->addItem(name, fileInfo.filePath()); m_pSoundWidget->m_pNotificationCombobox->blockSignals(false); } } } } } /***************************************************************** * 函数名称: customSoundEffectsSlot * 功能描述: 设置自定义音效并同时播放对应音效 * 参数说明: 对应音效的index ******************************************************************/ void UkmediaMainWidget::customSoundEffectsSlot(int index) { //音效文件完整路径 QString soundPath = m_pSoundList.at(index); playAlretSoundFromPath(this, soundPath); QString soundType; QComboBox *p = qobject_cast(sender()); if (p == nullptr) return; if (p->objectName().contains("m_pVolumeChangeCombobox")) { soundType = "audioVolumeChange"; ukcc::UkccCommon::buriedSettings("Audio", p->objectName(),"select",p->currentText()); } else if (p->objectName().contains("m_pNotificationCombobox")) { soundType = "notificationGeneral"; ukcc::UkccCommon::buriedSettings("Audio", p->objectName(),"select",p->currentText()); } if (m_pSoundSettings->keys().contains(soundType)) m_pSoundSettings->set(soundType, soundPath); if (m_pSoundSettings->keys().contains("customTheme")) m_pSoundSettings->set(SOUND_CUSTOM_THEME_KEY, true); } /***************************************************************** * 函数名称: resetCustomSoundEffects * 功能描述: 切换音效主题时,已设置的自定义音效重置为主题默认音效 * 参数说明: theme: 主题名 soundFile: 自定义音效类型 * 返回值: 重置成功返回true,失败返回false ******************************************************************/ bool UkmediaMainWidget::resetCustomSoundEffects(QString theme, QString soundFile) { int index; QString path = SOUND_FILE_PATH; path.replace("xxxTheme", theme); path.replace("xxxFile", soundFile); if (m_pSoundList.contains(path)) index = m_pSoundList.indexOf(path); else if (m_pSoundList.contains(path.replace("ogg", "oga"))) //社区音效主题资源文件为oga格式 index = m_pSoundList.indexOf(path); else index = -1; if (-1 == index) return false; //恢复主题默认音效并同步定制音效的gsetting值 if (soundFile == AUDIO_VOLUME_CHANGE) { m_pSoundSettings->set(AUDIO_VOLUME_CHANGE, path); m_pSoundWidget->m_pVolumeChangeCombobox->blockSignals(true); m_pSoundWidget->m_pVolumeChangeCombobox->setCurrentIndex(index); m_pSoundWidget->m_pVolumeChangeCombobox->blockSignals(false); } else if (soundFile == NOTIFICATION_GENGERAL) { m_pSoundSettings->set(NOTIFICATION_GENGERAL, path); m_pSoundWidget->m_pNotificationCombobox->blockSignals(true); m_pSoundWidget->m_pNotificationCombobox->setCurrentIndex(index); m_pSoundWidget->m_pNotificationCombobox->blockSignals(false); } return true; } /***************************************************************** * 函数名称: playAlretSoundFromPath * 功能描述: 切换自定义音效时播放对应音效提示音 * 参数说明: path: 音效资源文件路径 ******************************************************************/ void UkmediaMainWidget::playAlretSoundFromPath (UkmediaMainWidget *w,QString path) { caPlayForWidget(w, 0, CA_PROP_APPLICATION_NAME, _("Sound Preferences"), CA_PROP_MEDIA_FILENAME, path.toLatin1().data(), CA_PROP_EVENT_DESCRIPTION, _("Testing event sound"), CA_PROP_CANBERRA_CACHE_CONTROL, "never", CA_PROP_APPLICATION_ID, "org.mate.VolumeControl", #ifdef CA_PROP_CANBERRA_ENABLE CA_PROP_CANBERRA_ENABLE, "1", #endif NULL); } ukui-media/audio/ukmedia_slider_tip_label_helper.h0000664000175000017500000000346615170052044021346 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #include #include "ukui_custom_style.h" class MediaSliderTipLabel:public QLabel { public: MediaSliderTipLabel(); ~MediaSliderTipLabel(); protected: void paintEvent(QPaintEvent*); }; class SliderTipLabelHelper : public QObject { Q_OBJECT friend class AppEventFilter; public: SliderTipLabelHelper(QObject *parent = nullptr); ~SliderTipLabelHelper() {} void registerWidget(QWidget *w); void unregisterWidget(QWidget *w); bool eventFilter(QObject *obj, QEvent *e); void mouseMoveEvent(QObject *obj, QMouseEvent *e); void mouseReleaseEvent(QObject *obj, QMouseEvent *e); void mousePressedEvent(QObject *obj,QMouseEvent *e); private: MediaSliderTipLabel *m_pTiplabel; }; class AppEventFilter : public QObject { friend class SliderTipLabelHelper; Q_OBJECT private: explicit AppEventFilter(SliderTipLabelHelper *parent); ~AppEventFilter() {} bool eventFilter(QObject *obj, QEvent *e); SliderTipLabelHelper *m_wm = nullptr; }; #endif // SLIDERTIPLABELHELPER_H ukui-media/audio/audio-device-control/0000775000175000017500000000000015170054730016651 5ustar fengfengukui-media/audio/audio-device-control/audio-device-control.pri0000664000175000017500000000021415170054730023376 0ustar fengfengINCLUDEPATH += $$PWD HEADERS += \ $$PWD/ukmedia_device_control_widget.h \ SOURCES += \ $$PWD/ukmedia_device_control_widget.cpp \ ukui-media/audio/audio-device-control/ukmedia_device_control_widget.cpp0000664000175000017500000006604515170054730025431 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "ukmedia_device_control_widget.h" QList devsInfo; QList devsInputInfo; /* * Class: UkmediaDevControlWidget * description: 输出设备的子类 */ //主窗口布局 UkmediaDevControlWidget::UkmediaDevControlWidget(QWidget *parent): KWidget(parent) { this->setIcon(QIcon::fromTheme("ukui-control-center")); this->setWidgetName(tr("Sound Equipment Control")); this->setWindowFlags(Qt::Dialog); this->setContentsMargins(0,0,0,0); initDevControlWidget(); } void UkmediaDevControlWidget::initDevControlWidget() { QVBoxLayout* vLayout = new QVBoxLayout(this); m_pOutputAndInputBar = new KNavigationBar(this); QStandardItem * outputItem = new QStandardItem(QIcon::fromTheme("audio-volume-overamplified-symbolic"),tr("Output Devices")); QStandardItem * inputItem = new QStandardItem(QIcon::fromTheme("ukui-microphone-on-symbolic"),tr("Input Devices")); // 设置项的样式 QString itemStyle = "QStandardItem { min-width: 17px; max-width: 17px; min-height: 36px; max-height: 36px; }"; outputItem->setData(itemStyle, Qt::UserRole); #define ITEM_FLAG 2 outputItem->setData(OutputItem, Qt::UserRole+ITEM_FLAG); inputItem->setData(InputItem, Qt::UserRole+ITEM_FLAG); QList list2; list2<addItem(outputItem); m_pOutputAndInputBar->addItem(inputItem); m_pOutputAndInputBar->setContentsMargins(0,0,0,0); m_pOutputAndInputBar->setFixedSize(188, 520); vLayout->addWidget(m_pOutputAndInputBar); sideBar()->setLayout(vLayout); //管理显示界面 //声明一个输出设备显示窗口 outputDevWidget = new OutputDevWidget; inputDevWidget = new InputDevWidget; // 创建QStackedWidget用于管理不同的界面 QStackedWidget* stackedWidget = new QStackedWidget(); stackedWidget->setContentsMargins(0,0,0,0); stackedWidget->setFixedSize(560, 518); stackedWidget->addWidget(outputDevWidget); stackedWidget->addWidget(inputDevWidget); QStandardItemModel *m_mode = m_pOutputAndInputBar->model(); m_pOutputAndInputBar->listview()->setCurrentIndex(m_mode->item(0,0)->index()); //添加右侧设备窗口布局 QVBoxLayout* vLayout2 = new QVBoxLayout; vLayout2->addWidget(stackedWidget); baseBar()->setLayout(vLayout2); //获取被点击的item的数据,跳转到对应的页面 connect(m_pOutputAndInputBar->listview(),&QListView::clicked,this,[=](const QModelIndex &index){ int flag = index.data(Qt::UserRole+ITEM_FLAG).toInt(); if (flag == OutputItem) { stackedWidget->setCurrentWidget(outputDevWidget); } else if(flag == InputItem){ stackedWidget->setCurrentWidget(inputDevWidget); } }); this->setLayoutType(HorizontalType); //设备初始化操作 outputDevWidget->updateOutputDev(); inputDevWidget->updateInputDev(); } UkmediaDevControlWidget::~UkmediaDevControlWidget() { } /* * Class: OutputDevWidget * description: 输出设备界面的显示、设备罗列、设备禁用与开启功能等功能实现 */ //输出设备窗口 OutputDevWidget::OutputDevWidget(QWidget *parent) { initOutputDevUi(); } void OutputDevWidget::initOutputDevUi() { m_pOutputDevWidget = new QFrame(this); m_pOutputDevWidget->setFrameShape(QFrame::Shape::Box); m_pOutputDevWidget->setContentsMargins(0, 0, 0, 0); //设置大小 m_pOutputDevWidget->setFixedSize(560,490); //标签:输出设备 m_pOutputDevLabel = new TitleLabel(m_pOutputDevWidget); m_pOutputDevLabel->setText(tr("Output Devices")); m_pOutputDevLabel->setFixedSize(480,24); m_pOutputDevLabel->setStyleSheet("QLabel{color: palette(windowText);}"); // 创建水平布局并添加标签和占位符小部件 QHBoxLayout* labelLayout = new QHBoxLayout(); labelLayout->addWidget(m_pOutputDevLabel); labelLayout->addStretch(); // 添加占位符小部件 // 设置水平布局的外边距 QMargins labelMargins(16, 0, 0, 0); // 设置左边外边距为 16px labelLayout->setContentsMargins(labelMargins); //设备窗口布局 //所有输出设备widget outputDevArea = new QScrollArea(this); outputDevArea->setFixedSize(512,410); outputDevArea->setFrameShape(QFrame::NoFrame);//bjc去掉outputDevArea的边框 outputDevArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); outputDevArea->verticalScrollBar()->setProperty("drawScrollBarGroove",false);//滚动条背景透明 displayOutputDevWidget = new QFrame(outputDevArea); displayOutputDevWidget->setFixedWidth(512); // displayOutputDevWidget->setAttribute(Qt::WA_TranslucentBackground); outputDevArea->setWidget(displayOutputDevWidget); m_pSubDevVlayout = new QVBoxLayout(displayOutputDevWidget); displayOutputDevWidget->setLayout(m_pSubDevVlayout); //设备窗口添加左右两边的间隙 QHBoxLayout *hlayout = new QHBoxLayout(); hlayout->setContentsMargins(0,0,0,0); hlayout->addItem(new QSpacerItem(10,410,QSizePolicy::Fixed)); hlayout->addWidget(outputDevArea); hlayout->addItem(new QSpacerItem(23,410,QSizePolicy::Fixed)); hlayout->setSpacing(0); // 创建确定按钮 m_pOutputConfirmBtn = new QPushButton(tr("Confirm"), this); m_pOutputConfirmBtn->setFixedSize(96, 36); m_pOutputConfirmBtn->setProperty("isImportant", true); // 创建水平布局并添加确定按钮和占位符小部件 QHBoxLayout* m_pHLayout = new QHBoxLayout(); m_pHLayout->addStretch(); // 添加占位符小部件 m_pHLayout->addWidget(m_pOutputConfirmBtn); m_pHLayout->addSpacing(24); // 添加右边框间距 //进行整体布局 QBoxLayout *m_pVlayout = new QVBoxLayout(m_pOutputDevWidget); m_pVlayout->setContentsMargins(0,0,0,0); m_pVlayout->addLayout(labelLayout); // m_pVlayout->addWidget(m_pOutputDevLabel); m_pVlayout->addItem(new QSpacerItem(16,8,QSizePolicy::Fixed)); m_pVlayout->addLayout(hlayout); // 将确定按钮添加到布局中 m_pVlayout->addLayout(m_pHLayout); m_pVlayout->addSpacing(30); // 添加下边框间距 m_pOutputDevWidget->setLayout(m_pVlayout); m_pOutputDevWidget->layout()->setContentsMargins(0,0,0,0); //确定按钮,关闭窗口 connect(m_pOutputConfirmBtn, &QPushButton::clicked, this, &OutputDevWidget::closeWindow); //监听dbus 移除输出设备信号 QDBusConnection::sessionBus().connect(QString(), "/org/ukui/media", "org.ukui.media","updateDevSignal",this,SLOT(updateOutputDev())); \ } void OutputDevWidget::closeWindow() { QWidget* currentWindow = window(); // 获取当前窗口 if (currentWindow) currentWindow->close(); // 关闭窗口 } //移除子设备窗口 void OutputDevWidget::updateOutputDev() { //将设备窗口中的子窗口删除 qDeleteAll(subDevWidgets); subDevWidgets.clear(); // 清空列表 devsInfo.clear(); //将设备窗口中的子窗重新刷新一遍 initOutputDevWidget(); } //初始化子设备窗口 void OutputDevWidget::initOutputDevWidget() { //获取设备函数 getPaDevInfo(); int outputCount = 0; int inputCount = 0; for (const pa_device_port_info &info : devsInfo) { if(info.direction == 1) outputCount++; else if(info.direction == 2) inputCount++; } for (const pa_device_port_info &info : devsInfo) { if(info.direction == 1) { qDebug() << "card:" << info.card << "name:" << info.name << "inOrOut" << info.direction << "dsc:" << info.description << "ava:" << info.available << "plugged_stauts" << info.plugged_stauts << "device_description:" << info.device_description << "device_product_name:" << info.device_product_name; QString devLabel = info.description + " (" + info.device_description + ")"; //添加子设备到输出设备窗口 addSubOutputDevWidget(info.card, info.name, devLabel, info.available, outputCount); } } } //添加子设备窗口 void OutputDevWidget::addSubOutputDevWidget(int card, QString portName, QString DeviceName, int Available, int itemNum) { //声明一个dev item类,写入声卡ID、设备名、label subDevWidget = new SubDevWidget(); //widget显示应用音量 QWidget *subDevItemwidget = new QWidget(displayOutputDevWidget); subDevItemwidget->setFixedSize(512,60); subDevItemwidget->setObjectName("subDevItemwidget"); subDevWidget->m_pDeviceNameLabel = new FixLabel(DeviceName, subDevItemwidget); subDevWidget->m_pDevDisableButton = new KSwitchButton(); subDevWidget->m_pDevDisableButton->setObjectName("DevDisableButton"); // 设置字符串属性 subDevWidget->setProperty("card", card); subDevWidget->setProperty("DeviceName", DeviceName); subDevWidget->m_pDevDisableButton->setProperty("card", card); subDevWidget->m_pDevDisableButton->setProperty("portName", portName); subDevWidget->m_pDevDisableButton->setProperty("DeviceName", DeviceName); //判断设备的可用状态来初始化 button 的选中状态 if(Available == 3){ subDevWidget->m_pDevDisableButton->setChecked(false); }else{ subDevWidget->m_pDevDisableButton->setChecked(true); } QHBoxLayout *outputDevControlLayout = new QHBoxLayout(subDevItemwidget); outputDevControlLayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); outputDevControlLayout->addWidget(subDevWidget->m_pDeviceNameLabel); outputDevControlLayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Expanding)); outputDevControlLayout->addWidget(subDevWidget->m_pDevDisableButton); outputDevControlLayout->addItem(new QSpacerItem(16, 60, QSizePolicy::Fixed)); outputDevControlLayout->setSpacing(0); subDevItemwidget->setLayout(outputDevControlLayout); subDevItemwidget->layout()->setContentsMargins(0,0,0,0); //设置设备子窗口样式 QString themeName = UKUI_THEME_DEFAULT; if (QGSettings::isSchemaInstalled("org.ukui.style")) { QGSettings *nThemeSetting = new QGSettings("org.ukui.style"); if (nThemeSetting->keys().contains("styleName")) { themeName = nThemeSetting->get("style-name").toString(); } } // 设置自定义属性 subDevWidget->m_pDevDisableButton->setProperty("customBackground", true); if (themeName == UKUI_THEME_DEFAULT || themeName == UKUI_THEME_LIGHT) subDevItemwidget->setStyleSheet("#subDevItemwidget{border-radius: 6px; background-color: #F5F5F5;}"); else if (themeName == UKUI_THEME_DARK) subDevItemwidget->setStyleSheet("#subDevItemwidget{border-radius: 6px; background-color: #333333;}"); m_pSubDevVlayout->addWidget(subDevItemwidget); displayOutputDevWidget->resize(512, itemNum*60); m_pSubDevVlayout->setSpacing(2); m_pSubDevVlayout->setContentsMargins(0,0,0,0); displayOutputDevWidget->setLayout(m_pSubDevVlayout); m_pSubDevVlayout->update(); subDevItemwidget->setProperty("label",DeviceName); subDevWidgets.append(subDevItemwidget); // 连接按钮的信号与槽 connectSubDevButton(subDevWidget->m_pDevDisableButton); } //将每个子设备窗口的button进行设备关联 void OutputDevWidget::connectSubDevButton(KSwitchButton *button) { connect(button, &KSwitchButton::stateChanged, [=](bool state) { int card = button->property("card").toInt(); QString portName = button->property("portName").toString(); qDebug() << "connectSubDevButton button" << state << card << portName; #ifdef PA_PORT_AVAILABLE_DISABLE int outputdevstate = state? PA_PORT_AVAILABLE_YES : PA_PORT_AVAILABLE_DISABLE; #else int outputdevstate = PA_PORT_AVAILABLE_YES; #endif setDevDisable(card, portName, outputdevstate); }); } //通过DBUS传输具体设备给pulseaudio来禁用或开启设备 void OutputDevWidget::setDevDisable(int cardId, QString portName, int devOutputState) { // 创建DBus连接 QDBusConnection connection = QDBusConnection::sessionBus(); // 创建DBus接口 QDBusInterface interface("org.PulseAudio.DeviceControl", "/org/pulseaudio/device_control","org.PulseAudio.DeviceControl", connection); qDebug() << " setDevDisable() " << cardId << portName << devOutputState; // 调用DBus方法 QDBusReply response = interface.call("SetDeviceStatus", cardId, portName, devOutputState); qDebug() << response; } //通过DBUS从pulseaudio中获取设备信息 void OutputDevWidget::getPaDevInfo() { // 注册自定义结构体类型 qRegisterMetaType("pa_device_port_info"); qDBusRegisterMetaType(); QDBusConnection bus = QDBusConnection::sessionBus(); if (!bus.isConnected()) { qDebug() << "Cannot connect to D-Bus."; return ; } QDBusMessage message = QDBusMessage::createMethodCall("org.PulseAudio.DeviceControl", "/org/pulseaudio/device_control", "org.PulseAudio.DeviceControl", "GetAllDeviceInfo"); QDBusMessage reply = bus.call(message); const QDBusArgument &dbusArgs = reply.arguments().at(0).value().variant().value(); dbusArgs.beginArray(); while (!dbusArgs.atEnd()) { pa_device_port_info info; dbusArgs.beginStructure(); dbusArgs >> info.card; dbusArgs >> info.direction; dbusArgs >> info.available; dbusArgs >> info.plugged_stauts; dbusArgs >> info.name; dbusArgs >> info.description; dbusArgs >> info.device_description; // dbusArgs >> info.device_product_name; //过滤掉不可用端口 if(info.available == PA_PORT_AVAILABLE_NO || info.plugged_stauts == PORT_STAUTS_UNPLUGGED) { dbusArgs.endStructure(); continue; } qDebug() << " getPaDevInfo()" << info.available; // dbusArgs >> info; devsInfo.push_back(info); dbusArgs.endStructure(); } dbusArgs.endArray(); } QFrame* OutputDevWidget::myLine() { QFrame *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; } OutputDevWidget::~OutputDevWidget() { } /* * Class: InputDevWidget * description: 输入设备界面的显示、设备罗列、设备禁用与开启功能等功能实现 */ //输出设备窗口 InputDevWidget::InputDevWidget(QWidget *parent) { initInputDevUi(); } void InputDevWidget::initInputDevUi() { m_pInputDevWidget = new QFrame(this); m_pInputDevWidget->setFrameShape(QFrame::Shape::Box); m_pInputDevWidget->setContentsMargins(0, 0, 0, 0); //设置大小 m_pInputDevWidget->setFixedSize(560, 490); //标签:输出设备 m_pInputDevLabel = new TitleLabel(m_pInputDevWidget); m_pInputDevLabel->setText(tr("Input Devices")); m_pInputDevLabel->setFixedSize(480, 24); m_pInputDevLabel->setStyleSheet("QLabel{color: palette(windowText);}"); // 创建水平布局并添加标签和占位符小部件 QHBoxLayout* labelLayout = new QHBoxLayout(); labelLayout->addWidget(m_pInputDevLabel); labelLayout->addStretch(); // 添加占位符小部件 // 设置水平布局的外边距 QMargins labelMargins(16, 0, 0, 0); // 设置左边外边距为 16px labelLayout->setContentsMargins(labelMargins); //设备窗口布局 //所有输出设备widget inputDevArea = new QScrollArea(this); inputDevArea->setFixedSize(512, 410); inputDevArea->setFrameShape(QFrame::NoFrame);//bjc去掉inputDevArea的边框 inputDevArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); inputDevArea->verticalScrollBar()->setProperty("drawScrollBarGroove",false);//滚动条背景透明 displayInputDevWidget = new QFrame(inputDevArea); displayInputDevWidget->setFixedWidth(512); // displayInputDevWidget->setAttribute(Qt::WA_TranslucentBackground); inputDevArea->setWidget(displayInputDevWidget); m_pSubDevVlayout = new QVBoxLayout(displayInputDevWidget); displayInputDevWidget->setLayout(m_pSubDevVlayout); //设备窗口添加左右两边的间隙 QHBoxLayout *hlayout = new QHBoxLayout(); hlayout->setContentsMargins(0,0,0,0); hlayout->addItem(new QSpacerItem(10, 410, QSizePolicy::Fixed)); hlayout->addWidget(inputDevArea); hlayout->addItem(new QSpacerItem(23, 410, QSizePolicy::Fixed)); hlayout->setSpacing(0); // 创建确定按钮 m_pInputConfirmBtn = new QPushButton(tr("Confirm"), this); m_pInputConfirmBtn->setFixedSize(96, 36); m_pInputConfirmBtn->setProperty("isImportant", true); // 创建水平布局并添加确定按钮和占位符小部件 QHBoxLayout* m_pHLayout = new QHBoxLayout(); m_pHLayout->addStretch(); // 添加占位符小部件 m_pHLayout->addWidget(m_pInputConfirmBtn); m_pHLayout->addSpacing(24); // 添加右边框间距 //进行整体布局 QBoxLayout *m_pVlayout = new QVBoxLayout(m_pInputDevWidget); m_pVlayout->setContentsMargins(0,0,0,0); m_pVlayout->addLayout(labelLayout); // m_pVlayout->addWidget(m_pInputDevLabel); m_pVlayout->addItem(new QSpacerItem(16, 8, QSizePolicy::Fixed)); m_pVlayout->addLayout(hlayout); // 将确定按钮添加到布局中 m_pVlayout->addLayout(m_pHLayout); m_pVlayout->addSpacing(30); // 添加下边框间距 m_pInputDevWidget->setLayout(m_pVlayout); m_pInputDevWidget->layout()->setContentsMargins(0,0,0,0); //确定按钮,关闭窗口 connect(m_pInputConfirmBtn, &QPushButton::clicked, this, &InputDevWidget::closeWindow); //监听dbus 移除输出设备信号 QDBusConnection::sessionBus().connect(QString(), "/org/ukui/media", "org.ukui.media", "updateDevSignal", this, SLOT(updateInputDev())); } void InputDevWidget::closeWindow() { QWidget* currentWindow = window(); // 获取当前窗口 if (currentWindow) currentWindow->close(); // 关闭窗口 } //移除子设备窗口 void InputDevWidget::updateInputDev() { //将设备窗口中的子窗口删除 qDeleteAll(subDevWidgets); subDevWidgets.clear(); // 清空列表 devsInputInfo.clear(); //将设备窗口中的子窗重新刷新一遍 initInputDevWidget(); } //初始化子设备窗口 void InputDevWidget::initInputDevWidget() { //获取设备函数 getPaDevInfo(); int inputCount = 0; for (const pa_device_port_info &info : devsInputInfo) { if(info.direction == 2) inputCount++; } for (const pa_device_port_info &info : devsInputInfo) { if(info.direction == 2) { qDebug() << "card:" << info.card << "name:" << info.name << "inOrOut" << info.direction << "dsc:" << info.description << "ava:" << info.available << "plugged_stauts" << info.plugged_stauts << "device_description:" << info.device_description << "device_product_name:" << info.device_product_name; QString devLabel = info.description + " (" + info.device_description + ")"; //添加子设备到输出设备窗口 addSubInputDevWidget(info.card,info.name,devLabel,info.available,inputCount); } } } //添加子设备窗口 void InputDevWidget::addSubInputDevWidget(int card, QString portName, QString DeviceName, int Available, int itemNum) { //声明一个dev item类,写入声卡ID、设备名、label subDevWidget = new SubDevWidget(); //widget显示应用音量 QWidget *subDevItemwidget = new QWidget(displayInputDevWidget); subDevItemwidget->setFixedSize(512, 60); subDevItemwidget->setObjectName("subDevItemwidget"); subDevWidget->m_pDeviceNameLabel = new FixLabel(DeviceName, subDevItemwidget); subDevWidget->m_pDevDisableButton = new KSwitchButton(); subDevWidget->m_pDevDisableButton->setObjectName("DevDisableButton"); // 设置字符串属性 subDevWidget->setProperty("card", card); subDevWidget->setProperty("DeviceName", DeviceName); subDevWidget->m_pDevDisableButton->setProperty("card", card); subDevWidget->m_pDevDisableButton->setProperty("portName", portName); subDevWidget->m_pDevDisableButton->setProperty("DeviceName", DeviceName); //判断设备的可用状态来初始化 button 的选中状态 if(Available == 3){ subDevWidget->m_pDevDisableButton->setChecked(false); }else{ subDevWidget->m_pDevDisableButton->setChecked(true); } QHBoxLayout *inputDevControlLayout = new QHBoxLayout(subDevItemwidget); inputDevControlLayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); inputDevControlLayout->addWidget(subDevWidget->m_pDeviceNameLabel); inputDevControlLayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Expanding)); inputDevControlLayout->addWidget(subDevWidget->m_pDevDisableButton); inputDevControlLayout->addItem(new QSpacerItem(16, 20, QSizePolicy::Fixed)); inputDevControlLayout->setSpacing(0); subDevItemwidget->setLayout(inputDevControlLayout); subDevItemwidget->layout()->setContentsMargins(0,0,0,0); //设置设备子窗体颜色 QString themeName = UKUI_THEME_DEFAULT; if (QGSettings::isSchemaInstalled("org.ukui.style")) { QGSettings *nThemeSetting = new QGSettings("org.ukui.style"); if (nThemeSetting->keys().contains("styleName")) { themeName = nThemeSetting->get("style-name").toString(); } } if (themeName == UKUI_THEME_DEFAULT || themeName == UKUI_THEME_LIGHT) subDevItemwidget->setStyleSheet("#subDevItemwidget{border-radius: 6px; background-color: #F5F5F5;}"); else if (themeName == UKUI_THEME_DARK) subDevItemwidget->setStyleSheet("#subDevItemwidget{border-radius: 6px; background-color: #333333;}"); //设置窗体圆角 subDevItemwidget->setStyleSheet("QWidget { border-radius: 60px; }"); m_pSubDevVlayout->addWidget(subDevItemwidget); displayInputDevWidget->resize(512, itemNum*60); m_pSubDevVlayout->setSpacing(2); m_pSubDevVlayout->setContentsMargins(0,0,0,0); displayInputDevWidget->setLayout(m_pSubDevVlayout); m_pSubDevVlayout->update(); subDevItemwidget->setProperty("label",DeviceName); subDevWidgets.append(subDevItemwidget); // 连接按钮的信号与槽 connectSubDevButton(subDevWidget->m_pDevDisableButton); } //将每个子设备窗口的button进行设备关联 void InputDevWidget::connectSubDevButton(KSwitchButton *button) { connect(button, &KSwitchButton::stateChanged, [=](bool state) { int card = button->property("card").toInt(); QString portName = button->property("portName").toString(); qDebug() << "connectSubDevButton button" << state << card << portName; #ifdef PA_PORT_AVAILABLE_DISABLE int inputDevState = state? PA_PORT_AVAILABLE_YES : PA_PORT_AVAILABLE_DISABLE; #else int inputDevState = PA_PORT_AVAILABLE_YES; #endif setDevDisable(card, portName, inputDevState); }); } //通过DBUS传输具体设备给pulseaudio来禁用或开启设备 void InputDevWidget::setDevDisable(int cardId, QString portName, int inputDevState) { // 创建DBus连接 QDBusConnection connection = QDBusConnection::sessionBus(); // 创建DBus接口 QDBusInterface interface("org.PulseAudio.DeviceControl", "/org/pulseaudio/device_control", "org.PulseAudio.DeviceControl", connection); qDebug() << " setDevDisable " << cardId << portName << inputDevState; // 调用DBus方法 QDBusReply response = interface.call("SetDeviceStatus", cardId, portName, inputDevState); qDebug() << response; } //通过DBUS从pulseaudio中获取设备信息 void InputDevWidget::getPaDevInfo() { // 注册自定义结构体类型 qRegisterMetaType("pa_device_port_info"); qDBusRegisterMetaType(); QDBusConnection bus = QDBusConnection::sessionBus(); if (!bus.isConnected()) { qDebug() << "Cannot connect to D-Bus."; return ; } QDBusMessage message = QDBusMessage::createMethodCall("org.PulseAudio.DeviceControl", "/org/pulseaudio/device_control", "org.PulseAudio.DeviceControl", "GetAllDeviceInfo"); QDBusMessage reply = bus.call(message); const QDBusArgument &dbusArgs = reply.arguments().at(0).value().variant().value(); // QList devsInputInfo; dbusArgs.beginArray(); while (!dbusArgs.atEnd()) { pa_device_port_info info; dbusArgs.beginStructure(); dbusArgs >> info.card; dbusArgs >> info.direction; dbusArgs >> info.available; dbusArgs >> info.plugged_stauts; dbusArgs >> info.name; dbusArgs >> info.description; dbusArgs >> info.device_description; dbusArgs >> info.device_product_name; //过滤掉不可用端口 if(info.available == PA_PORT_AVAILABLE_NO || info.plugged_stauts == PORT_STAUTS_UNPLUGGED) { dbusArgs.endStructure(); continue; } // dbusArgs >> info; devsInputInfo.push_back(info); dbusArgs.endStructure(); } dbusArgs.endArray(); } QFrame* InputDevWidget::myLine() { QFrame *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; } InputDevWidget::~InputDevWidget() { } /* * Class: SubDevWidget * description: 单个设备的子类,其中会记录声卡ID、端口名、label名 */ SubDevWidget::SubDevWidget(QWidget *parent) { } void SubDevWidget::setLabelText(QString deviceLabel) { this->m_pDeviceNameLabel->setText(deviceLabel); } SubDevWidget::~SubDevWidget() { } ukui-media/audio/audio-device-control/ukmedia_device_control_widget.h0000664000175000017500000001252515170054730025070 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifndef UKMEDIA_DEVCONTROL_WIDGET_H #define UKMEDIA_DEVCONTROL_WIDGET_H #include "kwidget.h" #include #include #include #include "knavigationbar.h" #include #include #include #include #include #include "kswitchbutton.h" #include #include #include #include #include #include #include #include "../ukui_custom_style.h" #include "../../common/ukmedia_common.h" using namespace kdk; //记录单个设备类 class SubDevWidget : public QWidget { Q_OBJECT public: SubDevWidget(QWidget *parent = nullptr); ~SubDevWidget(); friend class OutputDevWidget; friend class InputDevWidget; void setLabelText(QString deviceLabel); private: int cardId; QString devPortName; QString devLabelName; KSwitchButton *m_pDevDisableButton; FixLabel *m_pDeviceNameLabel; public Q_SLOTS: }; //输出设备类 class OutputDevWidget : public QWidget { Q_OBJECT public: OutputDevWidget(QWidget *parent = nullptr); ~OutputDevWidget(); friend class UkmediaDevControlWidget; QFrame *displayOutputDevWidget; //显示子设备窗口 private: QListWidget *m_pOutputListWidget; //用于存放itemWidget SubDevWidget* subDevWidget; QFrame *m_pOutputDevWidget; //输出设备主窗口 QFrame *m_pOutputDevControlWidget; //设备禁用窗口 TitleLabel *m_pOutputDevLabel; //标签:输出设备 QScrollArea *outputDevArea; //可动态添加的窗口区域 QVBoxLayout *m_pSubDevVlayout; //设备窗口的动态垂直布局 KSwitchButton *m_pDevDisableButton; //输出设备禁用按钮 QPushButton *m_pOutputConfirmBtn; //“确定”按钮 QHBoxLayout *mConfirmBtnHLaout; //按钮布局 QFrame* myLine(); void initOutputDevUi(); void initOutputDevWidget(); //初始化输出设备页面 void addSubOutputDevWidget(int card, QString portName, QString DeviceName, int Available, int itemNum); void connectSubDevButton(KSwitchButton *button); QList subDevWidgets; // 保存子设备窗口的容器 public Q_SLOTS: void closeWindow(); void updateOutputDev(); //移除子设备窗口 void setDevDisable(int cardId,QString portName,int devState); //通过DBUS发送信号给pulseaudio,进行设备禁用 void getPaDevInfo(); //通过DBUS从pulseaudio中获取设备信息 }; //输入设备类 class InputDevWidget : public QWidget { Q_OBJECT public: InputDevWidget(QWidget *parent = nullptr); ~InputDevWidget(); friend class UkmediaDevControlWidget; QFrame *displayInputDevWidget; //显示子设备窗口 private: QListWidget *m_pInputListWidget; //用于存放itemWidget SubDevWidget* subDevWidget; QFrame *m_pInputDevWidget; //输出设备主窗口 QFrame *m_pInputDevControlWidget; //设备禁用窗口 TitleLabel *m_pInputDevLabel; //标签:输出设备 QScrollArea *inputDevArea; //可动态添加的窗口区域 QVBoxLayout *m_pSubDevVlayout; //设备窗口的动态垂直布局 KSwitchButton *m_pDevDisableButton; //输入设备禁用按钮 QPushButton *m_pInputConfirmBtn; //“确定”按钮 QHBoxLayout *mConfirmBtnHLaout; //按钮布局 QFrame* myLine(); void initInputDevUi(); void initInputDevWidget(); //初始化输出设备页面 void addSubInputDevWidget(int card, QString portName, QString DeviceName, int Available, int itemNum); void connectSubDevButton(KSwitchButton *button); QList subDevWidgets; // 保存子设备窗口的容器 public Q_SLOTS: void closeWindow(); void updateInputDev(); //移除子设备窗口 void setDevDisable(int cardId,QString portName,int devState); //通过DBUS发送信号给pulseaudio,进行设备禁用 void getPaDevInfo(); //通过DBUS从pulseaudio中获取设备信息 }; //设备管理主类 class UkmediaDevControlWidget : public KWidget { Q_OBJECT enum KNavigationBarPosition { OutputItem = 1, InputItem }; public: explicit UkmediaDevControlWidget(QWidget *parent = nullptr); ~UkmediaDevControlWidget(); private: QFrame *m_pDevControlWidget; KNavigationBar* m_pOutputAndInputBar; OutputDevWidget *outputDevWidget; InputDevWidget *inputDevWidget; void initDevControlWidget(); }; #endif // UKMEDIA_DEVCONTROL_WIDGET_H ukui-media/ukui-volume-control-applet-qt/0000775000175000017500000000000015170054730017401 5ustar fengfengukui-media/ukui-volume-control-applet-qt/ukmedia_osd_display_widget.h0000664000175000017500000000555315170054730025136 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #include #include #include #include #include #include #include #include // Don't include X11 globally to avoid None macro conflict // #include #include #include #include #include #include #include "windowmanager/windowmanager.h" #include "ukuistylehelper/ukuistylehelper.h" #define OSDWIDGET_SIZE 92,92 #define ICON_SIZE 48,48 #define TRANSPARENCY "org.ukui.control-center.personalise" #define THEME "org.ukui.style" //任务栏多屏显示声音应用获取屏幕可用区域接口 #define PANEL_SETTINGS "org.ukui.panel.settings" #define PANEL_SIZE_KEY "panelsize" #define PANEL_POSITION_KEY "panelposition" class UkmediaOsdDisplayWidget : public QWidget { Q_OBJECT enum PanelPosition{ Bottom = 0, //!< The bottom side of the screen. Top, //!< The top side of the screen. Left, //!< The left side of the screen. Right //!< The right side of the screen. }; public: UkmediaOsdDisplayWidget(QWidget *parent = nullptr); ~UkmediaOsdDisplayWidget(); friend class DeviceSwitchWidget; void initAttribute(); void initGsettings(); void initPanelGSettings(); QRect caculatePosition(); //任务栏多屏显示声音应用获取屏幕可用区域接口 void geometryChangedHandle(); void setIcon(QString iconStr); void dialogShow(); QTimer *mTimer; QLabel *iconLabel; QString mIconName; QGSettings *m_pThemeSetting; QPixmap drawLightColoredPixmap(const QPixmap &source, const QString &style); public Q_SLOTS: void repaintWidget(); void timeoutHandle(); void ukuiThemeChangedSlot(const QString &themeStr); double getGlobalOpacity(); private: QFrame *m_frame; QGSettings *m_panelGSettings = nullptr; int m_panelPosition; int m_panelSize; protected: void paintEvent(QPaintEvent *event); void resizeEvent(QResizeEvent* event); }; #endif // UKMEDIAOSDDISPLAYWIDGET_H ukui-media/ukui-volume-control-applet-qt/config.h0000664000175000017500000000220415170054730021015 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #include #include #include "xatom/xatom-helper.h" #include #include #include UkmediaOsdDisplayWidget::UkmediaOsdDisplayWidget(QWidget *parent) : QWidget (parent) { setFixedSize(OSDWIDGET_SIZE); m_frame = new QFrame(this); m_frame->setFixedSize(QSize(72,72)); m_frame->move(10,10); iconLabel = new QLabel(m_frame); iconLabel->setFixedSize(QSize(ICON_SIZE)); iconLabel->move((m_frame->width() - iconLabel->width())/2,( m_frame->height() - iconLabel->height())/2); initGsettings(); initAttribute(); initPanelGSettings(); } /** * @brief UkmediaOsdDisplayWidget::initAttribute * 初始化界面属性 */ void UkmediaOsdDisplayWidget::initAttribute() { mTimer = new QTimer(); connect(mTimer,SIGNAL(timeout()),this,SLOT(timeoutHandle())); connect(QApplication::primaryScreen(), &QScreen::geometryChanged, this, &UkmediaOsdDisplayWidget::geometryChangedHandle); connect(static_cast(QCoreApplication::instance()), &QApplication::primaryScreenChanged, this, &UkmediaOsdDisplayWidget::geometryChangedHandle); setWindowFlags(Qt::FramelessWindowHint | Qt::Tool | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint | Qt::Popup); setAttribute(Qt::WA_TranslucentBackground,true); setAutoFillBackground(true); } /** * @brief UkmediaOsdDisplayWidget::initGsettings * 初始化配置文件 */ void UkmediaOsdDisplayWidget::initGsettings() { if (QGSettings::isSchemaInstalled(THEME)) { m_pThemeSetting = new QGSettings(THEME); connect(m_pThemeSetting, &QGSettings::changed, this, &UkmediaOsdDisplayWidget::ukuiThemeChangedSlot); } } void UkmediaOsdDisplayWidget::initPanelGSettings() { const QByteArray id(PANEL_SETTINGS); if (QGSettings::isSchemaInstalled(id)) { if (m_panelGSettings == nullptr) { m_panelGSettings = new QGSettings(id); } if (m_panelGSettings->keys().contains(PANEL_POSITION_KEY)) { m_panelPosition = m_panelGSettings->get(PANEL_POSITION_KEY).toInt(); } if (m_panelGSettings->keys().contains(PANEL_SIZE_KEY)) { m_panelSize = m_panelGSettings->get(PANEL_SIZE_KEY).toInt(); } connect(m_panelGSettings, &QGSettings::changed, this, [&] (const QString &key) { if (key == PANEL_POSITION_KEY) { m_panelPosition = m_panelGSettings->get(PANEL_POSITION_KEY).toInt(); } if (key == PANEL_SIZE_KEY) { m_panelSize = m_panelGSettings->get(PANEL_SIZE_KEY).toInt(); } if (this->isVisible()) { geometryChangedHandle(); } }); } } QRect UkmediaOsdDisplayWidget::caculatePosition() { QRect availableGeo = QGuiApplication::screenAt(QCursor::pos())->geometry(); int x, y; int margin = 8; switch (m_panelPosition) { case PanelPosition::Top: { x = availableGeo.x() + availableGeo.width() - this->width() - margin; y = availableGeo.y() + m_panelSize + margin; } break; case PanelPosition::Bottom: { x = availableGeo.x() + availableGeo.width() - this->width() - margin; y = availableGeo.y() + availableGeo.height() - m_panelSize - this->height() - margin; } break; case PanelPosition::Left: { x = availableGeo.x() + m_panelSize + margin; y = availableGeo.y() + availableGeo.height() - this->height() - margin; } break; case PanelPosition::Right: { x = availableGeo.x() + availableGeo.width() - m_panelSize - this->width() - margin; y = availableGeo.y() + availableGeo.height() - this->height() - margin; } break; } return QRect(x, y, this->width(), this->height()); } /** * @brief UkmediaOsdDisplayWidget::dialogShow * 弹出窗口 */ void UkmediaOsdDisplayWidget::dialogShow() { geometryChangedHandle(); repaintWidget(); show(); mTimer->start(2000); } /** * @brief UkmediaOsdDisplayWidget::timeoutHandle * 超时隐藏窗口 */ void UkmediaOsdDisplayWidget::timeoutHandle() { hide(); mTimer->stop(); } /** * @brief UkmediaOsdDisplayWidget::setIcon * 设置弹窗图标 */ void UkmediaOsdDisplayWidget::setIcon(QString iconStr) { mIconName.clear(); mIconName = iconStr; } /** * @brief UkmediaOsdDisplayWidget::repaintWidget * 重绘窗口 */ void UkmediaOsdDisplayWidget::repaintWidget() { if(m_pThemeSetting->get("style-name").toString() == "ukui-light"){ setPalette(QPalette(QColor("#F5F5F5")));//设置窗口背景 } else{ setPalette(QPalette(QColor("#232426")));//设置窗口背景色 } QPixmap m_pixmap = QIcon::fromTheme(mIconName).pixmap(QSize(48,48)); iconLabel->setPixmap(drawLightColoredPixmap(m_pixmap,m_pThemeSetting->get("style-name").toString())); } /** * @brief UkmediaOsdDisplayWidget::ukuiThemeChangedSlot * 更换系统主题自动刷新界面 */ void UkmediaOsdDisplayWidget::ukuiThemeChangedSlot(const QString &themeStr) { if (!this->isHidden()) { hide(); repaintWidget(); geometryChangedHandle(); show(); } } /** * @brief UkmediaOsdDisplayWidget::getGlobalOpacity * 获取系统透明度 */ double UkmediaOsdDisplayWidget::getGlobalOpacity() { double transparency=0.0; if(QGSettings::isSchemaInstalled(TRANSPARENCY)) { QGSettings gsetting(TRANSPARENCY); if(gsetting.keys().contains(QString("transparency"))) transparency=gsetting.get("transparency").toDouble(); } return transparency; } QT_BEGIN_NAMESPACE extern void qt_blurImage(QImage &blurImage, qreal radius, bool quality, int transposed); QT_END_NAMESPACE QPixmap UkmediaOsdDisplayWidget::drawLightColoredPixmap(const QPixmap &source, const QString &style) { int value = 255; if(style == "ukui-light"){ value = 0; } QColor gray(255,255,255); QColor standard (0,0,0); QImage img = source.toImage(); for (int x = 0; x < img.width(); x++) { for (int y = 0; y < img.height(); y++) { auto color = img.pixelColor(x, y); if (color.alpha() > 0) { if (qAbs(color.red()-gray.red())<20 && qAbs(color.green()-gray.green())<20 && qAbs(color.blue()-gray.blue())<20) { color.setRed(value); color.setGreen(value); color.setBlue(value); img.setPixelColor(x, y, color); } else { color.setRed(value); color.setGreen(value); color.setBlue(value); img.setPixelColor(x, y, color); } } } } return QPixmap::fromImage(img); } void UkmediaOsdDisplayWidget::geometryChangedHandle() { QRect rect = caculatePosition(); QString platform = QGuiApplication::platformName(); if (!platform.startsWith(QLatin1String("wayland"),Qt::CaseInsensitive)){ this->setGeometry(QRect((rect.x() - rect.width()*2), rect.y(), rect.width(), rect.height())); } else { kdk::WindowManager::setGeometry(this->windowHandle(), QRect((rect.x() - rect.width()*2), rect.y(), rect.width(), rect.height())); } } void UkmediaOsdDisplayWidget::paintEvent(QPaintEvent *event) { QPainter painter(this); QPainterPath rectPath; rectPath.addRoundedRect(this->rect().adjusted(10, 10, -10, -10), 12, 12); //画一个黑底 QPixmap pixmap(this->rect().size()); pixmap.fill(Qt::transparent); QPainter pixmapPainter(&pixmap); pixmapPainter.setRenderHint(QPainter::Antialiasing); pixmapPainter.setPen(Qt::transparent); pixmapPainter.setBrush(Qt::black); pixmapPainter.setOpacity(0.20); pixmapPainter.drawPath(rectPath); pixmapPainter.end(); //模糊这个黑底 QImage img = pixmap.toImage(); qt_blurImage(img, 8, false, false); //挖掉中心 pixmap = QPixmap::fromImage(img); QPainter pixmapPainter2(&pixmap); pixmapPainter2.setRenderHint(QPainter::Antialiasing); pixmapPainter2.setCompositionMode(QPainter::CompositionMode_Clear); pixmapPainter2.setPen(Qt::transparent); pixmapPainter2.setBrush(Qt::transparent); pixmapPainter2.drawPath(rectPath); //绘制阴影 painter.drawPixmap(this->rect(), pixmap, pixmap.rect()); //绘制描边 QPainterPath linePath; linePath.addRoundedRect(QRect(9, 9, m_frame->width()+1, m_frame->height()+1), 12, 12); painter.setRenderHint(QPainter::Antialiasing); painter.setPen(Qt::white); painter.setBrush(Qt::transparent); painter.setOpacity(0.20); painter.drawPath(linePath); //毛玻璃 qreal opacity = getGlobalOpacity(); painter.setRenderHint(QPainter::Antialiasing); painter.setPen(Qt::transparent); painter.setBrush(this->palette().base()); painter.setOpacity(opacity); painter.drawPath(linePath); // KWindowEffects API changed in KF6, use QWindow* instead of WId // KWindowEffects::enableBlurBehind(this->winId(), true, // QRegion(linePath.toFillPolygon().toPolygon())); QWidget::paintEvent(event); } void UkmediaOsdDisplayWidget::resizeEvent(QResizeEvent* event) { // iconLabel->move((width() - iconLabel->width())/2,(height() - iconLabel->height())/2); QWidget::resizeEvent(event); } UkmediaOsdDisplayWidget::~UkmediaOsdDisplayWidget() { delete mTimer; mTimer = nullptr; } ukui-media/ukui-volume-control-applet-qt/ukmedia_volume_slider.cpp0000664000175000017500000003033315170052044024453 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #include #include #include #include #include #include #include //DisplayerMode displayMode = MINI_MODE; //SwitchButtonState buttonState = SWITCH_BUTTON_NORMAL; //extern double transparency; //UkuiApplicationWidget::UkuiApplicationWidget(QWidget *parent) //{ //// this->setAttribute(Qt::WA_TranslucentBackground); //// this->setStyleSheet("QWiget{background:rgba(0,0,0,0);}"); // Q_UNUSED(parent); //} //UkuiMediaSliderTipLabel::UkuiMediaSliderTipLabel(){ // setAttribute(Qt::WA_TranslucentBackground); //} //UkuiMediaSliderTipLabel::~UkuiMediaSliderTipLabel(){ //} //void UkuiApplicationWidget::paintEvent(QPaintEvent *e) //{ // QStyleOption opt; // opt.init(this); // QPainter p(this); //// double transparence = transparency * 255; //// QColor color = palette().color(QPalette::Base); //// color.setAlpha(transparence); // QBrush brush = QBrush(QColor(0,0,0,0)); // p.setBrush(brush); //// p.setBrush(this->palette().base()); //// p.setBrush(QBrush(QColor(19, 19, 20, 0))); // p.setPen(Qt::NoPen); // QPainterPath path; // opt.rect.adjust(0,0,0,0); // path.addRoundedRect(opt.rect,0,0); // p.setRenderHint(QPainter::Antialiasing); // 反锯齿; // p.drawRoundedRect(opt.rect,0,0); // setProperty("blurRegion",QRegion(path.toFillPolygon().toPolygon())); // style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); //} //UkuiApplicationWidget::~UkuiApplicationWidget() //{ //} //void UkuiMediaSliderTipLabel::paintEvent(QPaintEvent *e) //{ // QStyleOptionFrame opt; // initStyleOption(&opt); // QStylePainter p(this); //// p.setBrush(QBrush(QColor(0x1A,0x1A,0x1A,0x4C))); // p.setBrush(QBrush(QColor(0xFF,0xFF,0xFF,0x00))); // p.setPen(Qt::NoPen); // p.drawRoundedRect(this->rect(), 1, 1); // QPainterPath path; // path.addRoundedRect(opt.rect,6,6); // p.setRenderHint(QPainter::Antialiasing); // setProperty("blurRegion",QRegion(path.toFillPolygon().toPolygon())); // p.drawPrimitive(QStyle::PE_PanelTipLabel, opt); // this->setProperty("blurRegion", QRegion(QRect(0, 0, 1, 1))); // QLabel::paintEvent(e); //} //UkuiMediaButton::UkuiMediaButton(QWidget *parent) //{ // Q_UNUSED(parent); // this->setFixedSize(36,36); //} //UkuiMediaButton::~UkuiMediaButton() //{ //} ///*! // * \brief // * \details // * 绘制窗体的颜色及圆角 // */ ////void UkuiMediaButton::paintEvent(QPaintEvent *event) ////{ //// QStyleOptionComplex opt; //// opt.init(this); //// QPainter p(this); ////// double transparence = transparency * 255; //// p.setBrush(this->palette().base()); //// p.setPen(Qt::NoPen); //// QPainterPath path; //// opt.rect.adjust(0,0,0,0); //// path.addRoundedRect(opt.rect,6,6); //// p.setRenderHint(QPainter::Antialiasing); // 反锯齿; //// p.drawRoundedRect(opt.rect,6,6); //// setProperty("blurRegion",QRegion(path.toFillPolygon().toPolygon())); ////// style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); //// style()->drawComplexControl(QStyle::CC_ToolButton,&opt,&p,this); ////} //void UkuiMediaButton::enterEvent(QEvent *event) //{ // buttonState = SWITCH_BUTTON_HOVER; //} //void UkuiMediaButton::leaveEvent(QEvent *event) //{ // buttonState = SWITCH_BUTTON_NORMAL; //} //void UkuiMediaButton::mouseMoveEvent(QMouseEvent *e) //{ // buttonState = SWITCH_BUTTON_HOVER; // QPushButton::mouseMoveEvent(e); //} //void UkuiMediaButton::mousePressEvent(QMouseEvent *e) //{ // buttonState = SWITCH_BUTTON_PRESS; // if (displayMode == MINI_MODE) { // Q_EMIT moveMiniSwitchBtnSignale(); // this->setFixedSize(34,34); // QSize iconSize(14,14); // this->setIconSize(iconSize); // this->setIcon(QIcon("/usr/share/ukui-media/img/complete-module.svg")); // } // else { // this->setFixedSize(34,34); // QSize iconSize(14,14); // this->setIconSize(iconSize); // this->setIcon(QIcon("/usr/share/ukui-media/img/mini-module.svg")); // Q_EMIT moveAdvanceSwitchBtnSignal(); // } // QPushButton::mousePressEvent(e); //} //void UkuiMediaButton::mouseReleaseEvent(QMouseEvent *e) //{ // buttonState = SWITCH_BUTTON_NORMAL; // if (displayMode == MINI_MODE) { // Q_EMIT miniToAdvanceSignal(); // this->setFixedSize(36,36); // QSize iconSize(16,16); // this->setIconSize(iconSize); // this->setIcon(QIcon("/usr/share/ukui-media/img/complete-module.svg")); // } // else { // Q_EMIT advanceToMiniSignal(); // this->setFixedSize(36,36); // QSize iconSize(16,16); // this->setIconSize(iconSize); // qDebug() << "设置图标2"; // this->setIcon(QIcon("/usr/share/ukui-media/img/mini-module.svg")); // } // QPushButton::mouseReleaseEvent(e); //} //UkmediaVolumeSlider::UkmediaVolumeSlider(QWidget *parent,bool needTip) //{ // Q_UNUSED(parent); // if (needTip) { // state = needTip; // m_pTiplabel = new UkuiMediaSliderTipLabel(); // m_pTiplabel->setWindowFlags(Qt::ToolTip); // // qApp->installEventFilter(new AppEventFilter(this)); // m_pTiplabel->setFixedSize(52,30); // m_pTiplabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); // } //} //void UkmediaVolumeSlider::mousePressEvent(QMouseEvent *ev) //{ // mousePress = true; // Q_EMIT silderPressSignal(); // if (state) { // m_pTiplabel->show(); // } // //注意应先调用父类的鼠标点击处理事件,这样可以不影响拖动的情况 // QSlider::mousePressEvent(ev); // //获取鼠标的位置,这里并不能直接从ev中取值(因为如果是拖动的话,鼠标开始点击的位置没有意义了) // double pos = ev->pos().x() / (double)width(); // setValue(pos *(maximum() - minimum()) + minimum()); // //向父窗口发送自定义事件event type,这样就可以在父窗口中捕获这个事件进行处理 // QEvent evEvent(static_cast(QEvent::User + 1)); // QCoreApplication::sendEvent(parentWidget(), &evEvent); // QSlider::mousePressEvent(ev); //} //void UkmediaVolumeSlider::mouseReleaseEvent(QMouseEvent *e) //{ // if(mousePress){ // Q_EMIT silderReleaseSignal(); // } // mousePress = false; // QSlider::mouseReleaseEvent(e); //} //void UkmediaVolumeSlider::initStyleOption(QStyleOptionSlider *option) //{ // QSlider::initStyleOption(option); //} //void UkmediaVolumeSlider::leaveEvent(QEvent *e) //{ // if (state) { // m_pTiplabel->hide(); // } //} //void UkmediaVolumeSlider::enterEvent(QEvent *e) //{ // if (state) { // m_pTiplabel->show(); // } //} //void UkmediaVolumeSlider::paintEvent(QPaintEvent *e) //{ // QRect rect; // QStyleOptionSlider m_option; // QSlider::paintEvent(e); // if (state) { // this->initStyleOption(&m_option); // rect = this->style()->subControlRect(QStyle::CC_Slider, &m_option,QStyle::SC_SliderHandle,this); // QPoint gPos = this->mapToGlobal(rect.topLeft()); // QString percent; // percent = QString::number(this->value()); // percent.append("%"); // m_pTiplabel->setText(percent); // m_pTiplabel->move(gPos.x()-(m_pTiplabel->width()/2)+9,gPos.y()-m_pTiplabel->height()-1); // } //} //UkmediaVolumeSlider::~UkmediaVolumeSlider() //{ // delete m_pTiplabel; //} //void UkuiButtonDrawSvg::init(QImage img, QColor color) //{ // themeIcon.image = img; // themeIcon.color = color; //} //void UkuiButtonDrawSvg::paintEvent(QPaintEvent *event) //{ // QStyleOption opt; // opt.init(this); // QPainter p(this); // p.setBrush(QBrush(QColor(0x13,0x13,0x14,0x00))); // p.setPen(Qt::NoPen); // QPainterPath path; // opt.rect.adjust(0,0,0,0); // path.addRoundedRect(opt.rect,6,6); // p.setRenderHint(QPainter::Antialiasing); // 反锯齿; // p.drawRoundedRect(opt.rect,6,6); // setProperty("blurRegion",QRegion(path.toFillPolygon().toPolygon())); // style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); //} //QRect UkuiButtonDrawSvg::IconGeometry() //{ // QRect res = QRect(QPoint(0,0),QSize(24,24)); // res.moveCenter(QRect(0,0,width(),height()).center()); // return res; //} //void UkuiButtonDrawSvg::draw(QPaintEvent* e) //{ // Q_UNUSED(e); // QPainter painter(this); // QRect iconRect = IconGeometry(); // if (themeIcon.image.size() != iconRect.size()) // { // themeIcon.image = themeIcon.image.scaled(iconRect.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); // QRect r = themeIcon.image.rect(); // r.moveCenter(iconRect.center()); // iconRect = r; // } // this->setProperty("fillIconSymbolicColor", true); // filledSymbolicColoredPixmap(themeIcon.image,themeIcon.color); // painter.drawImage(iconRect, themeIcon.image); //} //bool UkuiButtonDrawSvg::event(QEvent *event) //{ // switch (event->type()) // { // case QEvent::Paint: // draw(static_cast(event)); // break; // case QEvent::Move: // case QEvent::Resize: // { // QRect rect = IconGeometry(); // } // break; // case QEvent::MouseButtonPress: // case QEvent::MouseButtonRelease: // case QEvent::MouseButtonDblClick: // event->accept(); // break; // default: // break; // } // return QPushButton::event(event); //} //UkuiButtonDrawSvg::UkuiButtonDrawSvg(QWidget *parent) //{ // Q_UNUSED(parent); //} //UkuiButtonDrawSvg::~UkuiButtonDrawSvg() //{ //} //QPixmap UkuiButtonDrawSvg::filledSymbolicColoredPixmap(QImage &img, QColor &baseColor) //{ // for (int x = 0; x < img.width(); x++) { // for (int y = 0; y < img.height(); y++) { // auto color = img.pixelColor(x, y); // if (color.alpha() > 0) { // int hue = color.hue(); // if (!qAbs(hue - symbolic_color.hue()) < 10) { // color.setRed(baseColor.red()); // color.setGreen(baseColor.green()); // color.setBlue(baseColor.blue()); // img.setPixelColor(x, y, color); // } // } // } // } // return QPixmap::fromImage(img); //} //UkuiScrollArea::UkuiScrollArea(QWidget *parent){ //} //UkuiScrollArea::~UkuiScrollArea(){ //} //void UkuiScrollArea::paintEvent(QPaintEvent *event) //{ // QStyleOption opt; // opt.init(this); // QPainter p(this); // p.setBrush(this->palette().base()); // p.setPen(Qt::NoPen); // QPainterPath path; // opt.rect.adjust(0,0,0,0); // path.addRoundedRect(opt.rect,6,6); // p.setRenderHint(QPainter::Antialiasing); // 反锯齿; // p.drawRoundedRect(opt.rect,6,6); // setProperty("blurRegion",QRegion(path.toFillPolygon().toPolygon())); // style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); //// QWidget::paintEvent(event); //} //UkuiQMenu::UkuiQMenu(){ //} //bool UkuiQMenu::event(QEvent *e) //{ // qDebug() << "事件类型" << e->type() << "show"; // if (e->type() == QEvent::ContextMenu) { // this->show(); // } // else if (e->type() == QEvent::WinIdChange) { // qDebug() << "winid changed"; // } // else if(e->type() == QEvent::MouseButtonRelease) { //// this->hide(); // qDebug() << "事件类型" << e->type(); // } // return QMenu::event(e); //} //void UkuiQMenu::hideEvent(QHideEvent *e) //{ // this->activateWindow(); // this->setAttribute(Qt::WA_NoMouseReplay); // qDebug() << "菜单隐藏" << e->type(); //} //UkuiQMenu::~UkuiQMenu(){ //} ukui-media/ukui-volume-control-applet-qt/ukmedia_system_volume_widget.h0000664000175000017500000000334715170054730025536 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #include #include #include #include #include #include "ukmedia_custom_class.h" #include "../common/ukmedia_common.h" class UkmediaSystemVolumeWidget : public QWidget { Q_OBJECT public: explicit UkmediaSystemVolumeWidget(QWidget *parent = nullptr); friend class UkmediaMainWidget; void setVolumeSliderRange(bool status); private: void setLabelAlignment(Qt::Alignment alignment); Q_SIGNALS: private: QListWidget *m_pInputListWidget; QListWidget *m_pOutputListWidget; QLabel *m_pOutputLabel; QWidget *m_pSysSliderWidget; QLabel *m_pSystemVolumeLabel; AudioSlider *m_pSystemVolumeSlider; QLabel *m_pSystemVolumeSliderLabel; QFrame *volumeSettingFrame; UkuiSettingButton *volumeSettingButton; QPushButton *m_pSystemVolumeBtn; public Q_SLOTS: void onPaletteChanged(); }; #endif // UKMEDIASYSTEMVOLUMEWIDGET_H ukui-media/ukui-volume-control-applet-qt/xatom/0000775000175000017500000000000015170054730020531 5ustar fengfengukui-media/ukui-volume-control-applet-qt/xatom/xatom-helper.cpp0000664000175000017500000001374015170054730023647 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 "xatom-helper.h" #include #include #include #include static XAtomHelper *global_instance = nullptr; static Display *g_display = nullptr; XAtomHelper *XAtomHelper::getInstance() { if(!global_instance) global_instance = new XAtomHelper; return global_instance; } bool XAtomHelper::isFrameLessWindow(int winId) { auto hints = getInstance()->getWindowMotifHint(winId); if(hints.flags == MWM_HINTS_DECORATIONS && hints.functions == 1) { return true; } return false; } bool XAtomHelper::isWindowDecorateBorderOnly(int winId) { return isWindowMotifHintDecorateBorderOnly(getInstance()->getWindowMotifHint(winId)); } bool XAtomHelper::isWindowMotifHintDecorateBorderOnly(const MotifWmHints &hint) { bool isDeco = false; if(hint.flags & MWM_HINTS_DECORATIONS && hint.flags != MWM_HINTS_DECORATIONS) { if(hint.decorations == MWM_DECOR_BORDER) isDeco = true; } return isDeco; } bool XAtomHelper::isUKUICsdSupported() { // fixme: return false; } bool XAtomHelper::isUKUIDecorationWindow(int winId) { if(m_ukuiDecorationAtion == None) return false; Atom type; int format; ulong nitems; ulong bytes_after; uchar *data; bool isUKUIDecoration = false; XGetWindowProperty(g_display, winId, m_ukuiDecorationAtion, 0, LONG_MAX, false, m_ukuiDecorationAtion, &type, &format, &nitems, &bytes_after, &data); if(type == m_ukuiDecorationAtion) { if(nitems == 1) { isUKUIDecoration = data[0]; } } return isUKUIDecoration; } UnityCorners XAtomHelper::getWindowBorderRadius(int winId) { UnityCorners corners; Atom type; int format; ulong nitems; ulong bytes_after; uchar *data; if(m_unityBorderRadiusAtom != None) { XGetWindowProperty(g_display, winId, m_unityBorderRadiusAtom, 0, LONG_MAX, false, XA_CARDINAL, &type, &format, &nitems, &bytes_after, &data); if(type == XA_CARDINAL) { if(nitems == 4) { corners.topLeft = static_cast(data[0]); corners.topRight = static_cast(data[1 * sizeof(ulong)]); corners.bottomLeft = static_cast(data[2 * sizeof(ulong)]); corners.bottomRight = static_cast(data[3 * sizeof(ulong)]); } XFree(data); } } return corners; } void XAtomHelper::setWindowBorderRadius(int winId, const UnityCorners &data) { if(m_unityBorderRadiusAtom == None) return; ulong corners[4] = {data.topLeft, data.topRight, data.bottomLeft, data.bottomRight}; XChangeProperty(g_display, 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}; XChangeProperty(g_display, 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; XChangeProperty(g_display, 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; XChangeProperty(g_display, 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; XGetWindowProperty(g_display, winId, m_motifWMHintsAtom, 0, sizeof(MotifWmHints) / sizeof(long), false, AnyPropertyType, &type, &format, &nitems, &bytes_after, &data); if(type == None) { return hints; } else { hints = *(MotifWmHints *)data; XFree(data); } return hints; } XAtomHelper::XAtomHelper(QObject *parent) : QObject(parent) { // Open X11 display if not already open if(!g_display) { g_display = XOpenDisplay(nullptr); } if(!g_display) return; m_motifWMHintsAtom = XInternAtom(g_display, "_MOTIF_WM_HINTS", true); m_unityBorderRadiusAtom = XInternAtom(g_display, "_UNITY_GTK_BORDER_RADIUS", false); m_ukuiDecorationAtion = XInternAtom(g_display, "_KWIN_UKUI_DECORAION", false); } Atom XAtomHelper::registerUKUICsdNetWmSupportAtom() { // fixme: return None; } void XAtomHelper::unregisterUKUICsdNetWmSupportAtom() { // fixme: } ukui-media/ukui-volume-control-applet-qt/xatom/xatom-helper.h0000664000175000017500000000631315170054730023312 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 #include #include struct UnityCorners { ulong topLeft = 0; ulong topRight = 0; ulong bottomLeft = 0; ulong bottomRight = 0; }; typedef struct { ulong flags = 0; ulong functions = 0; ulong decorations = 0; long input_mode = 0; ulong status = 0; } MotifWmHints, MwmHints; #define MWM_HINTS_FUNCTIONS (1L << 0) #define MWM_HINTS_DECORATIONS (1L << 1) #define MWM_HINTS_INPUT_MODE (1L << 2) #define MWM_HINTS_STATUS (1L << 3) #define MWM_FUNC_ALL (1L << 0) #define MWM_FUNC_RESIZE (1L << 1) #define MWM_FUNC_MOVE (1L << 2) #define MWM_FUNC_MINIMIZE (1L << 3) #define MWM_FUNC_MAXIMIZE (1L << 4) #define MWM_FUNC_CLOSE (1L << 5) #define MWM_DECOR_ALL (1L << 0) #define MWM_DECOR_BORDER (1L << 1) #define MWM_DECOR_RESIZEH (1L << 2) #define MWM_DECOR_TITLE (1L << 3) #define MWM_DECOR_MENU (1L << 4) #define MWM_DECOR_MINIMIZE (1L << 5) #define MWM_DECOR_MAXIMIZE (1L << 6) #define MWM_INPUT_MODELESS 0 #define MWM_INPUT_PRIMARY_APPLICATION_MODAL 1 #define MWM_INPUT_SYSTEM_MODAL 2 #define MWM_INPUT_FULL_APPLICATION_MODAL 3 #define MWM_INPUT_APPLICATION_MODAL MWM_INPUT_PRIMARY_APPLICATION_MODAL #define MWM_TEAROFF_WINDOW (1L<<0) namespace UKUI { class Decoration; } class XAtomHelper : public QObject { // friend class UKUI::Decoration; Q_OBJECT public: static XAtomHelper *getInstance(); static bool isFrameLessWindow(int winId); 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 = None; Atom m_unityBorderRadiusAtom = None; Atom m_ukuiDecorationAtion = None; }; #endif // XATOMHELPER_H ukui-media/ukui-volume-control-applet-qt/xatom/xatom.pri0000664000175000017500000000015115170052044022366 0ustar fengfengINCLUDEPATH += $$PWD HEADERS += \ $$PWD/xatom-helper.h \ SOURCES += \ $$PWD/xatom-helper.cpp \ ukui-media/ukui-volume-control-applet-qt/translations/0000775000175000017500000000000015170054730022122 5ustar fengfengukui-media/ukui-volume-control-applet-qt/translations/ky.ts0000664000175000017500000005644315170054730023131 0ustar fengfeng ApplicationVolumeWidget Application Volume ۅتۉنۉچ جاسوو ،اتقارۇۇ ۅلچۅمۉ System Volume ساامالىق وبونۇ Sound Settings دووش تەڭشەكتەرى DeviceSwitchWidget Go Into Mini Mode 进入Mini模式 Output volume control 输出音量控制 Mute 静音 Sound preference(S) 声音首选项 Device Volume 设备音量 Application Volume 应用音量 is using 正在使用 Bluetooth 蓝牙 Error 错误 Unable to connect to the sound system, please check whether the pulseaudio service is running! 无法连接到系统声音,请检查pulseaudio服务是否正在运行! Dummy output 伪输出 Speaker (Realtek Audio) 扬声器(Realtek Audio) Headphone 模拟耳机 QObject pa_context_subscribe() failed pa_context_subscribe() جەڭىلۉۉ بولدۇ pa_context_get_card_info_list() failed pa_context_get_card_info_list() جەڭىلۉۉ بولدۇ pa_context_get_sink_info_list() failed pa_context_get_sink_info_list() جەڭىلۉۉ بولدۇ pa_context_get_source_info_list() failed pa_context_get_source_info_list() جەڭىلۉۉ بولدۇ Failed to initialize stream_restore extension: %s stream_restore ئۇزارتىشنى باشلىمىدى: %s pa_ext_stream_restore_read() failed pa_ext_stream_restore_read() جەڭىلۉۉ بولدۇ pa_context_get_sink_info_by_index() failed pa_context_get_sink_info_by_index() جەڭىلۉۉ بولدۇ pa_context_get_source_info_by_index() failed pa_context_get_source_info_by_index() جەڭىلۉۉ بولدۇ pa_context_get_card_info_by_index() failed pa_context_get_card_info_by_index() جەڭىلۉۉ بولدۇ Card callback failure كارتوچكا قايتۇرۇۋىلىنىش جەڭىلۉۉ بولدۇ Sink callback failure چۆككىگە كەتىرىش جەڭىلۉۉ بولۇش Source callback failure قاينارىدان قايتۇرۇۋىلىنىش جەڭىلۉۉ بولدۇ Fatal Error: Unable to connect to PulseAudio ماڭىزدۇۇ قاتاالىق : PulseAudio عا ۇلانا البادى pa_context_get_server_info() failed pa_context_get_server_info() اتقارماق جاسوو ،اتقارۇۇ جەڭىلۉۉ بولدۇ Sink input callback failure چۆكمە قايتما كىرگىزۉۉ جەڭىلۉۉ بولدۇ Source output callback failure كەلۉۉ جەر جانى ۅندۉرۉش قايتما تەڭشۅۅ جەڭىلۉۉ بولدۇ Client callback failure تىرمىنال قايتما تەڭشەگى جەڭىلۉۉ بولدۇ Server info callback failure مۇلازىمەتىر ۇچۇردۇ قايتارىپ الۇۇ جەڭىلۉۉ بولدۇ Failed to initialize device restore extension: %s اسپاپتى العاچىنا گەلتىرىشتى گەڭەيتىش باشتالبادى: %s pa_ext_device_restore_read_sink_formats() failed pa_ext_device_restore_read_sink_formats() جەڭىلۉۉ بولدۇ Failed to initialize device manager extension: %s شايمان باشقارعىچتى كەڭەيتۉۉنۉ قوزعوتۇۇ جەڭىلۉۉ بولدۇ : %s pa_ext_device_manager_read() failed pa_ext_device_manager_read() جەڭىلۉۉ بولدۇ pa_context_get_sink_input_info() failed pa_context_get_sink_input_info() جەڭىلۉۉ بولدۇ pa_context_get_client_info() failed pa_context_get_client_info() جەڭىلۉۉ بولدۇ pa_context_client_info_list() failed pa_context_client_info_list() جەڭىلۉۉ بولدۇ pa_context_get_sink_input_info_list() failed pa_context_get_sink_input_info_list() جەڭىلۉۉ بولدۇ pa_context_get_source_output_info_list() failed pa_context_get_source_output_info_list() جەڭىلۉۉ بولدۇ moduleInfoCb callback failure moduleInfoCb چاقىرۇۇ جەڭىلۉۉ بولدۇ Ukui Media Volume Control Ukui Media دووش كونتىرول جاسوو ،اتقارۇۇ pa_context_load_module() failed pa_context_load_module() جەڭىلۉۉ بولدۇ ukui-volume-control-applet-qt is already running! UkmediaDeviceWidget Input Device 输入设备 Microphone 麦克风 Output Device 输出设备 Speaker Realtek Audio 扬声器(Realtek Audio) Input device can not be detected 无法检测到输入设备 UkmediaMainWidget Output volume control ۅندۉرۉش ۅلچۅمۉن تىزگىندۅۅ جاسوو ،اتقارۇۇ Mute 静音 Sound preference(S) دووش مايىللىقى(س) System Volume ساامالىق وبونۇ App Volume ئەپ وبونۇ Current volume: گەزەكتەكى دووش: UkmediaMiniMasterVolumeWidget Speaker (Realtek Audio) 扬声器(Realtek Audio) Go Into Full Mode 进入完整模式 UkmediaSystemVolumeWidget Volume دووش Output ۅندۉرۉش Sound Settings دووش تەڭشەكتەرى UkmediaVolumeControl pa_context_set_sink_mute_by_index() failed pa_context_set_sink_mute_by_index() جەڭىلۉۉ بولدۇ pa_context_set_sink_volume_by_index() failed pa_context_set_sink_volume_by_index() جەڭىلۉۉ بولدۇ pa_context_set_source_mute_by_index() failed pa_context_set_source_mute_by_index() جەڭىلۉۉ بولدۇ pa_context_set_source_volume_by_index() failed pa_context_set_source_volume_by_index() جەڭىلۉۉ بولدۇ pa_context_move_sink_input_by_index() failed pa_context_move_sink_input_by_index() جەڭىلۉۉ بولدۇ pa_context_move_source_output_by_name() failed pa_context_move_source_output_by_name() جەڭىلۉۉ بولدۇ pa_context_set_source_output_volume() failed pa_context_set_source_output_volume() جەڭىلۉۉ بولدۇ pa_context_set_source_output_mute() failed pa_context_set_source_output_mute() جەڭىلۉۉ بولدۇ pa_context_set_card_profile_by_index() failed pa_context_set_card_profile_by_index() جەڭىلۉۉ بولدۇ pa_context_set_default_sink() failed pa_context_set_default_sink() جەڭىلۉۉ بولدۇ pa_context_set_default_source() failed pa_context_set_default_source() جەڭىلۉۉ بولدۇ pa_context_set_sink_port_by_name() failed pa_context_set_sink_port_by_name() جەڭىلۉۉ بولدۇ pa_context_set_source_port_by_name() failed pa_context_set_source_port_by_name() جەڭىلۉۉ بولدۇ pa_context_kill_sink_input() failed pa_context_kill_sink_input() جەڭىلۉۉ بولدۇ (plugged in) ( قىستىرما) (unavailable) (ىشتەتكەلى بولبويت ) (unplugged) ( قىستىرما) Failed to read data from stream ئېقىمدىن ساندۇۇ باياندامالاردى وقۇۇ جەڭىلۉۉ بولدۇ Peak detect جوعورۇ تۇۇ چوقۇ تەكشەرىش Failed to create monitoring stream كۉزۅتۉۉ اعىمى قۇرۇۇ جەڭىلۉۉ بولدۇ Failed to connect monitoring stream كۉزۅتۉۉ اعىمدى ۇلوو جەڭىلۉۉ بولدۇ Ignoring sink-input due to it being designated as an event and thus handled by the Event widget وقۇيا دەپ بېكىتىلگەنلىكى جانا وقۇيالىك ۋىكىپېدىيە بىر جاعىنان بىر تاراپ قىلىنغانلىقى سەجانابىدىن چۆكمە كىرگىزۉۉچۉ نازاردان ساق اتقارىلدى Establishing connection to PulseAudio. Please wait... PulseAudio عا ۇلانۇۇ ورنوتقون. ساقلاپتۇرۇڭ... pa_context_get_sink_info_by_name() failed pa_context_get_sink_info_by_name() جەڭىلۉۉ بولدۇ pa_context_get_source_info_by_name() failed pa_context_get_source_info_by_name() جەڭىلۉۉ بولدۇ UkuiMediaSetHeadsetWidget Sound Settings دووش تەڭشەكتەرى Cancel ارعادان قالتىرىش Select Sound Device دووش جابدۇۇسۇن تانداش Headphone ۇققۇچ Headset ۇققۇچ Microphone مىكروفون ukui-media/ukui-volume-control-applet-qt/translations/zh_CN.ts0000664000175000017500000005055115170054730023501 0ustar fengfeng ApplicationVolumeWidget Application Volume 应用 System Volume 系统音量 Sound Settings 声音设置 DeviceSwitchWidget Go Into Mini Mode 进入Mini模式 Output volume control 输出音量控制 Mute 静音 Sound preference 声音首选项 Device Volume 设备音量 Application Volume 应用音量 is using 正在使用 Bluetooth 蓝牙 Error 错误 Unable to connect to the sound system, please check whether the pulseaudio service is running! 无法连接到系统声音,请检查pulseaudio服务是否正在运行! Dummy output 伪输出 Speaker (Realtek Audio) 扬声器(Realtek Audio) Headphone 模拟耳机 QObject pa_context_subscribe() failed pa_context_get_card_info_list() failed pa_context_get_sink_info_list() failed pa_context_get_source_info_list() failed Failed to initialize stream_restore extension: %s pa_ext_stream_restore_read() failed pa_context_get_sink_info_by_index() failed pa_context_get_source_info_by_index() failed pa_context_get_card_info_by_index() failed Card callback failure Sink callback failure Source callback failure pa_context_get_server_info() failed Sink input callback failure Source output callback failure Client callback failure Server info callback failure Failed to initialize device restore extension: %s pa_ext_device_restore_read_sink_formats() failed Failed to initialize device manager extension: %s pa_ext_device_manager_read() failed pa_context_get_sink_input_info() failed pa_context_get_client_info() failed pa_context_client_info_list() failed pa_context_get_sink_input_info_list() failed pa_context_get_source_output_info_list() failed moduleInfoCb callback failure Ukui Media Volume Control pa_context_load_module() failed ukui-volume-control-applet-qt is already running! UkmediaDeviceWidget Input Device 输入设备 Microphone 麦克风 Output Device 输出设备 Speaker Realtek Audio 扬声器(Realtek Audio) Input device can not be detected 无法检测到输入设备 UkmediaMainWidget Output volume control 输出音量控制 Mute 静音 Sound preference 设置声音项 System Volume 系统音量 App Volume 应用音量 Current volume: 当前音量: Muted 已静音 UkmediaMiniMasterVolumeWidget Speaker (Realtek Audio) 扬声器(Realtek Audio) Go Into Full Mode 进入完整模式 UkmediaSystemVolumeWidget Volume 音量 Output 输出 Sound Settings 声音设置 UkmediaVolumeControl pa_context_set_sink_mute_by_index() failed pa_context_set_sink_volume_by_index() failed pa_context_set_source_mute_by_index() failed pa_context_set_source_volume_by_index() failed pa_context_move_sink_input_by_index() failed pa_context_move_source_output_by_name() failed pa_context_set_source_output_volume() failed pa_context_set_source_output_mute() failed pa_context_set_card_profile_by_index() failed pa_context_set_default_sink() failed pa_context_set_default_source() failed pa_context_set_sink_port_by_name() failed pa_context_set_source_port_by_name() failed pa_context_kill_sink_input() failed (plugged in) (unavailable) (unplugged) Failed to read data from stream Peak detect Failed to create monitoring stream Failed to connect monitoring stream Ignoring sink-input due to it being designated as an event and thus handled by the Event widget Establishing connection to PulseAudio. Please wait... pa_context_get_sink_info_by_name() failed pa_context_get_source_info_by_name() failed UkuiMediaSetHeadsetWidget Sound Settings 声音设置 Cancel 取消 Select Sound Device 选择输出设备 Headphone 耳机 Headset 耳麦 Microphone 麦克风 ukui-media/ukui-volume-control-applet-qt/translations/kk.ts0000664000175000017500000005625315170054730023112 0ustar fengfeng ApplicationVolumeWidget Application Volume جابلماس ەتۋ كولەمى System Volume سەستيما داۋىسى Sound Settings اۋا تەڭشەۋلەرى DeviceSwitchWidget Go Into Mini Mode 进入Mini模式 Output volume control 输出音量控制 Mute 静音 Sound preference(S) 声音首选项 Device Volume 设备音量 Application Volume 应用音量 is using 正在使用 Bluetooth 蓝牙 Error 错误 Unable to connect to the sound system, please check whether the pulseaudio service is running! 无法连接到系统声音,请检查pulseaudio服务是否正在运行! Dummy output 伪输出 Speaker (Realtek Audio) 扬声器(Realtek Audio) Headphone 模拟耳机 QObject pa_context_subscribe() failed pa_context_subscribe() جەڭىلىپ قالدى pa_context_get_card_info_list() failed pa_context_get_card_info_list() جەڭىلىپ قالدى pa_context_get_sink_info_list() failed pa_context_get_sink_info_list() جەڭىلىپ قالدى pa_context_get_source_info_list() failed pa_context_get_source_info_list() جەڭىلىپ قالدى Failed to initialize stream_restore extension: %s stream_restore ۇزاتۋدى باشلىمىدى: %s pa_ext_stream_restore_read() failed pa_ext_stream_restore_read() جەڭىلىپ قالدى pa_context_get_sink_info_by_index() failed pa_context_get_sink_info_by_index() اتقار ەتۋ جەڭىلىپ قالدى pa_context_get_source_info_by_index() failed pa_context_get_source_info_by_index() جەڭىلىپ قالدى pa_context_get_card_info_by_index() failed pa_context_get_card_info_by_index() جەڭىلىپ قالدى Card callback failure قاريتا قايتۇرۇۋىلىنىش جەڭىلىپ قالدى Sink callback failure چۆككىگە قايتارۋ جەڭىلىپ بولۋ Source callback failure قايناردان قايتۇرۇۋىلىنىش جەڭىلىپ قالدى Fatal Error: Unable to connect to PulseAudio قىسپاقتاۋ قاتەلىك: PulseAudio عا جالعانبادٸ pa_context_get_server_info() failed pa_context_get_server_info() جەڭىلىپ قالدى Sink input callback failure چۆكمە كىرگىزۋ سوز قايتارما رولدارٸ جەڭىلىپ قالدى Source output callback failure قاينار جاريالاۋ ارقىلى قايتۇرۇۋىلىنىش جەڭىلىپ قالدى Client callback failure قاريدار شاقٸرۋ جەڭىلىپ قالدى Server info callback failure قىزىمەت وتەۋ اسبابى حاباردٸ قايتارىپ الۋ جەڭىلىس قالدى Failed to initialize device restore extension: %s اسبابٸن قالپىنا كىلتىرۋدى كەڭەيتۋ باستالمادى: %s pa_ext_device_restore_read_sink_formats() failed pa_ext_device_restore_read_sink_formats() جەڭىلىپ قالدى Failed to initialize device manager extension: %s اسباب باسقارۋشنى كەڭەيتۋدى قالپىنا قايتارۋ جەڭىلىپ قالدى: %s pa_ext_device_manager_read() failed pa_ext_device_manager_read() اتقار ەتۋ جەڭىلىپ قالدى pa_context_get_sink_input_info() failed pa_context_get_sink_input_info() جەڭىلىپ قالدى pa_context_get_client_info() failed pa_context_get_client_info() جەڭىلىپ قالدى pa_context_client_info_list() failed pa_context_client_info_list() جەڭىلىپ قالدى pa_context_get_sink_input_info_list() failed pa_context_get_sink_input_info_list() جەڭىلىپ قالدى pa_context_get_source_output_info_list() failed pa_context_get_source_output_info_list() جەڭىلىپ قالدى moduleInfoCb callback failure moduleInfoCb شاقٸرۋ جەڭىلىپ قالدى Ukui Media Volume Control Ukui Media اۋا كونتىرول ەتۋ pa_context_load_module() failed pa_context_load_module() جەڭىلىپ قالدى ukui-volume-control-applet-qt is already running! UkmediaDeviceWidget Input Device 输入设备 Microphone 麦克风 Output Device 输出设备 Speaker Realtek Audio 扬声器(Realtek Audio) Input device can not be detected 无法检测到输入设备 UkmediaMainWidget Output volume control جاريالاۋ مولشەرىن مەڭگەرۋ ەتۋ Mute 静音 Sound preference(S) اۋا مايىللىقى(س) System Volume سەستيما داۋىسى App Volume جيۋ داۋىسى Current volume: كەزەكتەگى اۋا: UkmediaMiniMasterVolumeWidget Speaker (Realtek Audio) 扬声器(Realtek Audio) Go Into Full Mode 进入完整模式 UkmediaSystemVolumeWidget Volume اۋا Output جاريالاۋ Sound Settings اۋا تەڭشەۋلەرى UkmediaVolumeControl pa_context_set_sink_mute_by_index() failed pa_context_set_sink_mute_by_index() جەڭىلىپ قالدى pa_context_set_sink_volume_by_index() failed pa_context_set_sink_volume_by_index() جەڭىلىپ قالدى pa_context_set_source_mute_by_index() failed pa_context_set_source_mute_by_index() جەڭىلىپ قالدى pa_context_set_source_volume_by_index() failed pa_context_set_source_volume_by_index() جەڭىلىپ قالدى pa_context_move_sink_input_by_index() failed pa_context_move_sink_input_by_index()جەڭىلىپ قالدى pa_context_move_source_output_by_name() failed pa_context_move_source_output_by_name()جەڭىلىپ قالدى pa_context_set_source_output_volume() failed pa_context_set_source_output_volume() جەڭىلىپ قالدى pa_context_set_source_output_mute() failed pa_context_set_source_output_mute() جەڭىلىپ قالدى pa_context_set_card_profile_by_index() failed pa_context_set_card_profile_by_index() جەڭىلىپ قالدى pa_context_set_default_sink() failed pa_context_set_default_sink() جەڭىلىپ قالدى pa_context_set_default_source() failed pa_context_set_default_source() جەڭىلىپ قالدى pa_context_set_sink_port_by_name() failed pa_context_set_sink_port_by_name() جەڭىلىپ قالدى pa_context_set_source_port_by_name() failed pa_context_set_source_port_by_name() جەڭىلىپ قالدى pa_context_kill_sink_input() failed pa_context_kill_sink_input() جەڭىلىپ قالدى (plugged in) (قىستىرما) (unavailable) (ٸستەتكەلٸ بولمايدى) (unplugged) (قىستىرما) Failed to read data from stream اعىمنان ساندىق مالىمەتتەردى وقۋ جەڭىلىپ قالدى Peak detect جوعارعى ورە تەكسەرۋ Failed to create monitoring stream كۇزەتۋگە اعٸمٸ قۇرۋ جەڭىلىپ قالدى Failed to connect monitoring stream كۇزەتۋگە مەنى اۋلاۋ جەڭىلىپ قالدى Ignoring sink-input due to it being designated as an event and thus handled by the Event widget وقيعا دوپ بېكىتىلگەنلىكى ۋا وقيعالىك ۋىكىپېدىيە جاقتارٸن بٸر جاق قىلىنغانلىقى سەۋابىدىن چۆكمە كىرگىزۋ اسپابى نازاردان ساقتانۋ قىلىندى Establishing connection to PulseAudio. Please wait... PulseAudio عا جالعانۋ ورناتقان. ساقلاپتۇرۇڭ... pa_context_get_sink_info_by_name() failed pa_context_get_sink_info_by_name() جەڭىلىپ قالدى pa_context_get_source_info_by_name() failed pa_context_get_source_info_by_name() جەڭىلىپ قالدى UkuiMediaSetHeadsetWidget Sound Settings اۋا تەڭشەۋلەرى Cancel كۇشىنەن قالدىرۋ Select Sound Device اۋا اسبابٸن تالداۋ Headphone تىڭداعىش Headset تىڭداعىش Microphone ميكروفون ukui-media/ukui-volume-control-applet-qt/translations/ms.ts0000664000175000017500000005075215170054730023122 0ustar fengfeng ApplicationVolumeWidget Application Volume System Volume Sound Settings DeviceSwitchWidget Go Into Mini Mode 进入Mini模式 Output volume control 输出音量控制 Mute 静音 Sound preference 声音首选项 Device Volume 设备音量 Application Volume 应用音量 is using 正在使用 Bluetooth 蓝牙 Error 错误 Unable to connect to the sound system, please check whether the pulseaudio service is running! 无法连接到系统声音,请检查pulseaudio服务是否正在运行! Dummy output 伪输出 Speaker (Realtek Audio) 扬声器(Realtek Audio) Headphone 模拟耳机 QObject pa_context_subscribe() failed pa_context_get_card_info_list() failed pa_context_get_sink_info_list() failed pa_context_get_source_info_list() failed Failed to initialize stream_restore extension: %s pa_ext_stream_restore_read() failed pa_context_get_sink_info_by_index() failed pa_context_get_source_info_by_index() failed pa_context_get_card_info_by_index() failed Card callback failure Sink callback failure Source callback failure pa_context_get_server_info() failed Sink input callback failure Source output callback failure Client callback failure Server info callback failure Failed to initialize device restore extension: %s pa_ext_device_restore_read_sink_formats() failed Failed to initialize device manager extension: %s pa_ext_device_manager_read() failed pa_context_get_sink_input_info() failed pa_context_get_client_info() failed pa_context_client_info_list() failed pa_context_get_sink_input_info_list() failed pa_context_get_source_output_info_list() failed moduleInfoCb callback failure Ukui Media Volume Control pa_context_load_module() failed ukui-volume-control-applet-qt is already running! UkmediaDeviceWidget Input Device 输入设备 Microphone 麦克风 Output Device 输出设备 Speaker Realtek Audio 扬声器(Realtek Audio) Input device can not be detected 无法检测到输入设备 UkmediaMainWidget Output volume control Mute 静音 Sound preference System Volume App Volume Current volume: Muted UkmediaMiniMasterVolumeWidget Speaker (Realtek Audio) 扬声器(Realtek Audio) Go Into Full Mode 进入完整模式 UkmediaSystemVolumeWidget Volume Output Sound Settings UkmediaVolumeControl pa_context_set_sink_mute_by_index() failed pa_context_set_sink_volume_by_index() failed pa_context_set_source_mute_by_index() failed pa_context_set_source_volume_by_index() failed pa_context_move_sink_input_by_index() failed pa_context_move_source_output_by_name() failed pa_context_set_source_output_volume() failed pa_context_set_source_output_mute() failed pa_context_set_card_profile_by_index() failed pa_context_set_default_sink() failed pa_context_set_default_source() failed pa_context_set_sink_port_by_name() failed pa_context_set_source_port_by_name() failed pa_context_kill_sink_input() failed (plugged in) (unavailable) (unplugged) Failed to read data from stream Peak detect Failed to create monitoring stream Failed to connect monitoring stream Ignoring sink-input due to it being designated as an event and thus handled by the Event widget Establishing connection to PulseAudio. Please wait... pa_context_get_sink_info_by_name() failed pa_context_get_source_info_by_name() failed UkuiMediaSetHeadsetWidget Sound Settings Cancel Select Sound Device Headphone Headset Microphone ukui-media/ukui-volume-control-applet-qt/translations/tr.ts0000664000175000017500000001066715170052044023125 0ustar fengfeng ApplicationVolumeWidget Application Volume Uygulama Sesi System Volume Sistem Sesi DeviceSwitchWidget Go Into Mini Mode Mini Moda Geç Output volume control Çıkış ses kontrolü Mute Sessiz Sound preference(S) Ses tercihi Device Volume Aygıt Sesi Application Volume Uygulama Sesi Speaker (Realtek Audio) Hoparlör (Realtek Audio) Headphone Kulaklık UkmediaDeviceWidget Input Device Giriş Sesi Microphone Mikrofon Output Device Çıkış Sesi Speaker Realtek Audio Hoparlör Realtek Audio Input device can not be detected Giriş cihazı algılanamıyor UkmediaMiniMasterVolumeWidget Speaker (Realtek Audio) Hoparlör (Realtek Audio) Go Into Full Mode Tam Moda Geçin ukui-media/ukui-volume-control-applet-qt/translations/ug.ts0000664000175000017500000005637215170054730023122 0ustar fengfeng ApplicationVolumeWidget Application Volume ئىلتىماس قىلىش مىقدارى System Volume سىستېما ئاۋازى Sound Settings ئاۋاز تەڭشەكلىرى DeviceSwitchWidget Go Into Mini Mode 进入Mini模式 Output volume control 输出音量控制 Mute 静音 Sound preference(S) 声音首选项 Device Volume 设备音量 Application Volume 应用音量 is using 正在使用 Bluetooth 蓝牙 Error 错误 Unable to connect to the sound system, please check whether the pulseaudio service is running! 无法连接到系统声音,请检查pulseaudio服务是否正在运行! Dummy output 伪输出 Speaker (Realtek Audio) 扬声器(Realtek Audio) Headphone 模拟耳机 QObject pa_context_subscribe() failed pa_context_subscribe() مەغلۇپ بولدى pa_context_get_card_info_list() failed pa_context_get_card_info_list() مەغلۇپ بولدى pa_context_get_sink_info_list() failed pa_context_get_sink_info_list() مەغلۇپ بولدى pa_context_get_source_info_list() failed pa_context_get_source_info_list() مەغلۇپ بولدى Failed to initialize stream_restore extension: %s stream_restore ئۇزارتىشنى باشلىمىدى: s% pa_ext_stream_restore_read() failed pa_ext_stream_restore_read() مەغلۇپ بولدى pa_context_get_sink_info_by_index() failed pa_context_get_sink_info_by_index() مەغلۇپ بولدى pa_context_get_source_info_by_index() failed pa_context_get_source_info_by_index() مەغلۇپ بولدى pa_context_get_card_info_by_index() failed pa_context_get_card_info_by_index() مەغلۇپ بولدى Card callback failure كارتا قايتۇرۇۋىلىنىش مەغلۇپ بولدى Sink callback failure قىبلىگە قايتۇرۇش مەغلۇپ بولۇش Source callback failure مەنبەدىن قايتۇرۇۋىلىنىش مەغلۇپ بولدى Fatal Error: Unable to connect to PulseAudio ئەجەللىك خاتالىق: PulseAudio غا ئۇلىنالمىدى pa_context_get_server_info() failed pa_context_get_server_info() مەغلۇپ بولدى Sink input callback failure چۆكمە كىرگۈزۈش سۆز قايتۇرما ئىقتىدارى مەغلۇپ بولدى Source output callback failure مەنبە چىقىرىش ئارقىلىق قايتۇرۇۋىلىنىش مەغلۇپ بولدى Client callback failure خېرىدار چاقىرىش مەغلۇپ بولدى Server info callback failure مۇلازىمىتېر ئۇچۇرىنى قايتۇرۇۋىلاش مەغلۇپ بولدى Failed to initialize device restore extension: %s ئۈسكۈنىنى ئەسلىگە كەلتۈرۈش ئۇزارتىشنى باشلىمىدى: s% pa_ext_device_restore_read_sink_formats() failed pa_ext_device_restore_read_sink_formats() مەغلۇپ بولدى Failed to initialize device manager extension: %s ئۈسكۈنە باشقۇرغۇچنى ئۇزارتىشنى قوزغىتىش مەغلۇپ بولدى: s% pa_ext_device_manager_read() failed pa_ext_device_manager_read() مەغلۇپ بولدى pa_context_get_sink_input_info() failed pa_context_get_sink_input_info() مەغلۇپ بولدى pa_context_get_client_info() failed pa_context_get_client_info() مەغلۇپ بولدى pa_context_client_info_list() failed pa_context_client_info_list() مەغلۇپ بولدى pa_context_get_sink_input_info_list() failed pa_context_get_sink_input_info_list() مەغلۇپ بولدى pa_context_get_source_output_info_list() failed pa_context_get_source_output_info_list() مەغلۇپ بولدى moduleInfoCb callback failure moduleInfoCb چاقىرىش مەغلۇپ بولدى Ukui Media Volume Control Ukui Media ئاۋاز كونتىرول قىلىش pa_context_load_module() failed pa_context_load_module() مەغلۇپ بولدى ukui-volume-control-applet-qt is already running! ukui-volume-control-applet-qt ئاللىقاچان ئىجرا بولىۋاتىدۇ! UkmediaDeviceWidget Input Device 输入设备 Microphone 麦克风 Output Device 输出设备 Speaker Realtek Audio 扬声器(Realtek Audio) Input device can not be detected 无法检测到输入设备 UkmediaMainWidget Output volume control چىقىرىش مىقدارىنى كونترول قىلىش Mute 静音 Sound preference(S) ئاۋاز مايىللىقى(س) System Volume سىستېما ئاۋازى App Volume ئەپ ئاۋازى Current volume: نۆۋەتتىكى ھەجىم: UkmediaMiniMasterVolumeWidget Speaker (Realtek Audio) 扬声器(Realtek Audio) Go Into Full Mode 进入完整模式 UkmediaSystemVolumeWidget Volume ئاۋاز چوڭلۇقى Output چىقىرىش Sound Settings ئاۋاز تەڭشەكلىرى UkmediaVolumeControl pa_context_set_sink_mute_by_index() failed pa_context_set_sink_mute_by_index() مەغلۇپ بولدى pa_context_set_sink_volume_by_index() failed pa_context_set_sink_volume_by_index() مەغلۇپ بولدى pa_context_set_source_mute_by_index() failed pa_context_set_source_mute_by_index() مەغلۇپ بولدى pa_context_set_source_volume_by_index() failed pa_context_set_source_volume_by_index() مەغلۇپ بولدى pa_context_move_sink_input_by_index() failed pa_context_move_sink_input_by_index() مەغلۇپ بولدى pa_context_move_source_output_by_name() failed pa_context_move_source_output_by_name() مەغلۇپ بولدى pa_context_set_source_output_volume() failed pa_context_set_source_output_volume() مەغلۇپ بولدى pa_context_set_source_output_mute() failed pa_context_set_source_output_mute() مەغلۇپ بولدى pa_context_set_card_profile_by_index() failed pa_context_set_card_profile_by_index() مەغلۇپ بولدى pa_context_set_default_sink() failed pa_context_set_default_sink() مەغلۇپ بولدى pa_context_set_default_source() failed pa_context_set_default_source() مەغلۇپ بولدى pa_context_set_sink_port_by_name() failed pa_context_set_sink_port_by_name() مەغلۇپ بولدى pa_context_set_source_port_by_name() failed pa_context_set_source_port_by_name() مەغلۇپ بولدى pa_context_kill_sink_input() failed pa_context_kill_sink_input() مەغلۇپ بولدى (plugged in) (قىستۇرما) (unavailable) (ئىشلەتكىلى بولمايدۇ) (unplugged) (قىستۇرما) Failed to read data from stream ئېقىمدىن سانلىق مەلۇماتلارنى ئوقۇش مەغلۇپ بولدى Peak detect يۇقىرى پەللە تەكشۈرۈش Failed to create monitoring stream كۆزىتىش ئېقىمىنى قۇرۇش مەغلۇپ بولدى Failed to connect monitoring stream كۆزىتىش ئېقىمىنى ئۇلاش مەغلۇپ بولدى Ignoring sink-input due to it being designated as an event and thus handled by the Event widget ۋەقە دەپ بېكىتىلگەنلىكى ۋە شۇنىڭ بىلەن ۋەقەلىك ۋىكىپېدىيە تەرىپىدىن بىر تەرەپ قىلىنغانلىقى سەۋەبىدىن چۆكمە كىرگۈزگۈچىنى نەزەردىن ساقىت قىلىش Establishing connection to PulseAudio. Please wait... PulseAudio غا ئۇلىنىش ئورناتقان. ساقلاپتۇرغىن... pa_context_get_sink_info_by_name() failed pa_context_get_sink_info_by_name() مەغلۇپ بولدى pa_context_get_source_info_by_name() failed pa_context_get_source_info_by_name() مەغلۇپ بولدى UkuiMediaSetHeadsetWidget Sound Settings ئاۋاز تەڭشەكلىرى Cancel ئەمەلدىن قالدۇرۇش Select Sound Device ئاۋاز ئۈسكۈنىسىنى تاللاش Headphone تىڭشىغۇچ Headset تىڭشىغۇچ Microphone مىكروفون ukui-media/ukui-volume-control-applet-qt/translations/bo_CN.ts0000664000175000017500000006233315170054730023461 0ustar fengfeng ApplicationVolumeWidget Application Volume བཀོལ་སྤྱོད་བྱེད་ཚད། System Volume མ་ལག་གི་བོངས་ཚད། Sound Settings སྒྲའི་སྒྲིག་བཀོད། DeviceSwitchWidget Go Into Mini Mode 进入Mini模式 Output volume control 输出音量控制 Mute 静音 Sound preference 声音首选项 Device Volume 设备音量 Application Volume 应用音量 is using 正在使用 Bluetooth 蓝牙 Error 错误 Unable to connect to the sound system, please check whether the pulseaudio service is running! 无法连接到系统声音,请检查pulseaudio服务是否正在运行! Dummy output 伪输出 Speaker (Realtek Audio) 扬声器(Realtek Audio) Headphone 模拟耳机 QObject pa_context_subscribe() failed pa_context_subscribe()ཕམ་ཁ་བྱུང་བ་རེད། pa_context_get_card_info_list() failed pa_context_get_card_info_list()ཕམ་ཉེས་བྱུང་བ། pa_context_get_sink_info_list() failed pa_context_get_sink_info_list()ཕམ་ཉེས་བྱུང་བ། pa_context_get_source_info_list() failed pa_context_get_source_info_list()ཕམ་ཁ་བྱུང་བ་རེད། Failed to initialize stream_restore extension: %s stream_restore་རིང་དུ་གཏོང་བའི་ཐོག་མའི་དུས་ཚོད་ལ་སླེབས་མ་ཐུབ་པ། %s pa_ext_stream_restore_read() failed pa_ext_stream_restore_read()ཕམ་ཁ་བྱུང་བ་རེད། pa_context_get_sink_info_by_index() failed pa_context_get_sink_info_by_index()ཕམ་ཁ་བྱུང་བ་རེད། pa_context_get_source_info_by_index() failed pa_context_get_source_info_by_index()ཕམ་ཁ་བྱུང་བ་རེད། pa_context_get_card_info_by_index() failed pa_context_get_card_info_by_index()ཕམ་ཉེས་བྱུང་བ། Card callback failure བྱང་བུ་ཕྱིར་སློག་བྱེད་མ་ཐུབ་པ། Sink callback failure ཕྱིར་ལྡོག་བྱེད་པར་ཕམ་ཉེས་བྱུང་བ། Source callback failure འབྱུང་ཁུངས་ཕྱིར་ལྡོག་བྱེད་མ་ཐུབ་པ། Fatal Error: Unable to connect to PulseAudio སྲོག་ལ་ཐུག་པའི་ནོར་འཁྲུལ། ཕུའུ་ལུའུ་ཧྲི་ཨོ་ཏོ་དང་འབྲེལ་མཐུད་བྱེད་ཐབས་བྲལ། pa_context_get_server_info() failed pa_context_get_server_info()ཕམ་ཉེས་བྱུང་བ། Sink input callback failure མ་དངུལ་འཇོག་པའི་ཕྱིར་ལྡོག་ལ་ཕམ་ཉེས་བྱུང Source output callback failure འབྱུང་ཁུངས་ཕྱིར་ལྡོག་བྱེད་པར་ཕམ་ཉེས་བྱུང་བ། Client callback failure མངགས་བཅོལ་བྱེད་མཁན་གྱིས་ཕྱིར་སློག་ Server info callback failure ཞབས་ཞུའི་ཡོ་བྱད་ཀྱི་ཆ་འཕྲིན་ཕྱིར་ལྡོག་བྱེད་མ་ཐུབ Failed to initialize device restore extension: %s 设备恢复扩展%s pa_ext_device_restore_read_sink_formats() failed pa_ext_device_restore_read_sink_formats()ཕམ་ཉེས་བྱུང་བ། Failed to initialize device manager extension: %s སྒྲིག་ཆས་དོ་དམ་གྱི་དུས་འགྱངས་ལ་ཐོག་མའི་དུས་འགྱངས་བྱེད་མ་ཐུབ་པ། %s pa_ext_device_manager_read() failed pa_ext_device_manager_read()ཕམ་ཉེས་བྱུང་བ། pa_context_get_sink_input_info() failed pa_context_get_sink_input_info()ཕམ་ཁ་བྱུང་བ་རེད། pa_context_get_client_info() failed pa_context_get_client_info()ཕམ་ཉེས་བྱུང་བ། pa_context_client_info_list() failed pa_context_client_info_list()ཕམ་ཉེས་བྱུང་བ། pa_context_get_sink_input_info_list() failed pa_context_get_sink_input_info_list()ཕམ་ཉེས་བྱུང་བ། pa_context_get_source_output_info_list() failed pa_context_get_source_output_info_list()ཕམ་ཁ་བྱུང་བ་རེད། Connection failed, attempting reconnect འབྲེལ་མཐུད་བྱེད་མ་ཐུབ་པར་ཡང་བསྐྱར་འབྲེལ་མཐུད་བྱེད་རྩིས་བྱས། moduleInfoCb callback failure moduleInfoCb ཕྱིར་ལྡོག་བྱེད་པར་ཕམ་ཉེས་བྱུང་བ། Ukui Media Volume Control ཝུའུ་ཁི་ལན་གྱི་སྨྱན་སྦྱོར་གྱི་བོངས་ pa_context_load_module() failed pa_context_load_module()ཕམ་ཉེས་བྱུང་བ། ukui-volume-control-applet-qt is already running! UkmediaDeviceWidget Input Device 输入设备 Microphone 麦克风 Output Device 输出设备 Speaker Realtek Audio 扬声器(Realtek Audio) Input device can not be detected 无法检测到输入设备 UkmediaMainWidget Output volume control ཐོན་ཚད་ཚོད་འཛིན་ Mute 静音 Sound preference སྒྲ་གདངས་ཀྱི་དགའ་ཕྱོགས་ System Volume མ་ལག་གི་བོངས་ཚད། App Volume ཉེར་སྤྱོད་གྲངས་འབོར། Current volume: མིག་སྔའི་བོངས་ཚད་ནི། UkmediaMiniMasterVolumeWidget Speaker (Realtek Audio) 扬声器(Realtek Audio) Go Into Full Mode 进入完整模式 UkmediaSystemVolumeWidget Volume བོངས་ཚད། Output ཐོན་ཚད། Sound Settings སྒྲའི་སྒྲིག་བཀོད། UkmediaVolumeControl pa_context_set_sink_mute_by_index() failed pa_context_set_sink_volume_by_index() failed pa_context_set_sink_volume_by_index()ཕམ་ཁ་བྱུང་བ་རེད། pa_context_set_source_mute_by_index() failed pa_context_set_source_volume_by_index() failed pa_context_move_sink_input_by_index() failed pa_context_move_source_output_by_name() failed pa_context_set_source_output_volume() failed pa_context_set_source_output_volume()ཕམ་ཁ་བྱུང་བ་རེད། pa_context_set_source_output_mute() failed pa_context_set_source_output_mute()ཕམ་ཁ་བྱུང་བ་རེད། pa_context_set_card_profile_by_index() failed pa_context_set_card_profile_by_index()ཕམ་ཉེས་བྱུང་བ། pa_context_set_default_sink() failed pa_context_set_default_sink()ཕམ་ཁ་བྱུང་བ་རེད། pa_context_set_default_source() failed pa_context_set_default_source()ཕམ་ཉེས་བྱུང་བ། pa_context_set_sink_port_by_name() failed pa_context_set_sink_port_by_name()ཕམ་ཁ་བྱུང་བ་རེད། pa_context_set_source_port_by_name() failed pa_context_set_source_port_by_name()ཕམ་ཉེས་བྱུང་བ། pa_context_kill_sink_input() failed pa_context_kill_sink_input()ཕམ་ཉེས་བྱུང་བ། (plugged in) (ནང་དུ་བཅུག་པ་)། (unavailable) (སྤྱོད་གོ་མི་ཆོད་པ)། (unplugged) (ཁ་ཐོར་ཡ་བྲལ་དུ་སོང་བ་)། Failed to read data from stream ཆུ་ཕྲན་ནས་གཞི་གྲངས་ཀློག་མ་ཐུབ་པ། Peak detect ཡང་རྩེར་ཞིབ་དཔྱད་ཚད་ལེན Failed to create monitoring stream ལྟ་ཞིབ་ཚད་ལེན་གྱི་ཆུ་ཕྲན་གསར་སྐྲུན་བྱེད་མ་ཐུབ་པ Failed to connect monitoring stream ལྟ་ཞིབ་ཚད་ལེན་གྱི་ཆུ་ཕྲན་སྦྲེལ་མཐུད་བྱེད་མ་ཐུབ་པ Ignoring sink-input due to it being designated as an event and thus handled by the Event widget དོན་རྐྱེན་ཞིག་ཏུ་བརྩིས་ནས་དོན་རྐྱེན་ཆུང་ཆུང་ཞིག་ཏུ་བརྩིས་ནས་ཐག་གཅོད་བྱས་པའི་རྐྱེན་གྱིས་ཆུ་ནང་དུ་འཛུལ་བར་སྣང་མེད་བྱས་པ་རེད། Establishing connection to PulseAudio. Please wait... ཕུའུ་ལུའུ་ཧྲི་ཨོ་ཏོ་དང་འབྲེལ་བ་བཙུགས་པ་རེད། སྐུ་མཁྱེན་སྒུག་རོགས།... pa_context_get_sink_info_by_name() failed pa_context_get_sink_info_by_index()ཕམ་ཁ་བྱུང་བ་རེད། pa_context_get_source_info_by_name() failed pa_context_get_source_info_by_index()ཕམ་ཁ་བྱུང་བ་རེད། UkuiMediaSetHeadsetWidget Sound Settings སྒྲའི་སྒྲིག་བཀོད། Cancel ཕྱིར་འཐེན། Select Sound Device སྒྲའི་སྒྲིག་ཆས་བདམས་པ། Headphone རྣ་ཉན། Headset རྣ་ཉན། Microphone སྐད་སྦུག ukui-media/ukui-volume-control-applet-qt/translations/vi.ts0000664000175000017500000005456415170054730023126 0ustar fengfeng ApplicationVolumeWidget Application Volume Khối lượng ứng dụng System Volume Khối lượng hệ thống Sound Settings Cài đặt âm thanh DeviceSwitchWidget Go Into Mini Mode 进入Mini模式 Output volume control 输出音量控制 Mute 静音 Sound preference 声音首选项 Device Volume 设备音量 Application Volume 应用音量 is using 正在使用 Bluetooth 蓝牙 Error 错误 Unable to connect to the sound system, please check whether the pulseaudio service is running! 无法连接到系统声音,请检查pulseaudio服务是否正在运行! Dummy output 伪输出 Speaker (Realtek Audio) 扬声器(Realtek Audio) Headphone 模拟耳机 QObject pa_context_subscribe() failed Đăng ký ngữ cảnh PA không thành công pa_context_get_card_info_list() failed pa_context_get_card_info_list() không thành công pa_context_get_sink_info_list() failed Lỗi khi lấy danh sách thông tin đầu ra âm thanh (sink) từ ngữ cảnh PulseAudio pa_context_get_source_info_list() failed pa_context_get_source_info_list() không thành công Failed to initialize stream_restore extension: %s Không thể khởi tạo stream_restore phần mở rộng: %s pa_ext_stream_restore_read() failed Đọc pa_ext_stream_restore thất bại pa_context_get_sink_info_by_index() failed Lấy thông tin thiết bị xuất âm bằng chỉ mục trong ngữ cảnh PulseAudio thất bại pa_context_get_source_info_by_index() failed pa_context_get_source_info_by_index() không thành công pa_context_get_card_info_by_index() failed pa_context_get_card_info_by_index() không thành công Card callback failure Lỗi gọi lại thẻ Sink callback failure Lỗi gọi lại chìm Source callback failure Lỗi gọi lại Nguồn thất bại pa_context_get_server_info() failed pa_context_get_server_info() không thành công Sink input callback failure Lỗi callback đầu vào sink Source output callback failure Lỗi phản hồi đầu ra nguồn Client callback failure Lỗi gọi lại từ khách hàng Server info callback failure Lỗi gọi lại thông tin máy chủ Failed to initialize device restore extension: %s Không thể khởi tạo tiện ích khôi phục thiết bị: %s pa_ext_device_restore_read_sink_formats() failed pa_ext_device_restore_read_sink_formats() không thành công Failed to initialize device manager extension: %s Khởi tạo phần mở rộng quản lý thiết bị thất bại: %s pa_ext_device_manager_read() failed Đọc pa_ext_device_manager thất bại pa_context_get_sink_input_info() failed pa_context_get_sink_input_info() không thành công pa_context_get_client_info() failed Pa context lấy thông tin khách hàng() thất bại pa_context_client_info_list() failed Hàm pa_context_client_info_list() thực thi thất bại pa_context_get_sink_input_info_list() failed Hàm pa_context_get_sink_input_info_list() thực thi thất bại pa_context_get_source_output_info_list() failed pa_context_get_source_output_info_list() không thành công moduleInfoCb callback failure moduleInfoCb gọi lại không thành công Ukui Media Volume Control Bộ điều khiển âm lượng đa phương tiện Ukui pa_context_load_module() failed pa_context_load_module() không thành công ukui-volume-control-applet-qt is already running! ukui-volume-control-applet-qt đã chạy! UkmediaDeviceWidget Input Device 输入设备 Microphone 麦克风 Output Device 输出设备 Speaker Realtek Audio 扬声器(Realtek Audio) Input device can not be detected 无法检测到输入设备 UkmediaMainWidget Output volume control Điều khiển âm lượng đầu ra Mute 静音 Sound preference Sở thích âm thanh System Volume Âm lượng hệ thống App Volume Âm lượng ứng dụng Current volume: Khối lượng hiện tại: Muted Đã tắt tiếng UkmediaMiniMasterVolumeWidget Speaker (Realtek Audio) 扬声器(Realtek Audio) Go Into Full Mode 进入完整模式 UkmediaSystemVolumeWidget Volume Âm thanh Output Ra Sound Settings Cài đặt âm thanh UkmediaVolumeControl pa_context_set_sink_mute_by_index() failed Hàm pa_context_set_sink_mute_by_index() thực thi thất bại pa_context_set_sink_volume_by_index() failed pa_context_set_sink_volume_by_index() không thành công pa_context_set_source_mute_by_index() failed pa_context_set_source_mute_by_index() không thành công pa_context_set_source_volume_by_index() failed pa_context_set_source_volume_by_index() không thành công pa_context_move_sink_input_by_index() failed Hàm pa_context_move_sink_input_by_index() thực thi thất bại pa_context_move_source_output_by_name() failed Hàm pa_context_move_source_output_by_name() thực thi thất bại pa_context_set_source_output_volume() failed pa_context_set_source_output_volume() không thành công pa_context_set_source_output_mute() failed Hàm pa_context_set_source_output_mute() thực thi thất bại pa_context_set_card_profile_by_index() failed Hàm pa_context_set_card_profile_by_index() thực thi thất bại pa_context_set_default_sink() failed pa_context_set_default_sink() không thành công pa_context_set_default_source() failed Hàm pa_context_set_default_source() thực thi thất bại pa_context_set_sink_port_by_name() failed pa_context_set_sink_port_by_name() không thành công pa_context_set_source_port_by_name() failed pa_context_set_source_port_by_name() không thành công pa_context_kill_sink_input() failed Hàm pa_context_kill_sink_input() thực thi thất bại (plugged in) (cắm vào) (unavailable) (không khả dụng) (unplugged) (đã ngắt kết nối) Failed to read data from stream Không thể đọc dữ liệu từ luồng Peak detect Phát hiện đỉnh Failed to create monitoring stream Không thể tạo luồng giám sát Failed to connect monitoring stream Không kết nối được luồng giám sát Ignoring sink-input due to it being designated as an event and thus handled by the Event widget Bỏ qua sink-input do được chỉ định là sự kiện (event) và sẽ được xử lý bởi widget Sự kiện Establishing connection to PulseAudio. Please wait... Thiết lập kết nối với PulseAudio. Hãy chờ... pa_context_get_sink_info_by_name() failed Hàm pa_context_get_sink_info_by_name() thực thi thất bại pa_context_get_source_info_by_name() failed pa_context_get_source_info_by_name() không thành công UkuiMediaSetHeadsetWidget Sound Settings Cài đặt âm thanh Cancel Hủy Select Sound Device Chọn thiết bị âm thanh Headphone Tai nghe Headset Tai nghe có mic Microphone Micrô ukui-media/ukui-volume-control-applet-qt/translations/mn_MN.ts0000664000175000017500000006401015170054730023477 0ustar fengfeng ApplicationVolumeWidget Application Volume ᠬᠡᠷᠡᠭᠯᠡᠯᠳᠡ System Volume ᠰᠢᠰᠲᠸᠮ ᠤ᠋ᠨ ᠳᠠᠭᠤᠨ ᠤ᠋ ᠬᠡᠮᠵᠢᠶ᠎ᠡ Sound Settings ᠳᠠᠭᠤ ᠳᠤᠬᠢᠷᠠᠭᠤᠯᠬᠤ DeviceSwitchWidget Go Into Mini Mode 进入Mini模式 Output volume control 输出音量控制 Mute 静音 Sound preference 声音首选项 Device Volume 设备音量 Application Volume 应用音量 is using 正在使用 Bluetooth 蓝牙 Error 错误 Unable to connect to the sound system, please check whether the pulseaudio service is running! 无法连接到系统声音,请检查pulseaudio服务是否正在运行! Dummy output 伪输出 Speaker (Realtek Audio) 扬声器(Realtek Audio) Headphone 模拟耳机 QObject pa_context_subscribe() failed pa_context_subscribe() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ pa_context_get_card_info_list() failed pa_context_get_card_info_list() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ pa_context_get_sink_info_list() failed pa_context_get_sink_info_list() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ pa_context_get_source_info_list() failed pa_context_get_source_info_list() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ Failed to initialize stream_restore extension: %s stream_restore ᠵᠢ/ ᠢ᠋ ᠠᠨᠭᠬᠠᠵᠢᠭᠤᠯᠵᠤ ᠦᠷᠬᠡᠳᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ᠄%s pa_ext_stream_restore_read() failed pa_ext_stream_restore_read() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ pa_context_get_sink_info_by_index() failed pa_context_get_sink_info_by_index() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ pa_context_get_source_info_by_index() failed pa_context_get_source_info_by_index() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ pa_context_get_card_info_by_index() failed pa_context_get_card_info_by_index() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ Card callback failure ᠺᠠᠷᠲ ᠢ᠋ ᠡᠬᠡᠬᠦᠯᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠦᠬᠡᠢ Sink callback failure ᠡᠬᠡᠬᠦᠯᠬᠦ ᠵᠢᠨ ᠭᠠᠷᠭᠠᠯᠳᠠ ᠢᠯᠠᠭᠳᠠᠪᠠ Source callback failure ᠡᠬᠡᠬᠦᠯᠬᠦ ᠵᠢᠨ ᠤᠷᠤᠭᠤᠯᠤᠯᠳᠠ ᠢᠯᠠᠭᠳᠠᠪᠠ Fatal Error: Unable to connect to PulseAudio ᠪᠤᠷᠤᠭᠤ᠄PulseAudio ᠳ᠋ᠤ᠌/ ᠲᠤ᠌ ᠴᠦᠷᠬᠡᠯᠡᠵᠦ ᠴᠢᠳᠠᠬᠤ ᠦᠬᠡᠢ pa_context_get_server_info() failed pa_context_get_server_info() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ Sink input callback failure ᠤᠰᠤᠨ ᠬᠤᠪᠢᠯ ᠢ᠋ ᠡᠬᠡᠬᠦᠯᠬᠦ ᠤᠷᠤᠭᠤᠯᠤᠯᠳᠠ ᠢᠯᠠᠭᠳᠠᠪᠠ Source output callback failure source-ouput ᠡᠬᠡᠬᠦᠯᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ Client callback failure ᠬᠡᠷᠡᠭᠯᠡᠭᠴᠢ ᠵᠢᠨ ᠦᠵᠦᠬᠦᠷᠯᠢᠭ ᠢ᠋ ᠡᠬᠡᠬᠦᠯᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ Server info callback failure ᠦᠢᠯᠡᠴᠢᠯᠡᠬᠦᠷ ᠢ᠋ ᠡᠬᠡᠬᠦᠯᠬᠦ ᠪᠤᠷᠤᠭᠤᠳᠠᠪᠠ Failed to initialize device restore extension: %s ᠳᠦᠬᠦᠬᠡᠷᠦᠮᠵᠢ ᠵᠢᠨ ᠰᠤᠷᠠᠭ ᠵᠠᠩᠬᠢ ᠵᠢ ᠠᠨᠭᠬᠠᠵᠢᠭᠤᠯᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠦᠬᠡᠢ pa_ext_device_restore_read_sink_formats() failed pa_ext_device_restore_read_sink_formats() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ Failed to initialize device manager extension: %s ᠳᠦᠬᠦᠬᠡᠷᠦᠮᠵᠢ ᠵᠢᠨ ᠬᠠᠮᠢᠶᠠᠷᠤᠯᠳᠠ ᠵᠢᠨ ᠪᠠᠭᠠᠵᠢ ᠵᠢ ᠠᠨᠭᠬᠠᠵᠢᠭᠤᠯᠵᠤ ᠦᠷᠬᠡᠳᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ᠄%s pa_ext_device_manager_read() failed pa_ext_device_manager_read() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ pa_context_get_sink_input_info() failed pa_context_get_sink_input_info() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ pa_context_get_client_info() failed pa_context_get_client_info() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ pa_context_client_info_list() failed pa_context_client_info_list() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ pa_context_get_sink_input_info_list() failed pa_context_get_sink_input_info_list() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ pa_context_get_source_output_info_list() failed pa_context_get_source_output_info_list() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ Connection failed, attempting reconnect ᠴᠦᠷᠬᠡᠯᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠦᠬᠡᠢ᠂ ᠰᠢᠨ᠎ᠡ ᠪᠡᠷ ᠴᠦᠷᠬᠡᠯᠡᠬᠦ ᠵᠢ ᠳᠤᠷᠰᠢᠵᠤ ᠪᠠᠢᠨ᠎ᠠ moduleInfoCb callback failure ᠬᠡᠰᠡᠭ ᠤ᠋ᠨ ᠰᠤᠷᠠᠭ ᠵᠠᠩᠬᠢCb ᠵᠢ/ ᠢ᠋ ᠡᠬᠡᠬᠦᠯᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ Ukui Media Volume Control Ukui ᠵᠠᠭᠤᠴᠢᠯᠠᠭᠤᠷ ᠤ᠋ᠨ ᠳᠠᠭᠤᠨ ᠤ᠋ ᠬᠡᠮᠵᠢᠶ᠎ᠡ ᠵᠢ ᠡᠵᠡᠮᠳᠡᠬᠦ pa_context_load_module() failed pa_context_load_module() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ ukui-volume-control-applet-qt is already running! UkmediaDeviceWidget Input Device 输入设备 Microphone 麦克风 Output Device 输出设备 Speaker Realtek Audio 扬声器(Realtek Audio) Input device can not be detected 无法检测到输入设备 UkmediaMainWidget Output volume control ᠳᠠᠭᠤᠨ ᠤ᠋ ᠬᠡᠮᠵᠢᠶᠡᠨ ᠤ᠋ ᠬᠢᠨᠠᠨ ᠡᠵᠡᠮᠳᠡᠯ ᠢ᠋ ᠭᠠᠷᠭᠠᠬᠤ Mute 静音 Sound preference ᠳᠠᠭᠤᠨ ᠤ᠋ ᠳᠦᠷᠦᠯ ᠢ᠋ ᠳᠤᠬᠢᠷᠠᠭᠤᠯᠬᠤ System Volume ᠰᠢᠰᠲᠸᠮ ᠤ᠋ᠨ ᠳᠠᠭᠤᠨ ᠤ᠋ ᠬᠡᠮᠵᠢᠶ᠎ᠡ App Volume ᠬᠡᠷᠡᠭᠯᠡᠯᠳᠡ ᠵᠢᠨ ᠳᠠᠭᠤᠨ ᠤ᠋ ᠬᠡᠮᠵᠢᠶ᠎ᠡ Current volume: ᠤᠳᠤᠬᠠᠨ ᠤ᠋ ᠳᠠᠭᠤᠨ ᠤ᠋ ᠬᠡᠮᠵᠢᠶ᠎ᠡ: UkmediaMiniMasterVolumeWidget Speaker (Realtek Audio) 扬声器(Realtek Audio) Go Into Full Mode 进入完整模式 UkmediaSystemVolumeWidget Volume ᠳᠠᠭᠤᠨ ᠤ᠋ ᠬᠡᠮᠵᠢᠶ᠎ᠡ Output ᠭᠠᠷᠭᠠᠬᠤ Sound Settings ᠳᠠᠭᠤ ᠳᠤᠬᠢᠷᠠᠭᠤᠯᠬᠤ UkmediaVolumeControl pa_context_set_sink_mute_by_index() failed pa_context_set_sink_volume_by_index() failed pa_context_set_sink_volume_by_index() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ pa_context_set_source_mute_by_index() failed pa_context_set_source_mute_by_index () ᠢᠯᠠᠭᠳᠠᠪᠠ᠃ pa_context_set_source_volume_by_index() failed pa_context_set_source_volume_by_index () ᠢᠯᠠᠭᠳᠠᠪᠠ᠃ pa_context_move_sink_input_by_index() failed pa_context_move_source_output_by_name() failed pa_context_set_source_output_volume() failed pa_context_set_source_output_volume() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ pa_context_set_source_output_mute() failed pa_context_set_source_output_mute() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ pa_context_set_card_profile_by_index() failed pa_context_set_card_profile_by_index() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ pa_context_set_default_sink() failed pa_context_set_default_sink() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ pa_context_set_default_source() failed pa_context_set_default_source() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ pa_context_set_sink_port_by_name() failed pa_context_set_sink_port_by_name() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ pa_context_set_source_port_by_name() failed pa_context_set_source_port_by_name() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ pa_context_kill_sink_input() failed pa_context_kill_sink_input() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ (plugged in) ( ᠬᠠᠪᠴᠢᠭᠤᠯᠤᠭᠰᠠᠨ ᠪᠠᠢᠨ᠎ᠠ) (unavailable) ( ᠬᠡᠷᠡᠭᠯᠡᠵᠦ ᠪᠤᠯᠬᠤ ᠦᠬᠡᠢ) (unplugged) ᠴᠦᠷᠬᠡᠯᠡᠬᠡ ᠪᠠᠢᠬᠤ ᠦᠬᠡᠢ Failed to read data from stream ᠳ᠋ᠠᠢᠲ᠋ᠠ ᠵᠢ ᠤᠨᠭᠰᠢᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠦᠬᠡᠢ Peak detect ᠣᠷᠭᠢᠯ ᠬᠡᠮᠵᠢᠭᠳᠡᠯ ᠦ᠋ᠨ ᠪᠠᠢᠴᠠᠭᠠᠯᠲᠠ Failed to create monitoring stream ᠬᠢᠨᠠᠨ ᠬᠡᠮᠵᠢᠬᠦ ᠤᠷᠤᠰᠬᠠᠯ ᠢ᠋ ᠪᠠᠢᠭᠤᠯᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠦᠬᠡᠢ Failed to connect monitoring stream ᠬᠢᠨᠠᠨ ᠬᠡᠮᠵᠢᠬᠦ ᠤᠷᠤᠰᠬᠠᠯ ᠢ᠋ ᠴᠦᠷᠬᠡᠯᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠦᠬᠡᠢ Ignoring sink-input due to it being designated as an event and thus handled by the Event widget ᠰᠠᠭᠤᠭᠳᠠᠭᠤᠯᠵᠤ ᠤᠷᠤᠭᠤᠯᠬᠤ ᠵᠢ ᠤᠮᠳᠤᠭᠠᠢᠯᠠᠨ᠎ᠠ᠂ ᠤᠴᠢᠷ ᠨᠢ ᠳᠡᠬᠦᠨ ᠢ᠋ ᠶᠠᠪᠤᠳᠠᠯ ᠵᠢᠨᠷ ᠳᠤᠭᠳᠠᠭᠠᠭᠰᠠᠨ᠂ ᠡᠢᠮᠤ ᠡᠴᠡ ᠶᠠᠪᠤᠳᠠᠯ ᠤ᠋ᠨ ᠵᠢᠵᠢᠭ ᠰᠡᠯᠪᠢᠭ ᠵᠢᠡᠷ ᠰᠢᠢᠳᠪᠦᠷᠢᠯᠡᠬᠦ ᠬᠡᠷᠡᠭᠳᠡᠢ Establishing connection to PulseAudio. Please wait... PulseAudio ᠳ᠋ᠤ᠌/ ᠲᠤ᠌ ᠴᠦᠷᠬᠡᠯᠡᠵᠦ ᠪᠠᠢᠨ᠎ᠠ᠂ ᠳᠦᠷ ᠬᠦᠯᠢᠶᠡᠬᠡᠷᠡᠢ... pa_context_get_sink_info_by_name() failed pa_context_get_sink_info_by_name() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ pa_context_get_source_info_by_name() failed pa_context_get_source_info_by_name() ᠵᠢ/ ᠢ᠋ ᠬᠦᠢᠴᠡᠬᠡᠬᠦ ᠢᠯᠠᠭᠳᠠᠪᠠ UkuiMediaSetHeadsetWidget Sound Settings ᠳᠠᠭᠤ ᠳᠤᠬᠢᠷᠠᠭᠤᠯᠬᠤ Cancel ᠦᠬᠡᠢᠰᠭᠡᠬᠦ᠌ Select Sound Device ᠭᠠᠷᠭᠠᠬᠤ ᠳᠦᠬᠦᠬᠡᠷᠦᠮᠵᠢ ᠵᠢ ᠰᠤᠨᠭᠭᠤᠬᠤ Headphone ᠰᠤᠨᠤᠰᠤᠭᠤᠷ Headset ᠰᠤᠨᠤᠰᠤᠭᠤᠷ Microphone ᠮᠢᠺᠷᠣᠹᠣᠨ ukui-media/ukui-volume-control-applet-qt/translations/zh_HK.ts0000664000175000017500000005315715170054730023510 0ustar fengfeng ApplicationVolumeWidget Application Volume 應用 System Volume 系統音量 Sound Settings 聲音設置 DeviceSwitchWidget Go Into Mini Mode 进入Mini模式 Output volume control 输出音量控制 Mute 静音 Sound preference 声音首选项 Device Volume 设备音量 Application Volume 应用音量 is using 正在使用 Bluetooth 蓝牙 Error 错误 Unable to connect to the sound system, please check whether the pulseaudio service is running! 无法连接到系统声音,请检查pulseaudio服务是否正在运行! Dummy output 伪输出 Speaker (Realtek Audio) 扬声器(Realtek Audio) Headphone 模拟耳机 QObject pa_context_subscribe() failed pa_context_subscribe() 執行失敗 pa_context_get_card_info_list() failed pa_context_get_card_info_list() 執行失敗 pa_context_get_sink_info_list() failed pa_context_get_sink_info_list() 執行失敗 pa_context_get_source_info_list() failed pa_context_get_source_info_list() 執行失敗 Failed to initialize stream_restore extension: %s 初始化stream_restore擴展失敗:%s pa_ext_stream_restore_read() failed pa_ext_stream_restore_read() 執行失敗 pa_context_get_sink_info_by_index() failed pa_context_get_sink_info_by_index() 執行失敗 pa_context_get_source_info_by_index() failed pa_context_get_source_info_by_index() 執行失敗 pa_context_get_card_info_by_index() failed pa_context_get_card_info_by_index() 執行失敗 Card callback failure 卡片回調失敗 Sink callback failure 輸出回調失敗 Source callback failure 輸入回調失敗 Fatal Error: Unable to connect to PulseAudio 錯誤:不能連接到pulseaudio pa_context_get_server_info() failed pa_context_get_server_info() 執行失敗 Sink input callback failure 水槽輸入回調失敗 Source output callback failure source-ouput回調失敗 Client callback failure 用戶端回調失敗 Server info callback failure 服務端回調錯誤 Failed to initialize device restore extension: %s 初始化設備信息失敗 pa_ext_device_restore_read_sink_formats() failed pa_ext_device_restore_read_sink_formats() 失敗 Failed to initialize device manager extension: %s 初始化設備管理器擴展失敗:%s pa_ext_device_manager_read() failed pa_ext_device_manager_read() 執行失敗 pa_context_get_sink_input_info() failed pa_context_get_sink_input_info() 執行失敗 pa_context_get_client_info() failed pa_context_get_client_info() 執行失敗 pa_context_client_info_list() failed pa_context_client_info_list() 執行失敗 pa_context_get_sink_input_info_list() failed pa_context_get_sink_input_info_list() 執行失敗 pa_context_get_source_output_info_list() failed pa_context_get_source_output_info_list() 執行失敗 Connection failed, attempting reconnect 連接失敗,嘗試重新連接中 moduleInfoCb callback failure 模組資訊Cb回調失敗 Ukui Media Volume Control UKUI 媒體音量控制 pa_context_load_module() failed pa_context_load_module() 失敗 ukui-volume-control-applet-qt is already running! UkmediaDeviceWidget Input Device 输入设备 Microphone 麦克风 Output Device 输出设备 Speaker Realtek Audio 扬声器(Realtek Audio) Input device can not be detected 无法检测到输入设备 UkmediaMainWidget Output volume control 輸出音量控制 Mute 静音 Sound preference 設置聲音項 System Volume 系統音量 App Volume 應用音量 Current volume: 目前音量: UkmediaMiniMasterVolumeWidget Speaker (Realtek Audio) 扬声器(Realtek Audio) Go Into Full Mode 进入完整模式 UkmediaSystemVolumeWidget Volume 音量 Output 輸出 Sound Settings 聲音設置 UkmediaVolumeControl pa_context_set_sink_mute_by_index() failed pa_context_set_sink_volume_by_index() failed pa_context_set_sink_volume_by_index() 執行失敗 pa_context_set_source_mute_by_index() failed pa_context_set_source_volume_by_index() failed pa_context_move_sink_input_by_index() failed pa_context_move_source_output_by_name() failed pa_context_set_source_output_volume() failed pa_context_set_source_output_volume() 執行失敗 pa_context_set_source_output_mute() failed pa_context_set_source_output_mute() 執行失敗 pa_context_set_card_profile_by_index() failed pa_context_set_card_profile_by_index() 執行失敗 pa_context_set_default_sink() failed pa_context_set_default_sink() 執行失敗 pa_context_set_default_source() failed pa_context_set_default_source() 執行失敗 pa_context_set_sink_port_by_name() failed pa_context_set_sink_port_by_name() 執行失敗 pa_context_set_source_port_by_name() failed pa_context_set_source_port_by_name() 執行失敗 pa_context_kill_sink_input() failed pa_context_kill_sink_input() 失敗 (plugged in) (插上了) (unavailable) ( 不可用 ) (unplugged) 沒有連接 Failed to read data from stream 從流中讀取數據失敗 Peak detect 峰值檢測 Failed to create monitoring stream 創建監控流失敗 Failed to connect monitoring stream 連接監控流失敗 Ignoring sink-input due to it being designated as an event and thus handled by the Event widget 忽略沉降輸入,因為它被指定為事件,因此由事件小部件處理 Establishing connection to PulseAudio. Please wait... 正在建立與PulseAudio的連接。 請等待... pa_context_get_sink_info_by_name() failed pa_context_get_sink_info_by_index() failed pa_context_get_source_info_by_name() failed pa_context_get_source_info_by_index() failed UkuiMediaSetHeadsetWidget Sound Settings 聲音設置 Cancel 取消 Select Sound Device 選擇輸出設備 Headphone 耳機 Headset 耳麥 Microphone 麥克風 ukui-media/ukui-volume-control-applet-qt/res.qrc0000664000175000017500000000033515170052044020676 0ustar fengfeng data/img/setting.svg data/img/tick.png data/img/tick.svg data/qss/ukuimedia.qss ukui-media/ukui-volume-control-applet-qt/ukui_list_widget_item.h0000664000175000017500000000265015170052044024142 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #include #include #include #include #include #include "ukmedia_custom_class.h" class UkuiListWidgetItem : public QWidget { Q_OBJECT public: UkuiListWidgetItem(QWidget *parent = 0); ~UkuiListWidgetItem(); void setLabelText(QString portText,QString deviceLabel); void setSelected(bool selected); void setButtonIcon(QString iconName); QLabel *deviceLabel; FixLabel *portLabel; QPushButton *deviceButton; private: QHBoxLayout *hLayout; protected: void mousePressEvent(QMouseEvent *event); }; #endif // UKUILISTWIDGETITEM_H ukui-media/ukui-volume-control-applet-qt/ukmedia_custom_class.h0000664000175000017500000000767115170054730023763 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kslider.h" #define MIDDLE_COLOR 178 #define BACKGROUND_COLOR QColor(0,0,0,0) using namespace kdk; typedef struct UkuiThemeIcon { QImage image; QColor color; }UkuiThemeIcon; enum PushButtonState{ PUSH_BUTTON_NORMAL, PUSH_BUTTON_CLICK, PUSH_BUTTON_PRESS }; static QColor symbolic_color = Qt::gray; class UkuiButtonDrawSvg:public QPushButton { Q_OBJECT public: UkuiButtonDrawSvg(QWidget *parent = nullptr); ~UkuiButtonDrawSvg(); QPixmap filledSymbolicColoredPixmap(QImage &source, QColor &baseColor); QRect IconGeometry(); void draw(QPaintEvent* e); void init(QImage image ,QColor color); friend class DeviceSwitchWidget; friend class UkmediaMainWidget; friend class UkmediaOsdDisplayWidget; friend class UkmediaSystemVolumeWidget; protected: void paintEvent(QPaintEvent *event); bool event(QEvent *e); private: int buttonState = PUSH_BUTTON_NORMAL; UkuiThemeIcon themeIcon; }; class UkuiApplicationWidget:public QWidget { Q_OBJECT public: UkuiApplicationWidget(QWidget *parent = nullptr); ~UkuiApplicationWidget(); protected: void paintEvent(QPaintEvent*); }; class UkuiSettingButton:public QLabel { Q_OBJECT public: UkuiSettingButton(QWidget *parent = nullptr); ~UkuiSettingButton(); friend class UkmediaMiniMasterVolumeWidget; friend class UkmediaMainWidget; friend class UkmediaSystemVolumeWidget; friend class ApplicationVolumeWidget; protected: void paintEvent(QPaintEvent *event); void enterEvent(QEvent *event); void leaveEvent(QEvent *event); void mousePressEvent(QMouseEvent *event); void mouseReleaseEvent(QMouseEvent *event); private: QColor m_foregroundColor; void setPressColor(); void setHoverColor(); void setNormalColor(); Q_SIGNALS: void clicked(void); private Q_SLOTS: void onPaletteChanged(); }; class AudioSlider : public KSlider { Q_OBJECT public: AudioSlider(QWidget *parent = nullptr); ~AudioSlider(); bool isMouseWheel = false; private: int blueValue = 0; Q_SIGNALS: void blueValueChanged(int value); //针对蓝牙a2dp模式下滑动条跳动,以10为间隔设置音量 protected: void wheelEvent(QWheelEvent *e); void keyReleaseEvent(QKeyEvent *e); void mouseMoveEvent(QMouseEvent *e); void mouseReleaseEvent(QMouseEvent *e); }; //文本长自动省略并添加悬浮 class FixLabel : public QLabel { Q_OBJECT public: explicit FixLabel(QWidget *parent = nullptr); explicit FixLabel(QString text , QWidget *parent = nullptr); ~FixLabel(); void setText(const QString &text, bool saveTextFlag = true); QString fullText; private: void paintEvent(QPaintEvent *event); private: }; class Divider : public QFrame { public: Divider(QWidget * parent = nullptr); ~Divider() = default; protected: void paintEvent(QPaintEvent *event); }; #endif ukui-media/ukui-volume-control-applet-qt/dbus-adaptor/0000775000175000017500000000000015170054730021766 5ustar fengfengukui-media/ukui-volume-control-applet-qt/dbus-adaptor/dbus-adaptor.pri0000664000175000017500000000024315170052044025062 0ustar fengfengINCLUDEPATH += $$PWD HEADERS += \ $$PWD/dbus-adaptor.h \ $$PWD/bluez-adaptor.h \ SOURCES += \ $$PWD/dbus-adaptor.cpp \ $$PWD/bluez-adaptor.cpp \ ukui-media/ukui-volume-control-applet-qt/dbus-adaptor/dbus-adaptor.h0000664000175000017500000001066315170054730024532 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #include //#include "ukmedia_main_widget.h" QT_BEGIN_NAMESPACE class QByteArray; template class QList; template class QMap; class QString; class QVariant; QT_END_NAMESPACE /* * Adaptor class for interface org.ukui.media */ class MediaAdaptor: public QDBusAbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.ukui.media") Q_CLASSINFO("D-Bus Introspection", "" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "") public: MediaAdaptor(QObject *parent); virtual ~MediaAdaptor(); public: // PROPERTIES public Q_SLOTS: // METHODS QStringList getAllInputDevices(); QStringList getAllOutputDevices(); QString getDefaultInputDevice(); bool getDefaultInputMuteState(); int getDefaultInputVolume(); QString getDefaultOutputDevice(); bool getDefaultOutputMuteState(); int getDefaultOutputVolume(); bool setDefaultInputDevice(const QString &deviceName); bool setDefaultInputMuteState(bool mute); bool setDefaultInputVolume(int value); bool setDefaultOutputDevice(const QString &deviceName); bool setDefaultOutputMuteState(bool mute); bool setDefaultOutputVolume(int value); int getBatteryLevel(const QString &deviceName); Q_SIGNALS: // SIGNALS void batteryChanged(const QString &device, int battery); }; #endif ukui-media/ukui-volume-control-applet-qt/dbus-adaptor/dbus-adaptor.cpp0000664000175000017500000001167515170054730025071 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #include #include #include #include #include #include /* * Implementation of adaptor class MediaAdaptor */ MediaAdaptor::MediaAdaptor(QObject *parent) : QDBusAbstractAdaptor(parent) { // constructor setAutoRelaySignals(true); } MediaAdaptor::~MediaAdaptor() { // destructor } QStringList MediaAdaptor::getAllInputDevices() { // handle method call org.ukui.media.getAllInputDevices QStringList out0; QMetaObject::invokeMethod(parent(), "getAllInputDevices", Q_RETURN_ARG(QStringList, out0)); return out0; } QStringList MediaAdaptor::getAllOutputDevices() { // handle method call org.ukui.media.getAllOutputDevices QStringList out0; QMetaObject::invokeMethod(parent(), "getAllOutputDevices", Q_RETURN_ARG(QStringList, out0)); return out0; } QString MediaAdaptor::getDefaultInputDevice() { // handle method call org.ukui.media.getDefaultInputDevice QString out0; QMetaObject::invokeMethod(parent(), "getDefaultInputDevice", Q_RETURN_ARG(QString, out0)); return out0; } bool MediaAdaptor::getDefaultInputMuteState() { // handle method call org.ukui.media.getDefaultInputMuteState bool out0; QMetaObject::invokeMethod(parent(), "getDefaultInputMuteState", Q_RETURN_ARG(bool, out0)); return out0; } int MediaAdaptor::getDefaultInputVolume() { // handle method call org.ukui.media.getDefaultInputVolume int out0; QMetaObject::invokeMethod(parent(), "getDefaultInputVolume", Q_RETURN_ARG(int, out0)); return out0; } QString MediaAdaptor::getDefaultOutputDevice() { // handle method call org.ukui.media.getDefaultOutputDevice QString out0; QMetaObject::invokeMethod(parent(), "getDefaultOutputDevice", Q_RETURN_ARG(QString, out0)); return out0; } bool MediaAdaptor::getDefaultOutputMuteState() { // handle method call org.ukui.media.getDefaultOutputMuteState bool out0; QMetaObject::invokeMethod(parent(), "getDefaultOutputMuteState", Q_RETURN_ARG(bool, out0)); return out0; } int MediaAdaptor::getDefaultOutputVolume() { // handle method call org.ukui.media.getDefaultOutputVolume int out0; QMetaObject::invokeMethod(parent(), "getDefaultOutputVolume", Q_RETURN_ARG(int, out0)); return out0; } bool MediaAdaptor::setDefaultInputDevice(const QString &deviceName) { // handle method call org.ukui.media.setDefaultInputDevice bool out0; QMetaObject::invokeMethod(parent(), "setDefaultInputDevice", Q_RETURN_ARG(bool, out0), Q_ARG(QString, deviceName)); return out0; } bool MediaAdaptor::setDefaultInputMuteState(bool mute) { // handle method call org.ukui.media.setDefaultInputMuteState bool out0; QMetaObject::invokeMethod(parent(), "setDefaultInputMuteState", Q_RETURN_ARG(bool, out0), Q_ARG(bool, mute)); return out0; } bool MediaAdaptor::setDefaultInputVolume(int value) { // handle method call org.ukui.media.setDefaultInputVolume bool out0; QMetaObject::invokeMethod(parent(), "setDefaultInputVolume", Q_RETURN_ARG(bool, out0), Q_ARG(int, value)); return out0; } bool MediaAdaptor::setDefaultOutputDevice(const QString &deviceName) { // handle method call org.ukui.media.setDefaultOutputDevice bool out0; QMetaObject::invokeMethod(parent(), "setDefaultOutputDevice", Q_RETURN_ARG(bool, out0), Q_ARG(QString, deviceName)); return out0; } bool MediaAdaptor::setDefaultOutputMuteState(bool mute) { // handle method call org.ukui.media.setDefaultOutputMuteState bool out0; QMetaObject::invokeMethod(parent(), "setDefaultOutputMuteState", Q_RETURN_ARG(bool, out0), Q_ARG(bool, mute)); return out0; } bool MediaAdaptor::setDefaultOutputVolume(int value) { // handle method call org.ukui.media.setDefaultOutputVolume bool out0; QMetaObject::invokeMethod(parent(), "setDefaultOutputVolume", Q_RETURN_ARG(bool, out0), Q_ARG(int, value)); return out0; } int MediaAdaptor::getBatteryLevel(const QString &deviceName) { // handle method call org.ukui.media.bluetooth.getBatteryLevel int out0; QMetaObject::invokeMethod(parent(), "getBatteryLevel", Q_RETURN_ARG(int, out0), Q_ARG(QString, deviceName)); return out0; } ukui-media/ukui-volume-control-applet-qt/dbus-adaptor/bluez-adaptor.h0000664000175000017500000000304015170054730024705 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifndef BLUEZ_ADAPTOR_H #define BLUEZ_ADAPTOR_H #include #include #include #include #define ERROR -1 class Bluetooth_Dbus : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.ukui.media") public: Bluetooth_Dbus(); ~Bluetooth_Dbus(); int batteryLevel = -1;//蓝牙耳机电量 QString macAddress = ""; void UkmediaDbusRegister(); void sendBatteryChangedSignal(QString macAddr, int battery); Q_SIGNALS: void batteryChanged(QString macAddr, int battery);//蓝牙电量改变信号 public Q_SLOTS: int getBatteryLevel(QString macAddr);//获取蓝牙耳机电量 }; #endif // BLUEZ_ADAPTOR_H ukui-media/ukui-volume-control-applet-qt/dbus-adaptor/bluez-adaptor.cpp0000664000175000017500000000330415170054730025243 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "bluez-adaptor.h" Bluetooth_Dbus::Bluetooth_Dbus() { UkmediaDbusRegister(); } void Bluetooth_Dbus::UkmediaDbusRegister() { QDBusConnection con = QDBusConnection::sessionBus(); if(!con.registerService("org.ukui.media")) qDebug()<<"registerService failed"< ukui-media/ukui-volume-control-applet-qt/ukmedia_monitor_window_thread.h0000664000175000017500000000257215170052044025660 0ustar fengfeng/* * Copyright 2020. kylinos.cn. * * 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, 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include class UkmediaMonitorWindowThread : public QThread { public: UkmediaMonitorWindowThread(); void get_window_nameAndid(); //结束线程标志位 bool bStopThread; bool bCreateWindow; bool bFirstEnterSystem; QVector windowId; void run(); }; #endif // UKMEDIAMONITORWINDOWTHREAD_H ukui-media/ukui-volume-control-applet-qt/ukui_media_set_headset_widget.h0000664000175000017500000000622515170054730025606 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #include #include #include #include #include #include #include #include #include #include #include "windowmanager/windowmanager.h" #include "ukuistylehelper/ukuistylehelper.h" #include //设置声卡的key controls #define LEVEL_BASIC (1<<0) #define LEVEL_ID (1<<2) #define HW_CARD "hw:0" //任务栏多屏显示声音应用获取屏幕可用区域接口 #define PANEL_SETTINGS "org.ukui.panel.settings" #define PANEL_SIZE_KEY "panelsize" #define PANEL_POSITION_KEY "panelposition" class UkuiMediaSetHeadsetWidget : public QWidget { Q_OBJECT enum PanelPosition{ Bottom = 0, //!< The bottom side of the screen. Top, //!< The top side of the screen. Left, //!< The left side of the screen. Right //!< The right side of the screen. }; public: UkuiMediaSetHeadsetWidget(QWidget *parent = nullptr); ~UkuiMediaSetHeadsetWidget(); void initSetHeadsetWidget(); int cset(char * name, char *card, char *c, int roflag, int keep_handle); void showControlId(snd_ctl_elem_id_t *id); int showControl(const char *space, snd_hctl_elem_t *elem,int level); void showWindow(); void initPanelGSettings(); QRect caculatePosition(); //任务栏多屏显示声音应用获取屏幕可用区域接口 friend class DeviceSwitchWidget; friend class UkmediaMainWidget; private: QToolButton *headphoneIconButton; QToolButton *headsetIconButton; QToolButton *microphoneIconButton; QLabel *headphoneLabel; QLabel *headsetLabel; QLabel *microphoneLabel; QLabel *selectSoundDeviceLabel; QPushButton *soundSettingButton; QPushButton *cancelButton; bool isShow = false; QGSettings *m_panelGSettings = nullptr; int m_panelPosition; int m_panelSize; private Q_SLOTS: void headphoneButtonClickedSlot(); //点击headphone按钮 void headsetButtonClickedSlot(); //点击headset 按钮 void microphoneButtonClickedSlot(); //点击microphone 按钮 void soundSettingButtonClickedSlot(); //点击声音设置按钮 void cancelButtonClickedSlot(); //点击取消按钮 protected: void paintEvent(QPaintEvent *event); }; #endif // UKUIMEDIASETHEADSETWIDGET_H ukui-media/ukui-volume-control-applet-qt/main.cpp0000664000175000017500000000766715170054730021051 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #include #include #include #include #include #include "ukmedia_main_widget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "xatom/xatom-helper.h" #include "windowmanager/windowmanager.h" int main(int argc, char *argv[]) { initUkuiLog4qt("ukui-media"); // Display *display = XOpenDisplay(NULL); // Screen *scrn = DefaultScreenOfDisplay(display); // if (scrn == nullptr) { // return 0; // } #if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); #endif #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); #endif QString id = QString("ukui-volume-control-applet-qt-" + QLatin1String(getenv("DISPLAY"))); QtSingleApplication app(id, argc, argv); qDebug() << " DISPLAY id:" << id; if (app.isRunning()) { QDBusInterface interface("org.ukui.media", "/org/ukui/media", "org.ukui.media", QDBusConnection::sessionBus()); if (interface.isValid()){ interface.call("advancedWidgetShow"); } app.sendMessage(QApplication::arguments().length() > 1 ? QApplication::arguments().at(1) : app.applicationFilePath()); qDebug() << QObject::tr("ukui-volume-control-applet-qt is already running!"); return EXIT_SUCCESS; } #ifndef QT_NO_TRANSLATION QString translatorFileName = QLatin1String("qt_"); translatorFileName += QLocale::system().name(); QTranslator *pTranslator = new QTranslator(); if (pTranslator->load(translatorFileName, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) app.installTranslator(pTranslator); #endif //加载qm文件 QTranslator translator; translator.load("/usr/share/ukui-media/translations/" + QLocale::system().name()); app.installTranslator(&translator); //加载qss文件 QFile qss(":/data/qss/ukuimedia.qss"); qss.open(QFile::ReadOnly); qss.close(); UkmediaMainWidget w; app.setActivationWindow(&w); w.setProperty("useStyleWindowManager", false); //禁用拖动 //start v11环境上此操作覆盖qtsdk接口的圆角效果,故屏蔽 // QString platform = QGuiApplication::platformName(); // if(!platform.startsWith(QLatin1String("wayland"),Qt::CaseInsensitive)){ // MotifWmHints window_hints; // window_hints.flags = MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS; // window_hints.functions = MWM_FUNC_ALL; // window_hints.decorations = MWM_DECOR_BORDER; // XAtomHelper::getInstance()->setWindowMotifHint(w.winId(), window_hints); // } // else { // w.setWindowFlags(Qt::CustomizeWindowHint | Qt::FramelessWindowHint); // } //end // XCloseDisplay(display); return app.exec(); } ukui-media/ukui-volume-control-applet-qt/ukmedia_volume_control.cpp0000664000175000017500000027257015170054730024670 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include //pa_sink_info *m_pDefaultSink; /* Used for profile sorting */ struct profile_prio_compare { bool operator() (pa_card_profile_info2 const * const lhs, pa_card_profile_info2 const * const rhs) const { if (lhs->priority == rhs->priority) return strcmp(lhs->name, rhs->name) > 0; return lhs->priority > rhs->priority; } }; struct sink_port_prio_compare { bool operator() (const pa_sink_port_info& lhs, const pa_sink_port_info& rhs) const { if (lhs.priority == rhs.priority) return strcmp(lhs.name, rhs.name) > 0; return lhs.priority > rhs.priority; } }; struct source_port_prio_compare { bool operator() (const pa_source_port_info& lhs, const pa_source_port_info& rhs) const { if (lhs.priority == rhs.priority) return strcmp(lhs.name, rhs.name) > 0; return lhs.priority > rhs.priority; } }; pa_context* UkmediaVolumeControl::context = nullptr; int UkmediaVolumeControl::reconnectTimeout = 3; pa_mainloop_api* UkmediaVolumeControl::api = nullptr; QTimer UkmediaVolumeControl::deviceChangedTimer; UkmediaVolumeControl::UkmediaVolumeControl() : showSinkInputType(SINK_INPUT_CLIENT), showSinkType(SINK_ALL), showSourceOutputType(SOURCE_OUTPUT_CLIENT), showSourceType(SOURCE_NO_MONITOR), canRenameDevices(false), m_connected(false), m_config_filename(nullptr) { profileNameMap.clear(); //创建声音初始化记录文件 customSoundFile = new CustomSound(); customSoundFile->createAudioFile(); initUserConfig(); UkmediaDbusRegister(); //start pulseaudio和ukui-volume进程启动时间差小于3s时,会捕获到pulseaudio初始化设备时的非预期数据导致出现设备切换动作 QTimer::singleShot(3*1000, [this]() { connectToPulse(this); }); //end } void UkmediaVolumeControl::initUserConfig() { m_pUsrConf = std::make_shared(); } /* * 设置输出设备静音 */ void UkmediaVolumeControl::setSinkMute(bool status) { pa_operation* o; if (!(o = pa_context_set_sink_mute_by_index(getContext(), sinkIndex, status, nullptr, nullptr))) { showError(tr("pa_context_set_sink_mute_by_index() failed").toUtf8().constData()); return; } pa_operation_unref(o); } /* * 设置输出设备音量通过info */ void UkmediaVolumeControl::setSinkVolumeByInfo(const pa_sink_info &info, int value) { pa_cvolume v = info.volume; for (int i = 0; i < v.channels; i++) v.values[i] = value; pa_operation* o; if (!(o = pa_context_set_sink_mute_by_index(getContext(), info.index, false, nullptr, nullptr))) { showError(tr("pa_context_set_sink_mute_by_index() failed").toUtf8().constData()); return; } if (!(o = pa_context_set_sink_volume_by_index(getContext(), info.index, &v, nullptr, nullptr))) { showError(tr("pa_context_set_sink_volume_by_index() failed").toUtf8().constData()); return; } pa_operation_unref(o); qDebug() << __func__ << info.index << value; } void UkmediaVolumeControl::setSourceVolumeByInfo(const pa_source_info &info,int value) { pa_cvolume v = info.volume; for (int i = 0; i < v.channels; i++) v.values[i] = value; pa_operation* o; if (!(o = pa_context_set_source_mute_by_index(getContext(), info.index,false, nullptr, nullptr))) { showError(tr("pa_context_set_source_mute_by_index() failed").toUtf8().constData()); return; } if (!(o = pa_context_set_source_volume_by_index(getContext(), info.index, &v, nullptr, nullptr))) { showError(tr("pa_context_set_source_volume_by_index() failed").toUtf8().constData()); return; } pa_operation_unref(o); qDebug() << __func__ << info.index << value; } /* * 设置输出设备音量 */ void UkmediaVolumeControl::setSinkVolume(int index, int value) { if (nullptr == m_pDefaultSink) { qWarning() << "m_pDefaultSink is nullptr, set sink volume failed"; return; } pa_cvolume v = m_pDefaultSink->volume; v.channels = channel; for (int i = 0; i < v.channels; i++) v.values[i] = value; if (balance != 0) pa_cvolume_set_balance(&v, &channelMap, balance); /* To set the volume in silent state, unmute the volume first */ if (sinkMuted) setSinkMute(false); pa_operation* o; if (!(o = pa_context_set_sink_volume_by_index(getContext(), index, &v, nullptr, nullptr))) { showError(tr("pa_context_set_sink_volume_by_index() failed").toUtf8().constData()); return; } pa_operation_unref(o); qDebug() << __func__ << "index" << value; } /* * 设置输入设备静音 */ void UkmediaVolumeControl::setSourceMute(bool status) { pa_operation* o; if (!(o = pa_context_set_source_mute_by_index(getContext(), sourceIndex, status, nullptr, nullptr))) { showError(tr("pa_context_set_source_mute_by_index() failed").toUtf8().constData()); return; } pa_operation_unref(o); } /* * 设置输入设备音量 */ void UkmediaVolumeControl::setSourceVolume(int index, int value) { if (nullptr == m_pDefaultSource) { qWarning() << "m_pDefaultSource is nullptr, set source volume failed"; return; } pa_cvolume v = m_pDefaultSource->volume; v.channels = inputChannel; for (int i = 0; i < v.channels; i++) v.values[i] = value; /* To set the volume in silent state, unmute the volume first */ if (sourceMuted) setSourceMute(false); pa_operation* o; if (!(o = pa_context_set_source_volume_by_index(getContext(), index, &v, nullptr, nullptr))) { showError(tr("pa_context_set_source_volume_by_index() failed").toUtf8().constData()); return; } pa_operation_unref(o); qDebug() << __func__ << "index" << value; } /* * 获取输出设备的静音状态 */ bool UkmediaVolumeControl::getSinkMute() { return sinkMuted; } /* * 获取输出设备的音量值 */ int UkmediaVolumeControl::getSinkVolume() { return sinkVolume; } /* * 获取输入设备的静音状态 */ bool UkmediaVolumeControl::getSourceMute() { return sourceMuted; } /* * 获取输入设备的音量值 */ int UkmediaVolumeControl::getSourceVolume() { return sourceVolume; } int UkmediaVolumeControl::getDefaultSinkIndex() { pa_operation *o; if (!(o = pa_context_get_server_info(getContext(), serverInfoIndexCb, this))) { showError(QObject::tr("pa_context_get_server_info() failed").toUtf8().constData()); return -1; } pa_operation_unref(o); return sinkIndex; } /* * 根据名称获取sink input静音状态 */ bool UkmediaVolumeControl::getSinkInputMuted(QString description) { int muteStatus = 1; QMap::iterator it; for (it=sinkInputMuteMap.begin();it!=sinkInputMuteMap.end();) { qDebug() << "getSinkInputMuted" << it.key() << description << it.value(); if (it.key() == description) { muteStatus = it.value(); break; } ++it; } return muteStatus==1?true:false; } /* * 根据模块名称获取对应模块索引 */ void UkmediaVolumeControl::getModuleIndexByName(QString name) { findModuleStr = name; pa_context_get_module_info_list(getContext(), moduleInfoCb,this); } /* * 滑动条更改设置sink input 音量值 */ void UkmediaVolumeControl::setSinkInputVolume(int index, int value,int channel) { pa_cvolume v = m_pDefaultSink->volume; v.channels = channel; for (int i=0;i appIndexList = findAppIndex(SoundType::SINK, appName); if (appIndexList.isEmpty()) return false; for (int index : appIndexList) { pa_operation* o; if (!(o = pa_context_set_sink_input_mute(getContext(), index, status, nullptr, nullptr))) { showError(tr("pa_context_set_sink_volume_by_index() failed").toUtf8().constData()); } pa_operation_unref(o); qDebug() << "set sink input muted" << index << status; } return true; } void UkmediaVolumeControl::moveSinkInput(QString appName, const char* sinkName) { QList appIndexList = findAppIndex(SoundType::SINK, appName); if (appIndexList.isEmpty()) return; for (int index : appIndexList) { pa_operation* o; if (!(o = pa_context_move_sink_input_by_name(getContext(), index, sinkName, simple_callback, nullptr))) { showError(tr("pa_context_move_sink_input_by_index() failed").toUtf8().constData()); } qDebug() << __func__ << "app" << index << appName << "sink:" << sinkName; pa_operation_unref(o); } } void UkmediaVolumeControl::moveSoureOutput(QString appName, const char* sourceName) { QList appIndexList = findAppIndex(SoundType::SOURCE, appName); if (appIndexList.isEmpty()) return; for (int index : appIndexList) { pa_operation* o; if (!(o = pa_context_move_source_output_by_name(getContext(), index, sourceName, simple_callback, nullptr))) showError(tr("pa_context_move_source_output_by_name() failed").toUtf8().constData()); pa_operation_unref(o); qDebug() << __func__ << "app" << index << appName << "source:" << sourceName; } } /* * 滑动条更改设置source output 音量值 */ void UkmediaVolumeControl::setSourceOutputVolume(int index, int value, int channel) { pa_cvolume v = m_pDefaultSink->volume; v.channels = channel; for (int i=0;i appIndexList = findAppIndex(SoundType::SOURCE, appName); if (appIndexList.isEmpty()) return false; for (int index : appIndexList) { pa_operation* o; if (!(o = pa_context_set_source_output_mute(getContext(), index, status, nullptr, nullptr))) showError(tr("pa_context_set_source_output_mute() failed").toUtf8().constData()); pa_operation_unref(o); qDebug() << "set source output muted" << index << status; } return true; } /* * 设置声卡的配置文件 */ bool UkmediaVolumeControl::setCardProfile(int index, const gchar *name) { qDebug() << "setCardProfile" << index << name; pa_operation* o; if (!(o = pa_context_set_card_profile_by_index(getContext(), index, name, nullptr, nullptr))) { showError(tr("pa_context_set_card_profile_by_index() failed").toUtf8().constData()); return false; } return true; } /* * 设置默认的输出设备 */ bool UkmediaVolumeControl::setDefaultSink(const gchar *name) { bool monoAudioState = false; QGSettings *m_pSoundSettings = new QGSettings(KEY_SOUNDS_SCHEMA); monoAudioState = m_pSoundSettings->get(MONO_AUDIO).toBool(); pa_operation* o; if (!(o = pa_context_set_default_sink(getContext(), name, nullptr, nullptr))) { showError(tr("pa_context_set_default_sink() failed").toUtf8().constData()); return false; } #if 0 if(monoAudioState) { if (!(o = pa_context_set_default_sink(getContext(), "mono", nullptr, nullptr))) { showError(tr("pa_context_set_default_sink() failed").toUtf8().constData()); return false; } } #endif qDebug() << " setDefaultSink " << name << defaultSinkName << monoAudioState; return true; } /* * 设置默认的输入设备 */ bool UkmediaVolumeControl::setDefaultSource(const gchar *name) { qDebug() << "setDefaultSource" << name; pa_operation* o; if (!(o = pa_context_set_default_source(getContext(), name, nullptr, nullptr))) { showError(tr("pa_context_set_default_source() failed").toUtf8().constData()); return false; } return true; } /* * 设置输出设备的端口 */ bool UkmediaVolumeControl::setSinkPort(const gchar *sinkName, const gchar *portName) { qDebug() << "setSinkPort" << "sinkname:" << sinkName << "portname:" << portName; pa_operation* o; if (!(o = pa_context_set_sink_port_by_name(getContext(), sinkName, portName, nullptr, nullptr))) { showError(tr("pa_context_set_sink_port_by_name() failed").toUtf8().constData()); return false; } return true; } /* * 设置输入设备端口 */ bool UkmediaVolumeControl::setSourcePort(const gchar *sourceName, const gchar *portName) { qDebug() << "setSourcePort" << sourceName << portName; pa_operation* o; if (!(o = pa_context_set_source_port_by_name(getContext(), sourceName, portName, nullptr, nullptr))) { showError(tr("pa_context_set_source_port_by_name() failed").toUtf8().constData()); return false; } return true; } /* * kill 索引为index 的sink input */ bool UkmediaVolumeControl::killSinkInput(int index) { pa_operation* o; if (!(o = pa_context_kill_sink_input(getContext(), index, nullptr, nullptr))) { showError(tr("pa_context_kill_sink_input() failed").toUtf8().constData()); return false; } return true; } UkmediaVolumeControl::~UkmediaVolumeControl() { while (!clientNames.empty()) { auto i = clientNames.begin(); g_free(i->second); clientNames.erase(i); } } static void updatePorts(UkmediaVolumeControl *d, std::map &ports) { std::map::iterator it; PortInfo p; for (auto & port : d->dPorts) { QByteArray desc; it = ports.find(port.first); if (it == ports.end()) { continue; } p = it->second; desc = p.description; if (p.available == PA_PORT_AVAILABLE_YES) { desc += UkmediaVolumeControl::tr(" (plugged in)").toUtf8().constData(); } else if (p.available == PA_PORT_AVAILABLE_NO) { if (p.name == "analog-output-speaker" || p.name == "analog-input-microphone-internal") { desc += UkmediaVolumeControl::tr(" (unavailable)").toUtf8().constData(); } else { desc += UkmediaVolumeControl::tr(" (unplugged)").toUtf8().constData(); } } port.second = desc; } qDebug() << "updatePorts" << p.name << p.description; Q_EMIT d->updatePortSignal(); it = ports.find(d->activePort); if (it != ports.end()) { p = it->second; // d->setLatencyOffset(p.latency_offset); } } static void setIconByName(QLabel* label, const char* name) { QIcon icon = QIcon::fromTheme(name); int size = label->style()->pixelMetric(QStyle::PM_ToolBarIconSize); QPixmap pix = icon.pixmap(size, size); label->setPixmap(pix); } void UkmediaVolumeControl::updateCard(UkmediaVolumeControl *c, const pa_card_info &info) { const char *description; QMap tempInput; QMap tempOutput; QList profileName; QMapportMap; QMap outputPortNameLabelMap; QMapinputPortNameLabelMap; QMap profilePriorityMap; std::set profile_priorities; // 记录声卡配置文件优先级 profile_priorities.clear(); for (pa_card_profile_info2 **p_profile = info.profiles2; *p_profile != nullptr; ++p_profile) { if (((*p_profile)->available == PA_PORT_AVAILABLE_UNKNOWN) || ((*p_profile)->available == PA_PORT_AVAILABLE_YES)) { profile_priorities.insert(*p_profile); profileName.append((*p_profile)->name); profilePriorityMap.insert((*p_profile)->name, (*p_profile)->priority); } } cardProfilePriorityMap.insert(info.index, profilePriorityMap); description = pa_proplist_gets(info.proplist, PA_PROP_DEVICE_DESCRIPTION); c->ports.clear(); for (uint32_t i = 0; i < info.n_ports; ++i) { PortInfo p; p.name = info.ports[i]->name; p.description = info.ports[i]->description; p.priority = info.ports[i]->priority; p.available = info.ports[i]->available; p.direction = info.ports[i]->direction; p.latency_offset = info.ports[i]->latency_offset; if (info.ports[i]->profiles2)//bug#92426 该蓝牙音响存在输入端口无输入配置文件会导致段错误 for (pa_card_profile_info2 ** p_profile = info.ports[i]->profiles2; *p_profile != nullptr; ++p_profile) { if (((*p_profile)->available == PA_PORT_AVAILABLE_UNKNOWN) || ((*p_profile)->available == PA_PORT_AVAILABLE_YES)) { p.profiles.push_back((*p_profile)->name); } } #ifdef PA_PORT_AVAILABLE_DISABLE if (p.direction == 1 && p.available != PA_PORT_AVAILABLE_NO && p.available != PA_PORT_AVAILABLE_DISABLE) { //portMap.insert(p.name,p.description.data()); //新增UI设计,combobox显示portname+description QString outputPortName = p.description.data();//端口名(如:扬声器,模拟耳机..) QString outputPortName_and_description = outputPortName + "(" + description + ")"; tempOutput.insert(p.name,outputPortName_and_description); QList portProfileName; for (auto p_profile : p.profiles) { portProfileName.append(p_profile.data()); outputPortNameLabelMap.insert(p.description.data(),p_profile.data()); qDebug() << "ctf profilename map insert -----------" << p.description.data() << p_profile.data(); } profileNameMap.insert(info.index,outputPortNameLabelMap); cardProfileMap.insert(info.index,portProfileName); } else if (p.direction == 2 && p.available != PA_PORT_AVAILABLE_NO && p.available != PA_PORT_AVAILABLE_DISABLE) { qDebug() << " add source port name "<< info.index << p.name << p.description.data(); QString inputPortName = p.description.data();//端口名(如:扬声器,模拟耳机..) QString inputPortName_and_description = inputPortName + "(" + description + ")"; tempInput.insert(p.name,inputPortName_and_description); for (auto p_profile : p.profiles) { inputPortNameLabelMap.insert(p.description.data(),p_profile.data()); } inputPortProfileNameMap.insert(info.index,inputPortNameLabelMap); } #else if (p.direction == 1 && p.available != PA_PORT_AVAILABLE_NO) { //portMap.insert(p.name,p.description.data()); //新增UI设计,combobox显示portname+description QString outputPortName = p.description.data();//端口名(如:扬声器,模拟耳机..) QString outputPortName_and_description = outputPortName + "(" + description + ")"; tempOutput.insert(p.name,outputPortName_and_description); QList portProfileName; for (auto p_profile : p.profiles) { portProfileName.append(p_profile.data()); outputPortNameLabelMap.insert(p.description.data(),p_profile.data()); qDebug() << "ctf profilename map insert -----------" << p.description.data() << p_profile.data(); } profileNameMap.insert(info.index,outputPortNameLabelMap); cardProfileMap.insert(info.index,portProfileName); } else if (p.direction == 2 && p.available != PA_PORT_AVAILABLE_NO) { qDebug() << " add source port name "<< info.index << p.name << p.description.data(); QString inputPortName = p.description.data();//端口名(如:扬声器,模拟耳机..) QString inputPortName_and_description = inputPortName + "(" + description + ")"; tempInput.insert(p.name,inputPortName_and_description); for (auto p_profile : p.profiles) { inputPortNameLabelMap.insert(p.description.data(),p_profile.data()); } inputPortProfileNameMap.insert(info.index,inputPortNameLabelMap); } #endif c->ports[p.name] = p; } inputPortMap.insert(info.index,tempInput); outputPortMap.insert(info.index,tempOutput); cardActiveProfileMap.insert(info.index,info.active_profile->name); c->profiles.clear(); for (auto p_profile : profile_priorities) { bool hasNo = false, hasOther = false; std::map::iterator portIt; QByteArray desc = p_profile->description; for (portIt = c->ports.begin(); portIt != c->ports.end(); portIt++) { PortInfo port = portIt->second; if (std::find(port.profiles.begin(), port.profiles.end(), p_profile->name) == port.profiles.end()) continue; if (port.available == PA_PORT_AVAILABLE_NO) { hasNo = true; } else { hasOther = true; break; } } if (hasNo && !hasOther) desc += tr(" (unplugged)").toUtf8().constData(); if (!p_profile->available) desc += tr(" (unavailable)").toUtf8().constData(); c->profiles.push_back(std::pair(p_profile->name, desc)); if (p_profile->n_sinks == 0 && p_profile->n_sources == 0) c->noInOutProfile = p_profile->name; } c->activeProfile = info.active_profile ? info.active_profile->name : ""; if (strstr(info.name, "bluez_card")) { getBatteryLevel(info.name); } /* Because the port info for sinks and sources is discontinued we need * to update the port info for them here. */ updatePorts(c,c->ports); Q_EMIT checkDeviceSelectionSianal(&info); } /* 获取声卡名,参考aplay -L */ QString card_get(int card) { snd_ctl_t *handle; int err, dev, idx; snd_ctl_card_info_t *info; snd_pcm_info_t *pcminfo; snd_ctl_card_info_alloca(&info); snd_pcm_info_alloca(&pcminfo); char name[32]; // sprintf(name, "hw:%d", card); snprintf(name, sizeof(name), "hw:%d", card); if ((err = snd_ctl_open(&handle, name, 0)) < 0) { qInfo("control open (%i): %s", card, snd_strerror(err)); return "Error"; } if ((err = snd_ctl_card_info(handle, info)) < 0) { qInfo("control hardware info (%i): %s", card, snd_strerror(err)); snd_ctl_close(handle); return "Error"; } dev = -1; while (1) { unsigned int count; if (dev < 0) break; snd_pcm_info_set_device(pcminfo, dev); snd_pcm_info_set_subdevice(pcminfo, 0); snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_PLAYBACK); if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) { // if (err != -ENOENT) // error("control digital audio info (%i): %s", card, snd_strerror(err)); continue; } qDebug() <<"card_get"<< card << snd_ctl_card_info_get_id(info); count = snd_pcm_info_get_subdevices_count(pcminfo); printf((" Subdevices: %i/%i\n"), snd_pcm_info_get_subdevices_avail(pcminfo), count); for (idx = 0; idx < (int)count; idx++) { snd_pcm_info_set_subdevice(pcminfo, idx); if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) { qInfo("control digital audio playback info (%i): %s", card, snd_strerror(err)); } else { printf((" Subdevice #%i: %s\n"), idx, snd_pcm_info_get_subdevice_name(pcminfo)); } } } snd_ctl_close(handle); return snd_ctl_card_info_get_id(info); } void UkmediaVolumeControl::insertJson(const JsonType &type, const QString &key, const QJsonValue &value) { switch (type) { case JsonType::JSON_TYPE_SOUNDEFFECT: // m_pUsrConf->insert(m_pUsrConf->getJsonByType(std::dynamic_pointer_cast(m_pUsrConf)->getJsonMap(), type), // key, value); break; case JsonType::JSON_TYPE_USERINFO: m_pUsrConf->insert(m_pUsrConf->getJsonByType(std::dynamic_pointer_cast(m_pUsrConf)->getJsonMap(), type), key, value); break; default: break; } } /* * 根据声卡索引查找声卡名 */ QString UkmediaVolumeControl::findCardName(int index,QMap cardMap) { QMap::iterator it; for(it=cardMap.begin();it!=cardMap.end();) { if (it.key() == index) { return it.value(); } ++it; } return ""; } /* * 初始化默认输出音量 */ void UkmediaVolumeControl::initDefaultSinkVolume(const pa_sink_info &info) { QString active_port_name = info.active_port ? info.active_port->name : ""; if (active_port_name.isEmpty() || active_port_name.contains("histen") || customSoundFile->isExist(stringRemoveUnrecignizedChar(active_port_name))) return; if (strstr(info.name, "bluez")) { if (defaultSinkName.contains("bluez")) { Q_EMIT initBlueDeviceVolumeSig(info.index, active_port_name); } return; } int volume; QString outputCardName = findCardName(defaultOutputCard, cardMap); if (active_port_name.contains("headphone", Qt::CaseInsensitive)) { volume = (outputCardName.contains("bluez", Qt::CaseSensitive)) ? OUTPUT_VOLUME : HEADPHONE_VOLUME; } else if (active_port_name.contains("hdmi", Qt::CaseInsensitive)) { volume = HDMI_VOLUME; } else if (active_port_name.contains("speaker", Qt::CaseInsensitive)) { volume = OUTPUT_VOLUME; } else if (active_port_name.contains("lineout", Qt::CaseInsensitive)) { volume = HEADPHONE_VOLUME; } else { volume = OUTPUT_VOLUME; } int paVolume = UkmediaCommon::getInstance().mediaVolumeToPaVolume(volume); setSinkVolumeByInfo(info, paVolume); customSoundFile->addXmlNode(active_port_name, false); qDebug() << "initDefaultSinkVolume" << active_port_name << volume; } /* * Update output device when the default output device or port is updated */ bool UkmediaVolumeControl::updateSink(UkmediaVolumeControl *w,const pa_sink_info &info) { bool is_new = false; QString cardStr ="hw:CARD="; int volume = (info.volume.channels >= 2) ? MAX(info.volume.values[0], info.volume.values[1]) : info.volume.values[0]; if (pa_proplist_gets(info.proplist, PA_PROP_DEVICE_MASTER_DEVICE)) { masterSinkDev = pa_proplist_gets(info.proplist, PA_PROP_DEVICE_MASTER_DEVICE); } // Initialize the output volume // initDefaultSinkVolume(info); if (strcmp(defaultSinkName.data(), info.name) == 0) { channel = info.volume.channels; channelMap = info.channel_map; sinkIndex = info.index; QString portName = ""; int cardId = 0; if (info.active_port) { portName = info.active_port->name; //获取当前声卡名 if(pa_proplist_gets(info.proplist, "alsa.card") && pa_proplist_gets(info.proplist, "alsa.device")) { cardId = atoi(pa_proplist_gets(info.proplist, "alsa.card")); cardStr += card_get(atoi(pa_proplist_gets(info.proplist, "alsa.card"))); cardStr += ",DEV="; cardStr += pa_proplist_gets(info.proplist, "alsa.device"); } } else { portName = ""; } // 是否发送音乐暂停信号 // if (isNeedSendPortChangedSignal(portName, sinkPortName, info.name)) { // sendPortChangedSignal(); // } // 是否发送声音输出设备切换弹窗信号 // sendOsdWidgetSignal(portName, info.description); // 同步sinkPortName if (sinkPortName != portName) { sinkPortName = portName; // sendDeviceChangedSignal(this); } // 更新输出设备、音量、静音状态 insertJson(JsonType::JSON_TYPE_USERINFO, AUDIO_CARD_KEY, cardStr); insertJson(JsonType::JSON_TYPE_USERINFO, AUDIO_CARDID_KEY, cardId); insertJson(JsonType::JSON_TYPE_USERINFO, AUDIO_VOLUME_KEY, UkmediaCommon::getInstance().paVolumeToMediaVolume(volume)); insertJson(JsonType::JSON_TYPE_USERINFO, AUDIO_MUTE_KEY, info.mute ? true : false); // 同步默认声卡index defaultOutputCard = info.card; // 同步音量,同步UI balance = pa_cvolume_get_balance(&info.volume,&info.channel_map); refreshVolume(SoundType::SINK, volume, info.mute); } qDebug() << "UpdateSink--->" << "sinkIndex:" << sinkIndex << "sinkPortName:" << sinkPortName << "sinkVolume:" << sinkVolume << "channel:" << channel << "defaultSinkName:" << defaultSinkName.data(); if (info.ports) { QMap temp; for (pa_sink_port_info ** sinkPort = info.ports; *sinkPort != nullptr; ++sinkPort) { temp.insert(info.name,(*sinkPort)->name); } QList > sinkPortMapList; if (sinkPortMap.isEmpty()) { sinkPortMap.insert(info.card,temp); } sinkPortMapList = sinkPortMap.values(); if (!sinkPortMapList.contains(temp)) { sinkPortMap.insert(info.card,temp); } w->ports.clear(); } return is_new; } QString UkmediaVolumeControl::stringRemoveUnrecignizedChar(QString str) { str.remove(" "); str.remove("/"); str.remove("("); str.remove(")"); str.remove("["); str.remove("]"); return str; } /* * stream suspend callback */ static void suspended_callback(pa_stream *s, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (pa_stream_is_suspended(s)) { // w->updateVolumeMeter(pa_stream_get_device_index(s), PA_INVALID_INDEX, -1); } } static void read_callback(pa_stream *s, size_t length, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); const void *data; double v; if (pa_stream_peek(s, &data, &length) < 0) { w->showError(UkmediaVolumeControl::tr("Failed to read data from stream").toUtf8().constData()); 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; } pa_stream* UkmediaVolumeControl::createMonitorStreamForSource(uint32_t source_idx, uint32_t stream_idx = -1, bool suspend = false) { pa_stream *s; 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", source_idx); if (!(s = pa_stream_new(getContext(), tr("Peak detect").toUtf8().constData(), &ss, nullptr))) { showError(tr("Failed to create monitoring stream").toUtf8().constData()); return nullptr; } if (stream_idx != (uint32_t) -1) pa_stream_set_monitor_stream(s, stream_idx); pa_stream_set_read_callback(s, read_callback, this); pa_stream_set_suspended_callback(s, suspended_callback, this); flags = (pa_stream_flags_t) (PA_STREAM_DONT_MOVE | PA_STREAM_PEAK_DETECT | PA_STREAM_ADJUST_LATENCY | (suspend ? PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND : PA_STREAM_NOFLAGS) /*| (!showVolumeMetersCheckButton->isChecked() ? PA_STREAM_START_CORKED : PA_STREAM_NOFLAGS)*/); if (pa_stream_connect_record(s, t, &attr, flags) < 0) { showError(tr("Failed to connect monitoring stream").toUtf8().constData()); pa_stream_unref(s); return nullptr; } return s; } void UkmediaVolumeControl::initDefaultSourceVolume(const pa_source_info &info) { QString active_port_name = info.active_port ? info.active_port->name : ""; if (active_port_name.isEmpty() || active_port_name.contains("3a-algo") || customSoundFile->isExist(stringRemoveUnrecignizedChar(active_port_name))) return; if (active_port_name.contains("hda")) { QString preSourcePortName = active_port_name; int index = preSourcePortName.indexOf("hda-"); preSourcePortName.remove(index, 4); if (customSoundFile->isExist(stringRemoveUnrecignizedChar(preSourcePortName))) { qDebug() << "因PA配置导致端口变化,不做初始化音量处理"; return; } } else if(active_port_name.contains("alc269vc")) { QString preSourcePortName = active_port_name; int index = preSourcePortName.indexOf("alc269vc-"); preSourcePortName.remove(index, 9); if (customSoundFile->isExist(stringRemoveUnrecignizedChar(preSourcePortName))) { qDebug() << "因PA配置导致端口变化,不做初始化音量处理"; return; } } int paVolume = UkmediaCommon::getInstance().mediaVolumeToPaVolume(MIC_VOLUME); setSourceVolumeByInfo(info, paVolume); customSoundFile->addXmlNode(active_port_name, false); qDebug() << __func__ << active_port_name; } void UkmediaVolumeControl::updateSource(const pa_source_info &info) { int volume = (info.volume.channels >= 2) ? MAX(info.volume.values[0], info.volume.values[1]) : info.volume.values[0]; // Record the masterDevice of noiseReduceSource if (pa_proplist_gets(info.proplist, PA_PROP_DEVICE_MASTER_DEVICE)) { masterDevice = pa_proplist_gets(info.proplist, PA_PROP_DEVICE_MASTER_DEVICE); } // Initialize the input volume // initDefaultSourceVolume(info); // Update some variables for the default input if (UKMedia_Equal(defaultSourceName.data(), info.name)) { sourceIndex = info.index; inputChannel = info.volume.channels; QString portName = ""; int inputCard = -1; // Status: this is a filter soource if (pa_proplist_gets(info.proplist, PA_PROP_DEVICE_MASTER_DEVICE)) { masterDevice = pa_proplist_gets(info.proplist, PA_PROP_DEVICE_MASTER_DEVICE); sourceInfo s = getSourceInfoByName(masterDevice); inputCard = s.card; portName = s.active_port_name; qInfo() << "This is a filter source:" << info.name << "master device:" << masterDevice; } // Status: generally else { inputCard = info.card; portName = (info.active_port) ? info.active_port->name : ""; } // 判断是否发送端口改变信号 if (sourcePortName != portName || defaultInputCard != inputCard) { // sendSourcePortChangedSignal(); qInfo() << "Send SIGNAL: sourcePortChanged." << sourcePortName << portName << defaultInputCard << inputCard; } // 如果当前端口为无 or 内置Mic,需要关闭侦听GSettings // if (portName.isEmpty() || portName.contains("internal") || portName.contains("Mic1")) { // if (QGSettings::isSchemaInstalled(KEY_SOUNDS_SCHEMA)) { // QGSettings soundSettings(KEY_SOUNDS_SCHEMA); // if(soundSettings.get(LOOP_BACK).toBool()) { // soundSettings.set(LOOP_BACK, "false"); // qInfo() << "Disable GSettings of the Loopback module"; // } // } // } // 当信号发送完,更新defaultInputCard和sourcePortName sourcePortName = portName; defaultInputCard = inputCard; refreshVolume(SoundType::SOURCE, volume, info.mute); qInfo() << __func__ << "Status1 defaultSource:" << sourceIndex << defaultSourceName << "sourcePort" << sourcePortName; } /* 开启降噪状态下,如果这个source存在多个端口,且不同端口音量相同时,不会触发 noiseReduceSource 回调,导致无法更新 portName */ else if (UKMedia_Equal(masterDevice.toLatin1().data(), info.name) && UKMedia_Equal(defaultSourceName, "noiseReduceSource")) { if (!UKMedia_Equal(info.active_port->name, sourcePortName.toLatin1().data()) || info.card != defaultInputCard) { // sendSourcePortChangedSignal(); qInfo() << "Send SIGNAL: sourcePortChanged. Status 2"; } defaultInputCard = info.card; sourcePortName = (info.active_port) ? info.active_port->name : ""; qInfo() << __func__ << "Status2 defaultSource:" << sourceIndex << defaultSourceName << "sourcePort" << sourcePortName; } if (info.ports) { QMap temp; for (pa_source_port_info **sourcePort = info.ports; *sourcePort != nullptr; ++sourcePort) { temp.insert(info.name, (*sourcePort)->name); } if (sourcePortMap.isEmpty()) { sourcePortMap.insert(info.card, temp); } if (!sourcePortMap.values().contains(temp)) { sourcePortMap.insert(info.card, temp); } } } void UkmediaVolumeControl::updateSinkInput(const pa_sink_input_info &info) { const char *t; if ((t = pa_proplist_gets(info.proplist, "module-stream-restore.id"))) { if (t && strcmp(t, "sink-input-by-media-role:event") == 0) { g_debug("%s", tr("Ignoring sink-input due to it being designated as an event and thus handled by the Event widget").toUtf8().constData()); return; } } const gchar *description = pa_proplist_gets(info.proplist, PA_PROP_APPLICATION_NAME); QString appId = pa_proplist_gets(info.proplist, PA_PROP_APPLICATION_ID); QString appIconName = pa_proplist_gets(info.proplist,PA_PROP_APPLICATION_ICON_NAME);\ const gchar *appBinary = pa_proplist_gets(info.proplist,PA_PROP_APPLICATION_PROCESS_BINARY); qDebug() << "description:" << description << "appId:" << appId << "appIconName:" << appIconName << "appBinary:" << appBinary; appBinary = AppDesktopFileAdaption(appBinary); if (appBinary) { appId = appBinary; appIconName = appBinary; description = appBinary; } else if (appBinary && UKMedia_Equal(appBinary, "kylin-recorder") && UKMedia_Equal(pa_proplist_gets(info.proplist, PA_PROP_MEDIA_NAME), "pulsesink probe")) { return; } else { return; } qDebug() << "updateSinkInput:" << "appIconName:"<< appIconName << "appId:" << appId << "desc:" << description << "appBinary:" << appBinary << info.volume.values[0]; //没制定应用名称的不加入到应用音量中 if (description && !strstr(description,"QtPulseAudio") && !strstr(description,"ALSA") && !strstr(description,"aplay")) { sinkInputIndexMap.insert(info.index,description); //记录每个sinkinput的index sinkInputValueMap.insert(description,info.volume.values[0]); //记录相同description的最新音量 sinkInputMuteMap.insert(description,info.mute); if (!sinkInputList.contains(description)) { sinkInputList.append(description); //记录应用的个数 来增删应用widget Q_EMIT addSinkInputSignal(description, appId, info.index, info.volume.values[0], info.volume.channels); } Q_EMIT sinkInputVolumeChangedSignal(description, appId, info.volume.values[0]); //应用音量发生改变时发送信号更新滑动条的值 // ukcc应用音量需求 sinkInputMap.insert(info.index, addSinkInputInfo(info)); Q_EMIT updateApp(appBinary); } } void UkmediaVolumeControl::updateSourceOutput(const pa_source_output_info &info) { const char *app; bool is_new = false; if ((app = pa_proplist_gets(info.proplist, PA_PROP_APPLICATION_ID))) { if (strcmp(app, "org.PulseAudio.pavucontrol") == 0 || strcmp(app, "org.gnome.VolumeControl") == 0 || strcmp(app, "org.kde.kmixd") == 0) { return; } } const gchar *description = pa_proplist_gets(info.proplist, PA_PROP_APPLICATION_NAME); QString appId = pa_proplist_gets(info.proplist, PA_PROP_APPLICATION_ID); QString appIconName = pa_proplist_gets(info.proplist, PA_PROP_APPLICATION_ICON_NAME); const gchar *appBinary = pa_proplist_gets(info.proplist,PA_PROP_APPLICATION_PROCESS_BINARY); qDebug() << "description:" << description << "appId:" << appId << "appIconName:" << appIconName << "appBinary:" << appBinary; appBinary = AppDesktopFileAdaption(appBinary); if (appBinary) { appId = appBinary; description = appBinary; appIconName = appBinary; } else { qDebug() << "updateSourceOutput appBinary is null"; return; } qDebug() << "updateSourceOutput:" << "appIconName:"<< appIconName << "appId:" << appId << "desc:" << description << "appBinary:" << appBinary << info.volume.values[0]; if(description && !strstr(description,"QtPulseAudio") && !strstr(description,"ALSA") && !strstr(description,"Volume Control") && !strstr(description,"aplay")) { sourceOutputIndexMap.insert(info.index,description); //记录所有source-output index sourceOutputValueMap.insert(description,info.volume.values[0]); //记录相同description的最新音量 sinkInputMuteMap.insert(description,info.mute); if (!sinkInputList.contains(description)) { sinkInputList.append(description); Q_EMIT addSinkInputSignal(description, appId, info.index, info.volume.values[0], info.volume.channels); } if (!sinkInputValueMap.keys().contains(description)) Q_EMIT sinkInputVolumeChangedSignal(description, appId, info.volume.values[0]); // ukcc应用音量需求 sourceOutputMap.insert(info.index, addSourceOutputInfo(info)); Q_EMIT updateApp(description); } } void UkmediaVolumeControl::updateClient(const pa_client_info &info) { g_free(clientNames[info.index]); clientNames[info.index] = g_strdup(info.name); } void UkmediaVolumeControl::updateServer(const pa_server_info &info) { defaultSourceName = info.default_source_name ? info.default_source_name : ""; defaultSinkName = info.default_sink_name ? info.default_sink_name : ""; qDebug() << "updateServer" << "defaultSinkName " << defaultSinkName << "defaultSourceName" << defaultSourceName; } #if HAVE_EXT_DEVICE_RESTORE_API void UkmediaVolumeControl::updateDeviceInfo(const pa_ext_device_restore_info &info) { // if (sinkWidgets.count(info.index)) { // SinkWidget *w; // pa_format_info *format; // w = sinkWidgets[info.index]; // w->updating = true; // /* Unselect everything */ // for (int j = 1; j < PAVU_NUM_ENCODINGS; ++j) // w->encodings[j].widget->setChecked(false); // for (uint8_t i = 0; i < info.n_formats; ++i) { // format = info.formats[i]; // for (int j = 1; j < PAVU_NUM_ENCODINGS; ++j) { // if (format->encoding == w->encodings[j].encoding) { // w->encodings[j].widget->setChecked(true); // break; // } // } // } // w->updating = false; // } } #endif void UkmediaVolumeControl::setConnectionState(gboolean connected) { if (m_connected != connected) { m_connected = connected; if (m_connected) { // connectingLabel->hide(); // notebook->show(); } else { // notebook->hide(); // connectingLabel->show(); } } } void UkmediaVolumeControl::removeCard(uint32_t index) { } void UkmediaVolumeControl::removeSink(uint32_t index) { QMap::iterator it; Q_EMIT removeSinkSignal(); for (it = sinkMap.begin(); it != sinkMap.end();) { if (it.key() == index) { qDebug() << "removeSink" << index; sinkMap.erase(it); break; } ++it; } } void UkmediaVolumeControl::removeSource(uint32_t index) { QMap::iterator it; for (it=sourceMap.begin();it!=sourceMap.end();) { if (it.key() == index) { qDebug() << "removeSource" << index; sourceMap.erase(it); break; } ++it; } } void UkmediaVolumeControl::removeSinkInput(uint32_t index) { QString description = ""; qDebug() << "removeSinkInput" << index; sinkInputMap.remove(index); description = sinkInputIndexMap.value(index); //先获取要移除sinkinput的description sinkInputIndexMap.remove(index);//移除对应index的sinkinput if(!sinkInputIndexMap.values().contains(description) || sinkInputIndexMap.isEmpty()) { sinkInputValueMap.remove(description);//如果所有相同description的index都被移除,需要移除valueMap对应的description sinkInputMuteMap.remove(description); if(description != "") Q_EMIT removeSinkInputSignal(description);//并移除对应app widget } } void UkmediaVolumeControl::removeSourceOutput(uint32_t index) { QString description = ""; sourceOutputMap.remove(index); description = sourceOutputIndexMap.value(index); sourceOutputIndexMap.remove(index); qDebug() << __func__ << index << description; if(description == "Loopback") isLoadLoopback = false; if(!sourceOutputIndexMap.values().contains(description) || sourceOutputIndexMap.isEmpty()) { sourceOutputValueMap.remove(description); sinkInputMuteMap.remove(description); //sinkinput包含同名应用时,不发送remove信号,只更新应用类型为仅输出可用 if(description != "" && !sinkInputIndexMap.values().contains(description)){ Q_EMIT removeSinkInputSignal(description); } else if (description != "") Q_EMIT updateApp(description); } } void UkmediaVolumeControl::removeClient(uint32_t index) { g_free(clientNames[index]); clientNames.erase(index); } void UkmediaVolumeControl::setConnectingMessage(const char *string) { QByteArray markup = ""; if (!string) markup += tr("Establishing connection to PulseAudio. Please wait...").toUtf8().constData(); else markup += string; markup += ""; } void UkmediaVolumeControl::showError(const char *txt) { char buf[256]; snprintf(buf, sizeof(buf), "%s: %s", txt, pa_strerror(pa_context_errno(context))); qWarning() << QString::fromUtf8(buf); } void UkmediaVolumeControl::decOutstanding(UkmediaVolumeControl *w) { if (w->n_outstanding <= 0) return; if (--w->n_outstanding <= 0) { w->setConnectionState(true); } } void UkmediaVolumeControl::cardCb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (eol < 0) { if (pa_context_errno(c) == PA_ERR_NOENTITY) return; w->showError(QObject::tr("Card callback failure").toUtf8().constData()); return; } if (eol > 0) { decOutstanding(w); return; } w->cardMap.insert(i->index,i->name); w->updateCard(w,*i); } void UkmediaVolumeControl::batteryLevelCb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (eol < 0) { if (pa_context_errno(c) == PA_ERR_NOENTITY) return; w->showError(QObject::tr("Card callback failure").toUtf8().constData()); return; } if (eol > 0) { decOutstanding(w); return; } qDebug() << "batteryLevelCb" << i->index << eol; const char *battery = pa_proplist_gets(i->proplist,"bluetooth.battery"); QString macAddr = pa_proplist_gets(i->proplist, "device.string"); if (battery) { qDebug() << "get bluetooth battery is:" << battery << atoi(battery); w->batteryLevel = atoi(battery); } else { w->batteryLevel = -1; } Q_EMIT w->bluetoothBatteryChanged(macAddr, w->batteryLevel); } void UkmediaVolumeControl::bluetoothCardCb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (eol < 0) { if (pa_context_errno(c) == PA_ERR_NOENTITY) return; w->showError(QObject::tr("Card callback failure").toUtf8().constData()); return; } if (eol > 0) { decOutstanding(w); return; } qDebug() << "bluetooth card cb" << i->index << eol; const char *battery = pa_proplist_gets(i->proplist,"bluetooth.battery"); QString macAddr = pa_proplist_gets(i->proplist, "device.string"); if (battery) { qDebug() << "bluetooth battery is ===" << battery << atoi(battery); // char *str = strtok(battery,"."); w->batteryLevel = atoi(battery); } else { w->batteryLevel = -1; } Q_EMIT w->bluetoothBatteryChanged(macAddr, w->batteryLevel); } #if HAVE_EXT_DEVICE_RESTORE_API static void ext_device_restore_subscribeCb(pa_context *c, pa_device_type_t type, uint32_t idx, void *userdata); #endif void UkmediaVolumeControl::sinkIndexCb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (eol < 0) { if (pa_context_errno(c) == PA_ERR_NOENTITY) return; w->showError(QObject::tr("Sink callback failure").toUtf8().constData()); return; } if (eol > 0) { decOutstanding(w); return; } int volume; QString portName = ""; if (i->volume.channels >= 2) { volume = MAX(i->volume.values[0],i->volume.values[1]); } else { volume = i->volume.values[0]; } w->channel = i->volume.channels; w->sinkIndex = i->index; w->balance = pa_cvolume_get_balance(&i->volume,&i->channel_map); /* 同步sinkPortName三种情况 * case1: 默认声卡端口可用,直接同步当前可用端口 * case2:默认声卡端口不可用,端口设置为空 * case3: 默认声卡为虚拟声卡,查找master.device声卡信息并同步 */ if (i->active_port) { portName = i->active_port->name; } else { portName = ""; } if (pa_proplist_gets(i->proplist, PA_PROP_DEVICE_MASTER_DEVICE)) { portName = w->findSinkMasterDeviceInfo(i->proplist, i->name); } // 是否发送音乐暂停信号 if (w->isNeedSendPortChangedSignal(portName, w->sinkPortName, i->name)) { // w->sendPortChangedSignal(); } // 是否发送声音输出设备切换弹窗信号 // w->sendOsdWidgetSignal(portName, i->description); // 同步sinkPortName w->sinkPortName = (w->sinkPortName != portName) ? portName : w->sinkPortName; // 同步默认声卡index w->defaultOutputCard = i->card; // 同步音量,同步UI w->refreshVolume(SoundType::SINK, volume, i->mute); qDebug() << "sinkIndexCb" << w->defaultOutputCard << w->sinkPortName << w->sinkIndex; } void UkmediaVolumeControl::sourceIndexCb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (eol < 0) { if (pa_context_errno(c) == PA_ERR_NOENTITY) return; w->showError(QObject::tr("Source callback failure").toUtf8().constData()); return; } if (eol > 0) { decOutstanding(w); return; } int volume; if (i->volume.channels >= 2) { volume = MAX(i->volume.values[0],i->volume.values[1]); } else { volume = i->volume.values[0]; } w->inputChannel = i->volume.channels; w->sourceIndex = i->index; QString portName = ""; int inputCard = -1; if (pa_proplist_gets(i->proplist, PA_PROP_DEVICE_MASTER_DEVICE)) { w->masterDevice = pa_proplist_gets(i->proplist, PA_PROP_DEVICE_MASTER_DEVICE); sourceInfo s = w->getSourceInfoByName(w->masterDevice); inputCard = s.card; portName = s.active_port_name; } else { inputCard = i->card; portName = (i->active_port) ? i->active_port->name : ""; } // 判断是否发送端口改变信号 if (w->sourcePortName != portName || w->defaultInputCard != inputCard) { // w->sendSourcePortChangedSignal(); qInfo() << "Send SIGNAL: sourcePortChanged." << w->sourcePortName << portName << w->defaultInputCard << inputCard; } // 当信号发送完,更新defaultInputCard和sourcePortName w->sourcePortName = portName; w->defaultInputCard = inputCard; w->refreshVolume(SoundType::SOURCE, volume, i->mute); qDebug() << __func__ << "defaultInputCard" << w->defaultInputCard << "sourcePort" << w->sourcePortName; } void UkmediaVolumeControl::sinkCb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (eol < 0) { if (pa_context_errno(c) == PA_ERR_NOENTITY) { return; } w->showError(QObject::tr("Sink callback failure").toUtf8().constData()); return; } if (eol > 0) { decOutstanding(w); return; } w->m_pDefaultSink = i; w->sinkMap.insert(i->index, w->addSinkInfo(*i)); qDebug() << __func__ << "Volume:" << i->volume.values[0] << "isMute:" << i->mute << i->name; w->updateSink(w,*i); } void UkmediaVolumeControl::sourceCb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (eol < 0) { if (pa_context_errno(c) == PA_ERR_NOENTITY) { return; } w->showError(QObject::tr("Source callback failure").toUtf8().constData()); return; } if (eol > 0) { decOutstanding(w); return; } w->m_pDefaultSource = i; qInfo() << "sourceCb" << i->index << i->name; w->sourceMap.insert(i->index, w->addSourceInfo(*i)); w->updateSource(*i); } void UkmediaVolumeControl::sinkInputCb(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (eol < 0) { if (pa_context_errno(c) == PA_ERR_NOENTITY) return; w->showError(QObject::tr("Sink input callback failure").toUtf8().constData()); return; } if (eol > 0) { decOutstanding(w); return; } w->updateSinkInput(*i); } void UkmediaVolumeControl::sourceOutputCb(pa_context *c, const pa_source_output_info *i, int eol, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (eol < 0) { if (pa_context_errno(c) == PA_ERR_NOENTITY) return; w->showError(QObject::tr("Source output callback failure").toUtf8().constData()); return; } if (eol > 0) { decOutstanding(w); return; } if (i->source == PA_ID_INVALID) return; if (strstr(i->name,"Loopback")) { w->sourceOutputIndexMap.insert(i->index,"Loopback"); qDebug() << "The module-loopback has been loaded." << w->sourceOutputIndexMap; } qDebug() << "sourceOutputCb" << i->name; w->updateSourceOutput(*i); } void UkmediaVolumeControl::clientCb(pa_context *c, const pa_client_info *i, int eol, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (eol < 0) { if (pa_context_errno(c) == PA_ERR_NOENTITY) return; w->showError(QObject::tr("Client callback failure").toUtf8().constData()); return; } if (eol > 0) { decOutstanding(w); return; } // qDebug() << "clientCb" << i->name; w->updateClient(*i); } void UkmediaVolumeControl::serverInfoCb(pa_context *, const pa_server_info *i, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (!i) { w->showError(QObject::tr("Server info callback failure").toUtf8().constData()); return; } pa_operation *o; //默认的输出设备改变时需要获取默认的输出音量 if (strcmp(w->defaultSinkName.data(),i->default_sink_name) != 0) { //默认输出改变时才走sinkindexcb if(!(o = pa_context_get_sink_info_by_name(w->getContext(),i->default_sink_name,sinkIndexCb,w))) { w->showError(tr("pa_context_get_sink_info_by_name() failed").toUtf8().constData()); } } if(!(o = pa_context_get_source_info_by_name(w->getContext(),i->default_source_name,sourceIndexCb,w))){ w->showError(tr("pa_context_get_source_info_by_name() failed").toUtf8().constData()); } qDebug() << "serverInfoCb" << i->default_sink_name << i->default_source_name; w->updateServer(*i); sendDeviceChangedSignal(w); decOutstanding(w); } void UkmediaVolumeControl::serverInfoIndexCb(pa_context *, const pa_server_info *i, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (!i) { w->showError(QObject::tr("Server info callback failure").toUtf8().constData()); return; } pa_operation *o; // qDebug() << "serverInfoIndexCb" << i->default_sink_name << i->default_source_name; w->updateServer(*i); decOutstanding(w); } void UkmediaVolumeControl::getBatteryLevel(QString dev) { pa_operation *o; if (!(o = pa_context_get_card_info_by_name(getContext(), dev.toLatin1().data(), batteryLevelCb, this))) { showError(QObject::tr("pa_context_get_card_info_by_index() failed").toUtf8().constData()); return ; } pa_operation_unref(o); } void UkmediaVolumeControl::extStreamRestoreReadCb( pa_context *c, const pa_ext_stream_restore_info *i, int eol, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (eol < 0) { decOutstanding(w); g_debug(QObject::tr("Failed to initialize stream_restore extension: %s").toUtf8().constData(), pa_strerror(pa_context_errno(c))); return; } if (eol > 0) { decOutstanding(w); return; } } void UkmediaVolumeControl::extStreamRestoreSubscribeCb(pa_context *c, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); pa_operation *o; if (!(o = pa_ext_stream_restore_read(c, extStreamRestoreReadCb, w))) { w->showError(QObject::tr("pa_ext_stream_restore_read() failed").toUtf8().constData()); return; } qDebug() << "extStreamRestoreSubscribeCb" ; pa_operation_unref(o); } #if HAVE_EXT_DEVICE_RESTORE_API void ext_device_restore_read_cb( pa_context *, const pa_ext_device_restore_info *i, int eol, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (eol < 0) { w->decOutstanding(w); g_debug(QObject::tr("Failed to initialize device restore extension: %s").toUtf8().constData(), pa_strerror(pa_context_errno(context))); return; } if (eol > 0) { w->decOutstanding(w); return; } /* Do something with a widget when this part is written */ w->updateDeviceInfo(*i); } static void ext_device_restore_subscribeCb(pa_context *c, pa_device_type_t type, uint32_t idx, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); pa_operation *o; if (type != PA_DEVICE_TYPE_SINK) return; if (!(o = pa_ext_device_restore_read_formats(c, type, idx, ext_device_restore_read_cb, w))) { w->showError(QObject::tr("pa_ext_device_restore_read_sink_formats() failed").toUtf8().constData()); return; } pa_operation_unref(o); } #endif void UkmediaVolumeControl::extDeviceManagerReadCb( pa_context *c, const pa_ext_device_manager_info *, int eol, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (eol < 0) { decOutstanding(w); g_debug(QObject::tr("Failed to initialize device manager extension: %s").toUtf8().constData(), pa_strerror(pa_context_errno(c))); return; } w->canRenameDevices = true; if (eol > 0) { decOutstanding(w); return; } qDebug() << "extDeviceManagerReadCb"; /* Do something with a widget when this part is written */ } void UkmediaVolumeControl::extDeviceManagerSubscribeCb(pa_context *c, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); pa_operation *o; if (!(o = pa_ext_device_manager_read(c, extDeviceManagerReadCb, w))) { w->showError(QObject::tr("pa_ext_device_manager_read() failed").toUtf8().constData()); return; } qDebug() << "extDeviceManagerSubscribeCb"; pa_operation_unref(o); } void UkmediaVolumeControl::subscribeCb(pa_context *c, pa_subscription_event_type_t t, uint32_t index, void *userdata) { UkmediaVolumeControl *w = 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) w->removeSink(index); else { pa_operation *o; if (!(o = pa_context_get_sink_info_by_index(c, index, sinkCb, w))) { w->showError(QObject::tr("pa_context_get_sink_info_by_index() failed").toUtf8().constData()); return; } pa_operation_unref(o); } break; case PA_SUBSCRIPTION_EVENT_SOURCE: if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) w->removeSource(index); else { pa_operation *o; if (!(o = pa_context_get_source_info_by_index(c, index, sourceCb, w))) { w->showError(QObject::tr("pa_context_get_source_info_by_index() failed").toUtf8().constData()); return; } pa_operation_unref(o); } break; case PA_SUBSCRIPTION_EVENT_SINK_INPUT: if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) w->removeSinkInput(index); else { pa_operation *o; if (!(o = pa_context_get_sink_input_info(c, index, sinkInputCb, w))) { w->showError(QObject::tr("pa_context_get_sink_input_info() failed").toUtf8().constData()); return; } pa_operation_unref(o); } break; case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT: if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) w->removeSourceOutput(index); else { pa_operation *o; if (!(o = pa_context_get_source_output_info(c, index, sourceOutputCb, w))) { w->showError(QObject::tr("pa_context_get_sink_input_info() failed").toUtf8().constData()); 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(c, index, clientCb, w))) { w->showError(QObject::tr("pa_context_get_client_info() failed").toUtf8().constData()); return; } pa_operation_unref(o); } break; case PA_SUBSCRIPTION_EVENT_SERVER: { pa_operation *o; if (!(o = pa_context_get_server_info(c, serverInfoCb, w))) { w->showError(QObject::tr("pa_context_get_server_info() failed").toUtf8().constData()); return; } pa_operation_unref(o); } break; #ifdef PA_SUBSCRIPTION_EVENT_BLUETOOTH_BATTERY case PA_SUBSCRIPTION_EVENT_BLUETOOTH_BATTERY: qDebug() << "PA_SUBSCRIPTION_EVENT_BLUETOOTH_BATTERY" ; pa_operation *o; if (!(o = pa_context_get_card_info_by_index(c, index, bluetoothCardCb, w))) { w->showError(QObject::tr("pa_context_get_card_info_by_index() failed").toUtf8().constData()); return; } pa_operation_unref(o); break; #endif case PA_SUBSCRIPTION_EVENT_CARD: if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { qDebug() << "remove cards------"; //移除outputPort w->removeSinkPortMap(index); w->removeOutputPortMap(index); w->removeSourcePortMap(index); w->removeInputPortMap(index); Q_EMIT w->updatePortSignal(); w->removeCardMap(index); w->removeCardProfileMap(index); w->removeProfileMap(index); w->removeInputProfile(index); w->removeCard(index); } else { pa_operation *o; if (!(o = pa_context_get_card_info_by_index(c, index, cardCb, w))) { w->showError(QObject::tr("pa_context_get_card_info_by_index() failed").toUtf8().constData()); return; } pa_operation_unref(o); } break; } } void UkmediaVolumeControl::contextStateCallback(pa_context *c, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); g_assert(c); switch (pa_context_get_state(c)) { 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; qDebug() << "pa_context_get_state" << "PA_CONTEXT_READY" << pa_context_get_state(c); reconnectTimeout = 3; pa_context_set_subscribe_callback(c, subscribeCb, w); if (!(o = pa_context_subscribe(c, (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))) { w->showError(QObject::tr("pa_context_subscribe() failed").toUtf8().constData()); return; } pa_operation_unref(o); /* Keep track of the outstanding callbacks for UI tweaks */ w->n_outstanding = 0; if (!(o = pa_context_get_server_info(c, serverInfoCb, w))) { w->showError(QObject::tr("pa_context_get_server_info() failed").toUtf8().constData()); return; } pa_operation_unref(o); w->n_outstanding++; if (!(o = pa_context_get_client_info_list(c, clientCb, w))) { w->showError(QObject::tr("pa_context_client_info_list() failed").toUtf8().constData()); return; } pa_operation_unref(o); w->n_outstanding++; if (!(o = pa_context_get_card_info_list(c, cardCb, w))) { w->showError(QObject::tr("pa_context_get_card_info_list() failed").toUtf8().constData()); return; } pa_operation_unref(o); w->n_outstanding++; if (!(o = pa_context_get_sink_info_list(c, sinkCb, w))) { w->showError(QObject::tr("pa_context_get_sink_info_list() failed").toUtf8().constData()); return; } pa_operation_unref(o); w->n_outstanding++; if (!(o = pa_context_get_source_info_list(c, sourceCb, w))) { w->showError(QObject::tr("pa_context_get_source_info_list() failed").toUtf8().constData()); return; } pa_operation_unref(o); w->n_outstanding++; if (!(o = pa_context_get_sink_input_info_list(c, sinkInputCb, w))) { w->showError(QObject::tr("pa_context_get_sink_input_info_list() failed").toUtf8().constData()); return; } pa_operation_unref(o); w->n_outstanding++; if (!(o = pa_context_get_source_output_info_list(c, sourceOutputCb, w))) { w->showError(QObject::tr("pa_context_get_source_output_info_list() failed").toUtf8().constData()); return; } pa_operation_unref(o); w->n_outstanding++; Q_EMIT w->paContextReady(); break; } case PA_CONTEXT_FAILED: w->setConnectionState(false); if (w->context != nullptr) { pa_context_unref(w->context); w->context = nullptr; } qWarning("Connection failed, attempting reconnect"); reconnectTimeout--; if (reconnectTimeout <= 0) { qWarning("reconnect pulseaudio Three times failed"); return; } g_timeout_add_seconds(5, connectToPulse, w); return; case PA_CONTEXT_TERMINATED: default: return; } } void UkmediaVolumeControl::moduleInfoCb(pa_context *c, const pa_module_info *i, int eol, void *userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (eol < 0) { if (pa_context_errno(c) == PA_ERR_NOENTITY) return; w->showError(QObject::tr("moduleInfoCb callback failure").toUtf8().constData()); return; } if(i && strcmp(i->name,w->findModuleStr.toLatin1().data()) == 0) { w->findModuleIndex = i->index; } } pa_context* UkmediaVolumeControl::getContext(void) { g_assert(context); return context; } gboolean UkmediaVolumeControl::connectToPulse(gpointer userdata) { UkmediaVolumeControl *w = static_cast(userdata); if (context) { qWarning("pulseAudio is connected"); return false; } pa_glib_mainloop *m = pa_glib_mainloop_new(g_main_context_default()); api = pa_glib_mainloop_get_api(m); pa_proplist *proplist = pa_proplist_new(); pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, QObject::tr("Ukui Media Volume Control").toUtf8().constData()); pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, "org.PulseAudio.pavucontrol"); pa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, "audio-card"); pa_proplist_sets(proplist, PA_PROP_APPLICATION_VERSION, "PACKAGE_VERSION"); context = pa_context_new_with_proplist(api, nullptr, proplist); g_assert(context); pa_proplist_free(proplist); pa_context_set_state_callback(context, contextStateCallback, w); if (pa_context_connect(context, nullptr, PA_CONTEXT_NOFAIL, nullptr) < 0) { if (pa_context_errno(context) == 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());*/ qFatal("connect pulseaudio failed"); } else { reconnectTimeout--; if (reconnectTimeout <= 0) { qWarning("reconnect pulseaudio Three times failed"); return false; } g_timeout_add_seconds(5, connectToPulse, w); } } return false; } /* * 根据名称获取sink input音量 */ int UkmediaVolumeControl::getSinkInputVolume(const gchar *name) { return sinkInputValueMap.value(name); } /* * 根据名称获取source output音量 */ int UkmediaVolumeControl::getSourceOutputVolume(const gchar *name) { return sourceOutputValueMap.value(name); } /* * 移除指定索引的output port */ void UkmediaVolumeControl::removeOutputPortMap(int index) { QMap>::iterator it; for (it=outputPortMap.begin();it!=outputPortMap.end();) { if (it.key() == index) { qDebug() << "removeoutputport" <>::iterator it; for (it=inputPortMap.begin();it!=inputPortMap.end();) { if (it.key() == index) { inputPortMap.erase(it); break; } ++it; } } /* * 移除指定索引的card */ void UkmediaVolumeControl::removeCardMap(int index) { QMap::iterator it; for (it=cardMap.begin();it!=cardMap.end();) { if (it.key() == index) { cardMap.erase(it); break; } ++it; } } void UkmediaVolumeControl::removeCardProfileMap(int index) { qDebug() << "removeCardProfileMap"; QMap>::iterator it; QMap>::iterator at; for (it=cardProfileMap.begin();it!=cardProfileMap.end();) { if (it.key() == index) { cardProfileMap.erase(it); break; } ++it; } for (at=cardProfilePriorityMap.begin();at!=cardProfilePriorityMap.cend();) { if (at.key() == index) { cardProfilePriorityMap.erase(at); if(cardProfilePriorityMap.keys().contains(index)) cardProfilePriorityMap.remove(index); break; } ++at; } } void UkmediaVolumeControl::removeSinkPortMap(int index) { qDebug() << "removeSinkPortMap///"; QMap>::iterator it; for(it=sinkPortMap.begin();it!=sinkPortMap.end();){ if(it.key() == index) { //usb耳机一个声卡存在两个端口,使用erase时只会擦除一个key时,导致拔出耳机时没有完全移除key值,需要使用remove将存入的两个相同的key值全部删除 //sinkPortMap.erase(it); sinkPortMap.remove(index); break; } ++it; } } void UkmediaVolumeControl::removeSourcePortMap(int index) { qDebug() << "removeSourcePortMap///"; QMap>::iterator it; for(it=sourcePortMap.begin();it!=sourcePortMap.end();){ if(it.key() == index) { sourcePortMap.remove(index); break; } ++it; } } void UkmediaVolumeControl::removeProfileMap(int index) { QMap>::iterator it; qDebug() << "removeProfileMap" << index; for (it=profileNameMap.begin();it!=profileNameMap.end();) { if(it.key() == index) { profileNameMap.erase(it); break; } ++it; } } int UkmediaVolumeControl::findOutputPort(QString name){ QMap>::iterator it; QMap::iterator at; QMap portMap; int count = 0; for (it=outputPortMap.begin();it!=outputPortMap.end();) { portMap = it.value(); for (at=portMap.begin();at!=portMap.end();) { if (at.key() == name) { ++count; } ++at; } ++it; } return count; } void UkmediaVolumeControl::removeInputProfile(int index) { QMap>::iterator it; qDebug() << "removeInputProfile" << index; for (it=inputPortProfileNameMap.begin();it!=inputPortProfileNameMap.end();) { if(it.key() == index){ inputPortProfileNameMap.erase(it); break; } ++it; } } bool UkmediaVolumeControl::isNeedSendPortChangedSignal(QString newPort, QString prePort, QString cardName) { // 端口不变时不发送信号 if (newPort == prePort) return false; // 切换到空端口发送信号 if (newPort.isEmpty() && !prePort.isEmpty() && cardName != "mono") { qDebug() << "Send SIGNAL: sinkPortChanged. Case: Switch to a null port" << newPort << prePort; return true; } // 拔插设备自动切换到扬声器端口时发送信号,用户主动切换输出时不做信号发送 if (newPort.contains(SPEAKER) && !cardName.contains("bluez") && !cardName.contains("usb")) { int newPortCount = -1; int prePortCount = -1; newPortCount = findOutputPort(newPort); prePortCount = findOutputPort(prePort); // 保证outputPortMap中只含有一个扬声器端口,并且之前的端口已不在map中 if (newPortCount == 1 && prePortCount == 0) { qDebug() << "Send SIGNAL: sinkPortChanged. Case: Switch to speaker port" << newPort << prePort; return true; } } return false; } void UkmediaVolumeControl::sendPortChangedSignal() { QDBusMessage message = QDBusMessage::createSignal("/","org.ukui.media","sinkPortChanged"); message<<"portChanged"; QDBusConnection::sessionBus().send(message); qDebug() << "sendPortChangedSignal sinkPortChanged "; } void UkmediaVolumeControl::sendSourcePortChangedSignal() { QDBusMessage message = QDBusMessage::createSignal("/","org.ukui.media","sourcePortChanged"); message<<"portChanged"; QDBusConnection::sessionBus().send(message); qDebug() << "sendSourcePortChangedSignal sourcePortChanged "; } int UkmediaVolumeControl::findPortSinkIndex(QString name) { QMap>::iterator it; QMap portNameMap; QMap::iterator tempMap; int cardIndex = -1; for (it=sinkPortMap.begin();it!=sinkPortMap.end();) { portNameMap = it.value(); for (tempMap=portNameMap.begin();tempMap!=portNameMap.end();) { if (tempMap.key() == name) { cardIndex = it.key(); break; } ++tempMap; } ++it; } return cardIndex; } QString UkmediaVolumeControl::findSinkPortName(int cardIndex) { QMap>::iterator it; QMap portNameMap; QMap::iterator tempMap; QString portName = ""; for (it=outputPortMap.begin();it!=outputPortMap.end();) { if(it.key() == cardIndex){ portNameMap = it.value(); for (tempMap=portNameMap.begin();tempMap!=portNameMap.end();) { portName = tempMap.key(); break; } ++tempMap; } ++it; } return portName; } QString UkmediaVolumeControl::findSinkMasterDeviceInfo(pa_proplist *proplist, const char *info_name) { masterSinkDev = pa_proplist_gets(proplist,PA_PROP_DEVICE_MASTER_DEVICE); if (!masterSinkDev.isEmpty()) { int cardIndex = findPortSinkIndex(masterSinkDev); if (cardIndex != -1) { qDebug() << "findSinkMasterDeviceInfo" << sinkPortName << findSinkPortName(cardIndex); defaultOutputCard = findPortSinkIndex(masterSinkDev); return findSinkPortName(cardIndex); } else { qDebug() << "can't find masterDevice info in findSinkMasterDeviceInfo()"; return ""; } } } int UkmediaVolumeControl::findMasterDeviceCardIndex(QString masterDev) { return findPortSinkIndex(masterDev); } /** * @brief UkmediaVolumeControl::refreshVolume * 发送信号刷新音量 */ void UkmediaVolumeControl::refreshVolume(int soundType, int info_Vol, bool info_Mute) { switch (soundType) { case SoundType::SINK: { qDebug() << __func__ << "defaultSinkName:" << defaultSinkName; if (defaultSinkName.compare("auto_null") == 0) { info_Mute = true; info_Vol = 0; } if (sinkMuted != info_Mute) { sinkMuted = info_Mute; Q_EMIT updateMute(sinkMuted); } if (sinkVolume != info_Vol) { sinkVolume = info_Vol; sinkMuted = info_Mute; sendVolumeUpdateSignal(); } } break; case SoundType::SOURCE: { if (sourceMuted != info_Mute) { sourceMuted = info_Mute; Q_EMIT updateSourceMute(sourceMuted); } if (sourceVolume != info_Vol) { sourceVolume = info_Vol; sourceMuted = info_Mute; sendSourceVolumeUpdateSignal(); } } break; default: break; } } void UkmediaVolumeControl::sendOsdWidgetSignal(QString portName, QString description) { if (portName == "histen-algo" || defaultSinkName.contains("auto_null")) { return; } if (osdFirstFlag) { m_description = description; osdFirstFlag = false; return; } if (portName != sinkPortName || (sinkPortName == portName && !m_description.contains(description) && !description.contains(m_description))) { QString iconStr = ""; if (portName.contains("headphone", Qt::CaseInsensitive)) { iconStr = "audio-headphones-symbolic"; } else if (portName.contains("headset", Qt::CaseInsensitive)) { iconStr = "audio-headset-symbolic"; } else { iconStr = "audio-volume-high-symbolic"; } // sendOSDDeviceChangedSignal(iconStr); qDebug() << "Send SIGNAL: osdWidgetSignal. Case: sink port has changed" << portName << sinkPortName << description << m_description; } m_description = description; } void UkmediaVolumeControl::UkmediaDbusRegister() { QDBusConnection::sessionBus().unregisterService("org.ukui.media"); QDBusConnection con=QDBusConnection::sessionBus(); if (!con.registerService("org.ukui.media")) qDebug() << "registerService failed" << con.lastError().message(); if (!con.registerObject("/org/ukui/media/control", this, QDBusConnection::ExportAllSlots| QDBusConnection::ExportAllSignals)) qDebug() << "registerObject failed" << con.lastError().message(); } sinkInfo UkmediaVolumeControl::addSinkInfo(const pa_sink_info& i) { sinkInfo info; info.name = i.name; info.index = i.index; info.description = i.description; info.volume = i.volume; info.mute = i.mute; info.card = i.card; if (i.active_port) { info.active_port_name = i.active_port->name; info.active_port_description = i.active_port->description; } if (pa_proplist_gets(i.proplist, PA_PROP_DEVICE_MASTER_DEVICE)) info.master_device = pa_proplist_gets(i.proplist, PA_PROP_DEVICE_MASTER_DEVICE); if (i.ports) { for (pa_sink_port_info** sinkPort = i.ports; *sinkPort != nullptr; ++sinkPort) { portInfo pInfo; pInfo.name = (*sinkPort)->name; pInfo.description = (*sinkPort)->description; pInfo.priority = (*sinkPort)->priority; pInfo.available = (*sinkPort)->available; info.sink_port_list.append(pInfo); } } return info; } sourceInfo UkmediaVolumeControl::addSourceInfo(const pa_source_info& i) { sourceInfo info; info.name = i.name; info.index = i.index; info.description = i.description; info.volume = i.volume; info.mute = i.mute; info.card = i.card; if (i.active_port) { info.active_port_name = i.active_port->name; info.active_port_description = i.active_port->description; } if (pa_proplist_gets(i.proplist, PA_PROP_DEVICE_MASTER_DEVICE)) info.master_device = pa_proplist_gets(i.proplist, PA_PROP_DEVICE_MASTER_DEVICE); if (i.ports) { for (pa_source_port_info** sourcePort = i.ports; *sourcePort != nullptr; ++sourcePort) { portInfo pInfo; pInfo.name = (*sourcePort)->name; pInfo.description = (*sourcePort)->description; pInfo.priority = (*sourcePort)->priority; pInfo.available = (*sourcePort)->available; info.source_port_list.append(pInfo); } } return info; } appInfo UkmediaVolumeControl::addSinkInputInfo(const pa_sink_input_info& info) { appInfo app; app.name = AppDesktopFileAdaption(pa_proplist_gets(info.proplist, PA_PROP_APPLICATION_PROCESS_BINARY)); #ifdef PA_PROP_APPLICATION_MOVE app.move = pa_proplist_gets(info.proplist, PA_PROP_APPLICATION_MOVE); #endif app.index = info.index; app.mute = info.mute; app.volume = info.volume.values[0]; app.channel = info.volume.channels; app.direction = PA_STREAM_PLAYBACK; app.masterIndex = info.sink; sinkInfo sink = getSinkInfoByIndex(info.sink); app.masterDevice = sink.name; return app; } appInfo UkmediaVolumeControl::addSourceOutputInfo(const pa_source_output_info& info) { appInfo app; app.name = AppDesktopFileAdaption(pa_proplist_gets(info.proplist, PA_PROP_APPLICATION_PROCESS_BINARY)); #ifdef PA_PROP_APPLICATION_MOVE app.move = pa_proplist_gets(info.proplist, PA_PROP_APPLICATION_MOVE); #endif app.index = info.index; app.mute = info.mute; app.volume = info.volume.values[0]; app.channel = info.volume.channels; app.direction = PA_STREAM_RECORD; app.masterIndex = info.source; sourceInfo source = getSourceInfoByIndex(info.source); app.masterDevice = source.name; return app; } /* * 通过 sink-input name 寻找对应 index */ QList UkmediaVolumeControl::findAppIndex(int soundType, QString appName) { QList appIndexList; QMap appMap; QMap::iterator it; if (SoundType::SINK == soundType) appMap = sinkInputIndexMap; else if (SoundType::SOURCE == soundType) appMap = sourceOutputIndexMap; for (it = appMap.begin(); it != appMap.end(); it++) { if (appName == it.value()) appIndexList.append(it.key()); } return appIndexList; } void UkmediaVolumeControl::simple_callback(pa_context *c, int success, void *userdata) { if (!success) { qDebug() << "move failed" << pa_strerror(pa_context_errno(c)); return; } } const gchar* UkmediaVolumeControl::AppDesktopFileAdaption(const gchar* appName) { if (appName == nullptr) return nullptr; //某些第三方应用获取到appName与它的desktop文件名不一致,需手动适配 if (UKMedia_Equal(appName, "aplay")) appName = nullptr; else if (UKMedia_Equal(appName, "kylin-kmre-audio")) appName = nullptr; else if (UKMedia_Equal(appName, "ukui-powermanagement")) appName = nullptr; else if (UKMedia_Equal(appName, "wpp")) appName = "wps-office-wpp"; else if (UKMedia_Equal(appName, "wine-preloader")) appName = "kylin-kwre-wechat"; else if (strstr(appName, "qaxbrowser")) appName = "qaxbrowser-safe"; else if (strstr(appName, "browser360")) appName = "browser360-cn"; return appName; } void UkmediaVolumeControl::sendDeviceChangedSignal(UkmediaVolumeControl* w) { //start 过滤设备端口切换或者设备拔插期间,subscribecb回调反馈的非预期数据 if (nullptr == w) { qDebug() << "w is null"; return; } static UkmediaVolumeControl* control = w; static bool isConnect = false; if (deviceChangedTimer.isActive()) { deviceChangedTimer.stop(); } deviceChangedTimer.setInterval(300); deviceChangedTimer.setSingleShot(true); if (!isConnect) { isConnect = QObject::connect(&deviceChangedTimer, &QTimer::timeout, control, [&control](){ qDebug() << "deviceChangedSignal"; if (control) { Q_EMIT control->deviceChangedSignal(); } }); } deviceChangedTimer.start(); //end } void UkmediaVolumeControl::sendVolumeUpdateSignal() { //start 过滤设备端口切换或者设备拔插期间,subscribecb回调反馈的非预期数据 static bool isConnect = false; if (m_updateVolumeTimer.isActive()) { m_updateVolumeTimer.stop(); } m_updateVolumeTimer.setInterval(150); m_updateVolumeTimer.setSingleShot(true); if (!isConnect) { isConnect = QObject::connect(&m_updateVolumeTimer, &QTimer::timeout, this, [&](){ qDebug() << "refreshVolume sinkVolume:" << sinkVolume; Q_EMIT this->updateVolume(sinkVolume); }); } m_updateVolumeTimer.start(); //end } void UkmediaVolumeControl::sendSourceVolumeUpdateSignal() { //start 过滤设备端口切换或者设备拔插期间,subscribecb回调反馈的非预期数据 static bool isConnect = false; if (m_updateSourceVolumeTimer.isActive()) { m_updateSourceVolumeTimer.stop(); } m_updateSourceVolumeTimer.setInterval(150); m_updateSourceVolumeTimer.setSingleShot(true); if (!isConnect) { isConnect = QObject::connect(&m_updateSourceVolumeTimer, &QTimer::timeout, this, [&](){ qDebug() << "refreshVolume sourceVolume:" << sourceVolume; Q_EMIT this->updateSourceVolume(sourceVolume); }); } m_updateSourceVolumeTimer.start(); //end } void UkmediaVolumeControl::sendOSDDeviceChangedSignal(QString icon) { //start 过滤设备端口切换或者设备拔插期间,subscribecb回调反馈的非预期数据 static QString iconStr = ""; iconStr = icon; static bool isConnect = false; if (m_osdDeviceChangedTimer.isActive()) { m_osdDeviceChangedTimer.stop(); } m_osdDeviceChangedTimer.setInterval(300); m_osdDeviceChangedTimer.setSingleShot(true); if (!isConnect) { isConnect = QObject::connect(&m_osdDeviceChangedTimer, &QTimer::timeout, this, [&](){ qDebug() << "device_changed_signal iconStr:" << iconStr; Q_EMIT this->device_changed_signal(iconStr); }); } m_osdDeviceChangedTimer.start(); //end } sinkInfo UkmediaVolumeControl::getSinkInfoByIndex(int index) { QMap::iterator it; for (it = sinkMap.begin(); it != sinkMap.end(); ++it) { if (index == it.key()) return it.value(); } qInfo() << "Can't find sink info by index" << index; return {}; } sinkInfo UkmediaVolumeControl::getSinkInfoByName(QString name) { QMap::iterator it; for (it = sinkMap.begin(); it != sinkMap.end(); ++it) { if (name == it.value().name) return it.value(); } qInfo() << "Can't find sink info by name" << name; return {}; } sourceInfo UkmediaVolumeControl::getSourceInfoByIndex(int index) { QMap::iterator it; for (it = sourceMap.begin(); it != sourceMap.end(); ++it) { if (index == it.key()) return it.value(); } qInfo() << "Can't find source info by index" << index; return {}; } sourceInfo UkmediaVolumeControl::getSourceInfoByName(QString name) { QMap::iterator it; for (it = sourceMap.begin(); it != sourceMap.end(); ++it) { if (name == it.value().name) return it.value(); } qInfo() << "Can't find source info by name" << name; return {}; } ukui-media/ukui-volume-control-applet-qt/ukui_list_widget_item.cpp0000664000175000017500000000546415170052044024503 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 setFixedSize(404,48); hLayout = new QHBoxLayout(this); portLabel = new FixLabel(this); deviceLabel = new QLabel(this); deviceButton = new QPushButton(this); portLabel->setFixedSize(326,24); deviceLabel->setFixedSize(326,24); deviceLabel->hide(); // 记录cardname deviceButton->setFixedSize(36,36); deviceButton->setCheckable(true); deviceButton->setProperty("isRoundButton", true); deviceButton->setProperty("useButtonPalette", true); deviceButton->setProperty("needTranslucent", true); hLayout->addWidget(deviceButton); hLayout->addSpacing(12); hLayout->addWidget(portLabel); hLayout->setSpacing(0); this->setLayout(hLayout); hLayout->setContentsMargins(14,6,24,6); this->show(); } void UkuiListWidgetItem::setButtonIcon(QString iconName) { QString iconStr; if (iconName.contains("headphone", Qt::CaseInsensitive)) iconStr = "audio-headphones-symbolic"; else if (iconName.contains("headset", Qt::CaseInsensitive)) iconStr = "audio-headset-symbolic"; else iconStr = "audio-volume-high-symbolic"; deviceButton->setIcon(QIcon::fromTheme(iconStr)); } void UkuiListWidgetItem::setLabelText(QString portLabel, QString deviceLabel) { this->portLabel->setText(portLabel,true); this->deviceLabel->setText(deviceLabel); } void UkuiListWidgetItem::mousePressEvent(QMouseEvent *event) { QWidget::mousePressEvent(event); qDebug() << "Mouse Press Event" << this->portLabel->fullText << this->deviceLabel->text(); //蓝牙输入去除勾选 if (this->deviceLabel->text().contains("bluez_card")) { if (isCheckBluetoothInput == false) isCheckBluetoothInput = true; else { isCheckBluetoothInput = false; QString cmd = "pactl set-card-profile "+this->deviceLabel->text()+" a2dp_sink"; system(cmd.toLocal8Bit().data()); } } } UkuiListWidgetItem::~UkuiListWidgetItem() { } ukui-media/ukui-volume-control-applet-qt/ukmedia_custom_class.cpp0000664000175000017500000002541615170054730024313 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #include #include #include #include #include #include #include #include #include #include #define TIMER_TIMEOUT (2*1000) #define FOREGROUND_COLOR_NORMAL qApp->palette().text().color() extern double transparency; UkuiApplicationWidget::UkuiApplicationWidget(QWidget *parent) { // this->setAttribute(Qt::WA_TranslucentBackground); // this->setStyleSheet("QWiget{background:rgba(0,0,0,0);}"); Q_UNUSED(parent); } void UkuiApplicationWidget::paintEvent(QPaintEvent *e) { QStyleOption opt; opt.initFrom(this); QPainter p(this); double transparence = transparency * 255; QColor color = palette().color(QPalette::Base); color.setAlpha(transparence); QBrush brush = QBrush(QColor(0,0,0,0)); p.setBrush(brush); p.setBrush(this->palette().base()); p.setBrush(QBrush(QColor(19, 19, 20, 0))); p.setPen(Qt::NoPen); QPainterPath path; opt.rect.adjust(0,0,0,0); path.addRoundedRect(opt.rect,0,0); p.setRenderHint(QPainter::Antialiasing); // 反锯齿; p.drawRoundedRect(opt.rect,0,0); setProperty("blurRegion",QRegion(path.toFillPolygon().toPolygon())); style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); } UkuiApplicationWidget::~UkuiApplicationWidget() { } static inline qreal mixQreal(qreal a, qreal b, qreal bias) { return a + (b - a) * bias; } QColor mixColor(const QColor &c1, const QColor &c2, qreal bias) { if (bias <= 0.0) { return c1; } if (bias >= 1.0) { return c2; } if (qIsNaN(bias)) { return c1; } qreal r = mixQreal(c1.redF(), c2.redF(), bias); qreal g = mixQreal(c1.greenF(), c2.greenF(), bias); qreal b = mixQreal(c1.blueF(), c2.blueF(), bias); qreal a = mixQreal(c1.alphaF(), c2.alphaF(), bias); return QColor::fromRgbF(r, g, b, a); } void UkuiSettingButton::onPaletteChanged() { m_foregroundColor = FOREGROUND_COLOR_NORMAL; this->repaint(); } void UkuiSettingButton::setPressColor() { QColor hightlight = this->palette().color(QPalette::Active,QPalette::Highlight); QColor mix = this->palette().color(QPalette::Active,QPalette::BrightText); m_foregroundColor = mixColor(hightlight, mix, 0.05); } void UkuiSettingButton::setHoverColor() { QColor hightlight = this->palette().color(QPalette::Active,QPalette::Highlight); QColor mix = this->palette().color(QPalette::Active,QPalette::BrightText); m_foregroundColor = mixColor(hightlight, mix, 0.2); } void UkuiSettingButton::setNormalColor() { m_foregroundColor = FOREGROUND_COLOR_NORMAL; } UkuiSettingButton::UkuiSettingButton(QWidget *parent) { connect(qApp, &QApplication::paletteChanged, this, &UkuiSettingButton::onPaletteChanged); onPaletteChanged(); } UkuiSettingButton::~UkuiSettingButton() { } void UkuiSettingButton::paintEvent(QPaintEvent *event) { QPalette pal = this->palette(); pal.setColor(QPalette::WindowText, m_foregroundColor); this->setPalette(pal); return QLabel::paintEvent(event); } void UkuiSettingButton::enterEvent(QEvent *event) { setHoverColor(); this->update(); } void UkuiSettingButton::leaveEvent(QEvent *event) { setNormalColor(); this->update(); } void UkuiSettingButton::mousePressEvent(QMouseEvent *event) { setPressColor(); this->update(); return QLabel::mousePressEvent(event); } void UkuiSettingButton::mouseReleaseEvent(QMouseEvent *event) { setHoverColor(); this->update(); Q_EMIT clicked(); return QLabel::mouseReleaseEvent(event); } AudioSlider::AudioSlider(QWidget *parent) : KSlider(parent) { Q_UNUSED(parent); setRange(0,100); setSliderType(SmoothSlider); setOrientation(Qt::Horizontal); setFocusPolicy(Qt::StrongFocus); setNodeVisible(false); } void AudioSlider::wheelEvent(QWheelEvent *e) { if (this->value() - blueValue >= 10 || blueValue - this->value() >= 10) { blueValue = this->value(); Q_EMIT blueValueChanged(blueValue); } return KSlider::wheelEvent(e); } void AudioSlider::keyReleaseEvent(QKeyEvent *e) { if (e->key() == Qt::Key_VolumeMute) { qDebug() << "AudioSlider keyReleaseEvent " << e->key(); // 过滤掉快捷键的操作 e->ignore(); // 忽略该事件 return; } //start 长按左键或者下键调低音量 if (e->isAutoRepeat()) { if (this->value() - blueValue >= 10 || blueValue - this->value() >= 10) { blueValue = this->value(); Q_EMIT blueValueChanged(blueValue); } return; } //end if (this->value() != blueValue) { blueValue = this->value(); Q_EMIT blueValueChanged(blueValue); } return KSlider::keyReleaseEvent(e); } void AudioSlider::mouseMoveEvent(QMouseEvent *e) { isMouseWheel = true; if (this->value() - blueValue >= 10 || blueValue - this->value() >= 10) { blueValue = this->value(); Q_EMIT blueValueChanged(blueValue); } return KSlider::mouseMoveEvent(e); } void AudioSlider::mouseReleaseEvent(QMouseEvent *e) { isMouseWheel = false; if (this->value() != blueValue) { blueValue = this->value(); Q_EMIT blueValueChanged(blueValue); } return KSlider::mouseReleaseEvent(e); } AudioSlider::~AudioSlider() { } void UkuiButtonDrawSvg::init(QImage img, QColor color) { themeIcon.image = img; themeIcon.color = color; } void UkuiButtonDrawSvg::paintEvent(QPaintEvent *event) { QStyleOptionComplex opt; opt.initFrom(this); QPainter p(this); QColor color; switch (buttonState) { case PUSH_BUTTON_NORMAL: color = QColor(0x25,0x25,0x25,0xFF); break; case PUSH_BUTTON_CLICK: color = QColor(0x37,0x90,0xFA,0xFF); break; case PUSH_BUTTON_PRESS: color = QColor(0x24,0x6D,0xD4,0xFF); break; default: break; } p.setBrush(QBrush(color)); p.setPen(Qt::NoPen); QPainterPath path; opt.rect.adjust(0,0,0,0); path.addRoundedRect(opt.rect,18,18); p.setRenderHint(QPainter::Antialiasing); // 反锯齿; p.drawRoundedRect(opt.rect,18,18); setProperty("blurRegion",QRegion(path.toFillPolygon().toPolygon())); style()->drawComplexControl(QStyle::CC_ToolButton,&opt,&p,this); // style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); // QStyleOption opt; // opt.initFrom(this); // QPainter p(this); // p.setBrush(QBrush(QColor(0x13,0x13,0x14,0x00))); // p.setPen(Qt::NoPen); // QPainterPath path; // opt.rect.adjust(0,0,0,0); // path.addRoundedRect(opt.rect,18,18); // p.setRenderHint(QPainter::Antialiasing); // 反锯齿; // p.drawRoundedRect(opt.rect,18,18); // setProperty("blurRegion",QRegion(path.toFillPolygon().toPolygon())); // style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); } QRect UkuiButtonDrawSvg::IconGeometry() { QRect res = QRect(QPoint(0,0),QSize(24,24)); res.moveCenter(QRect(0,0,width(),height()).center()); return res; } void UkuiButtonDrawSvg::draw(QPaintEvent* e) { Q_UNUSED(e); QPainter painter(this); QRect iconRect = IconGeometry(); if (themeIcon.image.size() != iconRect.size()) { themeIcon.image = themeIcon.image.scaled(iconRect.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); QRect r = themeIcon.image.rect(); r.moveCenter(iconRect.center()); iconRect = r; } this->setProperty("fillIconSymbolicColor", true); filledSymbolicColoredPixmap(themeIcon.image,themeIcon.color); painter.drawImage(iconRect, themeIcon.image); } bool UkuiButtonDrawSvg::event(QEvent *event) { switch (event->type()) { case QEvent::Paint: draw(static_cast(event)); break; case QEvent::Move: case QEvent::Resize: { QRect rect = IconGeometry(); } break; case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseButtonDblClick: event->accept(); break; default: break; } return QPushButton::event(event); } UkuiButtonDrawSvg::UkuiButtonDrawSvg(QWidget *parent) { Q_UNUSED(parent); } UkuiButtonDrawSvg::~UkuiButtonDrawSvg() { } QPixmap UkuiButtonDrawSvg::filledSymbolicColoredPixmap(QImage &img, QColor &baseColor) { for (int x = 0; x < img.width(); x++) { for (int y = 0; y < img.height(); y++) { auto color = img.pixelColor(x, y); if (color.alpha() > 0) { int hue = color.hue(); if (!qAbs(hue - symbolic_color.hue()) < 10) { color.setRed(baseColor.red()); color.setGreen(baseColor.green()); color.setBlue(baseColor.blue()); img.setPixelColor(x, y, color); } } } } return QPixmap::fromImage(img); } FixLabel::FixLabel(QWidget *parent): QLabel(parent) { } FixLabel::FixLabel(QString text , QWidget *parent): QLabel(parent) { this->setText(text); } FixLabel::~FixLabel() { } void FixLabel::paintEvent(QPaintEvent *event) { QFontMetrics fontMetrics(this->font()); int fontSize = fontMetrics.horizontalAdvance(fullText); if (fontSize > this->width()) { this->setText(fontMetrics.elidedText(fullText, Qt::ElideRight, this->width()), false); this->setToolTip(fullText); } else { this->setText(fullText, false); this->setToolTip(""); } QLabel::paintEvent(event); } void FixLabel::setText(const QString & text, bool saveTextFlag) { if (saveTextFlag) fullText = text; QLabel::setText(text); } Divider::Divider(QWidget * parent) : QFrame(parent) { this->setFixedSize(420,1); } void Divider::paintEvent(QPaintEvent * e) { QPainter p(this); QColor color = qApp->palette().color(QPalette::BrightText); color.setAlphaF(0.08); p.save(); p.setBrush(color); p.setPen(Qt::transparent); p.drawRoundedRect(this->rect(), 6, 6); p.restore(); return QFrame::paintEvent(e); } ukui-media/ukui-volume-control-applet-qt/ukui_volume_control_user_config.cpp0000664000175000017500000000407515170054730026602 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "ukui_volume_control_user_config.h" #include UkuiVolumeControlUserConfig::UkuiVolumeControlUserConfig() { initJsonMap(); } UkuiVolumeControlUserConfig::~UkuiVolumeControlUserConfig() { } std::unordered_map> UkuiVolumeControlUserConfig::getJsonMap() const { return m_jsonMap; } void UkuiVolumeControlUserConfig::initJsonMap() { for (const auto& [k, v] : m_keys) { std::shared_ptr json = nullptr; switch (k) { case JsonType::JSON_TYPE_SOUNDEFFECT: { json = std::make_shared(v()); break; } case JsonType::JSON_TYPE_USERINFO: { json = std::make_shared(v()); json->init(); break; } default: break; } if (json) { m_jsonMap.emplace(k, json); } } } QString UkuiVolumeControlUserConfig::getSoundEffectFullPath() { return SOUND_EFFECT_JSON; } QString UkuiVolumeControlUserConfig::getUserInfoFullPath() { QString path = getenv("HOME"); path.append(AUDIO_JSON); syslog(LOG_DEBUG, "usr info file path: %s.", path.toLatin1().data()); return path; } ukui-media/ukui-volume-control-applet-qt/ukmedia_application_volume_widget.cpp0000664000175000017500000001754615170054730027056 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #include #include #include #include #include ApplicationVolumeWidget::ApplicationVolumeWidget(QWidget *parent) : QWidget (parent) { connect(qApp, &QApplication::paletteChanged, this, &ApplicationVolumeWidget::onPaletteChanged); app_volume_list = new QStringList; //系统音量label systemVolumeLabel = new QLabel(tr("System Volume"),this); QWidget *systemVolumeLabelWidget = new QWidget(this); systemVolumeLabelWidget->setFixedSize(412,29); systemVolumeLabel->setFixedSize(372,29); QHBoxLayout *systemVolumeLabelLayout = new QHBoxLayout; systemVolumeLabelLayout->addWidget(systemVolumeLabel); systemVolumeLabelWidget->setLayout(systemVolumeLabelLayout); systemVolumeLabelLayout->setContentsMargins(16,0,24,0); //应用label applicationLabel = new QLabel(tr("Application Volume"),this); QWidget *applicationLabelWidget = new QWidget(this); applicationLabelWidget->setFixedSize(412,29); applicationLabel->setFixedSize(372,29); QHBoxLayout *applicationLabelLayout = new QHBoxLayout; applicationLabelLayout->addWidget(applicationLabel); applicationLabelWidget->setLayout(applicationLabelLayout); applicationLabelLayout->setContentsMargins(16,0,24,0); //文本颜色置灰 QPalette palette = applicationLabel->palette(); QColor color = palette.color(QPalette::PlaceholderText); palette.setColor(QPalette::WindowText,color); applicationLabel->setPalette(palette); systemVolumeLabel->setPalette(palette); //音量滑动条 systemVolumeSliderWidget = new QWidget(this); systemVolumeSliderWidget->setFixedSize(412,48); systemVolumeBtn = new QPushButton(systemVolumeSliderWidget); systemVolumeBtn->setFixedSize(36,36); systemVolumeBtn->setCheckable(true); systemVolumeBtn->setProperty("isRoundButton",true); //圆形按钮 systemVolumeBtn->setProperty("useButtonPalette",true); //灰色按钮 systemVolumeBtn->setProperty("needTranslucent", true); //灰色半透明按钮 systemVolumeSlider = new AudioSlider(systemVolumeSliderWidget); systemVolumeSlider->setOrientation(Qt::Horizontal); systemVolumeSlider->setFocusPolicy(Qt::StrongFocus); systemVolumeSlider->setProperty("needTranslucent", true); // Increase translucent effect systemVolumeSlider->setRange(0,100); systemVolumeSlider->setFixedSize(286,48); systemVolumeDisplayLabel = new QLabel(systemVolumeSliderWidget); systemVolumeDisplayLabel->setText("0%"); systemVolumeDisplayLabel->setFixedSize(52,48); //系统音量滑动条布局 QHBoxLayout *systemVolumeSliderLayout = new QHBoxLayout(); systemVolumeSliderLayout->addWidget(systemVolumeBtn); systemVolumeSliderLayout->addSpacing(0); systemVolumeSliderLayout->addWidget(systemVolumeSlider); systemVolumeSliderLayout->addWidget(systemVolumeDisplayLabel); systemVolumeSliderLayout->setSpacing(0); systemVolumeSliderWidget->setLayout(systemVolumeSliderLayout); systemVolumeSliderLayout->setContentsMargins(16,0,24,0); systemVolumeSlider->setObjectName("systemVolumeSlider"); //声音设置布局 volumeSettingButton = new UkuiSettingButton(volumeSettingFrame); volumeSettingButton->setText(tr("Sound Settings")); volumeSettingButton->setCursor(Qt::PointingHandCursor); volumeSettingButton->setScaledContents(true); volumeSettingFrame = new QFrame(this); volumeSettingFrame->setFixedHeight(52); QHBoxLayout *volumeSettingLayout = new QHBoxLayout(volumeSettingFrame); volumeSettingLayout->addWidget(volumeSettingButton); volumeSettingLayout->addStretch(); volumeSettingFrame->setLayout(volumeSettingLayout); volumeSettingLayout->setContentsMargins(16,0,24,0); //应用音量widget appArea = new QScrollArea(this); appArea->setFixedSize(412,245); appArea->setFrameShape(QFrame::NoFrame);//bjc去掉appArea的边框 appArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); appArea->verticalScrollBar()->setProperty("drawScrollBarGroove",false);//滚动条背景透明 QPalette pal = appArea->palette(); pal.setColor(QPalette::Window, QColor(0x00,0xff,0x00,0x00)); //改变appArea背景色透明 appArea->setPalette(pal); displayAppVolumeWidget = new UkuiApplicationWidget(appArea); displayAppVolumeWidget->setFixedWidth(412); displayAppVolumeWidget->setAttribute(Qt::WA_TranslucentBackground); appArea->setWidget(displayAppVolumeWidget); m_pVlayout = new QVBoxLayout(displayAppVolumeWidget); displayAppVolumeWidget->setLayout(m_pVlayout); // this->setObjectName("mainWidget"); // setObjectName("appWidget"); //整体布局 QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(systemVolumeLabelWidget); mainLayout->addSpacing(3); mainLayout->addWidget(systemVolumeSliderWidget); mainLayout->addSpacing(13); mainLayout->addWidget(applicationLabelWidget); mainLayout->addSpacing(3); mainLayout->addWidget(appArea); mainLayout->addSpacing(2); mainLayout->addWidget(volumeSettingFrame); mainLayout->setSpacing(0); this->setLayout(mainLayout); mainLayout->setContentsMargins(8,12,0,0); this->setFixedSize(420,436); } //void ApplicationVolumeWidget::fullushBlueRecordStream() //{ // isRecording = true; // outputFile.setFileName("/tmp/test.raw"); // outputFile.open( QIODevice::WriteOnly | QIODevice::Truncate ); // QAudioFormat format; // format.setSampleRate(8000); // format.setChannelCount(1); // format.setSampleSize(8); // format.setCodec("audio/pcm"); // format.setByteOrder(QAudioFormat::LittleEndian); // format.setSampleType(QAudioFormat::UnSignedInt); // QAudioDeviceInfo info = QAudioDeviceInfo::defaultInputDevice(); // qDebug() << "input device" << info.deviceName(); // if (!info.isFormatSupported(format)) // { // qWarning()<<"default format not supported try to use nearest"; // format = info.nearestFormat(format); // } // qDebug() << "准备蓝牙录音-------"; // audio = new QAudioInput(info, format, this); // audio->start(&outputFile); //} //void ApplicationVolumeWidget::deleteBlueRecordStream() //{ // isRecording = false; // qDebug() << "停止录制-------"; // audio->stop(); // outputFile.close(); // system("rm /tmp/test.raw"); // delete audio; //} void ApplicationVolumeWidget::setLabelAlignment(Qt::Alignment alignment) { systemVolumeDisplayLabel->setAlignment(alignment); } void ApplicationVolumeWidget::onPaletteChanged(){ QPalette palette = applicationLabel->palette(); QColor color = palette.color(QPalette::PlaceholderText); palette.setColor(QPalette::WindowText,color); applicationLabel->setPalette(palette); systemVolumeLabel->setPalette(palette); } void ApplicationVolumeWidget::setVolumeSliderRange(bool status) { int maxValue = (status) ? 125 : 100; systemVolumeSlider->blockSignals(true); systemVolumeSlider->setRange(0, maxValue); systemVolumeSlider->blockSignals(false); } ApplicationVolumeWidget::~ApplicationVolumeWidget() { // delete app_volume_list; // delete appIconBtn; } ukui-media/ukui-volume-control-applet-qt/ukmedia_monitor_window_thread.cpp0000664000175000017500000001073015170054730026212 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 UkmediaMonitorWindowThread::UkmediaMonitorWindowThread() { bStopThread = false; bCreateWindow = false; bFirstEnterSystem = true; } void UkmediaMonitorWindowThread::get_window_nameAndid() { Display *display; Window rootwin; display = XOpenDisplay( NULL ); rootwin = DefaultRootWindow( display ); Atom atom = XInternAtom(display, "WM_DELETE_WINDOW", false); XSelectInput( display, rootwin, SubstructureNotifyMask | ExposureMask);/*事件可以参考x.h*/ XEvent event; // while ( 1 ) // { // printf("get event\n"); fflush(stdout); while (true) { XNextEvent(display, &event ); // printf("event type is %d\n",event.type); fflush(stdout); if (event.type == Expose /*ReparentNotify*/ /*&& bCreateWindow == false*/) { printf("ReparentNotify\n\n"); fflush(stdout); if (bFirstEnterSystem == false) { Window x11window; Display *x11display; char **srname = (char **)malloc(sizeof(char *)); XReparentEvent *reparentevent = (XReparentEvent *)&event; //printf( "new window: %ld \n", (unsigned long)(reparentevent->window)); //printf( "new parent: %ld \n", (unsigned long)(reparentevent->parent)); fflush(stdout); windowId.append((unsigned long)reparentevent->window); /*获取到新建窗口的window ID*/ x11window = (unsigned long)(reparentevent->window); x11display =(reparentevent->display); XFetchName(x11display, x11window, srname); x11display = XOpenDisplay( NULL );/*!!!注意这里以后如果你想对获取到的window ID进行操作必须重新链接*/ fflush(stdout); free(srname); bCreateWindow = true; XCloseDisplay(x11display); } bFirstEnterSystem = false; } else if (event.type == /*DestroyNotify*//*ClientMessage*/ UnmapNotify /*&&*/ /*bCreateWindow*/) { bCreateWindow = false; // 执行用户定义的关闭窗口操作 fflush(stdout); Window x11window; Display *x11display; char **srname = (char **)malloc(sizeof(char *)); XUnmapEvent *reparentevent = (XUnmapEvent *)&event; //printf( "close window: %ld \n", (unsigned long)(reparentevent->window)); fflush(stdout); if (windowId.contains((unsigned long)reparentevent->window)) { int index = windowId.indexOf((unsigned long)reparentevent->window); //printf( "remove ---------------------------- index : %d \n\n",index); fflush(stdout); windowId.removeAt(index); } /*获取到新建窗口的window ID*/ x11window = (unsigned long)(reparentevent->window); x11display =(reparentevent->display); XFetchName(x11display, x11window, srname); x11display = XOpenDisplay( NULL );/*!!!注意这里以后如果你想对获取到的window ID进行操作必须重新链接*/ fflush(stdout); free(srname); XCloseDisplay(x11display); } } XCloseDisplay(display); } //线程执行函数 void UkmediaMonitorWindowThread::run() { // qDebug()<<"thread run!"; bStopThread = false; while(!bStopThread) { // printf("thread run! \n\n"); fflush(stdout); //to do what you want get_window_nameAndid(); //延时等待一会 QEventLoop eventloop; QTimer::singleShot(1000, &eventloop, SLOT(quit())); eventloop.exec(); } } ukui-media/ukui-volume-control-applet-qt/ukmedia_volume_control.h0000664000175000017500000003137515170054730024331 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #if HAVE_EXT_DEVICE_RESTORE_API # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "custom_sound.h" #include "../ukui-login-sound/ukui_login_sound_user_config.h" #include "../common/ukmedia_common.h" #include "ukui_volume_control_user_config.h" class PortInfo { public: QByteArray name; QByteArray description; uint32_t priority; int available; int direction; int64_t latency_offset; std::vector profiles; }; class UkmediaVolumeControl : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.ukui.media") public: UkmediaVolumeControl(); void UkmediaDbusRegister(); friend class UkmediaMainWidget; virtual ~UkmediaVolumeControl(); public: // Connect to pulseaudio pa_context* getContext(void); static gboolean connectToPulse(gpointer userdata); static void contextStateCallback(pa_context *c, void *userdata); static void subscribeCb(pa_context *c, pa_subscription_event_type_t t, uint32_t index, void *userdata); static void clientCb(pa_context *, const pa_client_info *i, int eol, void *userdata); static void serverInfoCb(pa_context *, const pa_server_info *i, void *userdata); static void cardCb(pa_context *, const pa_card_info *i, int eol, void *userdata); static void sinkCb(pa_context *c, const pa_sink_info *i, int eol, void *userdata); static void sourceCb(pa_context *, const pa_source_info *i, int eol, void *userdata); static void sinkInputCb(pa_context *, const pa_sink_input_info *i, int eol, void *userdata); static void sourceOutputCb(pa_context *, const pa_source_output_info *i, int eol, void *userdata); static void batteryLevelCb(pa_context *c, const pa_card_info *i, int eol, void *userdata); static void bluetoothCardCb(pa_context *c, const pa_card_info *i, int eol, void *userdata); static void moduleInfoCb(pa_context *c, const pa_module_info *i, int eol, void *userdata); // 待优化 void showError(const char *txt); static void decOutstanding(UkmediaVolumeControl *w); static void simple_callback(pa_context *c, int success, void *userdata); static void serverInfoIndexCb(pa_context *, const pa_server_info *i, void *userdata); static void sinkIndexCb(pa_context *c, const pa_sink_info *i, int eol, void *userdata); static void sourceIndexCb(pa_context *c, const pa_source_info *i, int eol, void *userdata); // 暂未使用 void setConnectionState(gboolean connected); void setConnectingMessage(const char *string = NULL); pa_stream* createMonitorStreamForSource(uint32_t source_idx, uint32_t stream_idx, bool suspend); static void extStreamRestoreReadCb(pa_context *,const pa_ext_stream_restore_info *i,int eol,void *userdata); static void extStreamRestoreSubscribeCb(pa_context *c, void *userdata); // void ext_device_restore_read_cb(pa_context *,const pa_ext_device_restore_info *i,int eol,void *userdata); // static void ext_device_restore_subscribe_cb(pa_context *c, pa_device_type_t type, uint32_t idx, void *userdata); static void extDeviceManagerReadCb(pa_context *,const pa_ext_device_manager_info *,int eol,void *userdata); static void extDeviceManagerSubscribeCb(pa_context *c, void *userdata); //#if HAVE_EXT_DEVICE_RESTORE_API // void updateDeviceInfo(const pa_ext_device_restore_info &info); //#endif // Update audio info void updateClient(const pa_client_info &info); void updateServer(const pa_server_info &info); void updateCard(UkmediaVolumeControl *c, const pa_card_info &info); bool updateSink(UkmediaVolumeControl *c, const pa_sink_info &info); void updateSource(const pa_source_info &info); void updateSinkInput(const pa_sink_input_info &info); void updateSourceOutput(const pa_source_output_info &info); void refreshVolume(int soundType, int info_Vol, bool info_Mute); // Sink volume bool getSinkMute(); int getSinkVolume(); void setSinkMute(bool status); void setSinkVolume(int index, int value); void setSinkVolumeByInfo(const pa_sink_info &info, int value); // Source volume bool getSourceMute(); int getSourceVolume(); void setSourceMute(bool status); void setSourceVolume(int index,int value); void setSourceVolumeByInfo(const pa_source_info &info, int value); // Sink-input volume bool getSinkInputMuted(QString description); int getSinkInputVolume(const gchar* name); bool setSinkInputMuted(QString name,bool status); void setSinkInputVolume(int index, int value, int channel); // Source-output volume int getSourceOutputVolume(const gchar *name); bool setSourceOutputMuted(QString appName, bool status); void setSourceOutputVolume(int index, int value, int channel); // sinkInfo sinkInfo addSinkInfo(const pa_sink_info& i); sinkInfo getSinkInfoByIndex(int index); sinkInfo getSinkInfoByName(QString name); // sourceInfo sourceInfo addSourceInfo(const pa_source_info& i); sourceInfo getSourceInfoByIndex(int index); sourceInfo getSourceInfoByName(QString name); int getDefaultSinkIndex(); void getModuleIndexByName(QString name); bool setCardProfile(int index,const gchar *name); bool setDefaultSink(const gchar *name); bool setDefaultSource(const gchar *name); bool setSinkPort(const gchar *sinkName,const gchar *name); bool setSourcePort(const gchar *portName, const gchar *name); // Remove info void removeCard(uint32_t index); void removeSink(uint32_t index); void removeSource(uint32_t index); void removeSinkInput(uint32_t index); void removeSourceOutput(uint32_t index); void removeClient(uint32_t index); void removeCardMap(int index); //移除指定索引的 card void removeCardProfileMap(int index); //移除声卡profile map void removeInputPortMap(int index); //移除指定索引的input port void removeOutputPortMap(int index); //移除指定索引的output port void removeSinkPortMap(int index); void removeSourcePortMap(int index); void removeProfileMap(int index); void removeInputProfile(int index); QString findCardName(int index, QMap cardMap); //根据声卡index查找声卡名 QString findSinkPortName(int cardIndex); int findOutputPort(QString name); int findPortSinkIndex(QString name); int findMasterDeviceCardIndex(QString masterDev); QString findSinkMasterDeviceInfo(pa_proplist *proplist, const char *info_name); // 开机音乐配置文件 void insertJson(const JsonType& type, const QString& key, const QJsonValue& value); // 初始化音量设计 void initDefaultSinkVolume(const pa_sink_info &info); void initDefaultSourceVolume(const pa_source_info &info); QString stringRemoveUnrecignizedChar(QString str); //移除xml中不能识别的字符 // 应用音量管理 appInfo addSinkInputInfo(const pa_sink_input_info & info); appInfo addSourceOutputInfo(const pa_source_output_info& info); bool killSinkInput(int index); void moveSinkInput(QString appName, const char* sinkName); void moveSoureOutput(QString appName, const char* sourceName); QList findAppIndex(int soundType, QString appName); const gchar* AppDesktopFileAdaption(const gchar* appName); // 信号 void sendPortChangedSignal(); void sendSourcePortChangedSignal(); bool isNeedSendPortChangedSignal(QString newPort, QString prePort, QString cardName); void sendOsdWidgetSignal(QString portName, QString description); private: static void sendDeviceChangedSignal(UkmediaVolumeControl* w=nullptr); void sendVolumeUpdateSignal(); void sendSourceVolumeUpdateSignal(); void sendOSDDeviceChangedSignal(QString icon=""); Q_SIGNALS: void paContextReady(); void updateSourceVolume(int value); void updateSourceMute(bool state); void checkDeviceSelectionSianal(const pa_card_info *info); void deviceChangedSignal(); void removeSinkSignal(); void device_changed_signal(QString str); void bluetoothBatteryChanged(QString dev, int battery); void initBlueDeviceVolumeSig(int index, QString name); void updatePortSignal(); void updateMute(bool state); void updateVolume(int value); void removeSinkInputSignal(QString name); void sinkInputVolumeChangedSignal(QString name, QString id, int value); void updateApp(QString name); void addSinkInputSignal(QString name, QString id, int index, int value, int channel); protected Q_SLOTS: void getBatteryLevel(QString dev); public: static pa_context *context; static int reconnectTimeout; static pa_mainloop_api* api; int n_outstanding = 0; bool canRenameDevices; std::map clientNames; SinkType showSinkType; SourceType showSourceType; SinkInputType showSinkInputType; SourceOutputType showSourceOutputType; // 输出 int sinkIndex; bool sinkMuted; int sinkVolume = 0; int channel = 2; float balance = 0.0; int defaultOutputCard = -1; QString sinkPortName = ""; QString masterSinkDev = ""; QByteArray defaultSinkName; pa_channel_map channelMap; const pa_sink_info *m_pDefaultSink = nullptr; // 输入 int sourceIndex; int sourceVolume = 0; int inputChannel = 0; bool sourceMuted; int defaultInputCard; QString sourcePortName = ""; QString masterDevice = ""; QByteArray defaultSourceName; const pa_source_info *m_pDefaultSource = nullptr; int batteryLevel; // 蓝牙电量 int findModuleIndex = -1; // 查找到模块的index QString findModuleStr = ""; // 需要查找的module name bool isLoadLoopback = false; // Card Info QMap cardMap; QMap> cardProfileMap; QMap cardActiveProfileMap; QMap> cardProfilePriorityMap; //记录声卡优先级配置文件 QMap> profileNameMap; //声卡输出配置文件 QMap> inputPortProfileNameMap; //声卡输入配置文件 // Sink Info QMap sinkMap; QMap> sinkPortMap; QMap> outputPortMap; // Source Info QMap sourceMap; QMap> sourcePortMap; QMap> inputPortMap; // 针对一个应用产生多个sinkinput,indexMap记录应用产生sinkinput的每个index, valueMap刷新应用的最新音量(待优化) QStringList sinkInputList; QMap sinkInputMap; QMap sinkInputMuteMap; QMap sinkInputValueMap; QMap sinkInputIndexMap; QMap sourceOutputMap; QMap sourceOutputIndexMap; QMap sourceOutputValueMap; QByteArray activePort; QByteArray activeProfile; QByteArray noInOutProfile; std::map ports; std::vector< std::pair > dPorts; std::vector< std::pair > profiles; private: void initUserConfig(); private: CustomSound *customSoundFile; gboolean m_connected; gchar* m_config_filename; QString m_description; // sink_description bool osdFirstFlag = true; //start 过滤设备端口切换或者设备拔插期间,subscribecb回调反馈的非预期数据 static QTimer deviceChangedTimer; QTimer m_osdDeviceChangedTimer; QTimer m_updateVolumeTimer; QTimer m_updateSourceVolumeTimer; //end std::shared_ptr m_pUsrConf; }; #endif // UKMEDIAVOLUMECONTROL_H ukui-media/ukui-volume-control-applet-qt/ukui_media_set_headset_widget.cpp0000664000175000017500000004376015170054730026146 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #include #include #include #include #include #include #include extern double transparency; UkuiMediaSetHeadsetWidget::UkuiMediaSetHeadsetWidget(QWidget *parent) : QWidget (parent) { headphoneIconButton = new QToolButton(); headsetIconButton = new QToolButton(); microphoneIconButton = new QToolButton(); soundSettingButton = new QPushButton(tr("Sound Settings")); cancelButton = new QPushButton(tr("Cancel")); selectSoundDeviceLabel = new QLabel(tr("Select Sound Device")); initSetHeadsetWidget(); initPanelGSettings(); headphoneIconButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); connect(headphoneIconButton,SIGNAL(clicked()),this,SLOT(headphoneButtonClickedSlot())); connect(headsetIconButton,SIGNAL(clicked()),this,SLOT(headsetButtonClickedSlot())); connect(microphoneIconButton,SIGNAL(clicked()),this,SLOT(microphoneButtonClickedSlot())); connect(soundSettingButton,SIGNAL(clicked()),this,SLOT(soundSettingButtonClickedSlot())); connect(cancelButton,SIGNAL(clicked()),this,SLOT(cancelButtonClickedSlot())); this->setWindowFlags(Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::ToolTip); } void UkuiMediaSetHeadsetWidget::initSetHeadsetWidget() { this->setFixedSize(402,252); headphoneIconButton->setFixedSize(96,96); headsetIconButton->setFixedSize(96,96); microphoneIconButton->setFixedSize(96,96); headphoneIconButton->setIconSize(QSize(32,32)); headsetIconButton->setIconSize(QSize(32,32)); microphoneIconButton->setIconSize(QSize(32,32)); selectSoundDeviceLabel->setFixedSize(402,24); soundSettingButton->setFixedSize(120,36); cancelButton->setFixedSize(107,36); soundSettingButton->setContextMenuPolicy(Qt::DefaultContextMenu); headphoneIconButton->setText(tr("Headphone")); headsetIconButton->setText(tr("Headset")); microphoneIconButton->setText(tr("Microphone")); headphoneIconButton->setIcon(QIcon::fromTheme("headphones-symbolic")); headsetIconButton->setIcon(QIcon::fromTheme("audio-headset-symbolic")); microphoneIconButton->setIcon(QIcon::fromTheme("audio-input-microphone")); headphoneIconButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); headsetIconButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); microphoneIconButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); selectSoundDeviceLabel->setAlignment(Qt::AlignCenter); QWidget *labelWidget = new QWidget; labelWidget->setFixedSize(402,24); QVBoxLayout *labelLayout = new QVBoxLayout; labelLayout->addWidget(selectSoundDeviceLabel); labelLayout->setContentsMargins(0,0,0,0); labelWidget->setLayout(labelLayout); QWidget *headsetButtonWidget = new QWidget(); headsetButtonWidget->setFixedSize(402,96); QHBoxLayout *headsetButtonLayout = new QHBoxLayout; headsetButtonLayout->addWidget(headphoneIconButton); headsetButtonLayout->addWidget(headsetIconButton); headsetButtonLayout->addWidget(microphoneIconButton); headsetButtonLayout->setSpacing(24); headsetButtonLayout->setContentsMargins(32,0,32,0); headsetButtonWidget->setLayout(headsetButtonLayout); QWidget *buttonWidget = new QWidget; buttonWidget->setFixedSize(402,36); // buttonWidget->setStyleSheet("QWidget{background-color:rgb(255,0,0);}"); QHBoxLayout *buttonLayout = new QHBoxLayout(); buttonLayout->addWidget(soundSettingButton); buttonLayout->addWidget(cancelButton); buttonLayout->setSpacing(108); buttonLayout->setContentsMargins(32,0,32,0); buttonWidget->setLayout(buttonLayout); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(labelWidget); mainLayout->addWidget(headsetButtonWidget); mainLayout->addWidget(buttonWidget); this->setLayout(mainLayout); this->setProperty("useSystemStyleBlur",true); this->setWindowTitle("whole window blur"); this->setAttribute(Qt::WA_TranslucentBackground); this->setContentsMargins(0,16,0,16); } void UkuiMediaSetHeadsetWidget::paintEvent(QPaintEvent *event) { QStyleOption opt; opt.initFrom(this); QPainter p(this); // double transparence = transparency * 255; QColor color = palette().color(QPalette::Base); // color.setAlpha(transparence); QBrush brush = QBrush(color); p.setBrush(brush); p.setPen(Qt::NoPen); QPainterPath path; opt.rect.adjust(0,0,0,0); path.addRoundedRect(opt.rect,12,12); p.setRenderHint(QPainter::Antialiasing); // 反锯齿; p.drawRoundedRect(opt.rect,12,12); setProperty("blurRegion",QRegion(path.toFillPolygon().toPolygon())); style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); QWidget::paintEvent(event); } /* * set input and output port. * input: intel mic * output: headphone */ void UkuiMediaSetHeadsetWidget::headphoneButtonClickedSlot() { isShow = false; // cset("name=Capture Source", HW_CARD, "2", 0, 0); QDBusMessage message =QDBusMessage::createSignal("/", "org.ukui.media", "headsetJack"); message<<"headphone"; QDBusConnection::sessionBus().send(message); this->hide(); } /* * set input and output port. * input: headsed mic * output: headphone */ void UkuiMediaSetHeadsetWidget::headsetButtonClickedSlot() { isShow = false; // cset("name=Capture Source", HW_CARD, "0", 0, 0); QDBusMessage message =QDBusMessage::createSignal("/", "org.ukui.media", "headsetJack"); message<<"headset"; QDBusConnection::sessionBus().send(message); this->hide(); } /* * set input and output port. * input: headphone mic * output: speaker */ void UkuiMediaSetHeadsetWidget::microphoneButtonClickedSlot() { isShow = false; // int ret = cset("name=Capture Source", HW_CARD, "1", 0, 0); QDBusMessage message =QDBusMessage::createSignal("/", "org.ukui.media", "headsetJack"); message<<"headphone mic" ; // if (ret < 0) // cset("name=Input Source", HW_CARD, "1", 0, 0); QDBusConnection::sessionBus().send(message); this->hide(); } /* * Jump to the control panel */ void UkuiMediaSetHeadsetWidget::soundSettingButtonClickedSlot() { QProcess *process; QStringList args; args.append("-m Audio"); QString command = "ukui-control-center"; process = new QProcess(); process->setProcessChannelMode(QProcess::MergedChannels); process->start(command, args); if (!process->waitForStarted()) { qDebug() << "start failed:" << process->errorString(); } else { qDebug() << "start success:"; } this->hide(); isShow = false; } /* * Cancel settings */ void UkuiMediaSetHeadsetWidget::cancelButtonClickedSlot() { this->hide(); isShow = false; } int UkuiMediaSetHeadsetWidget::cset(char * name, char *card, char *c, int roflag, int keep_handle) { int err; static snd_ctl_t *handle = NULL; snd_ctl_elem_info_t *info; snd_ctl_elem_id_t *id; snd_ctl_elem_value_t *control; snd_ctl_elem_info_alloca(&info); snd_ctl_elem_id_alloca(&id); snd_ctl_elem_value_alloca(&control); printf("name[%s]card[%s]c[%s]", name, card, c); if (snd_ctl_ascii_elem_id_parse(id, name)) { printf("Wrong control identifier: %s\n", name); return -1; } if (handle == NULL && (err = snd_ctl_open(&handle, card, 0)) < 0) { printf("Control %s open error: %s", card, snd_strerror(err)); return err; } snd_ctl_elem_info_set_id(info, id); if ((err = snd_ctl_elem_info(handle, info)) < 0) { //if (ignore_error) //return 0; printf("Cannot find the given element from control %s", card); if (! keep_handle) { snd_ctl_close(handle); handle = NULL; } return err; } snd_ctl_elem_info_get_id(info, id); if (!roflag) { snd_ctl_elem_value_set_id(control, id); if ((err = snd_ctl_elem_read(handle, control)) < 0) { //if (ignore_error) //return 0; printf("Cannot read the given element from control %s", card); if (! keep_handle) { snd_ctl_close(handle); handle = NULL; } return err; } err = snd_ctl_ascii_value_parse(handle, control, info, c); if (err < 0) { //if (!ignore_error) //error("Control %s parse error: %s\n", card, snd_strerror(err)); if (!keep_handle) { snd_ctl_close(handle); handle = NULL; } //return ignore_error ? 0 : err; return err; } if ((err = snd_ctl_elem_write(handle, control)) < 0) { //if (!ignore_error) //error("Control %s element write error: %s\n", card, snd_strerror(err)); if (!keep_handle) { snd_ctl_close(handle); handle = NULL; } //return ignore_error ? 0 : err; return err; } } if (! keep_handle) { snd_ctl_close(handle); handle = NULL; } snd_hctl_t *hctl; snd_hctl_elem_t *elem; if ((err = snd_hctl_open(&hctl, card, 0)) < 0) { printf("Control %s open error: %s", card, snd_strerror(err)); return err; } if ((err = snd_hctl_load(hctl)) < 0) { printf("Control %s load error: %s", card, snd_strerror(err)); return err; } elem = snd_hctl_find_elem(hctl, id); if (elem) showControl(" ", elem, LEVEL_BASIC | LEVEL_ID); else printf("Could not find the specified element"); snd_hctl_close(hctl); return 0; } void UkuiMediaSetHeadsetWidget::showControlId(snd_ctl_elem_id_t *id) { char *str; str = snd_ctl_ascii_elem_id_get(id); if (str) printf("%s", str); free(str); } int UkuiMediaSetHeadsetWidget::showControl(const char *space, snd_hctl_elem_t *elem, int level) { int err; unsigned int item, idx, count, *tlv; snd_ctl_elem_type_t type; snd_ctl_elem_id_t *id; snd_ctl_elem_info_t *info; snd_ctl_elem_value_t *control; snd_aes_iec958_t iec958; snd_ctl_elem_id_alloca(&id); snd_ctl_elem_info_alloca(&info); snd_ctl_elem_value_alloca(&control); if ((err = snd_hctl_elem_info(elem, info)) < 0) { printf("Control hw snd_hctl_elem_info error: %s\n", snd_strerror(err)); return err; } if (level & LEVEL_ID) { snd_hctl_elem_get_id(elem, id); showControlId(id); printf("\n"); } count = snd_ctl_elem_info_get_count(info); type = snd_ctl_elem_info_get_type(info); switch (type) { case SND_CTL_ELEM_TYPE_INTEGER: printf(",min=%li,max=%li,step=%li \n", snd_ctl_elem_info_get_min(info), snd_ctl_elem_info_get_max(info), snd_ctl_elem_info_get_step(info)); break; case SND_CTL_ELEM_TYPE_INTEGER64: printf(",min=%lli,max=%lli,step=%lli \n", snd_ctl_elem_info_get_min64(info), snd_ctl_elem_info_get_max64(info), snd_ctl_elem_info_get_step64(info)); break; case SND_CTL_ELEM_TYPE_ENUMERATED: { unsigned int items = snd_ctl_elem_info_get_items(info); printf(",items=%u\n", items); for (item = 0; item < items; item++) { snd_ctl_elem_info_set_item(info, item); if ((err = snd_hctl_elem_info(elem, info)) < 0) { printf("Control hw element info error: %s\n", snd_strerror(err)); return err; } printf("%s; Item #%u '%s' \n", space, item, snd_ctl_elem_info_get_item_name(info)); } break; } default: printf("\n"); break; } if (level & LEVEL_BASIC) { if (!snd_ctl_elem_info_is_readable(info)) goto __skip_read; if ((err = snd_hctl_elem_read(elem, control)) < 0) { printf("Control hw:0 element read error: %s\n", snd_strerror(err)); return err; } for (idx = 0; idx < count; idx++) { if (idx > 0) printf(",\n"); switch (type) { case SND_CTL_ELEM_TYPE_BOOLEAN: printf("%s \n", snd_ctl_elem_value_get_boolean(control, idx) ? "on" : "off"); break; case SND_CTL_ELEM_TYPE_INTEGER: printf("%li \n", snd_ctl_elem_value_get_integer(control, idx)); break; case SND_CTL_ELEM_TYPE_INTEGER64: printf("%lli \n", snd_ctl_elem_value_get_integer64(control, idx)); break; case SND_CTL_ELEM_TYPE_ENUMERATED: printf("%u \n", snd_ctl_elem_value_get_enumerated(control, idx)); break; case SND_CTL_ELEM_TYPE_BYTES: printf("0x%02x \n", snd_ctl_elem_value_get_byte(control, idx)); break; case SND_CTL_ELEM_TYPE_IEC958: snd_ctl_elem_value_get_iec958(control, &iec958); printf("[AES0=0x%02x AES1=0x%02x AES2=0x%02x AES3=0x%02x] \n", iec958.status[0], iec958.status[1], iec958.status[2], iec958.status[3]); break; default: printf("? \n"); break; } } __skip_read: if (!snd_ctl_elem_info_is_tlv_readable(info)) goto __skip_tlv; /* skip ASoC ext bytes controls that may have huge binary TLV data */ if (type == SND_CTL_ELEM_TYPE_BYTES && !snd_ctl_elem_info_is_readable(info) && !snd_ctl_elem_info_is_writable(info)) { printf("%s; ASoC TLV Byte control, skipping bytes dump \n", space); goto __skip_tlv; } tlv = (unsigned int *)malloc(4096); if ((err = snd_hctl_elem_tlv_read(elem, tlv, 4096)) < 0) { printf("Control hw:0 element TLV read error: %s \n", snd_strerror(err)); free(tlv); return err; } //decode_tlv(strlen(space), tlv, 4096); free(tlv); } __skip_tlv: return 0; } void UkuiMediaSetHeadsetWidget::showWindow() { QRect rect = caculatePosition(); QString platform = QGuiApplication::platformName(); if (!platform.startsWith(QLatin1String("wayland"),Qt::CaseInsensitive)){ this->setGeometry(QRect((rect.x() - rect.width()*2), rect.y(), rect.width(), rect.height())); this->showNormal(); } else { this->show(); kdk::WindowManager::setGeometry(this->windowHandle(), QRect((rect.x() - rect.width()*2), rect.y(), rect.width(), rect.height())); } isShow = true; this->show(); } void UkuiMediaSetHeadsetWidget::initPanelGSettings() { const QByteArray id(PANEL_SETTINGS); if (QGSettings::isSchemaInstalled(id)) { if (m_panelGSettings == nullptr) { m_panelGSettings = new QGSettings(id); } if (m_panelGSettings->keys().contains(PANEL_POSITION_KEY)) { m_panelPosition = m_panelGSettings->get(PANEL_POSITION_KEY).toInt(); } if (m_panelGSettings->keys().contains(PANEL_SIZE_KEY)) { m_panelSize = m_panelGSettings->get(PANEL_SIZE_KEY).toInt(); } connect(m_panelGSettings, &QGSettings::changed, this, [&] (const QString &key) { if (key == PANEL_POSITION_KEY) { m_panelPosition = m_panelGSettings->get(PANEL_POSITION_KEY).toInt(); } if (key == PANEL_SIZE_KEY) { m_panelSize = m_panelGSettings->get(PANEL_SIZE_KEY).toInt(); } if (this->isVisible()) { QRect rect = caculatePosition(); QString platform = QGuiApplication::platformName(); if (!platform.startsWith(QLatin1String("wayland"),Qt::CaseInsensitive)){ this->setGeometry(QRect((rect.x() - rect.width()*2), rect.y(), rect.width(), rect.height())); this->showNormal(); } else { this->show(); kdk::WindowManager::setGeometry(this->windowHandle(), QRect((rect.x() - rect.width()*2), rect.y(), rect.width(), rect.height())); } } }); } } QRect UkuiMediaSetHeadsetWidget::caculatePosition() { QRect availableGeo = QGuiApplication::screenAt(QCursor::pos())->geometry(); int x, y; int margin = 8; switch (m_panelPosition) { case PanelPosition::Top: { x = availableGeo.x() + availableGeo.width() - this->width() - margin; y = availableGeo.y() + m_panelSize + margin; } break; case PanelPosition::Bottom: { x = availableGeo.x() + availableGeo.width() - this->width() - margin; y = availableGeo.y() + availableGeo.height() - m_panelSize - this->height() - margin; } break; case PanelPosition::Left: { x = availableGeo.x() + m_panelSize + margin; y = availableGeo.y() + availableGeo.height() - this->height() - margin; } break; case PanelPosition::Right: { x = availableGeo.x() + availableGeo.width() - m_panelSize - this->width() - margin; y = availableGeo.y() + availableGeo.height() - this->height() - margin; } break; } return QRect(x, y, this->width(), this->height()); } UkuiMediaSetHeadsetWidget::~UkuiMediaSetHeadsetWidget() { } ukui-media/ukui-volume-control-applet-qt/ukmedia_volume_slider.h0000664000175000017500000001246215170052044024123 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #include #include #include #include #include #include #include #include #include #include #include #include //typedef struct UkuiThemeIcon //{ // QImage image; // QColor color; //}UkuiThemeIcon; //enum DisplayerMode{ // MINI_MODE, // ADVANCED_MODE //}; //enum SwitchButtonState{ // SWITCH_BUTTON_NORMAL, // SWITCH_BUTTON_HOVER, // SWITCH_BUTTON_PRESS //}; //static QColor symbolic_color = Qt::gray; //class UkuiMediaSliderTipLabel:public QLabel //{ // public: // UkuiMediaSliderTipLabel(); // ~UkuiMediaSliderTipLabel(); //protected: // void paintEvent(QPaintEvent*); //}; //class UkuiButtonDrawSvg:public QPushButton //{ // Q_OBJECT //public: // UkuiButtonDrawSvg(QWidget *parent = nullptr); // ~UkuiButtonDrawSvg(); // QPixmap filledSymbolicColoredPixmap(QImage &source, QColor &baseColor); // QRect IconGeometry(); // void draw(QPaintEvent* e); // void init(QImage image ,QColor color); // friend class DeviceSwitchWidget; // friend class UkmediaOsdDisplayWidget; //protected: // void paintEvent(QPaintEvent *event); // bool event(QEvent *e); //private: // UkuiThemeIcon themeIcon; //}; //class UkuiApplicationWidget:public QWidget //{ // Q_OBJECT //public: // UkuiApplicationWidget(QWidget *parent = nullptr); // ~UkuiApplicationWidget(); //protected: // void paintEvent(QPaintEvent*); //}; //class UkuiMediaButton:public QPushButton //{ // Q_OBJECT //public: // UkuiMediaButton(QWidget *parent = nullptr); // ~UkuiMediaButton(); // friend class UkmediaMiniMasterVolumeWidget; //Q_SIGNALS: // void advanceToMiniSignal(); // void miniToAdvanceSignal(); // void moveMiniSwitchBtnSignale(); // void moveAdvanceSwitchBtnSignal(); //protected: // void mousePressEvent(QMouseEvent *e)override; // void mouseMoveEvent(QMouseEvent *e)override; // void mouseReleaseEvent(QMouseEvent *e)override; //// void paintEvent(QPaintEvent *event); // void enterEvent(QEvent *event); // void leaveEvent(QEvent *event); //private: //}; //class UkmediaVolumeSlider : public QSlider //{ // Q_OBJECT //public: //// UkmediaVolumeSlider(QWidget *parent = nullptr); // UkmediaVolumeSlider(QWidget *parent = nullptr,bool needTip = false); // void initStyleOption(QStyleOptionSlider *option); // ~UkmediaVolumeSlider(); //Q_SIGNALS: // void silderPressSignal(); // void silderReleaseSignal(); //private: // UkuiMediaSliderTipLabel *m_pTiplabel; // bool state = false; // bool mousePress = false; //protected: // void mousePressEvent(QMouseEvent *ev); // void mouseReleaseEvent(QMouseEvent *e); // void mouseMoveEvent(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); // QSlider::mousePressEvent(e); //// setCursor(QCursor(Qt::OpenHandCursor)); //// m_displayLabel->move((this->width()-m_displayLabel->width())*this->value()/(this->maximum()-this->minimum()),3); //// QSlider::mouseMoveEvent(e); // } // void leaveEvent(QEvent *e); // void enterEvent(QEvent *e); // void paintEvent(QPaintEvent *e); //}; //class UkuiScrollArea : public QScrollArea //{ // Q_OBJECT //public: // UkuiScrollArea(QWidget *parent = nullptr); // ~UkuiScrollArea(); //protected: // void paintEvent(QPaintEvent *e); //}; //class UkuiQMenu:public QMenu //{ // Q_OBJECT //public: // UkuiQMenu(); // ~UkuiQMenu(); //protected: // void hideEvent(QHideEvent *e); // virtual bool event(QEvent *e) override; //}; #endif // UKMEDIAVOLUMESLIDER_H ukui-media/ukui-volume-control-applet-qt/ukui_volume_control_user_config.h0000664000175000017500000000157515170054730026251 0ustar fengfeng#ifndef UKUIVOLUMECONTROLUSERCONFIG_H #define UKUIVOLUMECONTROLUSERCONFIG_H #include #include #include #include "../common/user_config.h" #include "../common/sound_effect_json.h" #include "../common/user_info_json.h" class UkuiVolumeControlUserConfig : public UserConfig { public: UkuiVolumeControlUserConfig(); ~UkuiVolumeControlUserConfig(); public: std::unordered_map> getJsonMap() const; virtual void initJsonMap(); private: QString getSoundEffectFullPath(); QString getUserInfoFullPath(); private: std::unordered_map> m_keys { {JsonType::JSON_TYPE_SOUNDEFFECT, [this](){ return getSoundEffectFullPath();}}, {JsonType::JSON_TYPE_USERINFO, [this](){ return getUserInfoFullPath(); }}, }; }; #endif // UKUIVOLUMECONTROLUSERCONFIG_H ukui-media/ukui-volume-control-applet-qt/ukui-volume-control-applet-qt.pro0000664000175000017500000000560515170054730025776 0ustar fengfeng###################################################################### # Automatically generated by qmake (3.1) Fri Oct 11 17:35:00 2019 ###################################################################### QT += core gui dbus xml greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TEMPLATE = app TARGET = ukui-volume-control-applet-qt INCLUDEPATH += . QMAKE_CXXFLAGS += -std=c++11 PKGCONFIG += kysdk-waylandhelper include(QtSingleApplication/qtsingleapplication.pri) include(xatom/xatom.pri) include(dbus-adaptor/dbus-adaptor.pri) DEFINES += QT_DEPRECATED_WARNINGS # You can also make your code fail to compile if you use deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 # Default rules for deployment. target.path = /usr/bin CONFIG += \ c++17 \ no_keywords link_pkgconfig debug\ PKGCONFIG += \ glib-2.0 \ gsettings-qt6 \ Qt6Svg \ gio-2.0 \ dconf \ x11 \ libcanberra \ libpulse \ libpulse-mainloop-glib \ alsa \ kysdk-sysinfo \ kysdk-hardware \ kysdk-waylandhelper \ kysdk-qtwidgets \ KF6WindowSystem # libwnck-1.0 LIBS += -lukui-log4qt -lukcc HEADERS += \ ../common/ukmedia_common.h \ config.h \ custom_sound.h \ ukmedia_custom_class.h \ ukmedia_main_widget.h \ ukmedia_application_volume_widget.h \ ukmedia_monitor_window_thread.h \ ukmedia_osd_display_widget.h \ ukmedia_system_volume_widget.h \ ukmedia_volume_control.h \ ukui_list_widget_item.h \ ukui_media_set_headset_widget.h \ ../common/json.h \ ../common/sound_effect_json.h \ ../common/user_info_json.h \ ../common/user_config.h \ ukui_volume_control_user_config.h SOURCES += \ custom_sound.cpp \ main.cpp \ ukmedia_custom_class.cpp \ ukmedia_application_volume_widget.cpp \ ukmedia_main_widget.cpp \ ukmedia_monitor_window_thread.cpp \ ukmedia_osd_display_widget.cpp \ ukmedia_system_volume_widget.cpp \ ukmedia_volume_control.cpp \ ukui_list_widget_item.cpp \ ukui_media_set_headset_widget.cpp \ ../common/sound_effect_json.cpp \ ../common/user_info_json.cpp \ ../common/user_config.cpp \ ../common/ukmedia_common.cpp \ ukui_volume_control_user_config.cpp RESOURCES += \ res.qrc TRANSLATIONS += \ translations/zh_CN.ts \ translations/tr.ts \ translations/bo.ts \ translations/mn.ts isEmpty(QMAKE_LRELEASE) { system("/usr/lib/qt6/bin/lrelease translations/*.ts") } else { system($$QMAKE_LRELEASE translations/*.ts) } qm_file.files = translations/*.qm qm_file.path = $${PREFIX}/translations/ data_files.files = data/* data_files.path = $${PREFIX}/ INSTALLS += \ target qm_file data_files #DISTFILES += \ # xatom/xatom.pri ukui-media/ukui-volume-control-applet-qt/ukmedia_system_volume_widget.cpp0000664000175000017500000001471415170054730026071 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #include #include UkmediaSystemVolumeWidget::UkmediaSystemVolumeWidget(QWidget *parent) : QWidget(parent) { connect(qApp, &QApplication::paletteChanged, this, &UkmediaSystemVolumeWidget::onPaletteChanged); m_pOutputListWidget = new QListWidget(this); m_pInputListWidget = new QListWidget(this); m_pSysSliderWidget = new QWidget(this); m_pOutputListWidget->setFixedSize(412,245); m_pOutputListWidget->setFrameShape(QFrame::Shape::NoFrame); QPalette pal = m_pOutputListWidget->palette(); pal.setBrush(QPalette::Base, QColor(0,0,0,0)); //背景透明 m_pOutputListWidget->setPalette(pal); m_pOutputListWidget->setProperty("needTranslucent", true); m_pOutputListWidget->setSelectionMode(QAbstractItemView:: NoSelection); QWidget *outputWidget = new QWidget(this); outputWidget->setFixedSize(412,245); QHBoxLayout *outputWidgetLayout = new QHBoxLayout; outputWidgetLayout->addWidget(m_pOutputListWidget); outputWidgetLayout->setSpacing(0); outputWidget->setLayout(outputWidgetLayout); if (UkmediaCommon::getInstance().isHWKLanguageEnv(getenv("LANGUAGE"))) { outputWidgetLayout->setContentsMargins(5,0,0,0); } else { outputWidgetLayout->setContentsMargins(0,0,8,0); } m_pInputListWidget->hide(); m_pSysSliderWidget->setFixedSize(412,48); m_pSystemVolumeBtn = new QPushButton(m_pSysSliderWidget); m_pSystemVolumeBtn->setFixedSize(36,36); m_pSystemVolumeBtn->setCheckable(true); m_pSystemVolumeBtn->setProperty("isRoundButton",true); //圆形按钮 m_pSystemVolumeBtn->setProperty("useButtonPalette",true); //灰色按钮 m_pSystemVolumeBtn->setProperty("needTranslucent", true); //灰色半透明按钮 m_pSystemVolumeSlider = new AudioSlider(m_pSysSliderWidget); m_pSystemVolumeSlider->setOrientation(Qt::Horizontal); m_pSystemVolumeSlider->setFocusPolicy(Qt::StrongFocus); m_pSystemVolumeSlider->setProperty("needTranslucent", true); // Increase translucent effect m_pSystemVolumeSlider->setFixedSize(286,48); m_pSystemVolumeSlider->setRange(0,100); m_pSystemVolumeSliderLabel = new QLabel(m_pSysSliderWidget); m_pSystemVolumeSliderLabel->setText("0%"); m_pSystemVolumeSliderLabel->setFixedSize(52,48); //音量label布局 m_pSystemVolumeLabel = new QLabel(this); m_pSystemVolumeLabel->setText(tr("Volume")); m_pSystemVolumeLabel->setFixedSize(372,29);//防止字体显示不全 QWidget *systemVolumeLabelWidget = new QWidget(this); systemVolumeLabelWidget->setFixedSize(412,29); QHBoxLayout *systemVolumeLabelLayout = new QHBoxLayout; systemVolumeLabelLayout->addWidget(m_pSystemVolumeLabel); systemVolumeLabelWidget->setLayout(systemVolumeLabelLayout); systemVolumeLabelLayout->setContentsMargins(16,0,24,0); //输出label布局 m_pOutputLabel = new QLabel(this); m_pOutputLabel->setText(tr("Output")); m_pOutputLabel->setFixedSize(372,29); QWidget *outputLabelWidget = new QWidget(this); outputLabelWidget->setFixedSize(412,29); QHBoxLayout *outputLabelLayout = new QHBoxLayout; outputLabelLayout->addWidget(m_pOutputLabel); outputLabelWidget->setLayout(outputLabelLayout); outputLabelLayout->setContentsMargins(16,0,24,0); QPalette palette = m_pSystemVolumeLabel->palette(); QColor color = palette.color(QPalette::PlaceholderText); palette.setColor(QPalette::WindowText,color); m_pSystemVolumeLabel->setPalette(palette); m_pOutputLabel->setPalette(palette); volumeSettingButton = new UkuiSettingButton(volumeSettingFrame); volumeSettingButton->setText(tr("Sound Settings")); volumeSettingButton->setCursor(Qt::PointingHandCursor); volumeSettingButton->setScaledContents(true); volumeSettingFrame = new QFrame(this); volumeSettingFrame->setFixedHeight(52); QHBoxLayout *volumeSettingLayout = new QHBoxLayout; volumeSettingLayout->addWidget(volumeSettingButton); volumeSettingLayout->addStretch(); volumeSettingFrame->setLayout(volumeSettingLayout); volumeSettingLayout->setContentsMargins(16,0,24,0); //系统音量滑动条布局 QHBoxLayout *sysVolumeLay = new QHBoxLayout(); sysVolumeLay->addWidget(m_pSystemVolumeBtn); sysVolumeLay->addSpacing(3); sysVolumeLay->addWidget(m_pSystemVolumeSlider); sysVolumeLay->addWidget(m_pSystemVolumeSliderLabel); sysVolumeLay->setSpacing(0); m_pSysSliderWidget->setLayout(sysVolumeLay); sysVolumeLay->setContentsMargins(11,0,24,0); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(systemVolumeLabelWidget); mainLayout->addSpacing(3); mainLayout->addWidget(m_pSysSliderWidget); mainLayout->addSpacing(13); mainLayout->addWidget(outputLabelWidget); mainLayout->addSpacing(3); mainLayout->addWidget(outputWidget); mainLayout->addSpacing(2); mainLayout->addWidget(volumeSettingFrame); mainLayout->setSpacing(0); this->setLayout(mainLayout); mainLayout->setContentsMargins(8,12,0,0); this->setFixedSize(420,436); } void UkmediaSystemVolumeWidget::setLabelAlignment(Qt::Alignment alignment) { m_pSystemVolumeSliderLabel->setAlignment(alignment); } void UkmediaSystemVolumeWidget::onPaletteChanged(){ QPalette palette = m_pSystemVolumeLabel->palette(); QColor color = palette.color(QPalette::PlaceholderText); palette.setColor(QPalette::WindowText,color); m_pSystemVolumeLabel->setPalette(palette); m_pOutputLabel->setPalette(palette); } void UkmediaSystemVolumeWidget::setVolumeSliderRange(bool status) { int maxValue = (status) ? 125 : 100; m_pSystemVolumeSlider->blockSignals(true); m_pSystemVolumeSlider->setRange(0, maxValue); m_pSystemVolumeSlider->blockSignals(false); } ukui-media/ukui-volume-control-applet-qt/ukmedia_application_volume_widget.h0000664000175000017500000000402715170054730026511 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #include #include #include #include #include #include "ukmedia_custom_class.h" class ApplicationVolumeWidget : public QWidget { Q_OBJECT public: ApplicationVolumeWidget(QWidget *parent = nullptr); ~ApplicationVolumeWidget(); // void fullushBlueRecordStream(); // void deleteBlueRecordStream(); void setVolumeSliderRange(bool status); friend class DeviceSwitchWidget; friend class UkmediaMainWidget; private: void setLabelAlignment(Qt::Alignment alignment); private: QLabel *applicationLabel; QLabel *systemVolumeLabel; QPushButton *systemVolumeBtn; AudioSlider *systemVolumeSlider; QLabel *systemVolumeDisplayLabel; QWidget *systemVolumeSliderWidget; QWidget *displayAppVolumeWidget; QStringList *app_volume_list; QLabel *appVolumeLabel; QPushButton *appMuteBtn; QPushButton *appIconBtn; QSlider *appSlider; QScrollArea *appArea; QVBoxLayout *m_pVlayout; QFrame * volumeSettingFrame; UkuiSettingButton *volumeSettingButton; bool isRecording = false; protected: // void paintEvent(QPaintEvent *event); public Q_SLOTS: void onPaletteChanged(); }; #endif // APPLICATIONVOLUMEWIDGET_H ukui-media/ukui-volume-control-applet-qt/custom_sound.cpp0000664000175000017500000001372015170054730022632 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 ='0' && nodeName.at(0)<='9'){ nodeName = "Audio_"+nodeName; } while(!ele.isNull()) { if(ele.nodeName() == nodeName) return true; ele = ele.nextSiblingElement(); } } else { QFile::remove(audioPath); qDebug() << errorStr << "line: " << errorLine << "col: " << errorCol; } file.close(); } return false; } //添加第一个子节点及其子元素 int CustomSound::addXmlNode(QString nodeName, bool initState) { //打开文件 QString audioPath = QDir::homePath() + "/.config/customAudio.xml"; QFile file(audioPath); QDomDocument doc;//增加一个一级子节点以及元素 if(file.open(QFile::ReadOnly)){ if(doc.setContent(&file)){ file.close(); } else { file.close(); return -1; } } else return -1; //添加新节点 nodeName.remove(" "); nodeName.remove("/"); nodeName.remove("("); nodeName.remove(")"); nodeName.remove("["); nodeName.remove("]"); nodeName.remove(";"); if(nodeName != "" && nodeName.at(0)>='0' && nodeName.at(0)<='9'){ nodeName = "Audio_"+nodeName; } QDomElement root=doc.documentElement(); QDomElement node=doc.createElement(nodeName); QDomElement init=doc.createElement("init"); QDomText text; if(initState) text = doc.createTextNode("true"); else text = doc.createTextNode("false"); init.appendChild(text); node.appendChild(init); root.appendChild(node); qDebug() << "addXmlNode" << nodeName ; //修改first-run状态 QDomElement ele = root.firstChildElement(); while(!ele.isNull()) { if(ele.nodeName() == "firstRun"){ QString value = ele.firstChildElement().firstChild().nodeValue(); if(value == "true") ele.firstChildElement().firstChild().setNodeValue("false"); } ele = ele.nextSiblingElement(); } if(file.open(QFile::WriteOnly|QFile::Truncate)) { //输出到文件 QTextStream out_stream(&file); doc.save(out_stream,4); //缩进4格 file.close(); } return 0; } bool CustomSound::isFirstRun() { QString audioPath = QDir::homePath() + "/.config/customAudio.xml"; QFile file(audioPath); if(!file.exists()){ createAudioFile(); } if(file.open(QFile::ReadOnly)){ QDomDocument doc; if(doc.setContent(&file)){ file.close(); QDomElement root = doc.documentElement(); QDomElement ele = root.firstChildElement(); qDebug()<<"===================ele.nodeName() :"< #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dbus-adaptor/dbus-adaptor.h" #include "dbus-adaptor/bluez-adaptor.h" #include "custom_sound.h" #include "ukmedia_application_volume_widget.h" #include "ukmedia_osd_display_widget.h" #include "ukui_media_set_headset_widget.h" #include "ukmedia_volume_control.h" #include "ukui_list_widget_item.h" #include "ukmedia_system_volume_widget.h" extern "C" { #include #include #include #include } #include #include // 为平板模式提供设置音量值 #define UKUI_VOLUME_KEY "volumesize" //音量大小 #define UKUI_VOLUME_STATE "soundstate" //音量状态 #define UKUI_VOLUME_BRIGHTNESS_GSETTING_ID "org.ukui.quick-operation.panel" // 初始化音量 #define HEADPHONE_VOLUME 11141 #define HDMI_VOLUME 65536 #define OUTPUT_VOLUME 43909 #define MIC_VOLUME 65536 // 任务栏多屏显示声音应用获取屏幕可用区域接口 #define PANEL_SETTINGS "org.ukui.panel.settings" #define PANEL_SIZE_KEY "panelsize" #define PANEL_POSITION_KEY "panelposition" const QByteArray TRANSPARENCY_GSETTINGS = "org.ukui.control-center.personalise"; static const gchar *iconNameOutputs[] = { "audio-volume-muted-symbolic", "audio-volume-low-symbolic", "audio-volume-medium-symbolic", "audio-volume-high-symbolic", NULL }; static const gchar *iconNameInputs[] = { "microphone-sensitivity-muted-symbolic", "microphone-sensitivity-low-symbolic", "microphone-sensitivity-medium-symbolic", "microphone-sensitivity-high-symbolic", NULL }; typedef struct { const pa_card_port_info *headphones; const pa_card_port_info *headsetmic; const pa_card_port_info *headphonemic; const pa_card_port_info *internalmic; const pa_card_port_info *internalspk; } headsetPorts; class QDBusServiceWatcher; class UkmediaMainWidget : public QMainWindow { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.ukui.media")//ukui-media的dbus接口 public: explicit UkmediaMainWidget(QMainWindow *parent = nullptr); ~UkmediaMainWidget(); enum PanelPosition{ Bottom = 0, //!< The bottom side of the screen. Top, //!< The top side of the screen. Left, //!< The left side of the screen. Right //!< The right side of the screen. }; protected: bool eventFilter(QObject *obj, QEvent *event); void paintEvent(QPaintEvent *event); void keyPressEvent(QKeyEvent *event); Q_SIGNALS: void updateVolume(int value); void updateSourceVolume(int value); void updateMute(bool isMute); void updateSourceMute(bool isMute); void updateDevSignal(); public Q_SLOTS: QString getDefaultOutputDevice(); QString getDefaultInputDevice(); bool isNoneOutputDevice(); bool isNoneInputDevice(); QStringList getAllOutputDevices(); QStringList getAllInputDevices(); int getDefaultOutputVolume(); int getDefaultInputVolume(); bool getDefaultOutputMuteState(); bool getDefaultInputMuteState(); bool setDefaultOutputVolume(int value); bool setDefaultInputVolume(int value); bool setDefaultOutputMuteState(bool mute); bool setDefaultInputMuteState(bool mute); bool setDefaultOutputDevice(QString deviceName); bool setDefaultInputDevice(QString deviceName); void initPanelGSettings(); QRect caculatePosition(); //任务栏多屏显示声音应用获取屏幕可用区域接口 void advancedWidgetShow(); //显示advance widget QStringList getAppList(); QList getRecordAppInfo(); QList getPlaybackAppInfo(); QString getSystemInputDevice(); QString getSystemOutputDevice(); QString getAppInputDevice(QString appName); QString getAppOutputDevice(QString appName); int getAppVolume(QString appName); bool getAppMuteState(QString appName); bool setAppVolume(QString appName, int value); bool setAppMuteState(QString appName, bool state); bool setSystemInputDevice(QString portLabel); bool setSystemOutputDevice(QString portLabel); bool setAppOutputDevice(QString appName, int cardIndex, QString sinkPortName); bool setAppInputDevice(QString appName, int cardIndex, QString sourcePortName); bool isPortHidingNeeded(int soundType, int cardIndex, QString portLabel); private Q_SLOTS: void switchMonoAudio(); void switchModuleLoopBack(); void activatedSystemTrayIconSlot(QSystemTrayIcon::ActivationReason reason); //托盘图标 void jumpControlPanelSlot(); //跳转到控制面板 void setHeadsetPort(QString str); //根据弹出框选择的切换不同的端口 void handleTimeout(); void advancedSystemSliderChangedSlot(int value); //advancedSystemSlider值改变 void systemVolumeSliderChangedSlot(int value); void systemVolumeButtonClickedSlot(); void mouseMeddleClickedTraySlot(); //鼠标中间贱点击托盘图标 void appWidgetMuteButtonCLickedSlot();//应用音量静音按钮点击 void ukuiThemeChangedSlot(const QString &themeStr); //主题改变 void soundThemeChangedSlot(const QString &soundThemeStr); void fontSizeChangedSlot(const QString &themeStr); //字体大小改变 void initVolumeSlider(); //初始化滑动条 void addAppToAppwidget(QString name, QString iconName, int index, int value, int channel); //添加应用 void removeSubAppWidget(QString m_pAppName); //移除子应用窗口 void sinkInputVolumeChangedSlot(QString name, QString iconName, int value); //sinkInput音量更新 void soundSettingChanged(const QString &); //添加拔插headset提示 void checkAudioDeviceSelectionNeeded (const pa_card_info *info); void updateDevicePort(); void updateListWidgetItemSlot(); //更新输出设备列表 void outputListWidgetCurrentRowChangedSlot(int row); //output list widget选项改变 void inputListWidgetCurrentRowChangedSlot(int row); //output list widget选项改变 void updateAppVolume(QString str, int value, bool state); void deviceChangedShowSlot(QString dev_name);//osd提示弹窗 void onTransChanged(); void paintWithTrans(); void onPrepareForSleep(bool sleep); void getSessionActive(); //获取session的活跃状态 void batteryLevelChanged(QString dev_macAddr, int battery); void sendUpdateVolumeSignal(int soundType, int value); void sendUpdateMuteSignal(int soundType, bool isMute); void initBlueDeviceVolume(int index, QString name); void deviceAdjustSlots(int); private: void UkmediaDbusRegister(); //注册Dbus信号 void initSystemTrayIcon(); //初始化系统托盘 void initWidget(); //初始化弹出框 void initVolumeLed();//初始化静音灯 void initGsettings(); //初始化gsetting void initStrings(); void initLabelAlignment(); //初始化label对齐方式 void initSystemVolume(); //初始化系统音量 void myLine(QFrame *volumeLine,QFrame *volumeSettingLine);//添加分隔线 void dealSlot(); //处理槽函数 void systemTrayMenuInit(); //初始化右键菜单 void initOutputListWidgetItem();//初始化默认选择的输出设备 void initInputListWidgetItem();//初始化默认选择的输输入设备 QPixmap drawLightColoredPixmap(const QPixmap &source); //绘制高亮颜色图标 QPixmap drawDarkColoredPixmap(const QPixmap &source); //绘制深色颜色图标 void themeChangeIcons(); //主题更改时更新图标颜色 void hideWindow(); //隐藏窗口 void inputVolumeDarkThemeImage(int value,bool status); //绘制input button颜色 void outputVolumeDarkThemeImage(int value,bool status); //绘制output button颜色 void drawImagColorFromTheme(UkuiButtonDrawSvg *button, QString iconStr); //绘制iamge颜色 QString outputVolumeToIconStr(bool status,int volume); QString getAppName(QString desktopfp); //从desktop中获取应用名称 QString getAppIcon(QString desktopfp); //从desktop中获取应用图标 QString appNameToIconName(QString appName, QString appIconName); void initSubApplicationWidget(QString str); //子应用窗口布局 static gboolean verifyAlsaCard (int cardindex,gboolean *headsetmic,gboolean *headphonemic); static headsetPorts *getHeadsetPorts (const pa_card_info *c); void freePrivPortNames(); void setDeviceButtonState(int row); void addOutputListWidgetItem(QString portName, QString portLabel,QString cardName); //添加output listWidget item void addInputListWidgetItem(QString portName, QString cardName); //添加input listwidget item int findCardIndex(QString cardName, QMap cardMap);//查找声卡指定的索引 QString findCardName(int index,QMap cardMap); QString findHighPriorityProfile(int index,QString profile); QString findPortSink(int index,QString portName); QString findPortSource(int index,QString portName); bool outputPortIsNeedDelete(int index,QString name);//port是否需要在outputListWidget删除 bool outputPortIsNeedAdd(int index,QString name);//port是否需要在outputListWidget删除 bool inputPortIsNeedDelete(int index,QString name);//port是否需要在inputListWidget删除 bool inputPortIsNeedAdd(int index,QString name);//port是否需要在inputListWidget删除 int indexOfOutputPortInOutputListWidget(QString portName); int indexOfInputPortInInputListWidget(QString portName); void deleteNotAvailableOutputPort(); //删除不可用的输出端口 void addAvailableOutputPort(); //添加可用输出端口 void deleteNotAvailableInputPort(); //删除不可用的输入端口 void addAvailableInputPort(); //添加可用输入端口 QString findOutputPortName(int index,QString portLabel); //找到outputPortLabel对应的portName QString findInputPortName(int index,QString portLabel); //找到inputPortLabel对应的portName QString findOutputPortLabel(int index,QString portName); //查找名为portName对应的portLabel QString findInputPortLabel(int index,QString portName); //查找名为portName对应的portLabel void setCardProfile(QString name,QString profile); //设置声卡的配置文件 void setDefaultOutputPortDevice(QString devName,QString portName); //设置默认的输出端口 void setDefaultInputPortDevice(int cardIndex, QString portLabel); //设置默认的输入端口 QString findCardActiveProfile(int index); //查找声卡的active profile QString stringRemoveUnrecignizedChar(QString str);//移除xml文件中不能识别的字符 void findOutputListWidgetItem(QString cardName,QString portLabel); void findInputListWidgetItem(QString cardName,QString portLabel); QString blueCardName(); //记录蓝牙声卡名称 bool inputDeviceContainBluetooth(); QString findFile(const QString path,QString str); void inhibit(); void uninhibit(); void switchModuleEchoCancel(); //加载或卸载降噪模块 void resetVolumeSliderRange(); //重新设置滑动条的范围 void switchStartupPlayMusic(); //接收到开关按钮的gsetting信号 void monitorSessionStatus(); void playAlertSound(QString soundEvent); //播放提示音 void showUkuiVolumeControlTray(); private: FixLabel *systemWidgetLabel; FixLabel *appWidgetLabel; QHBoxLayout * m_tabBarLayout = nullptr; UkmediaVolumeControl *m_pVolumeControl; Bluetooth_Dbus *m_pBluetoothDbus; MediaAdaptor *m_pMediaDbus; UkmediaSystemVolumeWidget *systemWidget; QTabWidget *m_pTabWidget; ApplicationVolumeWidget *appWidget; //应用界面窗口 UkmediaOsdDisplayWidget *osdWidget; UkuiMediaSetHeadsetWidget *headsetWidget; //当声卡中有headsetmic 端口时插入4段式耳机的弹窗 UkmediaOsdDisplayWidget *headset; //需求:输出设备插拔提示弹出 Divider *volumeLine; Divider *volumeSettingLine; QTimer *timer; //定时器 QMenu *menu; //右键菜单 QGSettings *m_pSoundSettings; QGSettings *m_pStartUpSetting; QGSettings *m_pThemeSetting; //主题gsettting QGSettings *m_pTransparencySetting; //透明度gestting QGSettings *m_pVolumeSetting; // QGSettings *m_pFontSetting; //字体gsetting QGSettings *m_pSoundThemeSetting; QGSettings * m_pTransGsettings; //透明度配置文件 double m_pTransparency=0.0; //透明度 QSystemTrayIcon *soundSystemTrayIcon; //系统托盘 QAction *m_pSoundPreferenceAction; //声音首选项菜单项 QStringList *eventList; //提示声音类型列表 QStringList *eventIdNameList; //提示声音名称列表 QMap currentOutputPortLabelMap; QMap currentInputPortLabelMap; int headsetCard; //headset 声卡 gboolean hasHeadsetmic; //是否包含headset mic端口 gboolean hasHeadphonemic; //是否包含headphone mic端口 gboolean headsetPluggedIn; //headpset 是否插入 char *m_pHeadphonesName; //headphone名称 char *m_pHeadsetmicName; // char *m_pHeadphonemicName; char *m_pInternalspkName; char *m_pInternalmicName; QTimer *m_pTimer = nullptr; bool shortcutMute = false; bool firstEntry = true; QProcess *m_process; QString mThemeName = UKUI_THEME_DEFAULT; bool firstEnterSystem = true; ca_context *caContext; bool firstLoad = true; bool isPlay = false; //用于避免运行托盘时第一次响起的提示音 bool sinkInputMute = false; bool kylinVideoMuteSignal = false; bool kylinVideoVolumeSignal = false; bool isAppMuteBtnPress = false; int outputListWidgetRow = -1; QString m_languageEnvStr; QString m_sessionControllerName; QString m_sessionPath; QString m_sessionControllerService ; QString m_sessionControllerPath; QString m_sessionControllerManagerInterface; QString m_sessionControllerSeatInterface ; QString m_sessionControllerSessionInterface ; QString m_sessionControllerActiveProperty ; QString m_sessionControllerPropertiesInterface; QDBusUnixFileDescriptor m_inhibitFileDescriptor; QGSettings *m_panelGSettings = nullptr; int m_panelPosition; int m_panelSize; SystemVersion m_version; int m_enableDeviceCount = 0; }; #endif // UKMEDIAMAINWIDGET_H ukui-media/ukui-volume-control-applet-qt/ukmedia_main_widget.cpp0000664000175000017500000040517315170054730024105 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAINWINDOW_WIDTH 420 #define MAINWINDOW_HEIGHT 476 #define WIDGET_RADIUS 12 #define CONTENTS_MARGINS_WIDTH 0,0,0,0 #define VOLUMELINE_POS 0,140 #define SETTINGLINE_POS 0,424 #define KEYBINDINGS_CUSTOM_SCHEMA "org.ukui.media.sound" #define KEYBINDINGS_CUSTOM_DIR "/org/ukui/sound/keybindings/" #define EVENT_SOUNDS_KEY "event-sounds" #define MAX_CUSTOM_SHORTCUTS 1000 #define FILENAME_KEY "filename" #define NAME_KEY "name" double transparency = 0.8; extern bool isCheckBluetoothInput; UkmediaMainWidget::UkmediaMainWidget(QMainWindow *parent) : QMainWindow(parent), m_languageEnvStr(getenv("LANGUAGE")), m_version(UkmediaCommon::getInstance().getSystemVersion()) { //初始化系统托盘 m_pVolumeControl = new UkmediaVolumeControl(); //初始化gsetting initGsettings(); initStrings(); //初始化界面 initWidget(); initLabelAlignment(); //初始化托盘图标 initSystemTrayIcon(); //Dbus注册 UkmediaDbusRegister(); //处理槽函数 dealSlot(); } bool UkmediaMainWidget::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::ActivationChange) { if(QApplication::activeWindow() != this) { this->hide(); } } return QWidget::eventFilter(obj,event); } void UkmediaMainWidget::paintEvent(QPaintEvent *event) { } void UkmediaMainWidget::initSystemTrayIcon() { soundSystemTrayIcon = new QSystemTrayIcon(this); ca_context_create(&caContext); //为系统托盘图标添加菜单静音和声音首选项 soundSystemTrayIcon->setToolTip(tr("Output volume control")); #if (QT_VERSION <= QT_VERSION_CHECK(5,6,1)) m_pSoundPreferenceAction = new QAction(tr("Sound preference"),this); #elif (QT_VERSION > QT_VERSION_CHECK(5,6,1)) m_pSoundPreferenceAction = new QAction(tr("Sound preference")); #endif QString settingsIconStr = "document-page-setup-symbolic"; QIcon settingsIcon = QIcon::fromTheme(settingsIconStr); m_pSoundPreferenceAction->setIcon(settingsIcon); soundSystemTrayIcon->setVisible(true); initPanelGSettings(); int nOutputValue = UkmediaCommon::getInstance().paVolumeToMediaVolume(m_pVolumeControl->getSinkVolume()); bool outputStatus = m_pVolumeControl->getSinkMute(); outputVolumeDarkThemeImage(nOutputValue, outputStatus); //初始化右键菜单 systemTrayMenuInit(); } /* * 初始化右键菜单 */ void UkmediaMainWidget::systemTrayMenuInit() { menu = new QMenu(); menu->setAttribute(Qt::WA_NoMouseReplay); soundSystemTrayIcon->setContextMenu(menu); //设置右键菜单 menu->addAction(m_pSoundPreferenceAction); } /* * 初始化界面 */ void UkmediaMainWidget::initWidget() { QWidget::installEventFilter(this);//为这个窗口安装过滤器 this->setFixedSize(MAINWINDOW_WIDTH,MAINWINDOW_HEIGHT); this->setAttribute(Qt::WA_TranslucentBackground,true); //透明 this->setMouseTracking(true); this->setFocusPolicy(Qt::NoFocus); QString platform = QGuiApplication::platformName(); if(!platform.startsWith(QLatin1String("wayland"),Qt::CaseInsensitive)){ QPainterPath path; auto rect = this->rect(); path.addRoundedRect(rect, WIDGET_RADIUS, WIDGET_RADIUS); // KWindowEffects removed in KF6 // KWindowEffects::enableBlurBehind(this->winId(), true, QRegion(path.toFillPolygon().toPolygon())); // 背景模糊 } osdWidget = new UkmediaOsdDisplayWidget(); headsetWidget = new UkuiMediaSetHeadsetWidget; // osd弹窗 m_pTabWidget = new QTabWidget(this); m_pTabWidget->setFocusPolicy(Qt::StrongFocus); this->setCentralWidget(m_pTabWidget); m_pTabWidget->tabBar()->setFixedWidth(this->width()+1); systemWidget = new UkmediaSystemVolumeWidget(m_pTabWidget); appWidget = new ApplicationVolumeWidget(m_pTabWidget); //需求31268待2501再合入 #if 1 systemWidget->m_pSystemVolumeSlider->setEnabled(false); appWidget->systemVolumeSlider->setEnabled(false); systemWidget->m_pSystemVolumeBtn->setEnabled(false); appWidget->systemVolumeBtn->setEnabled(false); #endif volumeLine = new Divider(this); volumeSettingLine = new Divider(this); volumeLine->move(VOLUMELINE_POS); volumeSettingLine->move(SETTINGLINE_POS); m_pTabWidget->addTab(systemWidget, tr("")); m_pTabWidget->addTab(appWidget,tr("")); m_tabBarLayout = new QHBoxLayout(this); m_tabBarLayout->setContentsMargins(CONTENTS_MARGINS_WIDTH); systemWidgetLabel = new FixLabel(tr("System Volume")); systemWidgetLabel->setAlignment(Qt::AlignCenter); appWidgetLabel = new FixLabel(tr("App Volume")); appWidgetLabel->setAlignment(Qt::AlignCenter); m_tabBarLayout->addWidget(systemWidgetLabel); m_tabBarLayout->addWidget(appWidgetLabel); m_pTabWidget->tabBar()->setLayout(m_tabBarLayout); m_pTabWidget->tabBar()->setProperty("setRadius", WIDGET_RADIUS); // tabbar界面内圆角 m_pTabWidget->tabBar()->setProperty("useTabbarSeparateLine",false); // 去掉标签页中间的分隔线 m_pTabWidget->setAttribute(Qt::WA_TranslucentBackground,true); // 背景透明 解决切换黑屏问题 paintWithTrans(); } void UkmediaMainWidget::onTransChanged() { m_pTransparency = m_pTransGsettings->get("transparency").toDouble() + 0.15; m_pTransparency = (m_pTransparency > 1) ? 1 : m_pTransparency; paintWithTrans(); } void UkmediaMainWidget::paintWithTrans() { QPalette pal = m_pTabWidget->palette(); QColor color = qApp->palette().base().color(); color.setAlphaF(m_pTransparency); pal.setColor(QPalette::Base, color); m_pTabWidget->setPalette(pal); QPalette tabPal = m_pTabWidget->tabBar()->palette(); tabPal.setColor(QPalette::Base, color); QColor inactiveColor = qApp->palette().window().color(); inactiveColor.setAlphaF(0.86 *m_pTransparency); tabPal.setColor(QPalette::Window, inactiveColor); m_pTabWidget->tabBar()->setPalette(tabPal); } /* * 添加分隔线 */ void UkmediaMainWidget::myLine(QFrame *volumeLine,QFrame *volumeSettingLine) { volumeLine = new QFrame(this); volumeLine->setFrameShape(QFrame::NoFrame); volumeLine->setFrameStyle(QFrame::HLine); volumeLine->setFixedSize(420,1); volumeLine->setParent(this); QPalette palette = volumeLine->palette(); QColor color = QColor(239,239,239); color.setAlphaF(0.1); palette.setColor(QPalette::WindowText, color); volumeLine->setPalette(palette); volumeSettingLine = new QFrame(this); volumeSettingLine->setFrameShape(QFrame::NoFrame); volumeSettingLine->setFrameStyle(QFrame::HLine); volumeSettingLine->setFixedSize(420,1); volumeSettingLine->setParent(this); palette = volumeSettingLine->palette(); palette.setColor(QPalette::WindowText, color); volumeSettingLine->setPalette(palette); volumeLine->move(0,140); volumeSettingLine->move(0,424); } /* * 初始化静音灯 */ void UkmediaMainWidget::initVolumeLed() { bool outputStatus = m_pVolumeControl->getSinkMute();//获取输出设备的静音状态 if(outputStatus) { QDBusMessage message =QDBusMessage::createSignal("/","org.ukui.media","systemOutputVolumeIsMute"); message << "mute"; QDBusConnection::systemBus().send(message); } else { QDBusMessage message =QDBusMessage::createSignal("/","org.ukui.media","systemOutputVolumeIsMute"); message << "no"; QDBusConnection::systemBus().send(message); } themeChangeIcons(); } /* * 注册dbus */ void UkmediaMainWidget::UkmediaDbusRegister() { QDBusConnection::sessionBus().unregisterService("org.ukui.media"); m_pMediaDbus = new MediaAdaptor(this); QDBusConnection con=QDBusConnection::sessionBus(); if (!con.registerService("org.ukui.media")) qDebug() << "registerService failed" << con.lastError().message(); if (!con.registerObject("/org/ukui/media", this, QDBusConnection::ExportAllSlots| QDBusConnection::ExportAllSignals)) qDebug() << "registerObject failed" << con.lastError().message(); m_pBluetoothDbus = new Bluetooth_Dbus(); } /** * @brief UkmediaMainWidget::initVolumeSlider * 初始化音量滑动条和输出设备列表 **/ void UkmediaMainWidget::initVolumeSlider() { resetVolumeSliderRange(); int mediaVolume = UkmediaCommon::getInstance().paVolumeToMediaVolume(m_pVolumeControl->getSinkVolume()); QString percent = QString::number(mediaVolume); appWidget->systemVolumeDisplayLabel->setText(percent + "%"); systemWidget->m_pSystemVolumeSliderLabel->setText(percent + "%"); appWidget->systemVolumeSlider->blockSignals(true); systemWidget->m_pSystemVolumeSlider->blockSignals(true); systemWidget->m_pSystemVolumeSlider->setValue(mediaVolume); appWidget->systemVolumeSlider->setValue(mediaVolume); systemWidget->m_pSystemVolumeSlider->blockSignals(false); appWidget->systemVolumeSlider->blockSignals(false); initOutputListWidgetItem(); initInputListWidgetItem(); themeChangeIcons(); } /* * 初始化output/input list widget的选项 */ void UkmediaMainWidget::initOutputListWidgetItem() { QString outputCardName = findCardName(m_pVolumeControl->defaultOutputCard, m_pVolumeControl->cardMap); QString outputPortLabel = findOutputPortLabel(m_pVolumeControl->defaultOutputCard, m_pVolumeControl->sinkPortName); if (outputCardName == "histen.algo") { outputPortLabel = "HUAWEI Histen"; } findOutputListWidgetItem(outputCardName, outputPortLabel); int mediaVolume = UkmediaCommon::getInstance().paVolumeToMediaVolume(m_pVolumeControl->getSinkVolume()); appWidget->systemVolumeSlider->blockSignals(true); systemWidget->m_pSystemVolumeSlider->blockSignals(true); appWidget->systemVolumeSlider->setValue(mediaVolume); systemWidget->m_pSystemVolumeSlider->setValue(mediaVolume); appWidget->systemVolumeSlider->blockSignals(false); systemWidget->m_pSystemVolumeSlider->blockSignals(false); appWidget->systemVolumeDisplayLabel->setText(QString::number(mediaVolume) + "%"); systemWidget->m_pSystemVolumeSliderLabel->setText(QString::number(mediaVolume) + "%"); qDebug() << "initOutputListWidgetItem" << m_pVolumeControl->defaultOutputCard << outputCardName << m_pVolumeControl->sinkPortName << outputPortLabel; } void UkmediaMainWidget::initInputListWidgetItem() { QString inputCardName = findCardName(m_pVolumeControl->defaultInputCard, m_pVolumeControl->cardMap); QString inputPortLabel = findInputPortLabel(m_pVolumeControl->defaultInputCard, m_pVolumeControl->sourcePortName); findInputListWidgetItem(inputCardName, inputPortLabel); //bug#165600 接入两个及以上输入设备时,切换输入设备切换不成功(切换之后默认使用和音量值的仍是上个设备) //华为机器,降噪模块会将主设备映射在usb设备上 if (inputCardName.contains("usb", Qt::CaseInsensitive)) { m_pVolumeControl->getModuleIndexByName("module-echo-cancel"); QTimer::singleShot(300,this,[=](){ qDebug() << __func__ << "unload module echo cancel" << m_pVolumeControl->findModuleIndex; pa_context_unload_module(m_pVolumeControl->getContext(), m_pVolumeControl->findModuleIndex, nullptr, nullptr); }); } qDebug() << "initInputListWidgetItem" << m_pVolumeControl->defaultInputCard << inputCardName << m_pVolumeControl->sourcePortName << inputPortLabel; } QString UkmediaMainWidget::outputVolumeToIconStr(bool status,int volume) { QString iconStr; if (status) { iconStr = iconNameOutputs[0]; } else if (volume <= 0 && !shortcutMute) { iconStr = iconNameOutputs[0]; } else if(volume <= 0 && shortcutMute){ iconStr = iconNameOutputs[0]; } else if (volume > 0 && volume <= 33) { iconStr = iconNameOutputs[1]; } else if (volume >33 && volume <= 66) { iconStr = iconNameOutputs[2]; } else { iconStr = iconNameOutputs[3]; } return iconStr; } /* * 处理槽函数 */ void UkmediaMainWidget::dealSlot() { if (nullptr == m_pTimer) { m_pTimer = new QTimer(this); } m_pTimer->setInterval(100); m_pTimer->setSingleShot(true); connect(m_pTimer, SIGNAL(timeout()), this, SLOT(handleTimeout())); QDBusInterface *iface = new QDBusInterface("org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", QDBusConnection::systemBus(), this); connect(iface, SIGNAL(PrepareForSleep(bool)), this, SLOT(onPrepareForSleep(bool))); //根据设备检测发来的信号,显示相应的提示窗 connect(m_pVolumeControl,SIGNAL(device_changed_signal(QString)),this,SLOT(deviceChangedShowSlot(QString))); //声卡包含headsetmic 端口时插入4段式mic需要设置对应的port QDBusConnection::sessionBus().connect( QString(), "/", "org.ukui.media", "headsetJack", this, SLOT(setHeadsetPort(QString))); QTimer::singleShot(50, this, SLOT(initVolumeSlider())); connect(m_pVolumeControl,&UkmediaVolumeControl::updateMute,this,[=](bool state) { bool outputStatus = m_pVolumeControl->getSinkMute();//获取输出设备的静音状态 if(outputStatus) { QDBusMessage message =QDBusMessage::createSignal("/","org.ukui.media","systemOutputVolumeIsMute"); message << "mute"; QDBusConnection::systemBus().send(message); } else { QDBusMessage message =QDBusMessage::createSignal("/","org.ukui.media","systemOutputVolumeIsMute"); message << "no"; QDBusConnection::systemBus().send(message); } themeChangeIcons(); sendUpdateMuteSignal(SoundType::SINK, state); }); connect(m_pVolumeControl,&UkmediaVolumeControl::updateSourceMute,this,[=](bool state) { bool inputStatus = m_pVolumeControl->getSourceMute();//获取输入设备的静音状态 if(inputStatus) { QDBusMessage message =QDBusMessage::createSignal("/","org.ukui.media","systemInputVolumeIsMute"); message << "mute"; QDBusConnection::systemBus().send(message); } else { QDBusMessage message =QDBusMessage::createSignal("/","org.ukui.media","systemInputVolumeIsMute"); message << "no"; QDBusConnection::systemBus().send(message); } themeChangeIcons(); sendUpdateMuteSignal(SoundType::SOURCE, state); }); connect(m_pVolumeControl, &UkmediaVolumeControl::updateVolume, this, [=](int value){ int mediaVolume = UkmediaCommon::getInstance().paVolumeToMediaVolume(value); QString percent = QString::number(mediaVolume); appWidget->systemVolumeDisplayLabel->setText(percent + "%"); systemWidget->m_pSystemVolumeSliderLabel->setText(percent + "%"); appWidget->systemVolumeSlider->blockSignals(true); systemWidget->m_pSystemVolumeSlider->blockSignals(true); appWidget->systemVolumeSlider->setValue(mediaVolume); systemWidget->m_pSystemVolumeSlider->setValue(mediaVolume); appWidget->systemVolumeSlider->blockSignals(false); systemWidget->m_pSystemVolumeSlider->blockSignals(false); themeChangeIcons(); sendUpdateVolumeSignal(SoundType::SINK, mediaVolume); /* isPlay避免第一次运行同步音量时响起提示音 * m_pTimer快捷键等其他方式播放提示音 */ if (isPlay && m_pTimer) { m_pTimer->start(); } else if (!isPlay && !m_pVolumeControl->defaultSinkName.contains("auto_null")) { isPlay = true; } qDebug() << "UpdateSinkVolume" << value << mediaVolume << m_pVolumeControl->getSinkMute(); }); connect(m_pVolumeControl, &UkmediaVolumeControl::updateSourceVolume, this, [=](int value){ int mediaVolume = UkmediaCommon::getInstance().paVolumeToMediaVolume(value); sendUpdateVolumeSignal(SoundType::SOURCE, mediaVolume); themeChangeIcons(); initInputListWidgetItem(); qDebug() << "UpdateSourceVolume" << value << mediaVolume << m_pVolumeControl->getSourceMute(); }); // QDesktopWidget removed in Qt6, using QScreen instead QScreen *screen = QApplication::primaryScreen(); connect(screen,&QScreen::geometryChanged,this,[=](){ if(this->isHidden()) return; else this->hide(); }); // QDesktopWidget removed in Qt6 // No direct replacement for screenCountChanged in Qt6 // Commented out for now /* connect(...,this,[=](){ if(this->isHidden()) return; else this->hide(); }); */ connect(m_pVolumeControl, SIGNAL(addSinkInputSignal(QString, QString, int, int, int)), this, SLOT(addAppToAppwidget(QString, QString, int, int, int))); connect(m_pVolumeControl, SIGNAL(removeSinkInputSignal(QString)), this, SLOT(removeSubAppWidget(QString))); connect(m_pVolumeControl, SIGNAL(sinkInputVolumeChangedSignal(QString, QString, int)), this, SLOT(sinkInputVolumeChangedSlot(QString, QString, int))); connect(m_pVolumeControl,SIGNAL(deviceChangedSignal()),this,SLOT(updateListWidgetItemSlot())); QDBusConnection::sessionBus().connect( QString(), "/", "org.kylin.music", "sinkInputVolumeChanged", this, SLOT(updateAppVolume(QString,int,bool))); QDBusConnection::sessionBus().connect( QString(), "/", "org.kylin.video", "sinkInputVolumeChanged", this, SLOT(updateAppVolume(QString,int,bool))); connect(systemWidget->m_pOutputListWidget,&QListWidget::itemClicked,this,[=](){ if (outputListWidgetRow != systemWidget->m_pOutputListWidget->currentRow()) { outputListWidgetCurrentRowChangedSlot(systemWidget->m_pOutputListWidget->currentRow()); outputListWidgetRow = systemWidget->m_pOutputListWidget->currentRow(); } }); connect(systemWidget->m_pInputListWidget, SIGNAL(currentRowChanged(int)), this, SLOT(inputListWidgetCurrentRowChangedSlot(int))); /* * 完整模式下,应用音量界面,当滑动条值改变时更改系统音量 */ connect(appWidget->systemVolumeSlider,SIGNAL(valueChanged(int)),this,SLOT(advancedSystemSliderChangedSlot(int))); connect(systemWidget->m_pSystemVolumeSlider,SIGNAL(valueChanged(int)),this,SLOT(systemVolumeSliderChangedSlot(int))); connect(systemWidget->m_pSystemVolumeBtn,SIGNAL(clicked()),this,SLOT(systemVolumeButtonClickedSlot())); connect(appWidget->systemVolumeBtn,SIGNAL(clicked()),this,SLOT(appWidgetMuteButtonCLickedSlot())); connect(soundSystemTrayIcon,SIGNAL(activated(QSystemTrayIcon::ActivationReason)),\ this,SLOT(activatedSystemTrayIconSlot(QSystemTrayIcon::ActivationReason))); connect(m_pSoundPreferenceAction,SIGNAL(triggered()),this,SLOT(jumpControlPanelSlot())); connect(m_pVolumeControl,SIGNAL(updatePortSignal()),this,SLOT(updateDevicePort())); connect(m_pVolumeControl,SIGNAL(checkDeviceSelectionSianal(const pa_card_info *)),this,SLOT(checkAudioDeviceSelectionNeeded(const pa_card_info *))); connect(m_pVolumeControl,&UkmediaVolumeControl::removeSinkSignal,[=](){ qDebug() << "removre sink signal"; }); connect(appWidget->volumeSettingButton,SIGNAL(clicked()),this,SLOT(jumpControlPanelSlot())); connect(systemWidget->volumeSettingButton,SIGNAL(clicked()),this,SLOT(jumpControlPanelSlot())); monitorSessionStatus(); // connect(m_pVolumeControl, &UkmediaVolumeControl::bluetoothBatteryChanged, this, &UkmediaMainWidget::batteryLevelChanged); // connect(m_pVolumeControl, &UkmediaVolumeControl::initBlueDeviceVolumeSig, this, &UkmediaMainWidget::initBlueDeviceVolume); if(!QDBusConnection::sessionBus().connect("org.ukui.volume.control", "/org/ukui/volume/control", "org.ukui.volume.control", "deviceAdjust", this, SLOT(deviceAdjustSlots(int)))) { qDebug() << "Audio framwork interface error, connect " << "deviceChanged" << "failed!"; qDebug() << QDBusConnection::sessionBus().lastError().message(); } } void UkmediaMainWidget::initGsettings() { //检测系统主题 if (QGSettings::isSchemaInstalled(UKUI_THEME_SETTING)){ m_pThemeSetting = new QGSettings(UKUI_THEME_SETTING); m_pFontSetting = new QGSettings(UKUI_THEME_SETTING); QString fontType; if (m_pThemeSetting->keys().contains("styleName")) { mThemeName = m_pThemeSetting->get(UKUI_THEME_NAME).toString(); } if (m_pFontSetting->keys().contains("systemFont")) { fontType = m_pFontSetting->get("systemFont").toString(); } if (m_pFontSetting->keys().contains("systemFontSize")) { int font = m_pFontSetting->get("system-font-size").toInt(); QFont fontSize(fontType,font); // appWidget->systemVolumeLabel->setFont(fontSize); } connect(m_pFontSetting,SIGNAL(changed(const QString &)),this,SLOT(fontSizeChangedSlot(const QString &))); connect(m_pThemeSetting, SIGNAL(changed(const QString &)),this,SLOT(ukuiThemeChangedSlot(const QString &))); } //获取声音gsettings值 if (QGSettings::isSchemaInstalled(KEY_SOUNDS_SCHEMA)){ m_pSoundSettings = new QGSettings(KEY_SOUNDS_SCHEMA); // connect(m_pVolumeControl, SIGNAL(paContextReady()), this, SLOT(switchModuleEchoCancel())); // connect(m_pVolumeControl, SIGNAL(paContextReady()), this, SLOT(switchModuleLoopBack())); // connect(m_pVolumeControl, SIGNAL(paContextReady()),this,SLOT(switchMonoAudio())); // connect(m_pSoundSettings, SIGNAL(changed(const QString &)),this,SLOT(soundSettingChanged(const QString &))); } //监听开机音乐的开关键值 if (QGSettings::isSchemaInstalled(UKUI_SWITCH_SETTING)) { m_pStartUpSetting = new QGSettings(UKUI_SWITCH_SETTING); if (m_pStartUpSetting->keys().contains("startupMusic")) { bool state = m_pStartUpSetting->get(UKUI_STARTUP_MUSIC_KEY).toBool();//开机音乐 m_pVolumeControl->insertJson(JsonType::JSON_TYPE_USERINFO, AUDIO_STRATUP_SOUNDEFFECT_KEY, state); } connect(m_pStartUpSetting, SIGNAL(changed(const QString &)),this,SLOT(soundSettingChanged(const QString &))); } //获取透明度 if(QGSettings::isSchemaInstalled(TRANSPARENCY_GSETTINGS)) { m_pTransGsettings = new QGSettings(TRANSPARENCY_GSETTINGS); if(m_pTransGsettings->keys().contains(QString("transparency"))) { m_pTransparency=m_pTransGsettings->get("transparency").toDouble() + 0.15; m_pTransparency = (m_pTransparency > 1) ? 1 : m_pTransparency; connect(m_pTransGsettings, SIGNAL(changed(const QString &)), this, SLOT(onTransChanged())); } } if(QGSettings::isSchemaInstalled(UKUI_GLOBALTHEME_SETTINGS)){ m_pSoundThemeSetting = new QGSettings(UKUI_GLOBALTHEME_SETTINGS); if(m_pSoundThemeSetting->keys().contains("globalThemeName")){ QString soundThemestr = m_pSoundThemeSetting->get(GLOBAL_THEME_NAME).toString(); if(m_pSoundSettings->keys().contains("themeName") && soundThemestr != "custom"){ m_pSoundSettings->set(SOUND_THEME_KEY,soundThemestr); } } connect(m_pSoundThemeSetting, SIGNAL(changed(const QString &)),this,SLOT(soundThemeChangedSlot(const QString &))); } } void UkmediaMainWidget::initStrings() { const static QString s_login1Name = QStringLiteral("logind"); const static QString s_login1Service = QStringLiteral("org.freedesktop.login1"); const static QString s_login1Path = QStringLiteral("/org/freedesktop/login1"); const static QString s_login1ManagerInterface = QStringLiteral("org.freedesktop.login1.Manager"); const static QString s_login1SeatInterface = QStringLiteral("org.freedesktop.login1.Seat"); const static QString s_login1SessionInterface = QStringLiteral("org.freedesktop.login1.Session"); const static QString s_login1ActiveProperty = QStringLiteral("Active"); const static QString s_dbusPropertiesInterface = QStringLiteral("org.freedesktop.DBus.Properties"); m_sessionControllerName = s_login1Name; m_sessionControllerService = s_login1Service; m_sessionControllerPath = s_login1Path; m_sessionControllerManagerInterface = s_login1ManagerInterface; m_sessionControllerSeatInterface = s_login1SeatInterface; m_sessionControllerSessionInterface = s_login1SessionInterface; m_sessionControllerActiveProperty = s_login1ActiveProperty; m_sessionControllerPropertiesInterface = s_dbusPropertiesInterface; } void UkmediaMainWidget::initLabelAlignment() { // 哈维柯语言环境下默认做了label右对齐方式处理,因此只需要在其他语言环境下对这些label做左居中对齐方式处理即可 if (!UkmediaCommon::getInstance().isHWKLanguageEnv(m_languageEnvStr)) { systemWidget->setLabelAlignment(Qt::AlignRight | Qt::AlignVCenter); appWidget->setLabelAlignment(Qt::AlignRight | Qt::AlignVCenter); } } /* * 当检测到4段式3.5mm耳机插入时(声卡包含headsetmic端口) */ void UkmediaMainWidget::setHeadsetPort(QString str) { qDebug() << "setHeadsetPort " << str << m_pVolumeControl->sinkIndex; /* *output: headphone *input: intel mic */ if (strcmp(str.toLatin1().data(),"headphone") == 0) { m_pVolumeControl->setSinkPort(m_pVolumeControl->defaultSinkName,m_pHeadphonesName); m_pVolumeControl->setSourcePort(m_pVolumeControl->defaultSourceName,m_pInternalmicName); /* *当需要设置的输入端口不在同一个sink上时,需要设置默认的输入设备 */ } /*output: headphone input: headset mic*/ else if (strcmp(str.toLatin1().data(),"headset") == 0) { m_pVolumeControl->setSinkPort(m_pVolumeControl->defaultSinkName,m_pHeadphonesName); m_pVolumeControl->setSourcePort(m_pVolumeControl->defaultSourceName,m_pHeadsetmicName); /* *当需要设置的输入端口不在同一个sink上时,需要设置默认的输入设备 */ }/*output: speaker input: headphone mic*/ else if (strcmp(str.toLatin1().data(),"headphone mic") == 0) { m_pVolumeControl->setSinkPort(m_pVolumeControl->defaultSinkName,m_pInternalspkName); m_pVolumeControl->setSourcePort(m_pVolumeControl->defaultSourceName,m_pHeadphonemicName); /* *当需要设置的输入端口不在同一个sink上时,需要设置默认的输入设备 */ } } /* * 音量改变提示:策略是停止滑动时,播放提示声 */ void UkmediaMainWidget::handleTimeout() { playAlertSound("audio-volume-change"); } void UkmediaMainWidget::themeChangeIcons() { int nOutputValue = UkmediaCommon::getInstance().paVolumeToMediaVolume(m_pVolumeControl->getSinkVolume()); int nInputValue = UkmediaCommon::getInstance().paVolumeToMediaVolume(m_pVolumeControl->getSourceVolume()); bool outputStatus = m_pVolumeControl->getSinkMute(); bool inputStatus = m_pVolumeControl->getSourceMute(); inputVolumeDarkThemeImage(nInputValue,inputStatus); outputVolumeDarkThemeImage(nOutputValue,outputStatus); systemWidget->m_pSystemVolumeBtn->repaint(); appWidget->systemVolumeBtn->repaint(); if(outputStatus) soundSystemTrayIcon->setToolTip(tr("Current volume:") + tr("Muted") ); else soundSystemTrayIcon->setToolTip(tr("Current volume:") + QString::number(nOutputValue) + "%"); } /* * 隐藏窗口 */ void UkmediaMainWidget::hideWindow() { this->hide(); } /* * 字体改变需更新字体大小 */ void UkmediaMainWidget::fontSizeChangedSlot(const QString &themeStr) { QString fontType; if (m_pFontSetting->keys().contains("systemFont")) { fontType = m_pFontSetting->get("systemFont").toString(); } if (m_pFontSetting->keys().contains("systemFontSize")) { int font = m_pFontSetting->get("system-font-size").toInt(); QFont fontSize(fontType,font); } } /* * 系统主题改变 */ void UkmediaMainWidget::ukuiThemeChangedSlot(const QString &themeStr) { qDebug() << "ukuiThemeChangedSlot" << mThemeName; if (m_pThemeSetting->keys().contains("styleName")) { mThemeName = m_pThemeSetting->get(UKUI_THEME_NAME).toString(); } themeChangeIcons(); onTransChanged(); Q_EMIT qApp->paletteChanged(qApp->palette()); this->repaint(); } /* * 系统音效主题改变 */ void UkmediaMainWidget::soundThemeChangedSlot(const QString &soundThemeStr) { if(m_pSoundThemeSetting->keys().contains("globalThemeName")){ QString soundstr = m_pSoundThemeSetting->get(GLOBAL_THEME_NAME).toString(); if(m_pSoundSettings->keys().contains("themeName") && soundstr != "custom") m_pSoundSettings->set(SOUND_THEME_KEY,soundstr); } } /* * 滑动条控制输出音量 */ void UkmediaMainWidget::systemVolumeSliderChangedSlot(int value) { QString percent; percent = QString::number(value); appWidget->systemVolumeSlider->blockSignals(true); appWidget->systemVolumeSlider->setValue(value); appWidget->systemVolumeSlider->blockSignals(false); appWidget->systemVolumeDisplayLabel->setText(percent + "%"); systemWidget->m_pSystemVolumeSliderLabel->setText(percent + "%"); int paVolume = UkmediaCommon::getInstance().mediaVolumeToPaVolume(value); m_pVolumeControl->getDefaultSinkIndex(); m_pVolumeControl->setSinkVolume(m_pVolumeControl->sinkIndex, paVolume); firstEnterSystem = false; } /* * 完整模式下,应用音量选项中系统音量控制输出音量值 */ void UkmediaMainWidget::advancedSystemSliderChangedSlot(int value) { QString percent; percent = QString::number(value); systemWidget->m_pSystemVolumeSlider->blockSignals(true); systemWidget->m_pSystemVolumeSlider->setValue(value); systemWidget->m_pSystemVolumeSlider->blockSignals(false); appWidget->systemVolumeDisplayLabel->setText(percent + "%"); systemWidget->m_pSystemVolumeSliderLabel->setText(percent + "%"); int paVolume = UkmediaCommon::getInstance().mediaVolumeToPaVolume(value); m_pVolumeControl->getDefaultSinkIndex(); m_pVolumeControl->setSinkVolume(m_pVolumeControl->sinkIndex, paVolume); } void UkmediaMainWidget::systemVolumeButtonClickedSlot() { m_pVolumeControl->setSinkMute(!m_pVolumeControl->sinkMuted); QTimer * time = new QTimer; time->start(100); connect(time,&QTimer::timeout,[=](){ themeChangeIcons(); time->disconnect(); delete time; }); } /* * 完整模式中应用音量的静音控制 */ void UkmediaMainWidget::appWidgetMuteButtonCLickedSlot() { m_pVolumeControl->setSinkMute(!m_pVolumeControl->sinkMuted); QTimer * time = new QTimer; time->start(100); connect(time,&QTimer::timeout,[=](){ themeChangeIcons(); time->disconnect(); delete time; }); } /* * 声音托盘的触发事件,包括鼠标左键点击,双击,滚轮,右击 */ void UkmediaMainWidget::activatedSystemTrayIconSlot(QSystemTrayIcon::ActivationReason reason) { QString platform = QGuiApplication::platformName(); if(!platform.startsWith(QLatin1String("wayland"),Qt::CaseInsensitive)){ // KWindowInfo API changed in KF6 // const KWindowInfo info(this->winId(), NET::WMState); // if (!info.hasState(NET::SkipTaskbar) || !info.hasState(NET::SkipPager)) { // KWindowSystem::setState(this->winId(), NET::SkipTaskbar | NET::SkipPager); // } } switch(reason) { //鼠标中间键点击图标 case QSystemTrayIcon::MiddleClick: { if (this->isHidden()) { mouseMeddleClickedTraySlot(); } else { hideWindow(); } break; } //鼠标左键点击图标 case QSystemTrayIcon::Trigger: { if(this->isVisible()){ hideWindow(); break; } showUkuiVolumeControlTray(); break; } //鼠标左键双击图标 case QSystemTrayIcon::DoubleClick: { hideWindow(); break; } case QSystemTrayIcon::Context:{ if(this ->isHidden()) { menu->popup(QCursor::pos()); } else{ this->hideWindow(); } break; } default: break; } } /* * 鼠标滚轮点击托盘图标,设置输出音量的静音状态 */ void UkmediaMainWidget::mouseMeddleClickedTraySlot() { m_pVolumeControl->setSinkMute(!m_pVolumeControl->sinkMuted); QTimer * time = new QTimer; time->start(100); connect(time,&QTimer::timeout,[=](){ themeChangeIcons(); time->disconnect(); delete time; }); } /* * 点击菜单中声音设置跳转到控制面板的声音控制 */ void UkmediaMainWidget::jumpControlPanelSlot() { m_process = new QProcess(0); m_process->start("ukui-control-center", QStringList() << "-m" << "Audio"); m_process->waitForStarted(); this->hide(); return; } void UkmediaMainWidget::drawImagColorFromTheme(UkuiButtonDrawSvg *button, QString iconStr) { button->themeIcon.image = QIcon::fromTheme(iconStr).pixmap(24,24).toImage(); if (mThemeName == UKUI_THEME_LIGHT) button->themeIcon.color = QColor(0,0,0,216); else button->themeIcon.color = QColor(255,255,255,216); } /* 深色主题时输出音量图标 */ void UkmediaMainWidget::outputVolumeDarkThemeImage(int value,bool status) { QString iconStr = outputVolumeToIconStr(status,value); systemWidget->m_pSystemVolumeBtn->setIcon(QIcon::fromTheme(iconStr)); appWidget->systemVolumeBtn->setIcon(QIcon::fromTheme(iconStr)); bool isMute = m_pVolumeControl->getSinkMute(); if(iconStr == "audio-volume-muted-symbolic" && isMute) { systemWidget->m_pSystemVolumeBtn->setChecked(false); appWidget->systemVolumeBtn->setChecked(false); } else { systemWidget->m_pSystemVolumeBtn->setChecked(true); appWidget->systemVolumeBtn->setChecked(true); } soundSystemTrayIcon->setIcon(QIcon::fromTheme(iconStr)); } /* 输入音量图标 */ void UkmediaMainWidget::inputVolumeDarkThemeImage(int value,bool status) { QString inputIconStr; if (status) { inputIconStr = iconNameInputs[0]; } else if (value <= 0) { inputIconStr = iconNameInputs[0]; } else if (value > 0 && value <= 33) { inputIconStr = iconNameInputs[1]; } else if (value >33 && value <= 66) { inputIconStr = iconNameInputs[2]; } else { inputIconStr = iconNameInputs[3]; } } /* * 绘制高亮颜色图标 */ QPixmap UkmediaMainWidget::drawLightColoredPixmap(const QPixmap &source) { QColor gray(255,255,255); QColor standard (0,0,0); QImage img = source.toImage(); for (int x = 0; x < img.width(); x++) { for (int y = 0; y < img.height(); y++) { auto color = img.pixelColor(x, y); if (color.alpha() > 0) { if (qAbs(color.red()-gray.red())<20 && qAbs(color.green()-gray.green())<20 && qAbs(color.blue()-gray.blue())<20) { color.setRed(255); color.setGreen(255); color.setBlue(255); img.setPixelColor(x, y, color); } else { color.setRed(255); color.setGreen(255); color.setBlue(255); img.setPixelColor(x, y, color); } } } } return QPixmap::fromImage(img); } /* * 绘制深色颜色图标 */ QPixmap UkmediaMainWidget::drawDarkColoredPixmap(const QPixmap &source) { QColor gray(255,255,255); QImage img = source.toImage(); for (int x = 0; x < img.width(); x++) { for (int y = 0; y < img.height(); y++) { auto color = img.pixelColor(x, y); if (color.alpha() > 0) { if (qAbs(color.red()-gray.red())<20 && qAbs(color.green()-gray.green())<20 && qAbs(color.blue()-gray.blue())<20) { color.setRed(0); color.setGreen(0); color.setBlue(0); img.setPixelColor(x, y, color); } else { color.setRed(0); color.setGreen(0); color.setBlue(0); img.setPixelColor(x, y, color); } } } } return QPixmap::fromImage(img); } void UkmediaMainWidget::initPanelGSettings() { const QByteArray id(PANEL_SETTINGS); if (QGSettings::isSchemaInstalled(id)) { if (m_panelGSettings == nullptr) { m_panelGSettings = new QGSettings(id); } if (m_panelGSettings->keys().contains(PANEL_POSITION_KEY)) { m_panelPosition = m_panelGSettings->get(PANEL_POSITION_KEY).toInt(); } if (m_panelGSettings->keys().contains(PANEL_SIZE_KEY)) { m_panelSize = m_panelGSettings->get(PANEL_SIZE_KEY).toInt(); } connect(m_panelGSettings, &QGSettings::changed, this, [&] (const QString &key) { if (key == PANEL_POSITION_KEY) { m_panelPosition = m_panelGSettings->get(PANEL_POSITION_KEY).toInt(); } if (key == PANEL_SIZE_KEY) { m_panelSize = m_panelGSettings->get(PANEL_SIZE_KEY).toInt(); } if (this->isVisible()) { advancedWidgetShow(); } }); } } QRect UkmediaMainWidget::caculatePosition() { QRect availableGeo = QGuiApplication::screenAt(QCursor::pos())->geometry(); int x, y; int margin = 8; switch (m_panelPosition) { case PanelPosition::Top: { if (UkmediaCommon::getInstance().isHWKLanguageEnv(m_languageEnvStr)) { x = margin; y = availableGeo.y() + m_panelSize + margin; } else { x = availableGeo.x() + availableGeo.width() - this->width() - margin; y = availableGeo.y() + m_panelSize + margin; } } break; case PanelPosition::Bottom: { if (UkmediaCommon::getInstance().isHWKLanguageEnv(m_languageEnvStr)) { x = margin; y = availableGeo.y() + availableGeo.height() - m_panelSize - this->height() - margin; } else { x = availableGeo.x() + availableGeo.width() - this->width() - margin; y = availableGeo.y() + availableGeo.height() - m_panelSize - this->height() - margin; } } break; case PanelPosition::Left: { x = availableGeo.x() + m_panelSize + margin; y = availableGeo.y() + availableGeo.height() - this->height() - margin; } break; case PanelPosition::Right: { x = availableGeo.x() + availableGeo.width() - m_panelSize - this->width() - margin; y = availableGeo.y() + availableGeo.height() - this->height() - margin; } break; } return QRect(x, y, this->width(), this->height()); } /* * 完整模式界面的显示 */ void UkmediaMainWidget::advancedWidgetShow() { kdk::WindowManager::setSkipSwitcher(this->windowHandle(),true); //start main中屏蔽setWindowFlags,通过这里去除标题栏 kdk::UkuiStyleHelper::self()->removeHeader(this); //end QRect rect = caculatePosition(); QString platform = QGuiApplication::platformName(); qDebug() << "platform:" << platform; if(!platform.startsWith(QLatin1String("wayland"),Qt::CaseInsensitive)){ this->setGeometry(rect); this->showNormal(); } else { this->show(); kdk::WindowManager::setGeometry(this->windowHandle(), rect); } this->raise(); this->activateWindow(); } QString UkmediaMainWidget::appNameToIconName(QString appName, QString appIconName) { QString iconName = "/usr/share/applications/"; qDebug()<< "appNameToIconName" << appName << appIconName; iconName.append(appIconName); iconName.append(".desktop"); /* Some applications desktop file exist in /etc/xdg/autostart/ path */ QFileInfo file(iconName); if (!file.exists()) { iconName = "/etc/xdg/autostart/"; iconName.append(appIconName); iconName.append(".desktop"); } return iconName; } void UkmediaMainWidget::addAppToAppwidget(QString appName, QString appIconName, int index, int value, int channel) { //设置应用的图标 QString iconName = appNameToIconName(appName,appIconName); QString description = appName; QString pAppName = getAppName(iconName); QString pAppIcon = getAppIcon(iconName); initSubApplicationWidget(pAppIcon); qDebug() << "appName:" << appName << "appIconName:" << appIconName << "pAppIcon:" << pAppIcon << "iconName:" << iconName; appWidget->app_volume_list->append(appIconName); QString appSliderStr = appName; QString appMuteBtnlStr = appName; QString appVolumeLabelStr = appName; QStringList strList; strList<appSlider->setObjectName(appSliderStr); appWidget->appMuteBtn->setObjectName(appIconName); appWidget->appVolumeLabel->setObjectName(appVolumeLabelStr); //初始化应用音量滑动条 int sliderValue = UkmediaCommon::getInstance().paVolumeToMediaVolume(value); appWidget->appIconBtn->setCheckable(false); appWidget->appIconBtn->setChecked(false); appWidget->appSlider->blockSignals(true); appWidget->appSlider->setValue(sliderValue); appWidget->appVolumeLabel->setText(QString::number(sliderValue) + "%"); appWidget->appSlider->blockSignals(false); qDebug() << "初始化应用音量滑动条:" << appWidget->appSlider->value() << appSliderStr; /*滑动条控制应用音量*/ connect(appWidget->appSlider, &QSlider::valueChanged, [=](int value) { bool isMute = false; int paVolume = UkmediaCommon::getInstance().mediaVolumeToPaVolume(value); QString appName = appSliderStr.split("Slider").at(0); qDebug() << "调节应用音量滑动条的index和appName" << index << appName; //因为一个应用可能产生多个sinkinput,所以要对相同description的所有index设置音量,遍历indexmap设置 QMap::iterator it; bool isSinkInput = false; //应用可能同时产生sinkinput和source-output,此时需要忽略设置输入音量 for(it = m_pVolumeControl->sinkInputIndexMap.begin(); it != m_pVolumeControl->sinkInputIndexMap.end();) { if (it.value() == appName) { isSinkInput = true; m_pVolumeControl->setSinkInputVolume(it.key(), paVolume, channel); } it++; } for(it = m_pVolumeControl->sourceOutputIndexMap.begin(); it != m_pVolumeControl->sourceOutputIndexMap.end();) { if(it.value() == appName && !isSinkInput){ m_pVolumeControl->setSourceOutputVolume(it.key(), paVolume, channel); } it++; } if(kylinVideoVolumeSignal) { kylinVideoVolumeSignal = false; } else { QDBusMessage message =QDBusMessage::createSignal("/", "org.ukui.media", "sinkVolumeChanged"); message<findChild (appVolumeLabelStr); if (label == nullptr) return; label->setText(QString::number(value)+"%"); QSlider *slider = appWidget->findChild(appSliderStr); if (slider == nullptr) return; QPushButton *btn = appWidget->findChild(appMuteBtnlStr); if (btn == nullptr) return; QString audioIconStr = outputVolumeToIconStr(isMute,value); QSize iconSize(24,24); if (mThemeName == UKUI_THEME_DEFAULT) { btn->setIcon(QIcon(drawDarkColoredPixmap((QIcon::fromTheme(audioIconStr).pixmap(iconSize))))); } else if (mThemeName == UKUI_THEME_DARK || mThemeName == UKUI_THEME_DEFAULT) { btn->setIcon(QIcon(drawLightColoredPixmap((QIcon::fromTheme(audioIconStr).pixmap(iconSize))))); } }); connect(appWidget->appMuteBtn,&QPushButton::clicked,[=]() { bool isMute = false; int volume = -1; isMute = m_pVolumeControl->getSinkInputMuted(description); qDebug() << "appMuteBtn clicked" << "muted" << isMute << description <setSinkInputMuted(description, !isMute); if (isAppMuteBtnPress) { isAppMuteBtnPress = false; } } else { if(kylinVideoMuteSignal) kylinVideoMuteSignal = false; else{ QDBusMessage message =QDBusMessage::createSignal("/", "org.ukui.media", "sinkVolumeChanged"); message<displayAppVolumeWidget->findChild(appName); AudioSlider *slider = appWidget->displayAppVolumeWidget->findChild(appName); if (slider == nullptr || appIconName == nullptr) return; int mediaVolume = UkmediaCommon::getInstance().paVolumeToMediaVolume(value); label->setText(QString::number(mediaVolume) + "%"); slider->blockSignals(true); slider->setValue(mediaVolume); slider->blockSignals(false); qDebug() << "sinkInputVolumeChangedSlot" << appName << appIconName << value << appSliderStr; } void UkmediaMainWidget::switchStartupPlayMusic() { if (m_pStartUpSetting->keys().contains("startupMusic")) { bool state = m_pStartUpSetting->get(UKUI_STARTUP_MUSIC_KEY).toBool(); m_pVolumeControl->insertJson(JsonType::JSON_TYPE_USERINFO, AUDIO_STRATUP_SOUNDEFFECT_KEY, state); qDebug() << "the switch of startup sound effect is changed to " << state; } } void UkmediaMainWidget::soundSettingChanged(const QString &key) { qDebug() << "soundSettingChanged" << key; if (key == "dnsNoiseReduction") switchModuleEchoCancel(); else if(key == "loopback") switchModuleLoopBack(); else if (key == "volumeIncrease") resetVolumeSliderRange(); else if (key == "monoAudio") switchMonoAudio(); else if (key == "startupMusic") switchStartupPlayMusic(); } /* * 获取应用名称,从desktop全路径名下解析出应用名称 */ QString UkmediaMainWidget::getAppName(QString desktopfp) { GError** error=nullptr; GKeyFileFlags flags=G_KEY_FILE_NONE; GKeyFile* keyfile=g_key_file_new (); QByteArray fpbyte=desktopfp.toLocal8Bit(); char* filepath=fpbyte.data(); g_key_file_load_from_file(keyfile,filepath,flags,error); char* name=g_key_file_get_locale_string(keyfile,"Desktop Entry","Name", nullptr, nullptr); QString namestr=QString::fromLocal8Bit(name); g_key_file_free(keyfile); return namestr; } /* * 获取应用图标,从desktop全路径名下解析出应用图标 */ QString UkmediaMainWidget::getAppIcon(QString desktopfp) { GError** error=nullptr; GKeyFileFlags flags=G_KEY_FILE_NONE; GKeyFile* keyfile=g_key_file_new (); QByteArray fpbyte=desktopfp.toLocal8Bit(); char* filepath=fpbyte.data(); g_key_file_load_from_file(keyfile,filepath,flags,error); char* name=g_key_file_get_locale_string(keyfile,"Desktop Entry","Icon", nullptr, nullptr); QString namestr=QString::fromLocal8Bit(name); g_key_file_free(keyfile); /* If we can't find the app icon * we need to set a default icon for it */ namestr = (namestr == "") ? "application-x-desktop" : namestr; return namestr; } void UkmediaMainWidget::initSubApplicationWidget(QString pAppIcon) { //widget显示应用音量 QWidget *subAppwidget = new QWidget(appWidget->displayAppVolumeWidget); appWidget->appVolumeLabel = new QLabel(subAppwidget); appWidget->appVolumeLabel->setParent(subAppwidget); appWidget->appIconBtn = new QPushButton(subAppwidget); appWidget->appMuteBtn = new QPushButton(subAppwidget); appWidget->appMuteBtn->setVisible(false); appWidget->appSlider = new AudioSlider(subAppwidget); appWidget->appSlider->setOrientation(Qt::Horizontal); appWidget->appSlider->setProperty("needTranslucent", true); // Increase translucent effect appWidget->appIconBtn->setFixedSize(32,32); appWidget->appIconBtn->setFlat(true); appWidget->appIconBtn->setFocusPolicy(Qt::NoFocus); QSize icon_size(32,32); appWidget->appIconBtn->setIconSize(icon_size); appWidget->appIconBtn->setIcon(QIcon::fromTheme(pAppIcon)); appWidget->appIconBtn->setAttribute(Qt::WA_TranslucentBackground); QPalette palete = appWidget->appIconBtn->palette(); palete.setColor(QPalette::Highlight,Qt::transparent); palete.setBrush(QPalette::Button,QBrush(QColor(1,1,1,0))); appWidget->appIconBtn->setPalette(palete); QPalette paleteAppIcon = appWidget->appIconBtn->palette(); paleteAppIcon.setColor(QPalette::Highlight,Qt::transparent); paleteAppIcon.setBrush(QPalette::Button,QBrush(QColor(0,0,0,0))); appWidget->appIconBtn->setPalette(paleteAppIcon); appWidget->appSlider->setMaximum(100); appWidget->appSlider->setFixedSize(286,48); appWidget->appIconBtn->adjustSize(); appWidget->appIconBtn->setProperty("useIconHighlightEffect",true); appWidget->appIconBtn->setProperty("iconHighlightEffectMode",true); appWidget->appIconBtn->setStyleSheet("QPushButton{background:transparent;border:0px;" "padding-left:0px;}" "QPushButton:hover {" "background-color: #00000000;" "color: white;}"); //音量滑动条 QHBoxLayout *appSliderLayout = new QHBoxLayout(subAppwidget); subAppwidget->setFixedSize(412,48); appWidget->appVolumeLabel->setText("0%"); appWidget->appVolumeLabel->setFixedSize(52,48); if (!UkmediaCommon::getInstance().isHWKLanguageEnv(m_languageEnvStr)) { appWidget->appVolumeLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); } appSliderLayout->addWidget(appWidget->appIconBtn); appSliderLayout->addSpacing(3); appSliderLayout->addWidget(appWidget->appSlider); appSliderLayout->addSpacing(0); appSliderLayout->addWidget(appWidget->appVolumeLabel); appSliderLayout->setSpacing(0); subAppwidget->setLayout(appSliderLayout); appSliderLayout->setContentsMargins(10,0,30,0); appWidget->m_pVlayout->addWidget(subAppwidget); // //设置布局的垂直间距以及设置vlayout四周的间距 appWidget->displayAppVolumeWidget->resize(412,m_pVolumeControl->sinkInputList.count()*50); appWidget->m_pVlayout->setSpacing(2); appWidget->displayAppVolumeWidget->setLayout(appWidget->m_pVlayout); appWidget->m_pVlayout->update(); } /* * 当播放或录制应用退出时删除在应用音量界面上该应用的显示 */ void UkmediaMainWidget::removeSubAppWidget(QString m_pAppName) { g_debug ("Removing application stream %s", m_pAppName); /* We could call bar_set_stream_control here, but that would pointlessly * invalidate the channel bar, so just remove it ourselves */ int index = -1; for (int i=0;isinkInputList.count();i++) { if (m_pVolumeControl->sinkInputList.at(i) == m_pAppName) { index = i; m_pVolumeControl->sinkInputList.removeAt(i); break; } } if (index == -1) return; QLayoutItem *item; if ((item = appWidget->m_pVlayout->takeAt(index)) != 0) { QWidget *wid = item->widget(); appWidget->m_pVlayout->removeWidget(wid); wid->setParent(nullptr); delete wid; delete item; } qDebug() << "removeSubAppWidget" << m_pAppName << m_pVolumeControl->sinkInputList.count(); //设置布局的间距以及设置vlayout四周的间距 appWidget->displayAppVolumeWidget->resize(404,m_pVolumeControl->sinkInputList.count()*50); appWidget->m_pVlayout->setSpacing(2); appWidget->m_pVlayout->setContentsMargins(0,0,0,0); appWidget->m_pVlayout->update(); } /* In PulseAudio without ucm, ports will show up with the following names: Headphones - analog-output-headphones Headset mic - analog-input-headset-mic (was: analog-input-microphone-headset) Jack in mic-in mode - analog-input-headphone-mic (was: analog-input-microphone) However, since regular mics also show up as analog-input-microphone, we need to check for certain controls on alsa mixer level too, to know if we deal with a separate mic jack, or a multi-function jack with a mic-in mode (also called "headphone mic"). We check for the following names: Headphone Mic Jack - indicates headphone and mic-in mode share the same jack, i e, not two separate jacks. Hardware cannot distinguish between a headphone and a mic. Headset Mic Phantom Jack - indicates headset jack where hardware can not distinguish between headphones and headsets Headset Mic Jack - indicates headset jack where hardware can distinguish between headphones and headsets. There is no use popping up a dialog in this case, unless we already need to do this for the mic-in mode. From the PA_PROCOTOL_VERSION=34, The device_port structure adds 2 members availability_group and type, with the help of these 2 members, we could consolidate the port checking and port setting for non-ucm and with-ucm cases. */ #define HEADSET_PORT_SET(dst, src) \ do { \ if (!(dst) || (dst)->priority < (src)->priority) \ dst = src; \ } while (0) #define GET_PORT_NAME(x) (x ? g_strdup (x->name) : NULL) headsetPorts * UkmediaMainWidget::getHeadsetPorts (const pa_card_info *c) { headsetPorts *h; guint i; h = g_new0 (headsetPorts, 1); for (i = 0; i < c->n_ports; i++) { pa_card_port_info *p = c->ports[i]; // if (control->priv->server_protocol_version < 34) { if (g_str_equal (p->name, "analog-output-headphones") || g_str_equal (p->name, "[Out] Headphones1")) h->headphones = p; else if (g_str_equal (p->name, "analog-input-headset-mic") || g_str_equal (p->name, "[In] Headset")) h->headsetmic = p; else if (g_str_equal (p->name, "analog-input-headphone-mic") || g_str_equal (p->name, "[In] Headphones2")) h->headphonemic = p; else if (g_str_equal (p->name, "analog-input-internal-mic") || g_str_equal (p->name, "[In] Mic")) h->internalmic = p; else if (g_str_equal (p->name, "analog-output-speaker") || g_str_equal (p->name, "[Out] Speaker")) h->internalspk = p; // } else { #if (PA_PROTOCOL_VERSION >= 34) /* in the first loop, set only headphones */ /* the microphone ports are assigned in the second loop */ if (p->type == PA_DEVICE_PORT_TYPE_HEADPHONES) { if (p->availability_group) HEADSET_PORT_SET (h->headphones, p); } else if (p->type == PA_DEVICE_PORT_TYPE_SPEAKER) { HEADSET_PORT_SET (h->internalspk, p); } else if (p->type == PA_DEVICE_PORT_TYPE_MIC) { if (!p->availability_group) HEADSET_PORT_SET (h->internalmic, p); } #else // g_warning_once ("libgnome-volume-control running against PulseAudio %u, " // "but compiled against older %d, report a bug to your distribution", // control->priv->server_protocol_version, // PA_PROTOCOL_VERSION); #endif // } } return h; } gboolean UkmediaMainWidget::verifyAlsaCard (int cardindex, gboolean *headsetmic, gboolean *headphonemic) { char *ctlstr; snd_hctl_t *hctl; snd_ctl_elem_id_t *id; int err; *headsetmic = FALSE; *headphonemic = FALSE; ctlstr = g_strdup_printf ("hw:%i", cardindex); if ((err = snd_hctl_open (&hctl, ctlstr, 0)) < 0) { g_warning ("snd_hctl_open failed: %s", snd_strerror(err)); g_free (ctlstr); return FALSE; } g_free (ctlstr); if ((err = snd_hctl_load (hctl)) < 0) { // if (hasNo && !hasOther) g_warning ("snd_hctl_load failed: %s", snd_strerror(err)); snd_hctl_close (hctl); return FALSE; } snd_ctl_elem_id_alloca (&id); snd_ctl_elem_id_clear (id); snd_ctl_elem_id_set_interface (id, SND_CTL_ELEM_IFACE_CARD); snd_ctl_elem_id_set_name (id, "Headphone Mic Jack"); if (snd_hctl_find_elem (hctl, id)) *headphonemic = TRUE; snd_ctl_elem_id_clear (id); snd_ctl_elem_id_set_interface (id, SND_CTL_ELEM_IFACE_CARD); snd_ctl_elem_id_set_name (id, "Headset Mic Phantom Jack"); if (snd_hctl_find_elem (hctl, id)) *headsetmic = TRUE; if (*headphonemic) { snd_ctl_elem_id_clear (id); snd_ctl_elem_id_set_interface (id, SND_CTL_ELEM_IFACE_CARD); snd_ctl_elem_id_set_name (id, "Headset Mic Jack"); if (snd_hctl_find_elem (hctl, id)) *headsetmic = TRUE; } snd_hctl_close (hctl); return *headsetmic || *headphonemic; } void UkmediaMainWidget::checkAudioDeviceSelectionNeeded(const pa_card_info *info) { headsetPorts *h; gboolean start_dialog, stop_dialog; qDebug() << "check_audio_device_selection_needed" <name; start_dialog = FALSE; stop_dialog = FALSE; h = getHeadsetPorts(info); if (!h->headphones || (!h->headsetmic && !h->headphonemic)) { qDebug() << "no headset jack" ; /* Not a headset jack */ goto out; } else { qDebug() << "headset jack" << h->headphonemic << h->headphones; } if (headsetCard != (int) info->index) { int cardindex; gboolean hsmic = TRUE; gboolean hpmic = TRUE; const char *s; s = pa_proplist_gets (info->proplist, "alsa.card"); if (!s) { goto out; } cardindex = strtol(s, NULL, 10); if (cardindex == 0 && strcmp(s, "0") != 0) { goto out; } if (!verifyAlsaCard(cardindex, &hsmic, &hpmic)) { goto out; } headsetCard = info->index; hasHeadsetmic = hsmic && h->headsetmic; hasHeadphonemic = hpmic && h->headphonemic; } else { start_dialog = (h->headphones->available != PA_PORT_AVAILABLE_NO) && !headsetPluggedIn; stop_dialog = (h->headphones->available == PA_PORT_AVAILABLE_NO) && headsetPluggedIn; } headsetPluggedIn = h->headphones->available != PA_PORT_AVAILABLE_NO; m_pHeadphonesName = GET_PORT_NAME(h->headphones); m_pHeadsetmicName = GET_PORT_NAME(h->headsetmic); m_pHeadphonemicName = GET_PORT_NAME(h->headphonemic); m_pInternalspkName = GET_PORT_NAME(h->internalspk); m_pInternalmicName = GET_PORT_NAME(h->internalmic); if (firstLoad) { firstLoad = false; } else { if (headsetPluggedIn) { headsetWidget->showWindow(); } else { if (headsetWidget->isShow) { headsetWidget->hide(); headsetWidget->isShow = false; } } } qDebug() << "check_audio_device_selection_needed" <defaultSinkName); qDebug() << "ctf ----- onPrepareForSleep" << sleep << m_pVolumeControl->defaultSinkName; if(sleep) { cmd.append(" 1"); system(cmd.toLatin1().data()); // pa_context_suspend_sink_by_name(m_pVolumeControl->getContext(),m_pVolumeControl->defaultSinkName,1,nullptr,nullptr); uninhibit(); } else { cmd.append(" 0"); system(cmd.toLatin1().data()); // pa_context_suspend_sink_by_name(m_pVolumeControl->getContext(),m_pVolumeControl->defaultSinkName,0,nullptr,nullptr); inhibit(); } } /* 输出设备提示弹窗 */ void UkmediaMainWidget::deviceChangedShowSlot(QString dev_name) { if (SystemVersion::SYSTEM_VERSION_OKNILE == m_version \ || SystemVersion::SYSTEM_VERSION_KYLIN_2501_V11 == m_version) { return; } osdWidget->setIcon(dev_name); osdWidget->dialogShow(); } /* * 更新设备端口 */ void UkmediaMainWidget::updateDevicePort() { QMap>::iterator it; QMap::iterator at; QMap temp; currentInputPortLabelMap.clear(); currentOutputPortLabelMap.clear(); if (firstEntry) { for (it = m_pVolumeControl->outputPortMap.begin();it!=m_pVolumeControl->outputPortMap.end();) { temp = it.value(); for (at=temp.begin();at!=temp.end();) { qDebug() << "updateDevicePort" << firstEntry << it.key() << at.value() <cardMap); addOutputListWidgetItem(at.key(),at.value(),cardName); ++at; } ++it; } for (it = m_pVolumeControl->inputPortMap.begin();it!=m_pVolumeControl->inputPortMap.end();) { temp = it.value(); for (at=temp.begin();at!=temp.end();) { qDebug() << "updateDevicePort" << firstEntry << it.key() << at.value(); QString cardName = findCardName(it.key(),m_pVolumeControl->cardMap); addInputListWidgetItem(at.value(),cardName); ++at; } ++it; } } else { //记录上一次output label for (int i=0;im_pOutputListWidget->count();i++) { QMap::iterator at; QListWidgetItem *item = systemWidget->m_pOutputListWidget->item(i); UkuiListWidgetItem *wid = (UkuiListWidgetItem *)systemWidget->m_pOutputListWidget->itemWidget(item); int index; for (at=m_pVolumeControl->cardMap.begin();at!=m_pVolumeControl->cardMap.end();) { if (wid->deviceLabel->text() == at.value()) { index = at.key(); break; } ++at; } currentOutputPortLabelMap.insert(index,wid->portLabel->fullText); } for (int i=0;im_pInputListWidget->count();i++) { QListWidgetItem *item = systemWidget->m_pInputListWidget->item(i); UkuiListWidgetItem *wid = (UkuiListWidgetItem *)systemWidget->m_pInputListWidget->itemWidget(item); int index; int count; QMap::iterator at; for (at=m_pVolumeControl->cardMap.begin();at!=m_pVolumeControl->cardMap.end();) { if (wid->deviceLabel->text() == at.value()) { index = at.key(); break; } ++at; ++count; } currentInputPortLabelMap.insert(index,wid->portLabel->fullText); } systemWidget->m_pInputListWidget->blockSignals(true); deleteNotAvailableOutputPort(); addAvailableOutputPort(); initOutputListWidgetItem(); deleteNotAvailableInputPort(); addAvailableInputPort(); systemWidget->m_pInputListWidget->blockSignals(false); Q_EMIT updateDevSignal(); } if (systemWidget->m_pOutputListWidget->count() > 0 /*|| m_pInputWidget->m_pInputListWidget->count()*/) { //需求31268待2501再合入 #if 1 firstEntry = false; systemWidget->m_pSystemVolumeSlider->setEnabled(true); appWidget->systemVolumeSlider->setEnabled(true); systemWidget->m_pSystemVolumeBtn->setEnabled(true); appWidget->systemVolumeBtn->setEnabled(true); } else { systemWidget->m_pSystemVolumeSlider->setEnabled(false); appWidget->systemVolumeSlider->setEnabled(false); systemWidget->m_pSystemVolumeBtn->setEnabled(false); appWidget->systemVolumeBtn->setEnabled(false); } #elif 0 firstEntry = false; } #endif } void UkmediaMainWidget::updateListWidgetItemSlot() { qDebug() << "updateListWidgetItemSlot---------"; initOutputListWidgetItem(); initInputListWidgetItem(); themeChangeIcons(); switchModuleEchoCancel(); // switchModuleLoopBack(); } /* * output list widget选项改变,设置对应的输出设备 */ void UkmediaMainWidget::outputListWidgetCurrentRowChangedSlot(int row) { //当所有可用的输出设备全部移除,台式机才会出现该情况 if (row == -1) return; QListWidgetItem *item = systemWidget->m_pOutputListWidget->item(row); if (item == nullptr) { qDebug() <<"output current item is null"; } UkuiListWidgetItem *wid = (UkuiListWidgetItem *)systemWidget->m_pOutputListWidget->itemWidget(item); QListWidgetItem *inputCurrrentItem = systemWidget->m_pInputListWidget->currentItem(); UkuiListWidgetItem *inputWid = (UkuiListWidgetItem *)systemWidget->m_pInputListWidget->itemWidget(inputCurrrentItem); bool isContainBlue = inputDeviceContainBluetooth(); setDeviceButtonState(row); //当输出设备从蓝牙切换到其他设备时,需将蓝牙声卡的配置文件切换为a2dp-sink if (isContainBlue && (strstr(m_pVolumeControl->defaultSourceName,"headset_head_unit") || strstr(m_pVolumeControl->defaultSourceName,"bt_sco_sink"))) { QString cardName = blueCardName(); setCardProfile(cardName,"a2dp_sink"); } QStringList comboboxPortNameList = wid->portLabel->text().split("("); QStringList inputComboboxPortNameList; if (inputWid) { inputComboboxPortNameList = inputWid->portLabel->text().split("("); } QMap>::iterator outputProfileMap; QMap>::iterator inputProfileMap; QMap tempMap; QMap::iterator at; QString endOutputProfile = ""; QString endInputProfile = ""; int currentCardIndex = findCardIndex(wid->deviceLabel->text(),m_pVolumeControl->cardMap); int count,i; for (outputProfileMap=m_pVolumeControl->profileNameMap.begin();outputProfileMap!= m_pVolumeControl->profileNameMap.end();) { if (currentCardIndex == outputProfileMap.key()) { tempMap = outputProfileMap.value(); for (at=tempMap.begin(),i=0;at!= tempMap.end();++i) { if (at.key() == comboboxPortNameList.at(0)) { count = i; endOutputProfile = at.value(); } ++at; } } ++outputProfileMap; } if (inputCurrrentItem != nullptr) { QMap ::iterator it; QMap temp; int index = findCardIndex(inputWid->deviceLabel->text(),m_pVolumeControl->cardMap); for (inputProfileMap=m_pVolumeControl->inputPortProfileNameMap.begin(),count=0;inputProfileMap!= m_pVolumeControl->inputPortProfileNameMap.end();count++) { if (inputProfileMap.key() == index) { temp = inputProfileMap.value(); for (it=temp.begin();it!=temp.end();) { if (it.key() == inputComboboxPortNameList.at(0)) { endInputProfile = it.value(); } ++it; } } ++inputProfileMap; } } qDebug() << "outputListWidgetCurrentRowChangedSlot" << row << wid->deviceLabel->text() << endOutputProfile <deviceLabel->text() == inputWid->deviceLabel->text()) || \ wid->deviceLabel->text() == "alsa_card.platform-sound_DA_combine_v5" && inputWid->deviceLabel->text() == "3a.algo") { QString setProfile = endOutputProfile; if (!endOutputProfile.contains("input:analog-stereo") || !endOutputProfile.contains("HiFi")) { setProfile += "+"; setProfile +=endInputProfile; } if (endOutputProfile.contains("a2dp-sink") && endInputProfile.contains("headset-head-unit")) { setProfile = endOutputProfile; } setCardProfile(wid->deviceLabel->text(),setProfile); setDefaultOutputPortDevice(wid->deviceLabel->text(),wid->portLabel->fullText); } //如果选择的输入输出设备不是同一块声卡,需要设置一个优先级高的配置文件 else { int index = findCardIndex(wid->deviceLabel->text(),m_pVolumeControl->cardMap); QMap >::iterator it; QString profileName; for (it=m_pVolumeControl->cardProfileMap.begin();it!=m_pVolumeControl->cardProfileMap.end();) { if (it.key() == index) { if (strstr(endOutputProfile.toLatin1().data(),"headset_head_unit")) { endOutputProfile = "a2dp_sink"; } profileName = findHighPriorityProfile(index,endOutputProfile); } ++it; } QString setProfile = profileName; setCardProfile(wid->deviceLabel->text(),setProfile); setDefaultOutputPortDevice(wid->deviceLabel->text(),wid->portLabel->fullText); } qDebug() << "active output port:" << wid->portLabel->fullText << wid->deviceLabel->text(); } /* * input list widget选项改变,设置对应的输入设备 */ void UkmediaMainWidget::inputListWidgetCurrentRowChangedSlot(int row) { if (row == -1) { return; } QListWidgetItem *currentOutputItem = systemWidget->m_pOutputListWidget->currentItem(); UkuiListWidgetItem *currentOutputItemWidget = (UkuiListWidgetItem *)systemWidget->m_pOutputListWidget->itemWidget(currentOutputItem); QString currentOutputCardName = currentOutputItemWidget->deviceLabel->text(); QString currentOutputPortLabel = currentOutputItemWidget->portLabel->text().split("(").at(0); QListWidgetItem *destInputItem = systemWidget->m_pInputListWidget->item(row); UkuiListWidgetItem *destInputItemWidget = (UkuiListWidgetItem *)systemWidget->m_pInputListWidget->itemWidget(destInputItem); QString destInputCardName = destInputItemWidget->deviceLabel->text(); QString destInputPortLabel = destInputItemWidget->portLabel->text().split("(").at(0); qDebug() << __func__ << row << "destPortLabel" << destInputPortLabel << "destCardName" << destInputCardName << "defaultSourceName" << m_pVolumeControl->defaultSourceName; if (inputDeviceContainBluetooth()) { if (strstr(m_pVolumeControl->defaultSinkName, HEADSET_HEAD_UNIT) || strstr(m_pVolumeControl->defaultSourceName, HUAWEI_BTO_SOURCE)) { qDebug() << "To switch Input to a non-Bluetooth device, need to change Bluetooth card profile to 'a2dp'"; setCardProfile(blueCardName(), "a2dp_sink"); } } isCheckBluetoothInput = destInputCardName.contains("bluez_card") ? true : false; QString endInputProfile = ""; QString endOutputProfile = ""; QMap tempMap; QMap::iterator at; QMap>::iterator inputProfileMap; int destInputCardIndex = findCardIndex(destInputCardName, m_pVolumeControl->cardMap); for (inputProfileMap = m_pVolumeControl->inputPortProfileNameMap.begin(); inputProfileMap != m_pVolumeControl->inputPortProfileNameMap.end();) { if (inputProfileMap.key() == destInputCardIndex) { tempMap = inputProfileMap.value(); for (at = tempMap.begin();at != tempMap.end();) { if (at.key() == destInputPortLabel) { endInputProfile = at.value(); } ++at; } } ++inputProfileMap; } if (nullptr != currentOutputItem) { QMap tempMap; QMap ::iterator it; QMap>::iterator outputProfileMap; int OutputCardIndex = findCardIndex(currentOutputItemWidget->deviceLabel->text(), m_pVolumeControl->cardMap); for (outputProfileMap = m_pVolumeControl->profileNameMap.begin(); outputProfileMap != m_pVolumeControl->profileNameMap.end();) { if (outputProfileMap.key() == OutputCardIndex) { tempMap = outputProfileMap.value(); for(it = tempMap.begin(); it != tempMap.end();){ if(it.key() == currentOutputPortLabel) { endOutputProfile = it.value(); } ++it; } } ++outputProfileMap; } } //如果选择的输入输出设备为同一个声卡,则追加指定输入输出端口属于的配置文件 QString setProfile = ""; if (currentOutputItem != nullptr && destInputCardName == currentOutputCardName) { //有些声卡的配置文件默认只有输入/输出设备或者配置文件包含了输出输入设备,因此只需要取其中一个配置文件即可 if (endOutputProfile == "a2dp-sink" || endInputProfile == "headset_head_unit" || endOutputProfile == "HiFi") { setProfile += endInputProfile; } else { setProfile += endOutputProfile; setProfile += "+"; setProfile += endInputProfile; } } //如果选择的输入输出设备不是同一块声卡,需要设置一个优先级高的配置文件 else { QMap >::iterator it; for(it = m_pVolumeControl->cardProfileMap.begin(); it != m_pVolumeControl->cardProfileMap.end();) { if (it.key() == destInputCardIndex) { QStringList list= it.value(); setProfile = findHighPriorityProfile(destInputCardIndex, endInputProfile); if (list.contains(endOutputProfile)) { } } ++it; } } setCardProfile(destInputCardName, setProfile); setDefaultInputPortDevice(destInputCardIndex, destInputItemWidget->portLabel->fullText); } void UkmediaMainWidget::updateAppVolume(QString str, int value, bool state) { qDebug() << __func__ << str << value <findChild(str); if (slider == nullptr) { found = false; } if (found) { kylinVideoVolumeSignal = true; if(slider->value() == value) { kylinVideoVolumeSignal = false; } slider->setValue(value); } else { value = -1; } qDebug() << "found app slider, set value ----" << value; QPushButton *btn = appWidget->findChild(str); if (btn == nullptr) { found = false; } else { qDebug() << "founded app mute button ------------" << str << state; isAppMuteBtnPress = true; sinkInputMute = state; kylinVideoMuteSignal = true; btn->setChecked(true); btn->click(); } QDBusMessage message =QDBusMessage::createSignal("/", "org.ukui.media", "sinkVolumeChanged"); message<m_pOutputListWidget->count();i++) { QListWidgetItem *item = systemWidget->m_pOutputListWidget->item(i); UkuiListWidgetItem *wid = (UkuiListWidgetItem *)systemWidget->m_pOutputListWidget->itemWidget(item); if (i == row) { if (!wid->deviceButton->isChecked()) { wid->deviceButton->setChecked(true); } continue; } wid->deviceButton->setChecked(false); } } /* * 添加output port到output list widget */ void UkmediaMainWidget::addOutputListWidgetItem(QString portName, QString portLabel, QString cardName) { UkuiListWidgetItem *itemW = new UkuiListWidgetItem(this); int i = systemWidget->m_pOutputListWidget->count(); QListWidgetItem * item = new QListWidgetItem(systemWidget->m_pOutputListWidget); item->setSizeHint(QSize(200,48)); itemW->setButtonIcon(portName); itemW->setLabelText(portLabel,cardName); systemWidget->m_pOutputListWidget->blockSignals(true); systemWidget->m_pOutputListWidget->setItemWidget(item, itemW); systemWidget->m_pOutputListWidget->insertItem(i,item); systemWidget->m_pOutputListWidget->blockSignals(false); connect(itemW->deviceButton, &QPushButton::clicked, this, [=](){ int idx = indexOfOutputPortInOutputListWidget(itemW->portLabel->fullText); systemWidget->m_pOutputListWidget->setCurrentRow(idx); outputListWidgetCurrentRowChangedSlot(idx); outputListWidgetRow = idx; }); } /* * 添加input port到input list widget */ void UkmediaMainWidget::addInputListWidgetItem(QString portName, QString cardName) { UkuiListWidgetItem *itemW = new UkuiListWidgetItem(this); int i = systemWidget->m_pInputListWidget->count(); QListWidgetItem * item = new QListWidgetItem(systemWidget->m_pInputListWidget); item->setSizeHint(QSize(200,48)); //QSize(120, 40) spacing: 12px; systemWidget->m_pInputListWidget->blockSignals(true); systemWidget->m_pInputListWidget->setItemWidget(item, itemW); systemWidget->m_pInputListWidget->blockSignals(false); itemW->setLabelText(portName,cardName); itemW->setButtonIcon(portName); systemWidget->m_pInputListWidget->blockSignals(true); systemWidget->m_pInputListWidget->insertItem(i,item); systemWidget->m_pInputListWidget->blockSignals(false); } int UkmediaMainWidget::indexOfOutputPortInOutputListWidget(QString portName) { for (int row=0;rowm_pOutputListWidget->count();row++) { QListWidgetItem *item = systemWidget->m_pOutputListWidget->item(row); UkuiListWidgetItem *wid = (UkuiListWidgetItem *)systemWidget->m_pOutputListWidget->itemWidget(item); if (wid->portLabel->fullText == portName) { return row; } } return -1; } int UkmediaMainWidget::indexOfInputPortInInputListWidget(QString portName) { for (int row=0;rowm_pInputListWidget->count();row++) { QListWidgetItem *item = systemWidget->m_pInputListWidget->item(row); UkuiListWidgetItem *wid = (UkuiListWidgetItem *)systemWidget->m_pInputListWidget->itemWidget(item); if (wid->portLabel->fullText == portName) { return row; } } return -1; } /* * 当前的输出端口是否应该在output list widget上删除 */ bool UkmediaMainWidget::outputPortIsNeedDelete(int index, QString name) { QMap>::iterator it; QMap::iterator at; QMap portMap; // qDebug() << "outputPortIsNeedDelete" << index << name; for(it = m_pVolumeControl->outputPortMap.begin();it!=m_pVolumeControl->outputPortMap.end();) { if (it.key() == index) { portMap = it.value(); for (at=portMap.begin();at!=portMap.end();) { if (name == at.value()) { return false; } ++at; } } ++it; } return true; } /* * 当前的输出端口是否应该添加到output list widget上 */ bool UkmediaMainWidget::outputPortIsNeedAdd(int index, QString name) { QMap::iterator it; for(it=currentOutputPortLabelMap.begin();it!=currentOutputPortLabelMap.end();) { if ( index == it.key() && name == it.value()) { return false; } ++it; } return true; } /* * 当前的输出端口是否应该在input list widget上删除 */ bool UkmediaMainWidget::inputPortIsNeedDelete(int index, QString name) { QMap>::iterator it; QMap::iterator at; QMap portMap; for(it = m_pVolumeControl->inputPortMap.begin();it!=m_pVolumeControl->inputPortMap.end();) { if (it.key() == index) { portMap = it.value(); for (at=portMap.begin();at!=portMap.end();) { if (name == at.value()) { return false; } ++at; } } ++it; } return true; } /* * 当前的输出端口是否应该添加到input list widget上 */ bool UkmediaMainWidget::inputPortIsNeedAdd(int index, QString name) { QMap::iterator it; for(it=currentInputPortLabelMap.begin();it!=currentInputPortLabelMap.end();) { if ( index == it.key() && name == it.value()) { return false; } ++it; } return true; } /* * 移除output list widget上不可用的输出端口 */ void UkmediaMainWidget::deleteNotAvailableOutputPort() { qDebug() << "deleteNotAvailableOutputPort"; QMap::iterator it; for (it=currentOutputPortLabelMap.begin();it!=currentOutputPortLabelMap.end();) { if (outputPortIsNeedDelete(it.key(),it.value())) { qDebug() << "outputPortIsNeedDelete" << it.key() << it.value(); int index = indexOfOutputPortInOutputListWidget(it.value()); if (index == -1) { return; } systemWidget->m_pOutputListWidget->blockSignals(true); QListWidgetItem *item = systemWidget->m_pOutputListWidget->takeItem(index); systemWidget->m_pOutputListWidget->removeItemWidget(item); systemWidget->m_pOutputListWidget->blockSignals(false); it = currentOutputPortLabelMap.erase(it); continue; } ++it; } } /* * 添加可用的输出端口到output list widget */ void UkmediaMainWidget::addAvailableOutputPort() { QMap>::iterator at; QMap::iterator it; QMap tempMap; //增加端口 for(at=m_pVolumeControl->outputPortMap.begin();at!=m_pVolumeControl->outputPortMap.end();) { tempMap = at.value(); for (it=tempMap.begin();it!=tempMap.end();) { if (outputPortIsNeedAdd(at.key(),it.value())) { qDebug() << "add output list widget" << at.key()<< it.value() << it.key(); addOutputListWidgetItem(it.key(),it.value(),findCardName(at.key(),m_pVolumeControl->cardMap)); currentOutputPortLabelMap.insert(at.key(),it.value()); } ++it; } ++at; } } /* * 在input list widget删除不可用的端口 */ void UkmediaMainWidget::deleteNotAvailableInputPort() { //删除不可用的输入端口 QMap::iterator it; for (it=currentInputPortLabelMap.begin();it!=currentInputPortLabelMap.end();) { //没找到,需要删除 if (inputPortIsNeedDelete(it.key(),it.value())) { int index = indexOfInputPortInInputListWidget(it.value()); if (index == -1) return; systemWidget->m_pInputListWidget->blockSignals(true); QListWidgetItem *item = systemWidget->m_pInputListWidget->takeItem(index); systemWidget->m_pInputListWidget->removeItemWidget(item); systemWidget->m_pInputListWidget->blockSignals(false); it = currentInputPortLabelMap.erase(it); continue; } ++it; } // m_pVolumeControl->removeInputProfile(); } /* * 添加可用的输入端口到input list widget */ void UkmediaMainWidget::addAvailableInputPort() { QMap>::iterator at; QMap::iterator it; QMap tempMap; int i = systemWidget->m_pInputListWidget->count(); //增加端口 for (at=m_pVolumeControl->inputPortMap.begin();at!=m_pVolumeControl->inputPortMap.end();) { tempMap = at.value(); for (it=tempMap.begin();it!=tempMap.end();) { //需添加到list widget if (inputPortIsNeedAdd(at.key(),it.value())) { UkuiListWidgetItem *itemW = new UkuiListWidgetItem(this); QListWidgetItem * item = new QListWidgetItem(systemWidget->m_pInputListWidget); item->setSizeHint(QSize(200,48)); //QSize(120, 40) spacing: 12px; systemWidget->m_pInputListWidget->blockSignals(true); systemWidget->m_pInputListWidget->setItemWidget(item, itemW); systemWidget->m_pInputListWidget->blockSignals(false); itemW->setLabelText(it.value(),findCardName(at.key(),m_pVolumeControl->cardMap)); currentInputPortLabelMap.insert(at.key(),it.value()); systemWidget->m_pInputListWidget->blockSignals(true); systemWidget->m_pInputListWidget->insertItem(i,item); systemWidget->m_pInputListWidget->blockSignals(false); } ++it; } ++at; } } //查找指定声卡名的索引 int UkmediaMainWidget::findCardIndex(QString cardName, QMap cardMap) { QMap::iterator it; for(it=cardMap.begin();it!=cardMap.end();) { if (it.value() == cardName) { return it.key(); } ++it; } return -1; } /* * 根据声卡索引查找声卡名 */ QString UkmediaMainWidget::findCardName(int index,QMap cardMap) { QMap::iterator it; for(it=cardMap.begin();it!=cardMap.end();) { if (it.key() == index) { return it.value(); } ++it; } return ""; } QString UkmediaMainWidget::findHighPriorityProfile(int index, QString profile) { int priority = 0; QString profileName = ""; QMap profileNameMap; QMap::iterator tempMap; QMap>::iterator it; QString profileStr = findCardActiveProfile(index); QStringList list = profileStr.split("+"); QString includeProfile = ""; if (list.count() > 1) { if (profile.contains("output")) includeProfile = list.at(1); else if (profile.contains("input")) includeProfile = list.at(0); } else includeProfile = list.at(0); for (it = m_pVolumeControl->cardProfilePriorityMap.begin(); it != m_pVolumeControl->cardProfilePriorityMap.end(); ++it) { if (index == it.key()) { profileNameMap = it.value(); for (tempMap = profileNameMap.begin(); tempMap != profileNameMap.end(); ++tempMap) { if (!includeProfile.isEmpty() && tempMap.key().contains(includeProfile) && tempMap.key().contains(profile) && !tempMap.key().contains(includeProfile + "-") && !tempMap.key().contains(profile + "-")) { priority = tempMap.value(); profileName = tempMap.key(); qDebug() << "Status1: Find profileName" << profileName << "priority" << priority; } else if (tempMap.key().contains(profile) && tempMap.value() > priority) { priority = tempMap.value(); profileName = tempMap.key(); qDebug() << "Status2: Find profileName" << profileName << "priority" << priority; } } } } qInfo() << __func__ << "Select profile is" << profileName << "index" << index << "includeProfile" << includeProfile; return profileName; } /* * Find the corresponding sink according to the port name */ QString UkmediaMainWidget::findPortSink(int index,QString portName) { QMap>::iterator it; QMap portNameMap; QMap::iterator tempMap; QString sinkStr = ""; for (it=m_pVolumeControl->sinkPortMap.begin();it!=m_pVolumeControl->sinkPortMap.end();) { if(it.key() == index) { portNameMap = it.value(); for (tempMap=portNameMap.begin();tempMap!=portNameMap.end();) { if (tempMap.value() == portName) { sinkStr = tempMap.key(); // qDebug() <<"find port sink" << tempMap.value() << portName << tempMap.key() < portNameMap; QMap::iterator tempMap; QMap>::iterator it; for (it = m_pVolumeControl->sourcePortMap.begin(); it != m_pVolumeControl->sourcePortMap.end(); ++it) { if(it.key() == index) { portNameMap = it.value(); for (tempMap = portNameMap.begin(); tempMap != portNameMap.end(); ++tempMap) { if (tempMap.value() == portName) { sourceName = tempMap.key(); break; } } } } if (sourceName.isEmpty()) qDebug() << __func__ << "Can not find Source by index" << index << portName; return sourceName; } /* 查找名称为PortLbael 的portName */ QString UkmediaMainWidget::findOutputPortName(int index,QString portLabel) { QMap>::iterator it; QMapportMap; QMap::iterator tempMap; QString portName = ""; for (it = m_pVolumeControl->outputPortMap.begin();it != m_pVolumeControl->outputPortMap.end();) { if (it.key() == index) { portMap = it.value(); for (tempMap = portMap.begin();tempMap!=portMap.end();) { if (tempMap.value() == portLabel) { portName = tempMap.key(); break; } ++tempMap; } } ++it; } return portName; } /* 查找名称为PortName 的portLabel */ QString UkmediaMainWidget::findOutputPortLabel(int index,QString portName) { QMap>::iterator it; QMapportMap; QMap::iterator tempMap; QString portLabel = ""; for (it = m_pVolumeControl->outputPortMap.begin();it != m_pVolumeControl->outputPortMap.end();) { if (it.key() == index) { portMap = it.value(); for (tempMap = portMap.begin();tempMap!=portMap.end();) { // qDebug() <<"findOutputPortLabel" <>::iterator it; QMapportMap; QMap::iterator tempMap; QString portName = ""; for (it = m_pVolumeControl->inputPortMap.begin();it != m_pVolumeControl->inputPortMap.end();) { if (it.key() == index) { portMap = it.value(); for (tempMap = portMap.begin();tempMap!=portMap.end();) { if (tempMap.value() == portLabel) { portName = tempMap.key(); break; } ++tempMap; } } ++it; } return portName; } /* 查找名称为PortName 的portLabel */ QString UkmediaMainWidget::findInputPortLabel(int index,QString portName) { QMap>::iterator it; QMapportMap; QMap::iterator tempMap; QString portLabel = ""; for (it = m_pVolumeControl->inputPortMap.begin();it != m_pVolumeControl->inputPortMap.end();) { if (it.key() == index) { portMap = it.value(); for (tempMap = portMap.begin();tempMap!=portMap.end();) { if (tempMap.key() == portName) { portLabel = tempMap.value(); break; } ++tempMap; } } ++it; } return portLabel; } /* * 设置声卡的配置文件 */ void UkmediaMainWidget::setCardProfile(QString name, QString profile) { int index = findCardIndex(name, m_pVolumeControl->cardMap); m_pVolumeControl->setCardProfile(index, profile.toLatin1().data()); qDebug() << "set profile" << name << profile << index ; } /* * 设置默认的输出设备端口 */ void UkmediaMainWidget::setDefaultOutputPortDevice(QString devName, QString portName) { int cardIndex = findCardIndex(devName,m_pVolumeControl->cardMap); QString portStr = findOutputPortName(cardIndex,portName); qDebug() << "setDefaultOutputPortDevice" << cardIndex << portStr; QTimer *timer = new QTimer; timer->start(300); connect(timer,&QTimer::timeout,[=](){ QString sinkStr = findPortSink(cardIndex,portStr); qDebug() << "setDefaultOutputPortDevice" << sinkStr; /*默认的stream 和设置的stream相同 需要更新端口*/ if (strcmp(sinkStr.toLatin1().data(),m_pVolumeControl->defaultSinkName) == 0) { m_pVolumeControl->setSinkPort(sinkStr.toLatin1().data(),portStr.toLatin1().data()); } else { m_pVolumeControl->setDefaultSink(sinkStr.toLatin1().data()); m_pVolumeControl->setSinkPort(sinkStr.toLatin1().data(),portStr.toLatin1().data()); } delete timer; }); } /* * 设置默认的输入设备端口 */ void UkmediaMainWidget::setDefaultInputPortDevice(int cardIndex, QString portLabel) { QString portName = findInputPortName(cardIndex, portLabel); QTimer *timer = new QTimer; timer->start(100); connect(timer, &QTimer::timeout, [=]() { QString sourceName = findPortSource(cardIndex, portName); /* 默认stream相同,仅更新端口即可 */ if (UKMedia_Equal(sourceName.toLatin1().data(), m_pVolumeControl->defaultSourceName)) { m_pVolumeControl->setSourcePort(sourceName.toLatin1().data(), portName.toLatin1().data()); } /* 默认stream不同,先设置stream再设置端口 */ else { m_pVolumeControl->setDefaultSource(sourceName.toLatin1().data()); m_pVolumeControl->setSourcePort(sourceName.toLatin1().data(), portName.toLatin1().data()); } qDebug() << __func__ << "card" << cardIndex << "source" << sourceName << "port" << portName; delete timer; }); } /* * 查找指定索引声卡的active profile */ QString UkmediaMainWidget::findCardActiveProfile(int index) { QString activeProfileName = ""; QMap::iterator it; for (it=m_pVolumeControl->cardActiveProfileMap.begin();it!=m_pVolumeControl->cardActiveProfileMap.end();) { if (it.key() == index) { activeProfileName = it.value(); break; } ++it; } return activeProfileName; } void UkmediaMainWidget::findOutputListWidgetItem(QString cardName,QString portLabel) { for (int row=0;rowm_pOutputListWidget->count();row++) { QListWidgetItem *item = systemWidget->m_pOutputListWidget->item(row); UkuiListWidgetItem *wid = (UkuiListWidgetItem *)systemWidget->m_pOutputListWidget->itemWidget(item); qDebug() << "findOutputListWidgetItem" << "cardName:" << cardName << wid->deviceLabel->text() << "portName:" << portLabel << wid->portLabel->fullText; if (wid->deviceLabel->text() == cardName && portLabel == wid->portLabel->fullText) { systemWidget->m_pOutputListWidget->blockSignals(true); systemWidget->m_pOutputListWidget->setCurrentRow(row); setDeviceButtonState(row); systemWidget->m_pOutputListWidget->blockSignals(false); wid->deviceButton->repaint(); break; } else if (wid->deviceLabel->text() == cardName && (wid->portLabel->fullText.compare("HUAWEI Histen") == 0)) { systemWidget->m_pOutputListWidget->blockSignals(true); systemWidget->m_pOutputListWidget->setCurrentRow(row); setDeviceButtonState(row); systemWidget->m_pOutputListWidget->blockSignals(false); wid->deviceButton->repaint(); break; } } outputListWidgetRow = systemWidget->m_pOutputListWidget->currentRow(); } void UkmediaMainWidget::findInputListWidgetItem(QString cardName,QString portLabel) { for (int row=0;rowm_pInputListWidget->count();row++) { QListWidgetItem *item = systemWidget->m_pInputListWidget->item(row); UkuiListWidgetItem *wid = (UkuiListWidgetItem *)systemWidget->m_pInputListWidget->itemWidget(item); qDebug() << "findInputListWidgetItem" << "card name:" << cardName << "deviceLabel:" << wid->deviceLabel->text() << "portname" << portLabel << "portLabel:" << wid->portLabel->fullText; if (wid->deviceLabel->text() == cardName && wid->portLabel->fullText == portLabel) { systemWidget->m_pInputListWidget->blockSignals(true); systemWidget->m_pInputListWidget->setCurrentRow(row); systemWidget->m_pInputListWidget->blockSignals(false); if (wid->deviceLabel->text().contains("bluez_card")) { isCheckBluetoothInput = true; } qDebug() << "set input list widget" << row; break; } } } /* * 输入设备中是否包含蓝牙设备 */ bool UkmediaMainWidget::inputDeviceContainBluetooth() { for (int row=0;rowm_pInputListWidget->count();row++) { QListWidgetItem *item = systemWidget->m_pInputListWidget->item(row); UkuiListWidgetItem *wid = (UkuiListWidgetItem *)systemWidget->m_pInputListWidget->itemWidget(item); if (wid->deviceLabel->text().contains("bluez")) { return true; } } return false; } /* * 移除xml文件中不能识别的字符 */ QString UkmediaMainWidget::stringRemoveUnrecignizedChar(QString str) { str.remove(" "); str.remove("/"); str.remove("("); str.remove(")"); str.remove("["); str.remove("]"); return str; } QString UkmediaMainWidget::blueCardName() { for (int row=0;rowm_pInputListWidget->count();row++) { QListWidgetItem *item = systemWidget->m_pInputListWidget->item(row); UkuiListWidgetItem *wid = (UkuiListWidgetItem *)systemWidget->m_pInputListWidget->itemWidget(item); if (wid->deviceLabel->text().contains("bluez")) { return wid->deviceLabel->text(); } } return ""; } /** * @brief MainWindow::keyPressEvent 按esc键关闭主界面 * @param event */ void UkmediaMainWidget::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) { hideWindow(); } return QWidget::keyPressEvent(event); } /* *在path路径下找包含str字段的文件名(暂时用来寻找wine微信) */ QString UkmediaMainWidget::findFile(const QString path,QString str) { QDir dir(path); QString filename = ""; if(!dir.exists()) { return ""; } //获取filePath下所有文件夹和文件 dir.setFilter(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);//文件夹|文件|不包含./和../ //排序文件夹优先 dir.setSorting(QDir::DirsFirst); QStringList filer; QStringList list= dir.entryList(filer, QDir::Files | QDir::NoDotAndDotDot); if(list.size() == 0) { return ""; } for(int i = 0; i < list.size(); i++) { QFileInfo fileInfo(list.at(i)); if(fileInfo.isDir())//判断是否为文件夹 { findFile(fileInfo.filePath(),str);//递归开始 } else { if(fileInfo.fileName().contains(str) && !fileInfo.fileName().contains("企业微信") && !fileInfo.fileName().contains("卸载微信"))//设定后缀 { filename = fileInfo.fileName(); qDebug() << "在/usr/share/applications/找到文件" << filename; break; } } } return filename; } void UkmediaMainWidget::inhibit() { if (m_inhibitFileDescriptor.isValid()) { return; } QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", QStringLiteral("Inhibit")); message.setArguments(QVariantList({QStringLiteral("sleep"), "Screen Locker", "Ensuring that the screen gets locked before going to sleep", QStringLiteral("delay")})); QDBusPendingReply reply = QDBusConnection::systemBus().call(message); if (!reply.isValid()) { return; } reply.value().swap(m_inhibitFileDescriptor); } void UkmediaMainWidget::uninhibit() { if (!m_inhibitFileDescriptor.isValid()) { return; } m_inhibitFileDescriptor = QDBusUnixFileDescriptor(); } void UkmediaMainWidget::switchModuleEchoCancel() { if (UkmediaCommon::getInstance().isHuaweiPlatform()) return; bool switchState = false; if (m_pSoundSettings->keys().contains("dnsNoiseReduction")) switchState = m_pSoundSettings->get(DNS_NOISE_REDUCTION).toBool(); const QString sourceName = QString::fromUtf8(m_pVolumeControl->defaultSourceName); const bool isPciInput = sourceName.startsWith("alsa_input") && !sourceName.contains("input.usb"); const bool isAlreadyLoaded = (sourceName == "noiseReduceSource"); if (switchState) { if (isAlreadyLoaded) { qDebug() << "module-echo-cancel is already loaded"; } else if (isPciInput) { const char* args = "use_master_format=1 aec_method=webrtc " "aec_args=analog_gain_control=0 source_name=noiseReduceSource"; qDebug() << "Load module-echo-cancel"; pa_context_load_module(m_pVolumeControl->getContext(), "module-echo-cancel", args, nullptr, nullptr); } else { m_pVolumeControl->getModuleIndexByName("module-echo-cancel"); QTimer::singleShot(300, this, [=]() { qDebug() << "Unload module-echo-cancel, case1: switch to a non-PCI device" << m_pVolumeControl->findModuleIndex; pa_context_unload_module(m_pVolumeControl->getContext(), m_pVolumeControl->findModuleIndex, nullptr, nullptr); }); } } else { m_pVolumeControl->getModuleIndexByName("module-echo-cancel"); QTimer::singleShot(300, this, [=]() { qDebug() << "Unload module-echo-cancel, case2: Func is disable" << m_pVolumeControl->findModuleIndex; pa_context_unload_module(m_pVolumeControl->getContext(), m_pVolumeControl->findModuleIndex, nullptr, nullptr); }); } } void UkmediaMainWidget::switchModuleLoopBack() { bool loopbackState = false; if (m_pSoundSettings->keys().contains(LOOP_BACK)) { loopbackState = m_pSoundSettings->get(LOOP_BACK).toBool(); } if (loopbackState && !m_pVolumeControl->sourceOutputIndexMap.values().contains("Loopback")) { pa_operation *o; qDebug() << "load module loopback"; if (!(o=pa_context_load_module(m_pVolumeControl->getContext(),"module-loopback",nullptr,nullptr,nullptr))) { m_pVolumeControl->showError(QObject::tr("pa_context_load_module() failed").toUtf8().constData()); } m_pVolumeControl->isLoadLoopback = true; } else if (!loopbackState){ m_pVolumeControl->getModuleIndexByName("module-loopback"); QTimer::singleShot(300,this,[=](){ qDebug() << "unload module loopback" << m_pVolumeControl->findModuleIndex; pa_context_unload_module(m_pVolumeControl->getContext(),m_pVolumeControl->findModuleIndex,nullptr,nullptr); }); m_pVolumeControl->isLoadLoopback = false; } } /** * @brief UkmediaMainWidget::resetVolumeSliderRange * 重置系统音量滑动条范围(音量增强) * gsetting keys: volume-increase, volume-increase-value * volume-increase: 音量增强开关 * volume-increase: 音量增强最大值 **/ void UkmediaMainWidget::resetVolumeSliderRange() { if (!QGSettings::isSchemaInstalled(KEY_SOUNDS_SCHEMA)) return; bool status = m_pSoundSettings->get(VOLUME_INCREASE).toBool(); appWidget->setVolumeSliderRange(status); systemWidget->setVolumeSliderRange(status); if (!status) { QMap::iterator it; for (it = m_pVolumeControl->sinkMap.begin(); it != m_pVolumeControl->sinkMap.end(); ++it) { sinkInfo info = it.value(); int volume = (info.volume.channels >= 2) ? MAX(info.volume.values[0], info.volume.values[1]) : info.volume.values[0]; if (volume > PA_VOLUME_NORMAL) m_pVolumeControl->setSinkVolume(info.index, PA_VOLUME_NORMAL); } } } void UkmediaMainWidget::switchMonoAudio() { bool monoAudioState = false; if (m_pSoundSettings->keys().contains("monoAudio")) monoAudioState = m_pSoundSettings->get(MONO_AUDIO).toBool(); // pactl load-module module-remap-sink sink_name=mono channels=2 channel_map=mono,monomonoAudioState = false; m_pVolumeControl->getModuleIndexByName("module-remap-sink"); if(monoAudioState) { pa_operation *o; if (!(o=pa_context_load_module(m_pVolumeControl->getContext(),"module-remap-sink",\ "sink_name=mono channels=1 channel_map=mono",nullptr,nullptr))) { m_pVolumeControl->showError(QObject::tr("pa_context_load_module() failed").toUtf8().constData()); qDebug() << "load module-remap-sink module error" << monoAudioState << m_pVolumeControl->findModuleIndex; } // m_pVolumeControl->setDefaultSink("mono"); qDebug() << "1 monoAudioState " << monoAudioState; } else { if(m_pVolumeControl->masterSinkDev!="") m_pVolumeControl->setDefaultSink(m_pVolumeControl->masterSinkDev.toLatin1().data()); QTimer::singleShot(300,this,[=](){ qDebug() << "unload module-remap-sink" << m_pVolumeControl->findModuleIndex; pa_context_unload_module(m_pVolumeControl->getContext(),m_pVolumeControl->findModuleIndex,nullptr,nullptr); }); qDebug() << "0 monoAudioState " << monoAudioState<< "unload module-remap-sink" << m_pVolumeControl->findModuleIndex; } } void UkmediaMainWidget::monitorSessionStatus() { //get session path // m_sessionActiveBus = QDBusConnection::systemBus(); QString methodName; QVariantList args; methodName = QStringLiteral("GetSessionByPID"); args << (quint32) QCoreApplication::applicationPid(); QDBusMessage message = QDBusMessage::createMethodCall(m_sessionControllerService, m_sessionControllerPath, m_sessionControllerManagerInterface, methodName); message.setArguments(args); QDBusPendingReply session = QDBusConnection::systemBus().asyncCall(message); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(session, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) { QDBusPendingReply reply = *self; self->deleteLater(); if (!reply.isValid()) { qDebug()<< "The session is not registered with " << m_sessionControllerName << " " << reply.error().message(); return; } m_sessionPath = reply.value().path(); qDebug() << "Session path:" << m_sessionPath; //get sessionactive QDBusConnection::systemBus().connect(m_sessionControllerService, m_sessionPath, m_sessionControllerPropertiesInterface, QStringLiteral("PropertiesChanged"), this, SLOT(getSessionActive())); }); } void UkmediaMainWidget::getSessionActive() { const static QString s_dbusPropertiesInterface = QStringLiteral("org.freedesktop.DBus.Properties"); QDBusMessage message = QDBusMessage::createMethodCall(m_sessionControllerService, m_sessionPath, s_dbusPropertiesInterface, QStringLiteral("Get")); message.setArguments(QVariantList({m_sessionControllerSessionInterface, m_sessionControllerActiveProperty})); QDBusPendingReply reply = QDBusConnection::systemBus().asyncCall(message); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) { QDBusPendingReply reply = *self; self->deleteLater(); if (!reply.isValid()) { qDebug() << "Failed to get Active Property of " << m_sessionControllerName << " session:" << reply.error().message(); return; } const bool active = reply.value().toBool(); onPrepareForSleep(!active); qDebug() << "active -----" <defaultSinkName == ""){ return "NULL"; } return m_pVolumeControl->defaultSinkName; } QString UkmediaMainWidget::getDefaultInputDevice() { if(m_pVolumeControl->defaultSourceName == ""){ return "NULL"; } return m_pVolumeControl->defaultSourceName; } bool UkmediaMainWidget::isNoneOutputDevice() { bool ret = true; QMap::iterator it; for (it=m_pVolumeControl->sinkMap.begin();it!=m_pVolumeControl->sinkMap.end();++it) { if((it.value().name != "") && (!it.value().name.contains("auto_null"))){ ret = false; } } return ret; } bool UkmediaMainWidget::isNoneInputDevice() { bool ret = true; QMap::iterator it; for (it=m_pVolumeControl->sourceMap.begin();it!=m_pVolumeControl->sourceMap.end();++it) { if((it.value().name != "") && (!it.value().name.contains(".monitor"))){ ret = false; } } return ret; } QStringList UkmediaMainWidget::getAllOutputDevices() { QMap::iterator it; QStringList devicesList; for (it=m_pVolumeControl->sinkMap.begin();it!=m_pVolumeControl->sinkMap.end();) { devicesList.append(it.value().name); ++it; } return devicesList; } QStringList UkmediaMainWidget::getAllInputDevices() { QMap::iterator it; QStringList devicesList; for (it=m_pVolumeControl->sourceMap.begin();it!=m_pVolumeControl->sourceMap.end();) { devicesList.append(it.value().name); ++it; } return devicesList; } int UkmediaMainWidget::getDefaultOutputVolume() { int value = m_pVolumeControl->sinkVolume; if (m_pSoundSettings->keys().contains("volumeIncrease")) { if(m_pSoundSettings->get("volume-increase").toBool()) value = (m_pVolumeControl->sinkVolume) +0.5; } return UkmediaCommon::getInstance().paVolumeToMediaVolume(value); } int UkmediaMainWidget::getDefaultInputVolume() { int volume = m_pVolumeControl->sourceVolume; return UkmediaCommon::getInstance().paVolumeToMediaVolume(volume); } bool UkmediaMainWidget::getDefaultOutputMuteState() { return m_pVolumeControl->sinkMuted; } bool UkmediaMainWidget::getDefaultInputMuteState() { return m_pVolumeControl->sourceMuted; } bool UkmediaMainWidget::setDefaultOutputVolume(int value) { if (value < 0 || value > 125) { qDebug() << "setDefaultOutputVolume value is invalid !"; return false; } if (m_pSoundSettings->keys().contains("volumeIncrease")) { if (!m_pSoundSettings->get("volume-increase").toBool() && value == 100) value = 100; } int paVolume = UkmediaCommon::getInstance().mediaVolumeToPaVolume(value); m_pVolumeControl->setSinkVolume(m_pVolumeControl->sinkIndex, paVolume); return true; } bool UkmediaMainWidget::setDefaultInputVolume(int value) { if(value < 0 || value > 100) { qDebug() << "setDefaultOutputVolume value is invalid!"; return false; } int paVolume = UkmediaCommon::getInstance().mediaVolumeToPaVolume(value); m_pVolumeControl->setSourceVolume(m_pVolumeControl->sourceIndex, paVolume); return true; } bool UkmediaMainWidget::setDefaultOutputMuteState(bool mute) { if(mute == m_pVolumeControl->sinkMuted) return false; m_pVolumeControl->setSinkMute(mute); return true; } bool UkmediaMainWidget::setDefaultInputMuteState(bool mute) { if(mute == m_pVolumeControl->sourceMuted) return false; m_pVolumeControl->setSourceMute(mute); return true; } bool UkmediaMainWidget::setDefaultOutputDevice(QString deviceName) { QMap>::iterator it; QMap portNameMap; bool isSucceed = false; for (it=m_pVolumeControl->sinkPortMap.begin();it!=m_pVolumeControl->sinkPortMap.end();) { portNameMap = it.value(); if(portNameMap.keys().contains(deviceName)){ isSucceed = m_pVolumeControl->setDefaultSink(deviceName.toLatin1().data()); return isSucceed; } ++it; } return isSucceed; } bool UkmediaMainWidget::setDefaultInputDevice(QString deviceName) { QMap>::iterator it; QMap portNameMap; bool isSucceed = false; for (it=m_pVolumeControl->sourcePortMap.begin();it!=m_pVolumeControl->sourcePortMap.end();) { portNameMap = it.value(); if(portNameMap.keys().contains(deviceName)){ isSucceed = m_pVolumeControl->setDefaultSource(deviceName.toLatin1().data()); return isSucceed; } ++it; } return isSucceed; } void UkmediaMainWidget::sendUpdateVolumeSignal(int soundType, int value) { switch (soundType) { case SoundType::SINK: Q_EMIT updateVolume(value); break; case SoundType::SOURCE: Q_EMIT updateSourceVolume(value); break; default: return; } } void UkmediaMainWidget::sendUpdateMuteSignal(int soundType, bool isMute) { switch (soundType) { case SoundType::SINK: Q_EMIT updateMute(isMute); break; case SoundType::SOURCE: Q_EMIT updateSourceMute(isMute); break; default: return; } } void UkmediaMainWidget::batteryLevelChanged(QString macAddr, int battery) { qDebug() << "batteryLevelChanged" << macAddr << battery; m_pBluetoothDbus->batteryLevel = battery; m_pBluetoothDbus->macAddress = macAddr; m_pBluetoothDbus->sendBatteryChangedSignal(macAddr, battery); } /** * @brief UkmediaMainWidget::initBlueDeviceVolume * 蓝牙耳机初始化音量(针对硬件音量反馈延迟处理) * signal: initBlueDeviceVolumeSig **/ void UkmediaMainWidget::initBlueDeviceVolume(int index, QString name) { QTimer *m_timer = new QTimer(); m_timer->start(200); connect(m_timer, &QTimer::timeout, this, [=]() { m_pVolumeControl->setSinkVolume(index, OUTPUT_VOLUME); m_pVolumeControl->customSoundFile->addXmlNode(name,false); m_timer->stop(); }); qDebug() << "Initialize the volume of the Bluetooth device " << index << name; } void UkmediaMainWidget::deviceAdjustSlots(int type) { qDebug() << "deviceAdjustSlots enter type:" << type; switch ((type)) { case 1: { QDBusInterface mediaInterface("org.ukui.volume.control","/org/ukui/volume/control","org.ukui.volume.control"); QDBusMessage reply = mediaInterface.call("getAvailablePortList", 1); if (reply.arguments().isEmpty()) { qDebug() << __func__ << "arguments is nullptr..."; return; } 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; // 端口的类型 const QDBusArgument& dbusArgs = reply.arguments().at(0).value(); dbusArgs.beginArray(); while (!dbusArgs.atEnd()) { dbusArgs.beginStructure(); dbusArgs >> cardName; dbusArgs >> cardDesc; dbusArgs >> portName; dbusArgs >> portLabel; dbusArgs >> priority; dbusArgs >> direction; dbusArgs >> available; dbusArgs >> enabled; if (enabled) { ++m_enableDeviceCount; } // dbusArgs >> type; dbusArgs.endStructure(); } dbusArgs.endArray(); } break; default: break; } qDebug() << "deviceAdjustSlots m_enableDeviceCount:" << m_enableDeviceCount; if (m_enableDeviceCount == 0) { bool isMute = m_pVolumeControl->getSinkMute(); outputVolumeDarkThemeImage(0, isMute); if(isMute) soundSystemTrayIcon->setToolTip(tr("Current volume:") + tr("Muted") ); else soundSystemTrayIcon->setToolTip(tr("Current volume:") + QString::number(0)); } else { themeChangeIcons(); } m_enableDeviceCount = 0;//reset } void UkmediaMainWidget::playAlertSound(QString soundEvent) { QDBusInterface interface("org.ukui.sound.theme.player","/org/ukui/sound/theme/player","org.ukui.sound.theme.player",QDBusConnection::sessionBus()); if(!interface.isValid()){ qDebug() << qPrintable(QDBusConnection::sessionBus().lastError().message()); return; } QDBusReply reply = interface.call("playAlertSound", soundEvent); if(reply.isValid()){ bool value = reply.value(); qDebug() << QString("Method Client playAlertSound = %1").arg(value); } else qDebug() << "playAlertSound Method call failed !"; } // 获取系统默认输出设备 QString UkmediaMainWidget::getSystemOutputDevice() { return findOutputPortLabel(m_pVolumeControl->defaultOutputCard,m_pVolumeControl->sinkPortName); } // 获取系统默认输入设备 QString UkmediaMainWidget::getSystemInputDevice() { return findInputPortLabel(m_pVolumeControl->defaultInputCard,m_pVolumeControl->sourcePortName); } // 获取app名单 QStringList UkmediaMainWidget::getAppList() { return m_pVolumeControl->sinkInputList; } QList UkmediaMainWidget::getPlaybackAppInfo() { qDBusRegisterMetaType(); QList infoList; QMap::iterator it; for(it = m_pVolumeControl->sinkInputMap.begin(); it != m_pVolumeControl->sinkInputMap.end(); it++) { QVariant info; info.setValue(it.value()); infoList.append(info); } return infoList; } QList UkmediaMainWidget::getRecordAppInfo() { qDBusRegisterMetaType(); QList infoList; QMap::iterator it; for(it = m_pVolumeControl->sourceOutputMap.begin(); it != m_pVolumeControl->sourceOutputMap.end(); it++) { QVariant info; info.setValue(it.value()); infoList.append(info); } return infoList; } // 获取应用音量 int UkmediaMainWidget::getAppVolume(QString appName) { int volume = m_pVolumeControl->getSinkInputVolume(appName.toLatin1().data()); return UkmediaCommon::getInstance().paVolumeToMediaVolume(volume); } // 获取应用静音状态 bool UkmediaMainWidget::getAppMuteState(QString appName) { bool state = false; QMap::iterator it; for (it = m_pVolumeControl->sinkInputMuteMap.begin(); it != m_pVolumeControl->sinkInputMuteMap.end(); it++) { if (appName == it.key()) { state = it.value(); break; } } return state; } // 获取应用对应的输出设备 QString UkmediaMainWidget::getAppOutputDevice(QString appName) { int cardIndex = -1; int sinkIndex = -1; QString portName = ""; QString portLabel = ""; QMap::iterator it; QMap::iterator at; // 1. 获取应用对应sink name for(it = m_pVolumeControl->sinkInputMap.begin(); it != m_pVolumeControl->sinkInputMap.end(); it++) { appInfo info = it.value(); if (appName == info.name) { sinkIndex = info.masterIndex; break; } } // 2.获取端口 cardIndex = m_pVolumeControl->sinkMap.value(sinkIndex).card; portName = m_pVolumeControl->sinkMap.value(sinkIndex).active_port_description; for (at = currentOutputPortLabelMap.begin(); at != currentOutputPortLabelMap.end(); at++) { if (cardIndex == at.key() && at.value().contains(portName)) { portLabel = at.value(); break; } } if (portLabel.isEmpty()) { QString masterDevName = m_pVolumeControl->sinkMap.value(sinkIndex).master_device; int cardIndex = m_pVolumeControl->findMasterDeviceCardIndex(masterDevName); for (at = currentOutputPortLabelMap.begin(); at != currentOutputPortLabelMap.end(); at++) { if (cardIndex == at.key()) { portLabel = at.value(); break; } } } portLabel = (portLabel != "") ? portLabel : getSystemOutputDevice(); return portLabel; } // 获取应用对应的输入设备 QString UkmediaMainWidget::getAppInputDevice(QString appName) { int cardIndex = -1; int sourceIndex = -1; QString portName = ""; QString portLabel = ""; QMap::iterator it; QMap::iterator at; for(it = m_pVolumeControl->sourceOutputMap.begin(); it != m_pVolumeControl->sourceOutputMap.end(); it++) { appInfo info = it.value(); if (appName == info.name) { sourceIndex = info.masterIndex; break; } } cardIndex = m_pVolumeControl->sourceMap.value(sourceIndex).card; portName = m_pVolumeControl->sourceMap.value(sourceIndex).active_port_description; if (sourceIndex == -1 && portName == "") return getSystemInputDevice(); for (at = currentInputPortLabelMap.begin(); at != currentInputPortLabelMap.end(); at++) { if (cardIndex == at.key() && at.value().contains(portName)) { portLabel = at.value(); break; } } portLabel = (portLabel != "") ? portLabel : getSystemInputDevice(); return portLabel; } // 设置应用静音 bool UkmediaMainWidget::setAppMuteState(QString appName, bool state) { bool n_SinkInputStatus = m_pVolumeControl->setSinkInputMuted(appName, state); bool n_SourceOutputStatus = m_pVolumeControl->setSourceOutputMuted(appName, state); return (n_SinkInputStatus || n_SourceOutputStatus) ? true : false; } // 设置应用音量 bool UkmediaMainWidget::setAppVolume(QString appName, int value) { int appIndex = -1; QMap::iterator it; for(it = m_pVolumeControl->sinkInputMap.begin(); it != m_pVolumeControl->sinkInputMap.end(); it++) { appInfo info = it.value(); if (appName == info.name) { int paVolume = UkmediaCommon::getInstance().mediaVolumeToPaVolume(value); m_pVolumeControl->setSinkInputVolume(info.index, paVolume, info.channel); } } if (appName == "kylin-music" || appName == "kylin-video") { QDBusMessage message =QDBusMessage::createSignal("/", "org.ukui.media", "sinkVolumeChanged"); message<< appName << value << getAppMuteState(appName); QDBusConnection::sessionBus().send(message); qDebug() << "Send SIGNAL: sinkVolumeChanged. Case: Synchronous volume status" << appName; } return (appIndex != -1) ? true : false; } // 设置系统默认输出设备 bool UkmediaMainWidget::setSystemOutputDevice(QString portLabel) { int index = indexOfOutputPortInOutputListWidget(portLabel); if (index >= 0) { systemWidget->m_pOutputListWidget->setCurrentRow(index); outputListWidgetCurrentRowChangedSlot(index); outputListWidgetRow = index; return true; } return false; } // 设置系统默认输入设备 bool UkmediaMainWidget::setSystemInputDevice(QString portLabel) { int index = indexOfInputPortInInputListWidget(portLabel); if (index >= 0) { systemWidget->m_pInputListWidget->setCurrentRow(index); return true; } return false; } // 设置应用默认输出设备 bool UkmediaMainWidget::setAppOutputDevice(QString appName, int cardIndex, QString sinkPortName) { if (cardIndex == -1) return false; QString sinkStr = findPortSink(cardIndex, sinkPortName); if (sinkStr == "") return false; m_pVolumeControl->moveSinkInput(appName, sinkStr.toLatin1().data()); return true; } // 设置应用默认输入设备 bool UkmediaMainWidget::setAppInputDevice(QString appName, int cardIndex, QString sourcePortName) { if (cardIndex == -1) return false; QString sourceStr = findPortSource(cardIndex, sourcePortName); if (sourceStr == "") return false; m_pVolumeControl->moveSoureOutput(appName, sourceStr.toLatin1().data()); return true; } /** * @brief UkmediaMainWidget::isPortHidingNeeded * UKCC应用音量UI是否需要隐藏某端口 * soundType: 输入 or 输出 * cardIndex: 声卡idx * portLabel: 端口描述名 * return: True隐藏,False无需隐藏 **/ bool UkmediaMainWidget::isPortHidingNeeded(int soundType, int cardIndex, QString portLabel) { if (cardIndex < 0 || portLabel.isEmpty()) return true; portLabel = portLabel.split("(").at(0); if (soundType == SoundType::SINK) { QMap::iterator itor; for (itor = m_pVolumeControl->sinkMap.begin(); itor != m_pVolumeControl->sinkMap.end(); ++itor) { sinkInfo outInfo = itor.value(); QString active_port = outInfo.active_port_description; QList list; for (int i = 0; i < outInfo.sink_port_list.size(); ++i) list.append(outInfo.sink_port_list.at(i).description); // 确认对应sink,如果为活跃端口则显示 if (cardIndex == outInfo.card && list.contains(portLabel)) return (portLabel == active_port) ? false : true; } return true; } else if (soundType == SoundType::SOURCE) { QMap::iterator itor; for (itor = m_pVolumeControl->sourceMap.begin(); itor != m_pVolumeControl->sourceMap.end(); ++itor) { sourceInfo inInfo = itor.value(); QString active_port = inInfo.active_port_description; QList list; for (int i = 0; i < inInfo.source_port_list.size(); ++i) list.append(inInfo.source_port_list.at(i).description); if (cardIndex == inInfo.card && list.contains(portLabel)) return (portLabel == active_port) ? false : true; } return true; } return false; } void UkmediaMainWidget::showUkuiVolumeControlTray() { // v11 2603 pre 不再使用原来托盘,而是调用接口唤起qml声音插件 QDBusInterface interface("org.ukui.Sidebar", "/org/ukui/Sidebar", "org.ukui.Sidebar", QDBusConnection::sessionBus()); if (!interface.isValid()) { qDebug() << "DBus interface is not valid:" << qPrintable(interface.lastError().message()); return; } QDBusReply reply = interface.call("shortcutWidgetActive", "org.ukui.shortcut.audio", false); if (reply.isValid()) { bool result = reply.value(); qDebug() << "Call succeeded. Result:" << result; } else { qDebug() << "Call failed:" << qPrintable(reply.error().message()); } } UkmediaMainWidget::~UkmediaMainWidget() { if (m_panelGSettings) { delete m_panelGSettings; m_panelGSettings = nullptr; } if (soundSystemTrayIcon) { delete soundSystemTrayIcon; soundSystemTrayIcon = nullptr; } } ukui-media/ukui-volume-control-applet-qt/QtSingleApplication/0000775000175000017500000000000015170054730023313 5ustar fengfengukui-media/ukui-volume-control-applet-qt/QtSingleApplication/qtlocalpeer.h0000664000175000017500000000520515170052044025775 0ustar fengfeng/**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the Qt Solutions component. ** ** $QT_BEGIN_LICENSE:BSD$ ** You may use this file under the terms of the BSD license as follows: ** ** "Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are ** met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names ** of its contributors may be used to endorse or promote products derived ** from this software without specific prior written permission. ** ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef QTLOCALPEER_H #define QTLOCALPEER_H #include #include #include #include "qtlockedfile.h" class QtLocalPeer : public QObject { Q_OBJECT public: QtLocalPeer(QObject *parent = 0, const QString &appId = QString()); bool isClient(); bool sendMessage(const QString &message, int timeout); QString applicationId() const { return id; } Q_SIGNALS: void messageReceived(const QString &message); protected Q_SLOTS: void receiveConnection(); protected: QString id; QString socketName; QLocalServer* server; QtLP_Private::QtLockedFile lockFile; private: static const char* ack; }; #endif // QTLOCALPEER_H ukui-media/ukui-volume-control-applet-qt/QtSingleApplication/qtsingleapplication.pri0000664000175000017500000000106115170052044030073 0ustar fengfengINCLUDEPATH += $$PWD DEPENDPATH += $$PWD QT *= network greaterThan(QT_MAJOR_VERSION, 4): QT *= widgets qtsingleapplication-uselib:!qtsingleapplication-buildlib { LIBS += -L$$QTSINGLEAPPLICATION_LIBDIR -l$$QTSINGLEAPPLICATION_LIBNAME } else { SOURCES += $$PWD/qtsingleapplication.cpp $$PWD/qtlocalpeer.cpp HEADERS += $$PWD/qtsingleapplication.h $$PWD/qtlocalpeer.h } win32 { contains(TEMPLATE, lib):contains(CONFIG, shared):DEFINES += QT_QTSINGLEAPPLICATION_EXPORT else:qtsingleapplication-uselib:DEFINES += QT_QTSINGLEAPPLICATION_IMPORT } ukui-media/ukui-volume-control-applet-qt/QtSingleApplication/qtlockedfile.cpp0000664000175000017500000001374215170052044026470 0ustar fengfeng/**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the Qt Solutions component. ** ** $QT_BEGIN_LICENSE:BSD$ ** You may use this file under the terms of the BSD license as follows: ** ** "Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are ** met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names ** of its contributors may be used to endorse or promote products derived ** from this software without specific prior written permission. ** ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qtlockedfile.h" /*! \class QtLockedFile \brief The QtLockedFile class extends QFile with advisory locking functions. A file may be locked in read or write mode. Multiple instances of \e QtLockedFile, created in multiple processes running on the same machine, may have a file locked in read mode. Exactly one instance may have it locked in write mode. A read and a write lock cannot exist simultaneously on the same file. The file locks are advisory. This means that nothing prevents another process from manipulating a locked file using QFile or file system functions offered by the OS. Serialization is only guaranteed if all processes that access the file use QLockedFile. Also, while holding a lock on a file, a process must not open the same file again (through any API), or locks can be unexpectedly lost. The lock provided by an instance of \e QtLockedFile is released whenever the program terminates. This is true even when the program crashes and no destructors are called. */ /*! \enum QtLockedFile::LockMode This enum describes the available lock modes. \value ReadLock A read lock. \value WriteLock A write lock. \value NoLock Neither a read lock nor a write lock. */ /*! Constructs an unlocked \e QtLockedFile object. This constructor behaves in the same way as \e QFile::QFile(). \sa QFile::QFile() */ QtLockedFile::QtLockedFile() : QFile() { #ifdef Q_OS_WIN wmutex = 0; rmutex = 0; #endif m_lock_mode = NoLock; } /*! Constructs an unlocked QtLockedFile object with file \a name. This constructor behaves in the same way as \e QFile::QFile(const QString&). \sa QFile::QFile() */ QtLockedFile::QtLockedFile(const QString &name) : QFile(name) { #ifdef Q_OS_WIN wmutex = 0; rmutex = 0; #endif m_lock_mode = NoLock; } /*! Opens the file in OpenMode \a mode. This is identical to QFile::open(), with the one exception that the Truncate mode flag is disallowed. Truncation would conflict with the advisory file locking, since the file would be modified before the write lock is obtained. If truncation is required, use resize(0) after obtaining the write lock. Returns true if successful; otherwise false. \sa QFile::open(), QFile::resize() */ bool QtLockedFile::open(OpenMode mode) { if (mode & QIODevice::Truncate) { qWarning("QtLockedFile::open(): Truncate mode not allowed."); return false; } return QFile::open(mode); } /*! Returns \e true if this object has a in read or write lock; otherwise returns \e false. \sa lockMode() */ bool QtLockedFile::isLocked() const { return m_lock_mode != NoLock; } /*! Returns the type of lock currently held by this object, or \e QtLockedFile::NoLock. \sa isLocked() */ QtLockedFile::LockMode QtLockedFile::lockMode() const { return m_lock_mode; } /*! \fn bool QtLockedFile::lock(LockMode mode, bool block = true) Obtains a lock of type \a mode. The file must be opened before it can be locked. If \a block is true, this function will block until the lock is aquired. If \a block is false, this function returns \e false immediately if the lock cannot be aquired. If this object already has a lock of type \a mode, this function returns \e true immediately. If this object has a lock of a different type than \a mode, the lock is first released and then a new lock is obtained. This function returns \e true if, after it executes, the file is locked by this object, and \e false otherwise. \sa unlock(), isLocked(), lockMode() */ /*! \fn bool QtLockedFile::unlock() Releases a lock. If the object has no lock, this function returns immediately. This function returns \e true if, after it executes, the file is not locked by this object, and \e false otherwise. \sa lock(), isLocked(), lockMode() */ /*! \fn QtLockedFile::~QtLockedFile() Destroys the \e QtLockedFile object. If any locks were held, they are released. */ ukui-media/ukui-volume-control-applet-qt/QtSingleApplication/qtlockedfile.h0000664000175000017500000000630715170052044026134 0ustar fengfeng/**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the Qt Solutions component. ** ** $QT_BEGIN_LICENSE:BSD$ ** You may use this file under the terms of the BSD license as follows: ** ** "Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are ** met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names ** of its contributors may be used to endorse or promote products derived ** from this software without specific prior written permission. ** ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef QTLOCKEDFILE_H #define QTLOCKEDFILE_H #include #ifdef Q_OS_WIN #include #endif #if defined(Q_OS_WIN) # if !defined(QT_QTLOCKEDFILE_EXPORT) && !defined(QT_QTLOCKEDFILE_IMPORT) # define QT_QTLOCKEDFILE_EXPORT # elif defined(QT_QTLOCKEDFILE_IMPORT) # if defined(QT_QTLOCKEDFILE_EXPORT) # undef QT_QTLOCKEDFILE_EXPORT # endif # define QT_QTLOCKEDFILE_EXPORT __declspec(dllimport) # elif defined(QT_QTLOCKEDFILE_EXPORT) # undef QT_QTLOCKEDFILE_EXPORT # define QT_QTLOCKEDFILE_EXPORT __declspec(dllexport) # endif #else # define QT_QTLOCKEDFILE_EXPORT #endif namespace QtLP_Private { class QT_QTLOCKEDFILE_EXPORT QtLockedFile : public QFile { public: enum LockMode { NoLock = 0, ReadLock, WriteLock }; QtLockedFile(); QtLockedFile(const QString &name); ~QtLockedFile(); bool open(OpenMode mode); bool lock(LockMode mode, bool block = true); bool unlock(); bool isLocked() const; LockMode lockMode() const; private: #ifdef Q_OS_WIN Qt::HANDLE wmutex; Qt::HANDLE rmutex; QVector rmutexes; QString mutexname; Qt::HANDLE getMutexHandle(int idx, bool doCreate); bool waitMutex(Qt::HANDLE mutex, bool doBlock); #endif LockMode m_lock_mode; }; } #endif ukui-media/ukui-volume-control-applet-qt/QtSingleApplication/qtsinglecoreapplication.h0000664000175000017500000000502515170052044030405 0ustar fengfeng/**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the Qt Solutions component. ** ** $QT_BEGIN_LICENSE:BSD$ ** You may use this file under the terms of the BSD license as follows: ** ** "Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are ** met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names ** of its contributors may be used to endorse or promote products derived ** from this software without specific prior written permission. ** ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef QTSINGLECOREAPPLICATION_H #define QTSINGLECOREAPPLICATION_H #include class QtLocalPeer; class QtSingleCoreApplication : public QCoreApplication { Q_OBJECT public: QtSingleCoreApplication(int &argc, char **argv); QtSingleCoreApplication(const QString &id, int &argc, char **argv); bool isRunning(); QString id() const; public Q_SLOTS: bool sendMessage(const QString &message, int timeout = 5000); Q_SIGNALS: void messageReceived(const QString &message); private: QtLocalPeer* peer; }; #endif // QTSINGLECOREAPPLICATION_H ukui-media/ukui-volume-control-applet-qt/QtSingleApplication/QtLockedFile0000664000175000017500000000003215170052044025533 0ustar fengfeng#include "qtlockedfile.h" ukui-media/ukui-volume-control-applet-qt/QtSingleApplication/qtlockedfile_unix.cpp0000664000175000017500000000661415170052044027533 0ustar fengfeng/**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the Qt Solutions component. ** ** $QT_BEGIN_LICENSE:BSD$ ** You may use this file under the terms of the BSD license as follows: ** ** "Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are ** met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names ** of its contributors may be used to endorse or promote products derived ** from this software without specific prior written permission. ** ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include "qtlockedfile.h" bool QtLockedFile::lock(LockMode mode, bool block) { if (!isOpen()) { qWarning("QtLockedFile::lock(): file is not opened"); return false; } if (mode == NoLock) return unlock(); if (mode == m_lock_mode) return true; if (m_lock_mode != NoLock) unlock(); struct flock fl; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 0; fl.l_type = (mode == ReadLock) ? F_RDLCK : F_WRLCK; int cmd = block ? F_SETLKW : F_SETLK; int ret = fcntl(handle(), cmd, &fl); if (ret == -1) { if (errno != EINTR && errno != EAGAIN) qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); return false; } m_lock_mode = mode; return true; } bool QtLockedFile::unlock() { if (!isOpen()) { qWarning("QtLockedFile::unlock(): file is not opened"); return false; } if (!isLocked()) return true; struct flock fl; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 0; fl.l_type = F_UNLCK; int ret = fcntl(handle(), F_SETLKW, &fl); if (ret == -1) { qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); return false; } m_lock_mode = NoLock; return true; } QtLockedFile::~QtLockedFile() { if (isOpen()) unlock(); } ukui-media/ukui-volume-control-applet-qt/QtSingleApplication/qtsingleapplication.cpp0000664000175000017500000002704615170052044030076 0ustar fengfeng/**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the Qt Solutions component. ** ** $QT_BEGIN_LICENSE:BSD$ ** You may use this file under the terms of the BSD license as follows: ** ** "Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are ** met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names ** of its contributors may be used to endorse or promote products derived ** from this software without specific prior written permission. ** ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qtsingleapplication.h" #include "qtlocalpeer.h" #include /*! \class QtSingleApplication qtsingleapplication.h \brief The QtSingleApplication class provides an API to detect and communicate with running instances of an application. This class allows you to create applications where only one instance should be running at a time. I.e., if the user tries to launch another instance, the already running instance will be activated instead. Another usecase is a client-server system, where the first started instance will assume the role of server, and the later instances will act as clients of that server. By default, the full path of the executable file is used to determine whether two processes are instances of the same application. You can also provide an explicit identifier string that will be compared instead. The application should create the QtSingleApplication object early in the startup phase, and call isRunning() to find out if another instance of this application is already running. If isRunning() returns false, it means that no other instance is running, and this instance has assumed the role as the running instance. In this case, the application should continue with the initialization of the application user interface before entering the event loop with exec(), as normal. The messageReceived() signal will be emitted when the running application receives messages from another instance of the same application. When a message is received it might be helpful to the user to raise the application so that it becomes visible. To facilitate this, QtSingleApplication provides the setActivationWindow() function and the activateWindow() slot. If isRunning() returns true, another instance is already running. It may be alerted to the fact that another instance has started by using the sendMessage() function. Also data such as startup parameters (e.g. the name of the file the user wanted this new instance to open) can be passed to the running instance with this function. Then, the application should terminate (or enter client mode). If isRunning() returns true, but sendMessage() fails, that is an indication that the running instance is frozen. Here's an example that shows how to convert an existing application to use QtSingleApplication. It is very simple and does not make use of all QtSingleApplication's functionality (see the examples for that). \code // Original int main(int argc, char **argv) { QApplication app(argc, argv); MyMainWidget mmw; mmw.show(); return app.exec(); } // Single instance int main(int argc, char **argv) { QtSingleApplication app(argc, argv); if (app.isRunning()) return !app.sendMessage(someDataString); MyMainWidget mmw; app.setActivationWindow(&mmw); mmw.show(); return app.exec(); } \endcode Once this QtSingleApplication instance is destroyed (normally when the process exits or crashes), when the user next attempts to run the application this instance will not, of course, be encountered. The next instance to call isRunning() or sendMessage() will assume the role as the new running instance. For console (non-GUI) applications, QtSingleCoreApplication may be used instead of this class, to avoid the dependency on the QtGui library. \sa QtSingleCoreApplication */ void QtSingleApplication::sysInit(const QString &appId) { actWin = 0; peer = new QtLocalPeer(this, appId); connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); } /*! Creates a QtSingleApplication object. The application identifier will be QCoreApplication::applicationFilePath(). \a argc, \a argv, and \a GUIenabled are passed on to the QAppliation constructor. If you are creating a console application (i.e. setting \a GUIenabled to false), you may consider using QtSingleCoreApplication instead. */ QtSingleApplication::QtSingleApplication(int &argc, char **argv, bool GUIenabled) : QApplication(argc, argv, GUIenabled) { sysInit(); } /*! Creates a QtSingleApplication object with the application identifier \a appId. \a argc and \a argv are passed on to the QAppliation constructor. */ QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char **argv) : QApplication(argc, argv) { sysInit(appId); } #if QT_VERSION < 0x050000 /*! Creates a QtSingleApplication object. The application identifier will be QCoreApplication::applicationFilePath(). \a argc, \a argv, and \a type are passed on to the QAppliation constructor. */ QtSingleApplication::QtSingleApplication(int &argc, char **argv, Type type) : QApplication(argc, argv, type) { sysInit(); } # if defined(Q_WS_X11) /*! Special constructor for X11, ref. the documentation of QApplication's corresponding constructor. The application identifier will be QCoreApplication::applicationFilePath(). \a dpy, \a visual, and \a cmap are passed on to the QApplication constructor. */ QtSingleApplication::QtSingleApplication(Display* dpy, Qt::HANDLE visual, Qt::HANDLE cmap) : QApplication(dpy, visual, cmap) { sysInit(); } /*! Special constructor for X11, ref. the documentation of QApplication's corresponding constructor. The application identifier will be QCoreApplication::applicationFilePath(). \a dpy, \a argc, \a argv, \a visual, and \a cmap are passed on to the QApplication constructor. */ QtSingleApplication::QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap) : QApplication(dpy, argc, argv, visual, cmap) { sysInit(); } /*! Special constructor for X11, ref. the documentation of QApplication's corresponding constructor. The application identifier will be \a appId. \a dpy, \a argc, \a argv, \a visual, and \a cmap are passed on to the QApplication constructor. */ QtSingleApplication::QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap) : QApplication(dpy, argc, argv, visual, cmap) { sysInit(appId); } # endif // Q_WS_X11 #endif // QT_VERSION < 0x050000 /*! Returns true if another instance of this application is running; otherwise false. This function does not find instances of this application that are being run by a different user (on Windows: that are running in another session). \sa sendMessage() */ bool QtSingleApplication::isRunning() { return peer->isClient(); } /*! Tries to send the text \a message to the currently running instance. The QtSingleApplication object in the running instance will emit the messageReceived() signal when it receives the message. This function returns true if the message has been sent to, and processed by, the current instance. If there is no instance currently running, or if the running instance fails to process the message within \a timeout milliseconds, this function return false. \sa isRunning(), messageReceived() */ bool QtSingleApplication::sendMessage(const QString &message, int timeout) { return peer->sendMessage(message, timeout); } /*! Returns the application identifier. Two processes with the same identifier will be regarded as instances of the same application. */ QString QtSingleApplication::id() const { return peer->applicationId(); } /*! Sets the activation window of this application to \a aw. The activation window is the widget that will be activated by activateWindow(). This is typically the application's main window. If \a activateOnMessage is true (the default), the window will be activated automatically every time a message is received, just prior to the messageReceived() signal being emitted. \sa activateWindow(), messageReceived() */ void QtSingleApplication::setActivationWindow(QWidget* aw, bool activateOnMessage) { actWin = aw; if (activateOnMessage) connect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow())); else disconnect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow())); } /*! Returns the applications activation window if one has been set by calling setActivationWindow(), otherwise returns 0. \sa setActivationWindow() */ QWidget* QtSingleApplication::activationWindow() const { return actWin; } /*! De-minimizes, raises, and activates this application's activation window. This function does nothing if no activation window has been set. This is a convenience function to show the user that this application instance has been activated when he has tried to start another instance. This function should typically be called in response to the messageReceived() signal. By default, that will happen automatically, if an activation window has been set. \sa setActivationWindow(), messageReceived(), initialize() */ void QtSingleApplication::activateWindow() { if (actWin) { actWin->setWindowState(actWin->windowState() & ~Qt::WindowMinimized); actWin->raise(); actWin->showNormal(); actWin->activateWindow(); } } /*! \fn void QtSingleApplication::messageReceived(const QString& message) This signal is emitted when the current instance receives a \a message from another instance of this application. \sa sendMessage(), setActivationWindow(), activateWindow() */ /*! \fn void QtSingleApplication::initialize(bool dummy = true) \obsolete */ ukui-media/ukui-volume-control-applet-qt/QtSingleApplication/qtlocalpeer.cpp0000664000175000017500000001562015170054730026336 0ustar fengfeng/**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the Qt Solutions component. ** ** $QT_BEGIN_LICENSE:BSD$ ** You may use this file under the terms of the BSD license as follows: ** ** "Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are ** met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names ** of its contributors may be used to endorse or promote products derived ** from this software without specific prior written permission. ** ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qtlocalpeer.h" #include #include #include #include #if defined(Q_OS_WIN) #include #include typedef BOOL(WINAPI*PProcessIdToSessionId)(DWORD,DWORD*); static PProcessIdToSessionId pProcessIdToSessionId = 0; #endif #if defined(Q_OS_UNIX) #include #include #include #endif namespace QtLP_Private { #include "qtlockedfile.cpp" #if defined(Q_OS_WIN) #include "qtlockedfile_win.cpp" #else #include "qtlockedfile_unix.cpp" #endif } const char* QtLocalPeer::ack = "ack"; QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId) : QObject(parent), id(appId) { QString prefix = id; if (id.isEmpty()) { id = QCoreApplication::applicationFilePath(); #if defined(Q_OS_WIN) id = id.toLower(); #endif prefix = id.section(QLatin1Char('/'), -1); } // QRegExp removed in Qt6, use QRegularExpression instead QRegularExpression re("[^a-zA-Z]"); prefix.remove(re); prefix.truncate(6); QByteArray idc = id.toUtf8(); quint16 idNum = qChecksum(idc.constData(), idc.size()); socketName = QLatin1String("qtsingleapp-") + prefix + QLatin1Char('-') + QString::number(idNum, 16); #if defined(Q_OS_WIN) if (!pProcessIdToSessionId) { QLibrary lib("kernel32"); pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId"); } if (pProcessIdToSessionId) { DWORD sessionId = 0; pProcessIdToSessionId(GetCurrentProcessId(), &sessionId); socketName += QLatin1Char('-') + QString::number(sessionId, 16); } #else socketName += QLatin1Char('-') + QString::number(::getuid(), 16); #endif server = new QLocalServer(this); QString lockName = QDir(QDir::tempPath()).absolutePath() + QLatin1Char('/') + socketName + QLatin1String("-lockfile"); lockFile.setFileName(lockName); lockFile.open(QIODevice::ReadWrite); } bool QtLocalPeer::isClient() { if (lockFile.isLocked()) return false; if (!lockFile.lock(QtLP_Private::QtLockedFile::WriteLock, false)) return true; bool res = server->listen(socketName); #if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(4,5,0)) // ### Workaround if (!res && server->serverError() == QAbstractSocket::AddressInUseError) { QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName); res = server->listen(socketName); } #endif if (!res) qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString())); QObject::connect(server, SIGNAL(newConnection()), SLOT(receiveConnection())); return false; } bool QtLocalPeer::sendMessage(const QString &message, int timeout) { if (!isClient()) return false; QLocalSocket socket; bool connOk = false; for(int i = 0; i < 2; i++) { // Try twice, in case the other instance is just starting up socket.connectToServer(socketName); connOk = socket.waitForConnected(timeout/2); if (connOk || i) break; int ms = 250; #if defined(Q_OS_WIN) Sleep(DWORD(ms)); #else struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 }; nanosleep(&ts, NULL); #endif } if (!connOk) return false; QByteArray uMsg(message.toUtf8()); QDataStream ds(&socket); ds.writeBytes(uMsg.constData(), uMsg.size()); bool res = socket.waitForBytesWritten(timeout); if (res) { res &= socket.waitForReadyRead(timeout); // wait for ack if (res) res &= (socket.read(qstrlen(ack)) == ack); } return res; } void QtLocalPeer::receiveConnection() { QLocalSocket* socket = server->nextPendingConnection(); if (!socket) return; while (true) { if (socket->state() == QLocalSocket::UnconnectedState) { qWarning("QtLocalPeer: Peer disconnected"); delete socket; return; } if (socket->bytesAvailable() >= qint64(sizeof(quint32))) break; socket->waitForReadyRead(); } QDataStream ds(socket); QByteArray uMsg; quint32 remaining; ds >> remaining; uMsg.resize(remaining); int got = 0; char* uMsgBuf = uMsg.data(); do { got = ds.readRawData(uMsgBuf, remaining); remaining -= got; uMsgBuf += got; } while (remaining && got >= 0 && socket->waitForReadyRead(2000)); if (got < 0) { qWarning("QtLocalPeer: Message reception failed %s", socket->errorString().toLatin1().constData()); delete socket; return; } QString message(QString::fromUtf8(uMsg)); socket->write(ack, qstrlen(ack)); socket->waitForBytesWritten(1000); socket->waitForDisconnected(1000); // make sure client reads ack delete socket; Q_EMIT messageReceived(message); //### (might take a long time to return) } ukui-media/ukui-volume-control-applet-qt/QtSingleApplication/qtsingleapplication.h0000664000175000017500000000761715170052044027545 0ustar fengfeng/**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the Qt Solutions component. ** ** $QT_BEGIN_LICENSE:BSD$ ** You may use this file under the terms of the BSD license as follows: ** ** "Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are ** met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names ** of its contributors may be used to endorse or promote products derived ** from this software without specific prior written permission. ** ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef QTSINGLEAPPLICATION_H #define QTSINGLEAPPLICATION_H #include class QtLocalPeer; #if defined(Q_OS_WIN) # if !defined(QT_QTSINGLEAPPLICATION_EXPORT) && !defined(QT_QTSINGLEAPPLICATION_IMPORT) # define QT_QTSINGLEAPPLICATION_EXPORT # elif defined(QT_QTSINGLEAPPLICATION_IMPORT) # if defined(QT_QTSINGLEAPPLICATION_EXPORT) # undef QT_QTSINGLEAPPLICATION_EXPORT # endif # define QT_QTSINGLEAPPLICATION_EXPORT __declspec(dllimport) # elif defined(QT_QTSINGLEAPPLICATION_EXPORT) # undef QT_QTSINGLEAPPLICATION_EXPORT # define QT_QTSINGLEAPPLICATION_EXPORT __declspec(dllexport) # endif #else # define QT_QTSINGLEAPPLICATION_EXPORT #endif class QT_QTSINGLEAPPLICATION_EXPORT QtSingleApplication : public QApplication { Q_OBJECT public: QtSingleApplication(int &argc, char **argv, bool GUIenabled = true); QtSingleApplication(const QString &id, int &argc, char **argv); #if QT_VERSION < 0x050000 QtSingleApplication(int &argc, char **argv, Type type); # if defined(Q_WS_X11) QtSingleApplication(Display* dpy, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0); QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap= 0); QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0); # endif // Q_WS_X11 #endif // QT_VERSION < 0x050000 bool isRunning(); QString id() const; void setActivationWindow(QWidget* aw, bool activateOnMessage = true); QWidget* activationWindow() const; // Obsolete: void initialize(bool dummy = true) { isRunning(); Q_UNUSED(dummy) } public Q_SLOTS: bool sendMessage(const QString &message, int timeout = 5000); void activateWindow(); Q_SIGNALS: void messageReceived(const QString &message); private: void sysInit(const QString &appId = QString()); QtLocalPeer *peer; QWidget *actWin; }; #endif // QTSINGLEAPPLICATION_H ukui-media/ukui-volume-control-applet-qt/QtSingleApplication/qtlockedfile_win.cpp0000664000175000017500000001466115170052044027346 0ustar fengfeng/**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the Qt Solutions component. ** ** $QT_BEGIN_LICENSE:BSD$ ** You may use this file under the terms of the BSD license as follows: ** ** "Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are ** met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names ** of its contributors may be used to endorse or promote products derived ** from this software without specific prior written permission. ** ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qtlockedfile.h" #include #include #define MUTEX_PREFIX "QtLockedFile mutex " // Maximum number of concurrent read locks. Must not be greater than MAXIMUM_WAIT_OBJECTS #define MAX_READERS MAXIMUM_WAIT_OBJECTS #if QT_VERSION >= 0x050000 #define QT_WA(unicode, ansi) unicode #endif Qt::HANDLE QtLockedFile::getMutexHandle(int idx, bool doCreate) { if (mutexname.isEmpty()) { QFileInfo fi(*this); mutexname = QString::fromLatin1(MUTEX_PREFIX) + fi.absoluteFilePath().toLower(); } QString mname(mutexname); if (idx >= 0) mname += QString::number(idx); Qt::HANDLE mutex; if (doCreate) { QT_WA( { mutex = CreateMutexW(NULL, FALSE, (TCHAR*)mname.utf16()); }, { mutex = CreateMutexA(NULL, FALSE, mname.toLocal8Bit().constData()); } ); if (!mutex) { qErrnoWarning("QtLockedFile::lock(): CreateMutex failed"); return 0; } } else { QT_WA( { mutex = OpenMutexW(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, (TCHAR*)mname.utf16()); }, { mutex = OpenMutexA(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, mname.toLocal8Bit().constData()); } ); if (!mutex) { if (GetLastError() != ERROR_FILE_NOT_FOUND) qErrnoWarning("QtLockedFile::lock(): OpenMutex failed"); return 0; } } return mutex; } bool QtLockedFile::waitMutex(Qt::HANDLE mutex, bool doBlock) { Q_ASSERT(mutex); DWORD res = WaitForSingleObject(mutex, doBlock ? INFINITE : 0); switch (res) { case WAIT_OBJECT_0: case WAIT_ABANDONED: return true; break; case WAIT_TIMEOUT: break; default: qErrnoWarning("QtLockedFile::lock(): WaitForSingleObject failed"); } return false; } bool QtLockedFile::lock(LockMode mode, bool block) { if (!isOpen()) { qWarning("QtLockedFile::lock(): file is not opened"); return false; } if (mode == NoLock) return unlock(); if (mode == m_lock_mode) return true; if (m_lock_mode != NoLock) unlock(); if (!wmutex && !(wmutex = getMutexHandle(-1, true))) return false; if (!waitMutex(wmutex, block)) return false; if (mode == ReadLock) { int idx = 0; for (; idx < MAX_READERS; idx++) { rmutex = getMutexHandle(idx, false); if (!rmutex || waitMutex(rmutex, false)) break; CloseHandle(rmutex); } bool ok = true; if (idx >= MAX_READERS) { qWarning("QtLockedFile::lock(): too many readers"); rmutex = 0; ok = false; } else if (!rmutex) { rmutex = getMutexHandle(idx, true); if (!rmutex || !waitMutex(rmutex, false)) ok = false; } if (!ok && rmutex) { CloseHandle(rmutex); rmutex = 0; } ReleaseMutex(wmutex); if (!ok) return false; } else { Q_ASSERT(rmutexes.isEmpty()); for (int i = 0; i < MAX_READERS; i++) { Qt::HANDLE mutex = getMutexHandle(i, false); if (mutex) rmutexes.append(mutex); } if (rmutexes.size()) { DWORD res = WaitForMultipleObjects(rmutexes.size(), rmutexes.constData(), TRUE, block ? INFINITE : 0); if (res != WAIT_OBJECT_0 && res != WAIT_ABANDONED) { if (res != WAIT_TIMEOUT) qErrnoWarning("QtLockedFile::lock(): WaitForMultipleObjects failed"); m_lock_mode = WriteLock; // trick unlock() to clean up - semiyucky unlock(); return false; } } } m_lock_mode = mode; return true; } bool QtLockedFile::unlock() { if (!isOpen()) { qWarning("QtLockedFile::unlock(): file is not opened"); return false; } if (!isLocked()) return true; if (m_lock_mode == ReadLock) { ReleaseMutex(rmutex); CloseHandle(rmutex); rmutex = 0; } else { foreach(Qt::HANDLE mutex, rmutexes) { ReleaseMutex(mutex); CloseHandle(mutex); } rmutexes.clear(); ReleaseMutex(wmutex); } m_lock_mode = QtLockedFile::NoLock; return true; } QtLockedFile::~QtLockedFile() { if (isOpen()) unlock(); if (wmutex) CloseHandle(wmutex); } ukui-media/ukui-volume-control-applet-qt/QtSingleApplication/qtsinglecoreapplication.pri0000664000175000017500000000050415170052044030745 0ustar fengfengINCLUDEPATH += $$PWD DEPENDPATH += $$PWD HEADERS += $$PWD/qtsinglecoreapplication.h $$PWD/qtlocalpeer.h SOURCES += $$PWD/qtsinglecoreapplication.cpp $$PWD/qtlocalpeer.cpp QT *= network win32:contains(TEMPLATE, lib):contains(CONFIG, shared) { DEFINES += QT_QTSINGLECOREAPPLICATION_EXPORT=__declspec(dllexport) } ukui-media/ukui-volume-control-applet-qt/QtSingleApplication/qtsinglecoreapplication.cpp0000664000175000017500000001235515170052044030744 0ustar fengfeng/**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the Qt Solutions component. ** ** $QT_BEGIN_LICENSE:BSD$ ** You may use this file under the terms of the BSD license as follows: ** ** "Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are ** met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names ** of its contributors may be used to endorse or promote products derived ** from this software without specific prior written permission. ** ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qtsinglecoreapplication.h" #include "qtlocalpeer.h" /*! \class QtSingleCoreApplication qtsinglecoreapplication.h \brief A variant of the QtSingleApplication class for non-GUI applications. This class is a variant of QtSingleApplication suited for use in console (non-GUI) applications. It is an extension of QCoreApplication (instead of QApplication). It does not require the QtGui library. The API and usage is identical to QtSingleApplication, except that functions relating to the "activation window" are not present, for obvious reasons. Please refer to the QtSingleApplication documentation for explanation of the usage. A QtSingleCoreApplication instance can communicate to a QtSingleApplication instance if they share the same application id. Hence, this class can be used to create a light-weight command-line tool that sends commands to a GUI application. \sa QtSingleApplication */ /*! Creates a QtSingleCoreApplication object. The application identifier will be QCoreApplication::applicationFilePath(). \a argc and \a argv are passed on to the QCoreAppliation constructor. */ QtSingleCoreApplication::QtSingleCoreApplication(int &argc, char **argv) : QCoreApplication(argc, argv) { peer = new QtLocalPeer(this); connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); } /*! Creates a QtSingleCoreApplication object with the application identifier \a appId. \a argc and \a argv are passed on to the QCoreAppliation constructor. */ QtSingleCoreApplication::QtSingleCoreApplication(const QString &appId, int &argc, char **argv) : QCoreApplication(argc, argv) { peer = new QtLocalPeer(this, appId); connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); } /*! Returns true if another instance of this application is running; otherwise false. This function does not find instances of this application that are being run by a different user (on Windows: that are running in another session). \sa sendMessage() */ bool QtSingleCoreApplication::isRunning() { return peer->isClient(); } /*! Tries to send the text \a message to the currently running instance. The QtSingleCoreApplication object in the running instance will emit the messageReceived() signal when it receives the message. This function returns true if the message has been sent to, and processed by, the current instance. If there is no instance currently running, or if the running instance fails to process the message within \a timeout milliseconds, this function return false. \sa isRunning(), messageReceived() */ bool QtSingleCoreApplication::sendMessage(const QString &message, int timeout) { return peer->sendMessage(message, timeout); } /*! Returns the application identifier. Two processes with the same identifier will be regarded as instances of the same application. */ QString QtSingleCoreApplication::id() const { return peer->applicationId(); } /*! \fn void QtSingleCoreApplication::messageReceived(const QString& message) This signal is emitted when the current instance receives a \a message from another instance of this application. \sa sendMessage() */ ukui-media/ukui-volume-control-applet-qt/QtSingleApplication/QtSingleApplication0000664000175000017500000000004115170052044027137 0ustar fengfeng#include "qtsingleapplication.h" ukui-media/ukui-volume-control-applet-qt/custom_sound.h0000664000175000017500000000222415170054730022274 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #include #include #include #include #include class CustomSound { public: CustomSound(); ~CustomSound(); bool createAudioFile(); bool isExist(QString nodeName); int addXmlNode(QString nodeNane, bool initState); bool isFirstRun(); private: QDomDocument *doc; QString audioPath; }; #endif // CUSTOMSOUND_H ukui-media/ukui-volume-control-applet-qt/data/0000775000175000017500000000000015170054730020312 5ustar fengfengukui-media/ukui-volume-control-applet-qt/data/sounds/0000775000175000017500000000000015170054730021625 5ustar fengfengukui-media/ukui-volume-control-applet-qt/data/sounds/xunguang.xml0000664000175000017500000000245215170052044024202 0ustar fengfeng genericnotification specialnotification dialogcomplete dialogwarning dialogerror devconnectionsucceeded devconnectionfailed devconnectionbreak powerconnected lowpower volumechange copyormovesucceed recyclebinclear operationnotsupported photo screen ukui-media/ukui-volume-control-applet-qt/data/sounds/shutdown.ogg0000664000175000017500000075772215170054730024222 0ustar fengfengOggSLm`<vorbismOggSLm`S#RSvorbis,Xiph.Org libVorbis I 20150105 (⛄⛄⛄⛄)ENCODER=libsndfilevorbis+BCV1L ŀАU`$)fI)(yHI)0c1c1c 4d( Ij9g'r9iN8 Q9 &cnkn)% Y@H!RH!b!b!r!r * 2 L2餓N:騣:(B -JL1Vc]|s9s9s BCV BdB!R)r 2ȀАU GI˱$O,Q53ESTMUUUUu]Wvevuv}Y[}Y[؅]aaaa}}} 4d #9)"9d ")Ifjihm˲,˲ iiiiiiifYeYeYeYeYeYeYeYeYeYeYeYeY@h*@@qq$ER$r, Y@R,r4Gs4s@BDFHJLNPRT> "9@@OggS@Lm`_r A>AZO<0a9BqejZ `)kc[[Gn0otwrύk97uXǰ0++˳~,Oi fY,Ze:r_ݕ M;cWO[&aʽE1q9Q'}@w)WyK K%+cY]NaͩR^lUzjEc6\'rG/45-gq[NjB{ x d+d `KmJEY3~zgJ]ze}G=Y0zDO[Ĺw,ƥwsepkP(W O Wѓ׮sD]֠ܝg8PZk.C<]_\s;Db8ǝ?FǢ0p%ƾNrssKe3yy.'cI[>f#g} )߮pْxnZe_P8T#nDf'pZA3QDZ '_=u/?x֥vl0A63=Op8N@~4Vą}*tV{F''9MmM =?-WaVk-f|I?aq⛇ Ggbs;T"P_#gvЮG<ҫ6s?8(^@T_~Ԥh&8jPҲg.$E]' (=[!"a0#Fy朙-us;|UӠ3ɜՠgxN+@Yd3.IKBfv~rVfǼ?e*W\*5xG%Uy=z=UUM?LwPMyahr-؋7ݝAF%(}ŤQ,:#~Tvx5SU/=k 3D;5.Sy#"LL Tϻ#BnZO!Ut'-#bUSV'IZz8rN j;5[7`[YU*炳so'}rCȝhbrHyGk\u=['am$&d{&r!:ދj7-S(-_k;dɮ3ZI6[W.ב`|m CgDD^w(JM/ͩ{hg(mbŶ֗jݒ&cbߍӰ-WyY Uh|{AdG7oX)v=u}CNxĠZq>%aR/Κ}WIw Ѫ:x)n}$ԍSeLo iBUwvXfݎKXW>k 4 n[d2+?X_nĠ݅Zo{ǽ^,:֩꺠Y[Q9#,(DCQ1C"xz{0:.hn]QJ.&.1a:jxٜ|״KH_s#b"LtybIxnLZlQW=~uO=>wZ䧕jUׅ0>h >p97iHI5(yD쇺/B|Nj'>ݦU7>|$!drA{:E;[?۵GH1<;:>dwvj ^f=}^{99,\>z "TQǼb`gWM?ۄN) ϕ8yWYC{Vc{2mLgUZwI ও`K%L䘤pףz=ֹoM"'k!{+UYNeı\a]`fo?_ޏ-ƵH? ø{{?D^a;ߋCXO}U\ Wa*yq(=x{*˴ Z?ܨY8fU0Ϋ7faPgI1n-:ϝ\4Kuo>=XLٹOm93u^0UrѼRݿX!YE2sn},l5z8 4|k|T7)glخj3{-_nb ^<̷w qMz]N$EqNn4񔳲woo.7.vUcG_ؗbPŹd{gO'|X5Os jnԿQ"qHRm.6 c5 ?aېfQh'YûwnBMګvzBdd@ .][륢(rɬ]I-^Y4qX*E #r*Aeλ9 $cr='u3;7ߺgBgW.<m֩lk,>y3#5[Kf{q+:dk~&S;m15pe|EL+-W;wC9W]ޯe\},ߝI,QꢏLZ+g-&2NGu9n+XRu~u_ٻ4[KR'zLp$_Vȥgc:]#/ +W3d^'S iH9kOPLOggS@#Lm`lSמQmX8;$17txصyw/@ n50.;M? m~,g ?q40*磔~\:)wN |n$T\'8Pu}yH|sVGi%C<Tz }M6@=z@*J=+z4~0puV`a*&ysusX5FǜA(=txc[% @hx 257.fvzjs9@pT;LTlTxD.]O8s ]0qb㍺19-=g|N r&3#JG=lQH[,6̺qHXѳ{KWPс,L/g@CD"= :+&U{Zn>f(vE94AÄ4WXs O. @NFNьRؗ\)]\^\(Fr6J2"<;eWc+]#KlSU{)r+P%d]%mpYk(!5m\7\ow^7!rSm/Aؼk謮FE|Ҵ|n6v)i2ՄWj"+1>G+{5 9`j?_zy E2V0#BBt{47G5`wuI:f4vNˬHӶH͹E Y#[u]0i;y8 VLFLO|ribECd|^/6[k_}8WAɸTڋ)?+䍴vdOX2ePo@hTӼp YjekqJi8My6|Yi ЅB2JPhN UZ Cg)(:T` 0qi]W?]^PqN@&. d'0" L.u'*K*Fk*>N&uRD}K5sߺ L9ӭ۸=]jvi x=֤ӶIjlHvwçAMN\ҧp9=H1< 49וuӊFko اI;i]wo/6 Z XAH>ndbx~ 0oX@Ք`wonpM%Oa~Wa9+ 4z,=aUjՁkؗM^rgw;߶a QWDhdsWA^% H E0m ֣gEL66,%UtZFnYD+̊#|dkFx*ε!J}lN7WZFu^F׋Px߱MFseÒ".55ړZ`TqrـUFl#Xh4ƣ]!B|sdM/`:=u1N|4fe߱/o;D :>aX?6KsKwڎEDI}/HuyY~Dzv!5鱗pcvDι"n{|!,)26'82PN[%Pӽ㓳С?_ĵacpoAkq !VoG\~ڧskS+p^\?{]y'0Pn\1)@z]Ak|p ]/ 4ߔk}~m(S8B֜ARMVwuS(k%o/d7 h4twZTVHܺ"P*׮8ˆHaǹhS*<՞7y8H 0@x+3]\Ӑ>({zP8 Bhzg Eha`&>p @Sb{ZHPW7^nۆE0"UBjP8ٗ@_%=HkT;lO =,Xw'36b_ws?H(kpcc>/q'{}8g"OF4Tc :kW6ǴnD?ImY6Лa/@c*D(_n"u ^o jTjJ)=`a8TY2;KI'H} o"oȸ%/?K?*>xsw>o9?cѤw9ȑ9{fjoAƫVlrdJG"KUAρ+- #VXtz[o# X.{AMuīt2uѻU}(r!=O,b7Viw6Vn ,ht+u?{\ C滜9t9'~,ѳ !uav_,' Sc~%?M3X7(rw-rSY1>W+P_W3jcjHWO5]Ee}]U _(EP3+HdZS?+;S|p wW?bcwcJ;hh!9hP'4霶\ UE;(Hu9Y! N2d2FᎾw0{Eo-TܕxR3lq_)Ac_ 9vXs˒U|͚Q<]U\bTebxF-J [`\׈ꤽ.x_n51.r!bBĮuƗ&oFxrR .1](zk1c#z`)?w}N3W/ec[4XE3 )G1ƪMjn\a=6X z@XjD\OggS@7Lm`.bV;r7dwJ.j{+!q G1\ :6pc"C u?[[km 52jUߵ>%Oyy_$;:BU̩z[ꡩLԝp"@hqexl2#͝Go)Х*@f 4 O']wj-yu=EWwqp`B8>B$Bk\TkQcRt՝DO)ϐI@W:$+zdAU:$G{8h֓РdS868%E));,x<9`/gZ.ͣ }ᯯ R{ [z΃[yˀhM&~&9b{*m<BDUܖD;vzړ}'7]vK_xo4ݭ@\YKo~)9ckѪ޳Ly2t0SLRm7,ze=jC(.7ؠ@G]KoYCà7&?UQ00hΛ0Ъl2wbovE{Fɘ~s\t'f{hb܉2zsv X/MlÓsk/k&?i-rҎ׫Yr :͵4HσlKuLSxt]E_Ϩv||"-RҘ@Gz3>'P͢ze,FUGÍLN5pWWi#nqbmTy9a>~ݿGLAWy@_$"FvwL^cZU8$& 956ds?cUjZcrOq}Mw{Ś,;zfY=DL8~/cbZ)mMW$DR{t(>9\?M[%ALߩ|" xYhOpN@\_&O oi%?}n'C >ށʧ[R" ݏu 0_t^_!?GJz7|?n 8B'ǧgrc}kTQŧYB#D*k|H)@tw,*BR |pt* GfTUϩtbi8*ǡJ,u}[td8$RPE)Ȑ%Nj,!{|[U^y.:5S>j?GѴ}FF p$#@< IUN,K2;u;"n}SDŽucrF[g} rl?xv]OΔJgOK3a|bo9Ž1R؎^(d,1[:0+7ij hzBq"qkcjnM)a/VA9P+*B%ۮVU#{9R*D{ZҥR"b5ɈOf'Q/hc%Y~ː\wUPLX>5Q sJ {@ħ ̫*h4ES|zT~L}N[*!~_>s'bgҽ˹$/%JӴW{V&=ns*Yo6U$pI njit>WC|K}\͞[%%_vŘ&IC͹ 2_3o*la. )ߞHqM_GIAY\~LWV>t"ɑ6&'R؋s7xC}ċ滩58^? 8.XΔs%9<`$Ii_VrY=iTuJiƠp67 7-]i&=3QAŽq? hxܲHoб j_ES[?کT:DW5[TZcaC^YBmk{fZ]5!m2)GI8$%P*SU/aqHopoGuT*d.Kv!qaf<>ݞ:RIͪN!P奐T:v%#RB*g}Gcq 4P|Q^)qPt5m{<Ј~HIIF`īS}rk0ƻox'=پȿҊmWiUf `<՟վ([ꮍ<빴ų\Rݔ$ٔMS \[ݻOOni0 h5S.9DM2}V bY[9Mo H4Jl,CnznxQ.?":"GF\W'!÷*dž5[+8~?Pclg hTUwirb5U*5[9B2ImʞYנrN-TfAl7??{BZJfY;UO?po&%.;M1+YZM9IU!T`Cjh]*PWGE$4 "rńy<۝y1fQ˱?'ơZscG"@LGYH"E)1yP+M2 a#W_ Cҽ7ąߔo#MS3] L}n۱WL8C h}$_j7Nfrut32G1e)d#-5R: {[Y^;qYk2w϶q<-2ʥ&.wB񏑉;S˭o>l;^}?c٘ŹV*$F?sԡ qmXbg$PnoUͼSa-a;yy %? TQ=Vgݐ6Ee&y kQ36]l4#d s)cȟL)YcId͓wqZot;v$!]\qs{ߵabM{o#z/o>p͇iWZmcF?Ef(mޞ^+~ɩtwq9@ѽPI(f] 9!hj&K?@oK\Sh#L͇/uKQO$"'h~Im 5${fsq4x)Pds|Dĥ0W/*Dl^ݰTuB(_6_n)HPv- ׏v4Ԕ:߯{=\n)B(3~_RI>J~tgV"Q )p8e^!#PU7x x>$"ݷ#gFk;A|$"d-PTy/(Jpig@'5RB'QDn0Fʇ>: FU>U !%2٥>/ HH@*yH9x.Ke|UZG1t-vMi0{$ídZ1At?:MW&gsz=/%U{CL~KYX||Ka,Q?floWڽvإ]{Y ~oH=ɯI2ZIQ^"J_E(Lq an]^.-.vۮePM0niVop9@xUcl(jT5%cSN4O~ޞ\5u'RY}'d<'FoԂ}x63p; +-`ks>&y2+O@*rr̋2gfM_'cOV^++{2ۂ]u:?w};.L4aP2m@+w?" 4>Fwu2eJ"V< ԺWL3\Xbwye+k?oo馔8 ,Uf|38<ߓu|>t}G)"EGyLi%Xq8uKp8;P6 OggS@KLm` ?H ^9\0 Vۂ07f==e}f;aF]~% E}~?<Iȭ_| y><8B4_8rCڤ(d86躩:vS-쨓׷k:25Yh{GD6*rRb0CR/AGN4K' :)w(Qm@ $뻦0lW(Э@:% B h0Y8^D:N_eM (ӷ+"KR)(psBF'AqX'M7c;hE@9H=NG">(G9i Dm@q$UL`)î+,}oIk:d5Ʌ5[E,莣0yUS(!0z2g';o [fC݉ 2 iP,#peoqGmH`MS/tw#bc>M\JλDIq=w2Q7_GW<]S܇८TH녬O=[ܷ34E}h wҙ -#3bb_z.~%|j_ꕮ$/PḖ&MG߀jSuܕO(֎~n{~lc{#{Ndי/ב }矖3W.!DԛxAf;m 禎uEZvꯦ9?y~.X` Rv~b[*Zߦ3m mv(TLX/j1-r:W!IZ7zYPg np7+}1n^9vV)I%RiC\E_B%vzܯ<@x Q/[KjZg! ^Iw;whBIp~?k%[`Yns|.NmyFE5RoM`"Z_5r/v[}oo:P:1m|P~xBjwói%@yπ$1+^nWPJI{oU{r7ڦ˽J^&I5 t̠AUB캘Icv^+S<+[qON~]t d>/iY{1{9PMWG}yAkXo{4h3M)ñ+'+8)mIA\_qbr7 V 8ыb0JZ85x0A%yF]Pt"7^00BOd@Ѝ,gVt7GƪׅxZ_}nqeDe^ Ͻ3-)^]:/WU+ZR $E)@H-\F4H;n~iտ;B17)%OPӡ f/CMmf㞃Xntͪqo 5u~6NEc&X;κQ_%t Ȝp==ϦJ%)F*tZՅFN緗  qgOuWVGN%D4Β_P! 5+P;UH#'U[v( (_BQ @K75 |> B+HNI"{( C!*5 _RSfkZzrOEA 7Cwڑ%=- l& <}L.s'&[rOFCzƼs i|%*)QXt; sR^EaFC9W\i+0cЌJ8זQl>V}BaLbՕ^>yN sݬ*!%$zno!'!O.)?wT'zz6(wdGS@S~#)ZZ[7X.=alӉ(;K0?Ck5C=w&\^W)| 0Yڵ& lޫeܛj?}uG➊sGzK_.^l,f?=7OҥZ\DS R`%*sJ3Hb칋wPre HudbTΈ=Ltv9qG|k@$Inn}" ;7[Սg9ܾؐE#>dY&m5ҏ}dD[i˿Vs.>_B܄O^j߉ ܯ A+Mz1Dgt_*&>"Gn$2-j[\qtIƝT)<|RC{:M꣏cNŤ H>N:Ϩ0wue3^u3Ue]B‚ȨaKѵriq>|;"Й'Vat|Jry:8JVᦎdq9 |M]=){+Yǩ;8Nf7Kj=}c qjxK&* AG=~JThM^sk @ƦJJďDH4x 3G5s\ubJ=A!g1nqtYeQR7澻doU_bd3AOoUpoX3< /Šl۷yY#S#4HsYrm[jm_=O}Pç߮y>L-Vog~b|tlXXP5ŐvWjĐC^E6%^ݡ8< qbeۏ!{Szpr֬!ٶ׏Xlϊϥ_ҩ +1 ,_RU7m߯*u\zoie5K605кsOɗ7wa6i8 .s9kAZ]x<}h':  oNcBhUJHM鮧dh,mqxlLl-%x3(P٩@+R%|z$9yNC2e Vi3||.M$ źCM~(&W=2qh.hJ0ISaXȤ=z~3&sѠi_lOo n}%Lٮ;q&B/6~;K/U%П#4 q]%uH!ܟύ*dA7h! 19cFŽX"Df}oi:g7f &M8䅎b.9a]Dh$FTz58AAd!Wydi2؝ ,^5;KG;Rg7 *}\YygrtxGG3޿uke+J;iUݞEMP̽!o')0ޛ/w~צ׼[X;Z0}F6D~/˒NX%fxsZjOl^ z<^gbz[[NLJIXR*.9r˅فM3a]ؖ_õP]?!϶$4< Ej8k2W/0(+l)"ƒ->cn?j_-}5 cG4uPwSjw)kHoKjujv#t:D%+@2GPQ: P)$"2Ծzk)]a |F_7'K}e㳳{%AKN +kd] 2c(O5n8%IWIsHGD?:KN8ReYZ9{S ْ&[\V}f>i0Q0R I '-zg98k6JHSd4l$%'q.#O9P|-EV}& 1]9?zk$g2r|ܸOS<,]`~FSկ# V4ӇMY92U%ҕEE˕5r*f6|ŗP՝@}o~X-w~>N*~D%lpOĢ- )"zsڇߞ{sg[֯fY|Ԣۄ.fFvy-ucCVO8irM4sĵbױwW"Wen ~-t1zy ^`m~ "Y?y+ǰbTlD?XJC EX atX9Oo%Q!]B4inwГ}'F )WC1İeMa3Y\KjLk?@U =2wI*?u!vQoNV!Zo2D3~gk;q߱<~9.(O)~ΚK_1K8rO1P.@qC=2l2oir4jTdz|-mv (y)IL*<jQb~j[@ W:vK 8qm-faunQģGpء*Lm2?m]=4[-ڨ c^rZ~P}dPE ;ʠw# ~9Q̊pT9ktjJSbejG2RqiG(8 y˒UCNS$Rs?4zѹSf~BNKDE3\b$r] ĵǝF᪕рDZDC2 45۩@=fBK}SZH*14r:!-߈oL\zCT"A EIy(-u{Swg8;S0џpQv+o:?I*޵y9s7˚qhWB LheH^aŝ7~"%\,黃j]O]E86tWɮԃ KKdQ^;x=0\ePL?uobpG;R?쿧=ǵ\[R ?js᳣3 <_h/M.D~0 fW]_泄j:sH(560;o_EE3'e8ts <)m;p>{ghS-m%45{&9\kHZ|W'!µ+5̓BSg<@CC.:nגXkˆ/8"J"1/rDr_&'$'MͬNSPdŎ/E]3;ӡH|JT_x )-&NCTJmZPJGHQ_rrS9I<*?fp6kqth[)HR+M}d;f)9?'~S }G@kR B=Iz@GqRs{3q|V!(Q]!B 9q캓H2񹹥&W~-.DS1xJ5DI @Q$dQ eZҫ?cKڎ q۳E(͵ 9$)CGfPA_䉝5Ug?[`w^w= +fb'xh_ޛSL!u:_Sڛ_]ed_˽nu\5n<.tyXI,SxlAҝ8Zl'4$.kvO5Mۣ\' Ջ̝vN.nry̵C{5_.NOtUW 3/G(g~=}Dm '{=y;E[VE{* Ftz#km: j={myw5۝ ޼pO|[2AI0#֙Oq4z9аY->qC hUBI}_nasښr A|nmƃjҽt(rh8'%f_K٤@$7ox7N B?CQpʬFfE OggS@sLm`n ƾ9\_MMMTlw;wm!ŝsu`HպSpS}z 7]r~@_xtU(?ʴW)aJcRԊ,TgIϏ5Ѩ=W}yIBYqL)֖kVCEzQvg'}g@Q8 q8e_H+p "Gn:~L-O )38U{';+$AqaZ_xh#o$oBzt hُtݣjzЏ Oѳ#'4J>ç>nΡpdWU"؊yP PTUvXPS07מ|/R kroӊM(>ZM~ nwW>aNr_9{yUaxc"1GG8t [2)܆#_ ZW > ǷbduʓBحa)НOvm/D|?a%UZe\͉%PzxD1>|ChS$SìÒ8X -/Ӵh ޞpNVn/6Ǫ ]V\ԅ3Gpu1qbnox4ҿ)+m{ 왏dma6}L)!6dtvcƶO璟ru- '}N/Zs{Z{a~ ӵdN3g97{܊if[d!"bck5$e3u>.67ۜf.}dd\6c" 9muHRCSx~B@?<o>> D\ 7 M_A??]5m{GA$ռR@dGsi U$m ܐ<ȵRgH03~8RtUE_FԄzh? JuImC*M* ЋT\J\\2GF:HGwjy }IY+9(qb2{F_>3׬ Ly&! 5Q]Ta&ER[;;AFhOޑB"j]s9HOyDjL;3[f/GI*2e h _]khdwT}to^ HiPYy{yHEF% n/}g6r *ɿg| rQ.ml $9Z^ToS_ƽI̟@*drBO33s -'BN+Z;o^w"-xsmof<~d7FRB ;Vͩ>`LƟ{I3xq67=m6]`@ Mt? 276!G֕.Pu\>4'/|&Wt\ Q;49Q=vw`@d͕%cZRs~R~g VJ',Id i)TgT|˼}667z+\ƃ\E:UJu6(Mx +_ps=}fG`8AT/oe[&79'ֵprh#xxuV1@FK}6vywV~3VyU o#?z`%W'5_e)*+8r >:!x'Bpp[r0}gc+)yn^uWj\z&ȡgfmt⻾j߻qz8ňyjtamK[=iO85k43#zn(PTCҭ {۬ ~<F7Pt BC9u=fԖSr-Q7"K-] y\CCVwP1;ܕ&H{=eHݪU:H*Φ(I;Jc8wkwnq #M~F?u|ǹG? W7L&F"L\~6ssi2CI2fSuU'H'!ݾ4\DK$据fK }$gIj|9&l9Ҩ [g4$I~=Yܮ)gȸ ƻ/] |=6?|֭ϙ{%eCv&(OZJ[m~p&Tq:_>&<Z0IQP}z@dRk|%@]B3Kfƛ T P~լdԎ"!,Oh)BfB AIZH̜ϽSa `թ}-8On )S^jR8Ax I4fa<6Eu\3Egڬ!쿇a>t8, *@%SpήTi 4й ]1w90pJP!g gxW 5_}Nx6xg런J vl[ ^G0ZĉYcW}tz $Gw;ut. ôz+T( ]U^^uzi<_Fy$H]R~P77Gv^zlx]O8'Gv75>Vw_ֻѤf,:FYߵǤ}&IonMF9'_~G]zC#o,ڽ=\<5Uq3?N\$]aX-qxOj.2 6h&Կ`1Gت0%|@;__>J)ΑґBG`U^0=ŊfΠ1Pvf'PJ7I9wehͦP- z}i%lPKܴ=<-j;ɦ"ar3ܯؾ !U qdzM Agh%/j|T}c̓Dz{QY`/POu>t ^:C1 o<j_cП.߯2¼ h>S_Fk?لvGhd;Vto 8[wNhxP3e&0LHMY5x܅FdAVT@BȎaɈ:3kHYqǜn#G @+1떟qkjR#^D] ͣ6$4:P &zhS%ع ۟G,iuSS;!Q뵜J,D]#LR:d)}A(*]23ře;Nx͈Οq"^  Hgc{Fz>Q<^ 5<=C Buq(u71򘳹s"K<& "~\mɄͫ1؂xX|=WÞBuG3"B賾~g8fؽR< ?&q_Im?tZꠧD/Â&ZQI2ѢԠTϥ˗k^\(V/o\}O㒶qPP%$֬}5؏6U^M+wT̬soh[8D|Szj+ܾUݯ,ܽsb 4pOuHcMZhOs͍?u3W1_;0ÉxbK<^]uŴQNL9vT9wfas0Rңt\p:B$z59$v)~g;tRX KoO8Cg~/Px˂ Ģ1;Fyp_mH7I&h%\.T9}ώZ.b~=ܚeNmj^;<`݈pNCuPogʲFPD#o)LGT &v ,#Z3&X%ڣVp/Ȕ`~9^΁*,!hI3hԳ'ǿ"CW݂^(yua(i#rCDSRkq8n>kR"Hpp(j"L$".Gj1e" D 3tvJt@) Yt)5oA9Ǡ}|GASb%:nx*Tf{ ?@+{&4qTcu .ǚ?۝?ko' Z6'N"yOfFӞ$Mogٸ.usZr {oVXKSD ]W_/`*wN\b|5 ~DƜ|%pt U鿻&c}q[׷dl x.49g̽moOچ}@1n4/2 HK=''+M۹lW u5[@YO_AZTBH.$x~ހs%{+>Kz ji ŢܺK^нtj_صjgq]rk*яPR6eǎp!ip:cH^7&6qָߛ}#Dz׋,J#d-hr5&HbMիWM|4EZZ(5B[ZGa9CH+}Sq.F'x|ӹoܣPϝBั!ƷgBR\^53/w>kZ2# /._5wi:>`GzhhƊsBRyJ8B 1d F?W/9.j W]EN΍B6⨣P7RAD"!;5"rXRukdHJI`D]kTxbQO ?I`Ym@mT/'*D)cT\Njϩ*6UQ1~Y mRk+2=:NxHԗr򴮧O[ttMMZ8a}s~WnjNf))6PLcƜ^O:mUB/*d`X<+{Zj xK]S޿'"{mdVɖLAʭw7P~NOU<^*|?ڄ&-/jJWǻ1-s˕o ޭ|{L7پ Ը~s}"E73p|ץ)Agj8U|!7WI7qxkw;q|p.Yryxɿm:*e&tԉ6O_O4Wfv5PKN'UFPF6(Y眂L=PPCfR%vRW9nr4j8DVj#)KBtqH>Bpf1Ons{KJ$]J]K'i5|uJ .ǫ!BmQJHaY!!ܻaWMΐl`hR^LD֥_j7V37߁jiˀxsDv x-3V՝ _= >7׿ iMGsu)9[׭k'*_Gflf\4=|~!فw;nIj̷Gݾs?8i|[$zܼ/~ QЦs}<]y1"En}y?}ݝԯ Y^+u [f](9(5c_ڏ1Jw^D&f{*-ڐ )Y^}uZ2zc9fRGaOz^gYcNP=C^/WsVe2bt1}k_s =ʌ5flFINhu<"~,>gYG}kp\H%-8>_k t*,Rq~~" *1@C}})C8NI8rk#߉'g%I)?$QSIxp3JOioI7(6`| Oio߱?ZigzCD"q1v6fCpIYd)"~9D e]TIiuJ[Nceӵ6'ݥf8>o&7CvE{jd+5k%7kU-&9Ho\>Prd"5|k:37//iվ/jx]wsp5<쫹HvX#C]4:bA Wٺxǝ7uI1(Ux<ăMU+waD)wb owbe'+iwwP\q^O۴q)`?n|'emo#QbWjIy‚;܍VPOe|Wn=||OggS@Lm` ZDAt.$;0ayjERM}]rY&4_nכSΡzS:r\Z>u堦 <tAgc zZy=ʘGAS{]D&x0svifgyRApW|:̅6'o>,wߺ65_fs':s Ր澛7G%%]Ap['`ԚIൡ7R)(~u}q1.W̾Wwzd{.Eu_F`l2:NszZ/?9H?[f."3[-Wuѻ]uҪ񌓹~Ex}?C@F;U/tDW*WGsi//O;ZemrJy׼ӯ?+{L/jSN+}{@4r/>޵~Z.aCyu"FEgn`XYOu>Liԙc>": IhI`SsgZM\oIc*%HOI-C]ҼX"?͊mc+ʚ]oeFWNquim2ԫv5XyHo'}kQ؍6Iq2EQw}7N^- /='vW.r}*#vEԋ,/I3g@^Ƽ>[Y Xǐ% 4'sH%֗G/ *W!P]6n DfN[~>PRn ,'6CGn=å} Z*zɫ^חV2vHEfFS9"/[;߹4ʺRh?%:Js;5%ODq;S6}E:dBîE.TzXd|[wܸo<8k|Vw|,15*| ,}`qXy,#c<{쑷 A|m̃?`#X|@"w`#,ok =r224Uf:@yqSHePY;#4霕9i:w H;@$EE53[iS#qB'@3Qi_}2J%<@t!\ 2/wrADI|0ڬbdo82FU0 þtz?W}S'>`f5V8h}W8[ .$^L*6%\ݯ vqTo R҃_5ǸTx^5[#u)V(Eeh@Nz}4oIJx rp!݊*,pnb,x/ͧi725?_s4ُbb-ܶS*[ndzsyoΘ>+ XwvѯW{ކi"A.Llnd|!J5c}s^7~{kK鐸Jy?td_D~^vw<_i}ڗnpZnmg1/E57ϓ› |۷  ݼ88/;[/<(K> y(SY֌Zkt/HCZv|.0АSBTUPsחg?{yS8 {p (^ACz`C@#AlOhQ.Ht=Q@Fu _fі9(p2\":A嚞vk5P9rw]6[|QZMH*(*deɃ*0@]WX'3=dZd#1hxoGIƲb0^{_';hHWE^.\κ S,yb3k:\ s[^NKr1v;`H5_At؇K\Z!UíƜ`kEY!8ɐFGD)OPL2%Zf0@Dm)k¿WZq0A ՝p3? [j`y1c=/_:|:o6וZ*)칮p;Ɇ'`bY ~ qGsCa.t2^N]uOqtQ1^n3+8?\XW[g^{t8N{M͋3ur^(cvV_SZ˹\+cxʳ4K1iF= LK^54 N'j\_^m9,.s[VmvNԝEڗ!j)sb pTd6?v 8sӛlF\pi<);4j aРŨ6r{5S+EP{s.?ۘׯorϫVZ+f*R$Iڽ7P\ LgO]O _4 ?Vh@h?詇ah9*M!OG #$.Qp4G)`~>& KlE_݄6=; N(k\> `( L8MI[*x>8?|'e(&>/+vEs `#B? ٧٢^vBjIžZ&xg3 .\Wmx;+ԋx\Lmp 14:FGR^ Q;g@m|V5kUCDu'i^V؄GI^/@wyǓ*tDWiʯ*H05h4hB\_kwkK50i0AZ.lɄƕC/tCCw+K*xoZ6ap|b,yʄmlVf#ztE>ǏR=Kr,nm31Ȧ=)6L&Iɱ. w V$TBg2{+?&R!DGhTm+#Q":-A4Je?4a[zo/s<{O>Ei׺a @A_6yh #˟ҽ@ 5(Upq>{ 2(O/H1M l5pGg~WѷLG\'K~Ts ;ߴaNE+5R73>7V_jA9c*Tc#Fݳ}HEN;?EWفzR4Mq@u.Mlx@]kێ (=1 T 3l@蠚'g4g4ę OؗF =9@;2[-~s/ZXꊔ~# ^좺[';$㿊 b>\Q i?~~ nkh|ܷG~RrDz5q #;l~7j>*aSFY,xŀӼA[.;w+r2~=釴 p2SPbu m9џR-5:Ԍe~hoP>k}rLfJ̿E%QR\ 9^KE4 9ia<>I!y! Ō[Փ.w2|c餜dUŸЩ76X쯜,Qvq+{eҠ4*y.&ƜgK@{a80&F\LȓoovݒGLΕ/Ut; Hx)ajòwm6$󙽾/alx9DX7NjM! r\ ۾"2/w:HѱUT:MﻫqprmyQԿ_ #frz|kPihNVP3__xR0~ReC= $Q߼{NM*MFB7/mpwHϲᣂ@CCݻV*HԶz'SЮ@B(p@_'cdUHL9Sah"Yk4?mWD#J?z| 2!+O( "I&O wo[4H6L$${ <]hh<4a~@,%Y\C-6X}nU5u?g hhM^kw+2'Էo1b.y/|:pZ}"eUY~tc% 3\rI@㷁|)Օ],Ad^v8'/Fp 9運4-_}9Lt"3j_OcD/MUyMث8Kp'!nRlnH\MJu{^~=ڒk>ΆOcPT#V`ÂpĨd5??="l1> ujr, o d}Y,KnYS;u}UZ1pRjP uK[Ee iJn8y z%hO9ϥR~\ܺ{{vE ڸqSZS/Ad|VXqwd#%9/[I6:]jە;~x]:&l8R]_=ې\9וf·u_\Pxc35ra~\?k<@yIavuhGqW(np>۹GX mBMZ/BQfm|f3ĹdF{OLLoѴȳ7e`ln^ܘb_ԂЫT8ϭ犢y_%ҲBZRn3<{{ @?kmԹY)"!=L? ~GM-EB vK8$Y8wjבHu= I7Ћ<9/BQPf;b0!rRy\M]U69U,/NW j߫Vx.S+STE`{ܮV {Kvˇ¥nJPe /.ڼ~y='9/KP|H׆ZJs}N^kV PKVOrAWW8 `ICq ii D,K$E%­rO}h7y-[:xwf;mZ$s!B1.ݟNS_꯫՟ƶPw fknŋe~5@cNi|bz[[Yoe&*@(3%?vPL2kmL"L}ԫnlD>}+\b.OIUyQ:w_n>1rGby]KOFԼTſ~P8Ԕ|o+MIM>^tgڸifs\حFP}x$,Nk/ر=+_sJJi*QMQuo QUKQRt;TjIo68qpTcgywRs(*@$$$9˧no#;%C@6(VD S@Nw&K<>_ϒT@l!؆#q8:IBهH,\>JCK5dCNu Jt4mOh(TfG^J~ai[f.~M>gHEN+o5E{,96qc'cÏ>U$:0wjtsjc>Nzj.oG.Xf&gCK`gi1Zџ5Tsd9epnsgFuCIy <^ϟ4)Aչݚ{yn:JG~u);otv 5K n9yP ټ՟tEΤ%t%ƺ\D=^dzQ Qݹ)jdj%0֊XM\.{6߀^w`i"fl?_j*ˑ)*pe|#7]WW=yPCv˺Euvߡ؈]w6#ghN^ym=H7^`x>wzX5\1?Ϭu.Mw tC eCKϾvdWu6LSU@CS8@YA~ 3?V2r4 K{ t\3CY3!4z3M(,5Pp#E5+GԎ\$^(8pz[qG|l> ]" UVhX zMKju?~msi7,4󔘻zP*X8IEkxz+ t:_Փ+N})iϔw~I ﰋ%ǧ^U?{ʽsI NI/m/֤QRv,#0ٚ|qB%j7UX8F#w nGIxXY[f;| x2^q}tmju!bq 7nXU ?[xunkwlcGn rZ"^c4B\_Xϔ_q۟ƿ7V%UF o"+<:bYwJ6.A)TN˷xyAƆGר[֦C%~mMqf7 c] jUې:<J@}վԆ^-O+]H~^RE{䋫ܕMZEٮVe[tZm|Kl_)ogLR&^>`F ĠɻY߬pxZ)?5A^3,VYGՖoyߜ hI*HH̱Zp~OggS@Lm`  vG-w'sjry|k$W5[rfv_}t;n=W7o58v, ^*S}ZTx23נ?To6ex4-㗷B?]}~pzEN 쳿$*{Q>^g7Nn3.hٯgbm1n}լԪT嬕œcNdh$q&2p bq*P%W ׹ T(BLﯺ_} :hlNh:pt7R @ADR ΪB{YA>l,DUy?AJ D~G3tyY4PCӋ{ãy=sH r8'V& 8kԻdb4S Gl =?&4._wROM&w_/lt VVTxfI idސ%]ޏ`57J1(]mlj,M)͌p FV5AߝᡘV4[q۱4 ~oِv.v4yN YBolpj\xJ͆i OKYtpOWkj9G O#t!)험6ޭ[ӟXG`RPؚl/.(׷f!ŭf'^L{h}Y ߐU^y?c:\ * L`"%(,O};>Rt6狍)?vďA0__u*M;+N)~'|ף/neF㶭wM'eQqh_T#ntk;Jx'|u\޳Pgt]@7j_6km~srP.o6N{<(uO^9N}Ho? ^_|p#TVr{X̵"qёIIDJ~ ÔtP.H&ẩӝzo+gv>{zJEl` 7JW;8H,9`$EN]?t\dq /8]j!9 c}ݠgᳱ>{@Y{_zCȟ@` c>uWhqۇdrs enk6%raYR*KTOݔ28f"P|ml*/ fX.jU8sB4__s7XX_N1E:qfi(%Rv 7ՋbƚeaM^L(xlb>^',']Kyλw:ˎ*s*7tL"7ijvgp.o|<5_Ӻk_cw߯RlV{UAcDK~4ix83Vo{ܣzqm˗IO/׵s+Gr_=b!u'W~ܪŰ0?ஏ&yg26qWQ \czKvgI*-7<$vޮv?6r*GY=@a2=eݻ^SV{9\_o/亷5ZVbt{f3_RWyb (P i9!ZBU$I8@x `I-ǐ;Fceg"}?]kLg^ T,s?km*q"zq\qQl_'/vhf@:Sy@_Јzǩ8 ϔIN* TPxHxۨ; $ g)zg̒9Ivө3qjrl>|3pS֋H[7/;uZd6v_1䜒= pޛ{Co>e-L?n%Qw6߼!$7土ȃ&?m1ϖ^nqBIO[Nj/rN8DW5\f29?E+Y>K3^xo1*ox?V%auUC q07-4`n1m㾿%0{Rjqj1C#Ow`@2\xn:RQ,0/n[64i,kTзH:6AN u[F}&˰swy¬Qm^Njo}ŗλHVE'i6dtCˇk&lyn4ݗpκS۳WqqNP;`ܶ_z$Pݯ({•zhF)zeO>AŵtW2ۈ\ CKfqeFº(27'|^l`ʏT;3Xu{W&r^s۷ѣ},1L:<_n@kgf Af>xwF;o$DS!j{ $FBċ&?`.»a <.|XHgOI #Hhe^j Џ2\4Bө䓍p&A- QBq9Rwھ*]&dߔT%2Y{Z>$T N $$U<"(֏TT"&/ZI9mWrWO,+wkȴ6zi'w7pmE#kDLD8i^ K*F6 41Y[r0b n5*0ꡬқCFj7 `NhdgAhƍ4VDDQM/G}j|E?5H-f_{v%ތKi+t}Wr3eƫEطUw%ȝ8YV+ukiM_=e3gЫL " +Є7l,bdܻEݴ}޵^onZG= e%TgeL"23?yW >黡GP榨iP"WN|#SzwR{h4~#؇c[gCR[\7 @(/DXR7q;Xih_q4K^/ @Jh[@8ė Y@[f9dIW?S_5 4]ɀYϦ5+sBN-*X) 2S@Xyk˱^lU]t%U:yX՟ܳJj##'Fbyt[,m$u8>>:Nq/V:|~edǃ7#XT?fFOiiVpnp̣Aເg*2ZMfPXLr8;G%/`"O.l/ㆾ_-u_!VI/ZF7v 2Y͔^U~>^6qYCe3,K) _ҥڶ9OzyK|5|+Y Ϥ׹TOggS@Lm` D gyP]z _:-' lo<@k,]}Hhw8Ç-o`^ {6h` -kB#?ɫvm }I 6<4E4~RWۤh; f:{/ĤhM<>mNT ڪMqX5;2Nـ4 N7)ے Ae A表wj2*OQMs;pHE86F.rbS ,)GrS%2(MQj]PomSS?9Qi$=70Iq-Jk-r=!+ ʬZBP+/jXMrT?@ø,A$Eg‰y]ğ< /.:lKhI&sDn5^]вU1Hc9,lW.޼En{J\z2uҜ)p4ײK{o~ J~2U6[-GB{;Ee,̤rΉ.ۯ.>~['4e=OC~YZwKˊ5]fyꨳϣ}Bɐe5f'MkзcdTn|yPrq"xj1y'^A֟`;>z1bCqY'%2KB:厩Sa:\\YEt:㭵ajϔSQ%XK7c겮}`\ur KuWG^[{fx$pˍr(7V>  HvbEύM$3>DOmD.|Yjt7Q c]a \7k _+Kah[t|h%j@A+Q84k:%P<@AY0 z+\,Uq[5]uܴMqPKۃE{% r^uvOZN4#m}ypsހe.Xٌ<Ἵ&}=!vx͘֋L'#{+S WSE2yβW *dj9vmm}]^}nW0z}6B1Ϻ 6ઢMHɭa\_{ZIJWH0o!mUa)(J 07[X_\~[3ҞՁ`}Ae},kxJh.3a>1cӨxS>Է:ZY.6}'}f01?Yq)΂+r'D,WOٔ_mxU5J(4>9ՇpvAX\+@HUMr!ͯٿcjs9cD0ǿ(|cxpyg%hLs_/ c sFy&1]HC(?h.BFm~Eޥ]o13ZaB=|䵽1R=pn*!~ ~h9g(YVnyHI6xIun 7.饽>4:$9Fo<͂\m` lе@Su,\pc=XoL\~?K7rV lor@hp!vaɆD1#!k ,s*n FaK&){=f#JAX5c:ʹ?(!))-Zʔuћ,kWvs^(?r2ڎw0@*ߨ[61u8|T˿$i+`yQhXCw'0"hPuecGeZO>ɭcSeג5"xO-fױhC~Ka\-Ia_5i=zv'Xrb"29vvZg ׈)&ZUc2N nZ 0q Pա.h([ccGW=(-- ;?py>> .E@ӡ/uqդ8P5 dgVaX~д8 ua@kj^=n慰Pݨ7z=ZQP~ȋyabvRqo~D;7#ǠF _Zj\lZ;IL81whP/I'Lؗ[.rmtz#Oh1~zdCs}s1VEVޠJߞa<+lKfP=Z,Ԁq`\%\[$Kϫ [VhyN.sϜIF%3j ~O B3kAƟ-Fh B>o{7գ2fhMw>NB˛8_څ{E6{"ԘkV߉X59z]\Q)`O?܁z'OhjQ>q[h40 *xT|sHyTſȟ $q$|BsQe,-Є@9EoT [~)P4,AW; hysI[ ~nVw.0a4$?>~S  -er|gjUu7Kc5srMGM noQ{ufM+6(`2 G^e(]ʾ 9hЯIO$lꕤ+4TiLBظ>co9Y@TA;Jm|-טiofNiJsP)(J: r&P~ryF[5?)1"J<@B8Tā< "h tUߛENp\C6Ձ'Ҕ(IU:D*J;YCMIb;Dw1wBȘվ@)d:H< Dcוwv(OlpJ@}tmtrzUs Љ9Xq:}WP/{sR ;GO#nn\(Qh8Uh|_x<{_c]c~V˧O1sWg2}bsO=PlњFi |[ȆA~I/n7}R7 &xZ)?)wmBLlɟ}]ɥ1h8]a@OG?XnQI-t&^eoJE_}ELc=sjysv`5"[K4]Y9(Mx*r/uDz0K?}4j%wq*s7hsLZB%l*lu獾 ov;~i:;VjېEs/(,B>zѥJ/ȸ-c ?"K\pVA7z+#)v)n}ς%x2\=ܖ})\bJ7~o]F}e֡t~V~eF]L{vw}*xM.]_MF܏-a.g*g?hG,fَ+JpcS<1.R{ $ǧNa>i],_ȗ^<>5]S<0۵g9S]֖g)ǺS*euF% Lh (erg~pi In \\ʝ2{_hF*pǜ\)_<VV*R& ?#r\ok]v|\7u!svu5aN Ki_}zοOCDWs:b5#U +{N4%8M)\CQQ_lc,4R[%(ʶ{ï[57h_5mt& 19+˼xVxka@w|ךoh*ܵk(g-r!#Waϣ^i^5KKs~h0"I֦! 4sޠzڒsI3|6߀O|ng9_P?\[_Z~LJz}U$s{x_p\S5$!H>s 0#k:cpʝ\/j݂luMog;xcZV&47N#euX/'L.׿&xH/E.^taE߃<ﯵe^ѵ?f񱿥E,9ڒSP'te:581$zŗ!p.xlczLx Q av[yK@yKd"ln,Ɛ#j8׭Z!x|h۶|:>%I_Pw02hZziTQ3}SfQIܤΧx>)হpW#&tRh `G|ʥ-g3[ܱ{XTi>F}efݟ'GmTJhD:}͏?ӗO_[z;Kqv#V VfE-5#bc.:Q~Nqv7~NONjki7 Aoi0xމ09ϐd.=f?8\ e3|̿rM>KX-bs_{eoS`fkНSjIN +5 k]dљf>5#tr=ٗwLrCm~?c$>qT]\<ߊJ3?uzoSg֯oț((t.g`WR>vt47{~ zUzyJ1s^~VEǯZ1g~ZIo|WTo+ڼ#Y8cbǛxZM0u[3-<FA?<)<-(jUWmgP4#%ʨڑ jfگnm_1Y79Qhi_\:bŁD? reM((T`\f{8 `C}=G`op"##s<Ll8dʙCA${7# P P 惊 Hev$E4yDpBQ =jj.**גթUw TsȆJZOsa E $ [h󔎜DZ8_26E%N 47t_ϼ7 \9/?S*Z9V֬sp%57' fc+Y1q 0y@yL~-ɸ\GX?zO&:Q2ԓwW<gXZlukaJng:$mͬGH©E>d%5lGpv/].8LD ߡX7#?yԶ\]>ZoCǖ6! iX2I&Vz^>a,Ov3EI^ &6dKcڲ\*AgfqVt:8M܄llG䬆ֿۭʶIBJqi'mGg<oX}  j5HMW_ذf:Xj!5OggSLm`C~:|G@ֆ+QOf珵-clLFƯ@Glܑ-qՀ  ggXd`"9{8@ʕ*')h |P?vR p2/} :O@\F5 ՄT)i$| &XD-6#72WgN:8%jrmgW *!2"]C?Q4S[ȔCԊ2E hd怄p@ycƍt9TgrOqn`: ߩ %pBIuuTM=}<:}=фV?6Z}Ć̶!gmHG:&돴,IY-(ZC.]Tds(\y7`GUkomO`S#^c`-U5/^6 H"E GZe[4!}0pd$82dw/iSBB Nzm;ú$t;hMs2ÁQ̀lƽyFcXd5Sl+-'KJIc*GcNB&-V:F]}E#ƵR#uɛXg Y5S_(Q!QKY+OBh\R%\u0Z<󏯲^]w'ԡ\dSuvu|VGMkS~u_"VF>7MN\]$ccvI-ٚj&/b%FE, =\amc%}Í\=ZOs|R>:GB2:mk-zZj1BƀvuZַI#n܀ěwnQP@u(o T߮;@ (pi(6&p?vtSXqh`>v B$ߴ8(DM'R~H* :ug@^ StG{:CQI']q5  CR=#}K@NA,$jXŽ=Ƭ \i@O8mԆ-T~?Vr-1֊rߝ)i԰·]m.8GrmQE*@7yP`X&xvPP0ɾ d#%A')@ 0(0-9Hv8ҙ5@'{?)5Dc^I6!~3}yLM70QܡxJshYv`M:׃PX=ΰK H6'WbX[}J: pG5|lxzI?^5_O Юpws{܊yK r =uzYwPDo ͔%uU4c9ܲ /}l}Odxzv:B1⇵`O4UCCx) ؚ]_K2qgMb ~a5oP]~Sȅyzz#ZX+1eypyI[[~ͩ u` clC7~VZC]F FoM Y &3K3[FILZ!.?R_mv[q Mhd!gh>0UA9oqmƤ/IH>tJ(+wI* _=Y ))se.!ˁk;Z&T+4,PG5RTH#%j^cLQEe]v6Cwӡ>ߢSԎ>aLB:trP4eC󯮏;>ۤu:|_A[߄V %ވX:S9FnܣkzG[>]m7{kŬ{XP_o5-7^ySw: # =Si/WֿV 3@vB%BFʗ@@R@ (LL@F vfB TN29"ǻ} 8h.PPD))HWY~~X?y@'7HKWu+¢NS&@+IRDU>oL*͖*N=>ʱ$NfD=LT x/& 38|39PJX8e͏.0\v7Vp}4m )=Kďks uP;w,ϻ\coίCdEGEV־)rQ4.od -[?6@4'Y{t+^8 jШx0!-E {ZK/9pii[Fț(҈02fYp/P6m hGP{]+/,N{}=Hx$ug092oL'}WrevNWmf[e/o摭]q9d 7=͛!"x!o)YiEZ?m@lZ._Ɲsz1T^HNv39엂,WM*HW+iU[lLF"rPqx?df65=7І'ϓܷ]on[T*nVy^_9]qG&rm}8>[3=c7lӴ>-Y$}f3gn9=:ݷmb'Ԗv/t`7>ƅ*՗B*:?#JJO :?巯 j b<~7`zk7>3yno;8^ 7)Yd\1/SDUUzo_z{6tDΨVM>k|YݧР4l@qVv=^CrqHGHVtד[MH:eRrINe~qU_~3.fCC"_A+QD胺ߊX/Y43q^43_I|%)Kh?e޻OZ(d81WHGW1 Y)+]7|NQ/&BI k~S2;z~P%Y֓^{QՖ Ƨrٷ'reAm׆ 3>s\f@oEL_7귘0fo<W(1+,zB5Ti~U2X˽>ؖfs$5y0{h|/S9ʱDNy(2AKϘT R(("Q3b0V|wznrB}_+gpBk mNOU0m86д?h$l/7mɀsxUw_R]B+=sStKZOe|=7kr,֮;XWO uqo} ̼eOd-u<N*Q=Af[ͥj3P 6r_= NXUG3)Qdmr]/(l  ?WoN|6߶(|o)[ԡ5q܈7&4k}{=XmHM}cNz_tq[K`֪]4(a,ZZ[]J4-:"a.3 `;SqW_}{))wP +Qpg;Y7 )⺻ɕvO@C咳f8X>xԝuyQ3)PG!82бϪ\TKjAmXX7?5@S8@Z6'}5*V}x͏QuQ!s߲_=ݔGMҾ"cl(ԞL\N,k[L i|Oh;7 ؉oEDo/ ̎g? p|ngg=Vu+gG}9!vᎇ'85=\{ =KGѧ[m/axgC8pZF|KgU%=Wu PB$QIU=؝؏ou<ݍ85ui|->ńp%gdd2-9%x],Skis-?I]CRJ,+FwW9'q2j˥GurF,a},yaP*y e\5"aip!wiW=?#I6ӏn޾eOap%S*; UٟM -ᔦύ]|xa7Ocs[v?[w\^/z:.2m,"}f{>s^j]^)zu3gYc 㨲 q/mmuS&9{>v<k1.}z#"<ͻ]Ƣ2}煱E~Ƙ5~ÇKvӹT*0:@lGJtږuC~1>jwRsӽ;<yCK,]\dgc}UOloJp?_;;;%ib<٫M5p69T{;Xp1:Bo-PbX*tG}o_6*=܃u瀞.;x?-1j۩0׻lhg 5unQQp8<'$TǗ]EC[s^#FA.A|ĝkjcb_i~:i2]шMw-GK_*o@6My[^B'//_Ng5Gf,gFtQFpP-N%nZR-5~xW$ul*O2'S-?ͣ߼+nWdTFޛ uR(_CF]Sfpx:s, Ӵ^RO 6S5m%]#m6_PZ͹0BWG4ڟ~w/2$L2l?y<Fl+? -ߟG7{Vއk0:c|׼д5{~v*~!j#=R+u)rx6#\Q>8d>?>-R]<,CxOxʫiޭ4-y?Ž1 F.]ٞK!Ƿs.?epRzU_n9hb?w k=:-3gړOl*ֆ0{SOh90/9/㛕n^+rשyVA=XOFܺfM!Mή7`_w݆DZB]ex:o(}0mο[_kFR$ETwY$jE^:|WIhpiTh 7Y?}"{6_x!ePb*E_L9C:{d_{\*(dVAm8:9D֎Y'Qm(b x'h~ tGsw?f=ULR5@R{L>j]4Ms"q`;PR*ds@ԟp1Bu۲Q24BUqo9S#Ib3{W|LׄyuΫ]\L]J~757?kp>%L6V1 ?yPsYZd] qJZ=|=k/:so۰?H^Ov<죢Z_lrCmV9]}cxŸdu}^wz>8lWg.嚎士ֻ簛sDs;׏Ik4^uOggS@Lm`¼N^|K UIk#}%`tZ@G<_Ps$ACW* s!@(\A<ڀ^hN?EK/ |=vNB{U;-B5 KEo5΃ f-xh%]`zLFKQHG Ph R#k(@RXp+MV]>CDF!2!nbޏs?G~rN9H|<_ޑԢpRB @S:}MMSԞ:Cj(J pikBsoGrU1cxe493&3/SR3FSjeֵkh^]5 w|K}znt}`B54WAJ#SZokˊ<<^bO]]L է`mޑF3=ϊ4&Ow eioؤzGhuK]EQı{׻CYUM}ZG.*~-m>^MUU֬?U:>!TN!~qu^$^哵o>˛logJ"G%k[Ȉ֭?B(M7kL_<{Q R@OMld38-oY78ͺ z $ۭu hķ_wQ?$ !/$FDWAIksKsb~ *P5jhF:@eǶU!!*MCܞHQasAN‘'`EEWfO-itGPB{B.(<$:&IVZ#zc^W ϑ-Ql諞Mz8Bq?$0O?k۬IP8@4p-.$/޾zDϯ^esB=(EA2/\mS6f}D=ؓ?پk };ѷEb'ed»])Kd+0:!GVzOvR=Ĭdd$Um`F E1،x[6,^gd|V1 j(Uj`xP:v\&T]upJj?,| Dt~)^:<5E?]:G]:}9Ľ.,(m=jVCxe"I%U»lְbv>^*|)iX ~P+Mn\=Ke~D? 9=>uLSK/68؞UE %?£Q[ESQ h$` 1ZOfga @r>fS#+ ?Jc@ n(:  @PljPK_&9IRBROo~)_5тdC:/$4jFPZpeN( BAW#u"Q{Thf.HH%LL;IBo" `7wH1I-WK}a&KU3|D]:\~.iHL"`y-p_?9ėV`?YYq.Ms=^of,fkEws`*$ݏ! U\;o>F6*ث|cbO0Ǚ򿗘snP;ug6m{~tv}JV$_d  I /~vMh5JC?1A4`}TUZK-,?: |.j}2kdQ(=9oo&8эSlToj1.K4][z؁R5.3 UyuUyrzh7_ 9oZ@R j1jzqd.93W7G֬u_3[=^]Z 7ױ.83+ޜlQk* Gyy3LFW @z yo[dMO6n5<x`{V?o"ɯ~LZa@'v @CL_ T`ew^ J׮h<2šf@AZ(Ù'pr %lX}mvܜ'楜z Yz!OtT hIzGf ̳D@Bpܪ'w[B_gTlNNUfe#QjF \ۏ Z{P=%xL Q{Cp J9܇a>GXݐ6I =:n^5#T晪ffRʧz# 07pD G8Ջ젃68ٶ5l f8;;{&x#*j^E,`' \V%[ɘ[mss}}? \ ;Gn5_cY{/:{.33qBmpXD Q]9ĘcoUu %0`w>DWC1~[KƧyV9s|Gg튴zRySmqi(XSW@0n?: M?u!C%@N+~v'J'6w|]%)Y*(T&{y"eQz|i*:!k<H=x:9(_E8߭4*),1JzJGE`F.E T(ϥ *)_!jS3PxAZ+9Prs>8ys ϣ֞9&w& ;p"x>@$ t0ݳ4Qi7e6][oSNҳW)o5l|M)fU0?ml8|.}1PݛD١Wl]ȷ ,MN~A1 ǡ٦q ~Ttۤވʘ&ˋ] 7 Ώjru񦛜?q_Cυ1/,ZW 9S26r0׭1˭瘣.44Ahp ~?Z$ 5V?L۾Exgo  fn|89F@{|K[h^8PUsl(n+jŹ Y k9q|忏 ՊhZ+LR?{< YВ;Cȗt(๯P NV ^ @W8~#Qn)y Vd&-mg[:WjbDAMM :PxϦ540M[UZIț+â,aӍt{yɾڌd3?s\&)}&WI0/kT\ȑa R˫8eҳJ$j x'21 0!OT4\'[E!0X85̜fNmqAMj3p{M"k`Hp!] Vu8~JsrHodjg:8ϫa/n1LhFEXS;}\HFz-8$F |0+J^6JMh]4&h) ,IӼHL^X 6-/ `B(J-@SY؀ zTޱoF)auyʷym:dZ'V6k4K1悢P]e<֟3 jr/teR6.V#-l  my{hGm!x](GਚZ0FgtġL V $@EOL 3a@%'~j"t*aB II oOu04y~%,[HٸʾhRzw/cKƷ̗[M=XHٕ2zϕT* Qҏ۷Ԡ+֏~NeM'1Pf^(7o~2^V5ăm/I#} v&L}Mص&|Q0}7\:,s39s+7{F{yBq3q%Pki/$_6?2'jutѿ*kQ\YP!{OaѥۂM>mT;[]~6|s@/-EYlF302ʗZOǿߗsY~s"Χ.&o PՕh?%bD! @d ^+/1&gז%,np@ X%>I$ѣDѰ(>0T)*@Xp$-"Q̈́U>yU٨ݖ{E7CxopoG8 Y*:|_)j]ZYC[d Q0R9Ƹ w-YӅNjӪhEn:-7 Z]?q句#j .hwA y.@`P}x]@a?^>X>]P(-}(*ЩEv /RtqRTD=p _e! d|l22+ #'1zڡSn 4g8PPDW)Ws+<0c;= fǫ^pzhw]1`Y"R`HSЃ#1F[^A>z\vL4˼3bKwv_3rXk i 9vH' cjϢz7<ͺY\Zz~Qc&KG}^ _/ +[lt c}<"?C[p {.u9?e=5^Ժ rbܓEOe(~;K=a~7j߉ {-=+WPA__)= wy hzC3ӟo @*5J]_ED1;'t#]v5TW7_7)B99y^&KۄeS$K2Xn't3@0  ŏ,9"Oε4z0{O(ii0ro77d:x7 H2iasص[Ga \##|G +Qaj)tch7%*O;eABSM8iYy9>_6Ndm4i~vU|L]W\0~dUQ.Senεfē,_܎@6H9ԌDZj7 O>mw=S.YHyȼ,oj̕j-$Dc4@WQcCX{3̽j2i]E\fH"Y8~{FﴑO EsN򤡣߃Y-;!ew BRiȆ CLQ:kƾ&ĵqXe@ްL÷M0~OS[Þlj=LA  :O{ls# xtZJHloyi"_MZ}<= Zs-3!;&Kuu[yS:iayw<>՛*# r ~ywnۅ5ĎLe*5E yL'}ܘց֍ޓ jJvoO`ƞk0[@q$pӌȾF.w5 ~,3HS5n:`/4ǹbA[$t"WCLqcٿȹz2P`.~)=lmD Z蹌3XS'LG,b;],FF]DEٛO!ofnAkY Aa|L9il:^f|]}JA.9̀r\OggS@CLm``)0R+W!~6R?y QQ>F\) B7zOOl)-䘮:-gaPWjk 񱯲Pn {-K<_GCv, N;|Kqp (.Os@mg8$Pzg Jܕ3o|ЂQ|;y Q,i!#cmo9 Um13ٯZ.D/LL?&̓$S #%\pHe] \jmRf/ؕ=ցAQVV\\ϧ52%kWt9`G]=Z D8/ aBlGMds%?JlZQ j-#-6(yh  怳#e7>tۛ :l{f"{ ۉ\-󋫾!n󪶎$u 4vP}R)Q_)fx»:gBk8$ݥkb'@ P!P))5: Uq>( T T@q$*xSW"vɣ !FD@kI/  ڙԮ{WN<NdDCׂO]5`>P U xpHb1j_}[:{6R}nU&NRuؒ4,op9qT)|R->듎&L_܌r45MZk5Ht|/Dqe ݎ~PkśލC~K'iS94\X`'w"7m|>['6 RդF\!-s:껉10'~8_6L+5nwxh|iP (&Rp:TXU#Qɐd/ߏw!6̭^,1n  K1dX:Fe1nK%uYlsїi[4Ƣy׸wVY1:u30U+1$=J_#_$=y׺~o> ާNSX V(of0],*_$^ŪTDSA.w\cd*l5\<ͪﭗߔ07Z(ebyK';RԵ/|Od[n6%Tt"|c-/&:}*^<xJӟ]]1)P!~k\_#s256bQCX>e-e1mZMhߞr)S{{J[ᗯ ؏X~M{r)dNaOQ,Wż]|@pI:hxޔv(x#T+WeSi|lltH}*4VRmjUZC8Yq( ls:g6_#xҼ|%;TFÇj$LT@@=̆Wo p7w#讴$ V[&_ԦIWou?9<ЙCQ }"N՞h{L /M*įV _w6 Sk곝_~Wrӳ H/0m]7 1L][?WSy@ F*,c@UZlQvsR@×yP%  ?wkc i`_uY Qrfƒ9CٗhTx6dT.k&kr< Yzb]Xצ|:NmLjPqr{>aWpMvrsj/^gV 㯪m+r"Os-rƋim+j.#`T% rf{[e7qA]ӦS /Jh}۾"!; Ow\?^ws)mKL{4a'ߛq͵heBh ıXaOriE^6gQJE'}5wFS4Kkq2:2ęhs_cnչ$iș೮F2)D\D1`ڠ[" \wZډ觏5O~Q2һA:sd3Ch8A[f}`R˃ɤ1IډX!2D y-^inasI++&/\MC mblZ7vhCNU0=ϗh |4)Ev3%u/Vےۣ~RGVSN\5%~]?u̫U?MRHw.v{DP$r9Koj۝(;d\~\4$:SGao xMC|E h]=4 s5VM6G|'mwZb1Ӱ7\6OY^C:z8]_? y$$< 6^IxRږ0Z줥&r׻ |+M_5_OggS@WLm`~)ܹ?)C  Q6y@sxn[mgs<'P'quA\FeRk1d_[ϩd7sݬk4(ST yS ~ PiW'Ezkf"INq ɱvUJp# 5 hW] }\R"v4QOdwڡ:ZCS.TI!r#ߪɞlZ<GwuHd9I-|CkVM#zk^@ f7B7S`)'7V!fR\AM9y۫ue.Mp3AX~Lmݻ׆6<3\܉c:mgoOk^햟UgG7*9R9{j>]yay785B8Gv3sƣw]g539Q>Bkr^+CocSwكDI_$;Vg^*V$;A__*l{;5уSF*:@ub[W 5˨QӗMOU4Vꯠy-Mx+r.k,؝Zߣs uM>:ׯuY36.e׌ǻVyZCOĤA{{`:!4[^v~O-q m ʫ}~:rE %S@PQqY>ԯCB(:U9Yo|(-֤ʼnohmj0DOn_HF~-FZC2$+UWR#p9IxPtQ{jXbE5©kv1K{aloxѺK;:iYCd'ֺke 3.v3(lmW5Z:U#D:?[.i$B?Q*Q֟TI42U̖y#wX(Uj5JodnK4l 9?1߿_~nɗ; HjjJN뾂?)~=~G~̭0c.)ص^k؀(jP>+LȠ`.wr0=]tg|dk\:`Ra ]%a蔾 {IG)Ҷp4mT>1.ld֐#h?Ȼt7Hj|{*o~!]VǮmM{%Cߢ@wz =׺{o¶upz7c]|DsN-~e4y}8&k3S'='^qb{*W;;5/x@+# JRh,gbZwަ%Gi1!?ϘX0~=I=aC1f LDk<#c@qdH4!7(6 V,|:xIm[^߫p?c 2?LB7_! č"J7歟G"7dL(R =O`ߛZ|vr7vwv ?`m rZ o,Z$R-AJ(F8ӑ -?N7V~*TR_S@)a܅U:3Pտ*HR8\D A'kE2:pđפO"Yw Kjx]#)JK~ԮWĮgJ8u* h%uVUս]馦Gtw!"nsx!7F uhl3ӵVR+z4Smzq+3{Q}Z6Anos9kR TVsb"g}h8>[5Q1yh+?k!E~7vhl7ѥ6?}1l^Sov {D*_ŔNN=Ĵ~&S,ÌG=F0 'QsSܱ{: 6+S_ٝT}7wLʪ,C*[~#yW]!/B]c̿TKM2WK[p}+ Dt*IhdoBt7kUԿV %*2YkQcD.-ShLj:[ U=93Gjf=ɆR:΄$ӳŘ-$~y^g^/uL idYjMn!foCs{7p?@tqV-IxJ>ԹD\[q.~{.+R m.L$ٗRW3 < e42Q3~wp՜ ׃L:Q~qj+qЏ+?|\س[QRQQwؒ#zYkpXT Oh` (0?d&CKy~= V]O>dH>W8 Nef^ VY8'z(ڎ(=%ΐ~/ҕpJ*D!(>$4tHgTšHU ?KJk֖ q+a\5a`jR}HghHedgٯy~|4hр!@x]x-4$C2qmh7[bU}!=2]TwZy3S4l[TGjhH{o?.s$YHU7I8S+c^^WifW'>>od[^c4A5t&K(dپ9:h+>7EDuZ %pE<[ZJsЊbmm$ѶR̻vB;=ǻ]-:[â7t4vƈlڭ ۄ̬ڍL=+7/i#&84<_N#El\Eg܅gA{%MA=(T'++.Ÿ=K=MGPHT>}NKt?60QkgKdʵdvq^VKZq+nj/X*}sh9f)2ukz$Z=|Q$ۜxE#}\syDFS*٤w$=|BJ7zYI>t+Nfs9I΋О7Sݸׯ K9M^\tW ^_! ASx~A^}v˼9>2y#n거8CQpwE+5X/~ƀGYyXaa^߿;8`j_hTv y[k4;/ro*_mn Zq hY=+^:<-@XA(ϓu᧭}7s~]Qu{/yHp]4#+ ciZvEzrHbS62X~^ڵniENK8XTeC(CE=‰BUNhNkzthTR[c *:ʞgj%92_b~_NܘBjj 0c4U0j&u#}7 ix1wKɪz[p~Fjڢk+HBZz8\p9'+c}?C=-ހ_]dߑ 8|gQ 0gHP!w,佝^Ӟ9IݺCsBÇK(dỉh:%ʮuMtan<7!I_X;VkKߍ<;c/7+-ω$d*}׿^)qKai =Yyx#o&)-`"53#??u?O1{fӀ|X ktsbC!;>^.twoS&^w:"y7@-T9Aޣi1DMӐ3d%߳ԫ6F,9emM쒡v:w{z]Qzu+>5݉d8\G[ ?nɂ}uBxGz©;U{$R 3|P`L7@}7ӛmz`KŪs}%GoƯ˹~zue=:٘~(r%ۮ`aԿ vu~WAQwʛSOggS@kLm`/D1j‘I9MM`0Dg%b0\C}/Msqޣ5?{Ǿ.“1r 1YWSd_Wk@6uTտW nn䥊Ƥ } T[_Ʃ8$8%ʒTgzh}S!/$vt wECc/՚w=`Do@kE5麸Uj:Nf޸S>^FVͻ ]HtD|h-NDMCԹ0yipdWՃ!a˳4Ǫuczu(Ѿ8aԂ@e8$g6qzd(j:p"U2 #"Grʔ9$ ,I@&#Xe]&.XUፑ*Vo"'=n~vEv}_~9S0F=b=ퟃl9/UM1Ė{_3}A7jV1vJ 59l3N8>W2_p {[fu X m>WP(ҮrGߧ{aR~$qTͪ_؋\_@%@3y{e0Bnau&&(WqO?._dEt' _"˹ܑ߯˥: EGrŖ˻E ~?+NS*-Jca)Si4fA?U}oN~x.qMn$R}h!^ܐK$}_ͩ=XׯQ0Y:sցuN|j/NLx F ov\k eo}{.?^cs|mF}6j 7.˛8 BOM_V\>XA4&|DuKeY3 OO9̆Hp~,ߊ[Ko> nur}: Sc{^/9*~pzZ}D]82j+߿ ꔠ$qb kB~OVZ.ix*p^aߡ8uבtR!Mdɽ5'^)_Hcɬyd|@VNy*un#&B涻N#.aX/W̐S[zۮGE.eJ\b.lhvP,qU$r֜0%C"X)k-ݿvV;7uШrUEʈzAsE+o7G W DH,bfHx &n M,Q(5hݦZ>_7XRRѴp?VUۦu7^l6 -ܚY ^vpՓw|O)<3[\hl# -n{?Η^ꃝuj Vq> @1Ap |w[wF}yblAä@z%6 G\/\&_BE¯ p>׀NΪR4Ց{[pT"٩DCx`Z A  :pO=(&H$;bDxHΓ9FqXӹ 8Ԟ2@Č%ΊP|Fg% }d_(Q CpͿSS/8љ5*{+4!ݿWL B>HnKQ1k5Dy; ɝz -Bd6w?Rڣ#ػS~OyPz7⸾i6j_fanYDF᳠SS&ѣ& vQO?Qn?yIvD~~h~4VOŝXs#Q@Ŕz/o]Y)Ac|~9X)>5ssD~Q-жWADU$y\cdykscUwh@R>[I)+u^Skv@"=^M!_Zb4m2uZwq"uY28o4S.H>?wv>d(ȧ~_e4/'_Tz ^5.jw\b0q{WTn2)\39m2j{\~e~9[k[q޹ FC0*=EŹ2nucu|<lM@ NЖl@<@ j;H4_P7 5x@; Pk:np j|<$8> RBcgt?} 5EW*5 _OM݃2P}vYbD@:(^Tu8>@X::Z֥JHpI㉦Ie6MԠ;tF'!6삯(wwgڣ-]ѵwɷ(I , WamfQlhVѷC`CZ&L92cK+_rʎʲ֬\ iX > B`G$ `^ T>3\7|dvFwwP 7DCԝVջ*9؄2Nںޗu36dn_&QiƞX RYsUEyNbV<{wqύFQ2w]KYL 1k:v7\()ܒuDLD /lɨh#s!{HfpStOMm`{}a:3:ʆnЙxFq-aB92txr z70ZEj.!YȮIW$RH KQt``q IbQ8P gRT< @(}S{@ of}2:#Eׯߑj@"L|-BmjbN(`Pj "gnRʿN5)Sy-.DF~Vu!MmdogŦ\L*9Ob;J/_|=AʃXZ,YzW><sx ,)4Ɣ 8}pveXeɀIps96ΪǁpԵ7os9B W['jvm2Ibq_lE,qQg_*?{cQurG ]-פ۶,#IY;IEے\7~ ЭJ4or~[5/<.㲥iqq,:O>qQ :ZnbGV-M]F ܻ}Cۃ@aK^ȾK^Ƈxbƌi\{4|옓(6hٖ,$ X1c<)ʞ㱍r؅^eiMsqz3w˥t,X$6IQkMYswu6o}Z$[8,pI^v~}F^5r p Ս tP0OggS@Lm`st~)ڇB4Gmti&5c^SuMn8 :EPs'9nԳY~w>+_3T]MLWmܵ'晻.k.*N=Z:Iw"U/Y[Q9î48M'y.@ R Hq.}_Y޳wHPs}*~.s6(>Ǧ`In?>I1̲LP"0.=pk$,|/x/zF{[,ڶٚ˞w' dC燎!fDA8@uaP`=ٵ=9M0c^e>K",J9kط?/6:N5rD|ףoyw_ZB +ng"Ex6Z9K%]]3/T_6l}H \|hv`L5;Bo%o]1gkfOsqDj,4 3ʿmq5Hh2d]Itv~L}(gA%}[IVb^_YMF ע߅#3-O^Q*ቫO;r4K=O4e/)'~"y$!yiS3;+G,Ǔ[nD`ce񋏇wnDj]4_cq^ֵ{LSOF6veUnٸ?^/܅F1 GwP@P j-o~\2R]1֩t5%WȧN9qy[cH67*T~YӲFmGH # d/*@]_j Y!a4QPk  tJ'2UA OEw@ ;Eo] 4t~kb_ߙPf~;&Ǩ]ZToEYO#G?ggď3>F'Rg}ɮyAײJD#SHySPbFr'…!!QRE_Toi=S\mp!Y^?c}֜Ø8FNrmn#OZ 7۬D]=8xؠ8ʗt۬ HAӊꚗ{Yl|\sKZC]|}WSdy=U3muƳ.bMm&3Yu9(]FH ,z(=w_K`ʠ{[۟o?Rq|\ZAv7h%A~JdLk=W,򢋟핁M55]ԔR[nܷUD5(nT_^UfbbTN74>O$`Cj/#` F"컔N;ӏqQT(p^u1N.x<Xslv}I5wWQ;?+3o(؃EA94_HC$"zihh+/ZNgo4J{OLnڏvYKcʄ͛Mu68.ݛ5:k aҘWF(>~Ϧޱ z8C<&r45W^\>$ITk%Åjd֡7;\*'u8#mR翳U3]e)?KuOpՈN>v'7ozǶξ;b.C"u|O$.j$ָ@!rHx P 2s_eI#(` TZgq.@Ъ ɹr~!*i!@Wɡ*@T*PsH*~U{֓ǎpd2}s!z?P5ܞ/V zBsxį㲘d'_R0doM9OEn7)7dkhI#~2k 30閜|u9v&g'u9"?5+L ޫ՞ފ:'|j4,g/^ӪEMug檛]DOU~] ǥ~^ɯ jj%mS[J<62l?`Nm[?q?J]#n?[4şj77gZ{=vPK9z|<8. Qȳ q7ko)<ڶB?8RN:8e3=T@aBST6!G8j &4q$Jl4 C`ǰ!rh(Wph&Lˈ*.{\Sv)5΀IUt:NBYļb/'VǡEhx{BFI !spFU/|D+6ޘul/v*-]Y', T4CˣmM ekO ߸sO1{'7CGgG7J~6y$d*^d}L$ ׍Hܩ<_fZNxI&mV[\Ι2q#g6KmQ"zu~ފ8~E[=[/NpicDGpN` ӯw_[<EUZq9|Ƈ%AKU_wy3|<1Z'S׊\$`FLJty_/LY[lfXg]ϧM%f;OW.qY@SK~`?x cɞC6:^̗ ֽ)L[w=WOgQXyO6~aݮghۜ?}nER%w8#O3.2/\;a7G l>|W`-b*K{ RPΩr{pX ޹&e/(RVL?{V=?/l-F_MR M4ou\gyx}՟.߯_e,JHU)OggS@Lm`b5>[Ϡ N9drH5/_sWuy#AWAPx{_L|xEwϢ#úBeDɍTH'J˭\:@h?녮 !w'`iGM$ueON*?}|SJf_=Dp ##u䨞 tN3 ]c&?PݐNKGqplW;׬cqŗ6r?H>gə̲)ɖnr5hɢעp7}.Z !.h6s.h1~PPuQcj< J9/w:+6'Z}ӈ"Dϊҏ־**xq@n{Up>uP3 θn-84MHΟ-Ox-ZIĉ&faG$yw\Ͷ3GGV+KK9 <ڵRn>nZlbFT2-қl{/-$ݍ:cf_x~pIS?B*b䮌z0i|K w(uå^} ںd(۬ך<, ]{M %{2?gBì9eˤS^W&|)*3#iQcxE[G\.("'J'|M2[Ϗ+w'l.$Ft:[\a>IVk}-Qb~ec\;͍]&Ы|,睖0x$zbChTqM{iϟ.W.sQ?ir;٨nR*cGOXWs"h,Sز.mo{Sii'a2/&F;̋2R]vH]~ _ E-3'M%I|^Z5OKRPŏ6l(䶕?\o` 2F918i"R}w`N LG=hFs-M8J2g߰e{r Xm/S=9"[*dWQ-!Hz}>RET Nm.C7<8" jG=ga%k=xP+ڇHDdJ V )ԏ:UuRE.ȢRTh2ϕKp|[<{`eMtLsgp4u99Tm֤S!xyrګz4HPCT4? ezw4z˽MZ:lh*}`ŒcX04iLXMB6o'GC3.hb#`A*R 56YDfLԬ=%bG*&{库5r 9jpYǟ}躷΋+EYqrwayO'WzuQz5w:?_???xY÷8NVbV}XKiN˗*?.u+ƾy\Beջ朕uH越OkQ(ԃ+4:,VecT-rcԮOhs֍^*^ke&jgH E' su^%EN)*U:Vp@l) 3MFuxZG _pnxO88EGyec2av}P?:[3pLX&- kzpРuy .#T1Z"9yx 5}=H"Ԇ?Kr ЖeI|Rk4V+)Hy夑)DMBP%"VWRfh9DySDTDTR"(~Bp%rp rrPO$cJ 94yET]*;D['-x2#-($ȐH\1weqet?yŎƇ]\XEN9Aߟ~_)Q-2yb&ݭ_k6O+#ϼCΜ@~+35^.+%ORַ}(_M7]ϐVU)o}mcZY_ IЎlIw':>nz?T[>(Q3o@|wӕZZࠕ߉8As#Tj#BFQ`>2IY`s&yozCBk%uT.7?eY-8:^y%yLՏc}I:Ip'_H#ӛk>iŦ@NiUT&ʢ.Y%BN Ek5O]T{)luz"TEAKӛ#ë'E |$@Gu dhmWk>bkO=t1]{"`n/χU-F}[ǭ"UJI soum[ewulGIKwޣϏv 5"ʔ7߮K짡Q*~ >qg.d{jwnu\p+ WmVsit?n9p xAnlS Y;Zݯ2Al_V{P553I4ٯ]I1!smX(6*=v/^ \_g cA$5LmCG5`:i??hm gO[(--!& #wWt5DFQioG ploֵXPjPB[@YwvqEhC5Z`ˌ<;v^|r2Qf/cA۬Ln\z!?' mpwy`~P"жnxC]acOۙ2Iilw}Ϊ=uc{il6=ħfvݽ#U'lv9 'Yq.䃪V=J=IҬ C`BܕdBnOby@ [U2*1m8oOD'XX]?1O NƾX7Wq`ShyS~cɦ|]~g:OTpH]ō2~i\ղ|'bd `蘯xL7%؞Qz_!sm&r<(OggS@Lm`tcz> ܹMdW;:9L6R?q{Ɔc@7mBg 3zB0j Cv=fC 6"k۟t l]U{ UG?;x[k<#J3_C[ϟ@Wy{|;qs+ȳlR:_SV=o!tԢ|L"874dB$ȉ~zӿ|t-z!'|TթZiDʚHJuDUQ*y[hLOʣ[z &!$tIgJ:!Ԫ#I4 xnTL7v]ooKH@RIC{C=I$jzLXqa`]KY؛MpeT%63K,ONl`M :Ucrג-7ϑǯ8>),)]ReҖ^ɬMÕ7 >7'"s| W~ +@S{IG|^e>/o,dQSqOD;H̉3xrm͌N-|r_=/rOjeW̔xމ+FU~*# qz ?Y࿻?FwzWgfSd"ʇsq_ ݣN3Kxܝi*'Sqzvr!:AO1ɔ;#/sGRO̘.gec[!/+ˌ|6ZfRyho[ݵF)V,/͎_>Nz^&.vExSll6c]cS;v=ViXƊpae1w/W??⯇2AV쮶jCmi 8 ~ٛwMC#Ajx|םCLq٧[Sq6 vl jqSxn&`uoN {m}_dK?h8DM9>%+t*o `[SU<zߠσ PYTg_?Zd"$#zUVC+sP$jUMT Z{"SsZׁHЬ]LНTjYvk!ٜu9lbBq?#`9>p8p4$jyU}j1cVϮ̞d} LzCul`71A!@G6D;j4ő;F|[cE٘ h%Ӑڔx-R9wrUcakrafY NgzFl:`~*Kϗn|Q' wDaNo&Y=[^.+zuhLߟ@פ3^u 9gsS^H%*OMGўCw[ra,$dV?>f͞Gx#FY^6Gf=awto[X(ZVZg~]޳("F]FZAf)Upt5TEMi$w!#4Xf]o?1W].չ(yu%K:EVR?C*L4Eeƀ~kDAVԆ8 cI( p] Tѱ1bd"@%9p̟{њ:hv/Q~{AOI.=4rٵt+v?/ogq“٫zo]QWo3 +gw0NfMb: E ym:ݺ%މs}\3*')ɸjS pZ%-\r"C B qWSq|Dz`u[4@|X7^ĊXc>KZ1Omk~DŽ4׬McO?U]ι 렷nb?[T(^j-2* Sjt`ں)rB:247k}Ar=0y0Z,,!apY&7"-CR#2]Ý?~IWgkxYq!.{x=r'!|8я(S}uM#و>3R!$csl.И N.&  :LՋKjh}+3]nk GBm׫2DĘ ~ <03PC2x?*$x{}|38Zn8rĂCHW &0ѧ'ćFf/X?\ ?*xsg:@g?Zt сvq͵Dz<'OSG#ũyfEкp Խ}qp3`%v2Fe [R#XK6B0TMs)yy4s9CA}M=t= Qa Kwz$Kɵ:MLԪp$D 2V*#@a5Xh @H\kp)QŒ1}g]_]z %~e 7$Ӂﯸ Z4Hm8J{^g?R =^KM Zr[$9bqŽ_n>Z 78{xOa}>2-v¾;%\Op>9p\dfȌMTFsBaGXKڀRd'uSot>~5]/ѢF/yM:wnf:7/:תYSԾ(@vvRWs@uMyiÝF.:Gg]f+S\S%A}b,]N2par5N/IV2g֦RKxFN)3BQ:="Ke.LHGaJJH¤ICGUf1h:`%烟_MPJZ5KgydzmWڲ&Ulֺ-.|Cz\IJ̘^pek>lr:gqLd-%\vO$ܠC|e6>:UEា/N @>cK#Ot0[?{4CKWE0v,sg^zolD8}=Otޟ%P0zo3t>?Hp,/f7 \`V }a'B}_&v^@|Ij#9ȽkL`k3< q3wqjW8_{xUޚz>e1K=%bj|/TѾ2_\"r;6x]OsmUv8q9R?Q AtEri^K.LZg5]Υ7- 9r+U7.'_ǫGvn朱H=/:FitNLn:{_S͘~2g8/EmytG&]; =pE SoO9w>0]EDOֻ窬]75?kWy@6:~!bov  x` _f/ 7wu@-FC0|V@ HORH,}#lIGhmoG[pO<i7=2_/[bio|k  ;߻ Fñc9ƫOMNjau?Hg|27z fnŧ)"2f:օjIw6R򍢚y]rlzXQ^o'9&t@۞ot"ӰPpqeK ( f#nJ_,*iF ]$lY,&DrG@0CBfmkkExֽh[Z}vaLVqs+Ss24#] ^z`XB>wow7=Lr_a~0~?;b<a{_i%yϫ}מW>E!βn>ۯMs[M2wj0ۓF]_>K? i]XԩifFesz}j~Q_vfovI 1 @wV Uy*Wٻ`3tS߮h?{Wv(QZ}yQ&55V9gIp׀/5)Ck#zz~ۓ,JxppIw@@|W8+&gB@mET٨p]~7, BT}MeM`^.`@|{+(pЬRE=29 :>gڂd', aoGg%%΂~ w6).yq'D^go{ ˾ךȆ*w?c{٬~[ %§UJGnv,ߠzYi6jăػޫMLC4j'ַ/U_veo%q{͢(jzxX (9% b8@h E`Y6ZvK hi@T.j)Z<13!cHaǿgt>z96KH8uUvMN>V3>PMWMHf7͏r>$c:W;|y~}$a{U({9V_Oyy#"VbSܙ^e^r}NMUG.5Iqs(mUn{}X8u1FQm~{h.Vճi^Y4Ί,Nm\}W$FvUwf:bMUוM޹(v)r[Wڟ.(m0bEc{)3RFRvb]Uur,&v_sҒ.u럪V̟qS RJYPd<?'6rX6#`B>)do29<# ;imdt8WJwڑ/g7x?+NsIϛ׿hYW©cJwrG*@4 껸x[` g E8 B<<::Kx>'o<@p h@j`(g Ei&Z# 6QihET9RTbx}R;%6OL?4 (BOEy"l3"@-ӝw`Yl fZ~ lo:(A=ٝp7ȅeK},cZ^\1ݑ`}0^ xm6.aͫ ҫ,?ߝ6UeÒ!Eڷ$Θw>Ƭi$4ZW;֧aw"e9CU%@}}?5 }lol(1>Quu^֋lF,ԡ$Zj!w@(BT_ I@AM0Mo^DS$B۵lnVTS}1-vtdK>q5 <"yq}s[/rsM\$ݎ%Y8I4# $>JGXqmQY7^ĎoCEfW:IݍR1K+5O8Q~2ӏ*] q+1/4ݗ0K3o EܬGqwNbXutfcɍf$7;u^- Wrأl]e_S]h?⚓L{ fk6^zw.^Ղ_͖v .޷Ş^G $ _ԉl1Z8kL^j+KKόTk F~#hs5 P2%ޯwX e5A+xG;=Ye_y8+IlZIkİJ& 8#eJ,R](F vi;_*Xmijq=X6"VQ}ƣȖŖwq>NbUKglSng۸ȭ%_tDF;}\, =Iw\ɴM6zHc%חπز{~wwm}PT--ukǡ y3.ؔf?uu1ӠfĿ?жGޒ?-c,U2( `X(;ϤE6.;uOڱ>ͪrNI.%w SYH_LՄ*Q \| 0 =im g.m9(#[tZd. pI ( 6}) g(G^(Rx;?@3~| @/(F(9uVmSǧ P%A$@_ <~S8tg_к()W@N2UgQ2r@E3w?PE" n*:~2AOcLJw?!Ŝ`Kbkb> [2*kZ\{f|}ݷ^jcyr&lyG\GG(Y/z@Pm0qXN&̹Up/k^^2}R!&%k:%DdHG)ՐjdjHnzƙ>; 4WIsxv]wVKcAw&2a-m4>eENM:+x:f? ;fu~3*_eV6Y7@`eI3k]]:M}ߊ>PzBPInVϒr{Fėh3dFb_Ϳt"h/Qo=nߴpӐrݎK{|z6cC:x<7=?bnBZqZiQ'Jb<. f>]ʆgnڙ2FEG ڲ}u7˓N+|X V`c~)ܪ_AVW$uxMtd pC뿲 JX> 9!2-2Vp ]A) p  H @\R:##h: Ⱥ;C9^5 ?L70s=&Ցze4 3')dͯB-.?݇k3nI]|?_/Ʀ؛V9u[]A=)~D*.n/>73x 9F[Py܀+P*\%|} `ÿ]<4WV$*޻rngJvͳ>P~3Ì?٧MG6qqSSy?Q;?4iXg`M}RB3C~p~#Es7g/dŌl G/}CvN3Y",zOl4=ߥ)MH5%F,%Mszxg ^xͩ٪߰½JKm+/@頚֥Ft؋]*/دR5K~nc`RWE!^@{w\}'y \.e䕉O/.^1_k- @(h)W }|!EwwjYdrkϷ~kN`~~}GnZ{na"WnڞnUL[ޗ67)i΅4Uqd CFL}yy K7( |KZ4#I * 4u:Oa(Sw@6*8@8t hݪ64=-l!: >d  ϴ k[e'W#%'E#1 #&bõCh `Fǘ63avYk=/?T`J?i/wP=}-j[b]L{gmd~;7ۖxo-݌&o< U:ʑZ#'m/֒_(s9-܊ uKncI hF6L/ިHHU?n'ZE+? Jv,F<^aWX?|c-g *+oorGGe<sۯ4kxkMlb|ʥ=|sk`&ޛz(^+P+B2'F05|arwC#ń}+g5?y_N+Cy\kdCbev%)ŹuuH\<rrkcli;i</f&+q]gEaׇ|읍"5ȋ̬K_D5\k(~)?ABE5$W@BfcـB~t nqs>I8]?ZA+}U M[7ԠxbMH{k@HH~54$,öK "I1hC/QH-Z֒HZ2q{%pvCytȠOuPpΰ#N@}j@ g.>Q,њ#[^E,RW=2p2=W $l(4i@!ģZY|יx8~I?&F ުqNDFu(265Qo<ς?cHYk|8ܵ9yxME|<8q>=qS[S`m潥^-\K=pyrP%ۇ(,}yEdw늳 WPK4%dTq֑T XAy̿r/\0=J,+N6Qy\kw5ATsh&V&g܍N"8MNjmE4bI Nmr1aFG#*]}]ǓEmr^<Y^6,Pi9IJ24]pw2(m j_ۃ~ۮ嬁oko>F@Kx/Ʀw=d-k\)J7c%}(^`6hJyo&i`WKsx%pF,-c1M"rIXEL"Q`M\R'ZV* &vSތ^ij?weʧI[x S{.LnL_~X/?8_@OggSLm`do˞)?ߜ5I~K0IA*䣿!t;Q8 pCz"Np\SJ.oQ]@f6hxNԎ;*E lq>D pmq"&8T:s/~0,=U/)j71VRr&orǁV ?déPfuA  @*5Ux<\vc㳹Q؆H?y }* Py:$ @Ii7L8i!.v/S< v0u|Q[181iSz*e".߰Ml0>Sv_NvƧ/"˝`Y%_ٝ_c% 3[_UFkR/!jɪ cUdBk7:9nQ{Jy1%^pA_Ϲ|1n}l*m;kZEKRy]؆8L8N1[oy.6%ru)#dwՌ$c“k %Ify0˾".{#3YVa?祳Vhm(th‰Mzq[X A[v  Է,ΥrjX~Rs<9|&gٔ e[r:o Ee l2ZNmpS޲25Ywid>\P'=#IiyEe%1 myȿ,4eq]RD߯QbxW wƼv׾;fl֍3NU^:-]j*ޟZ*1yEl8I-A uH9<8m%O|3!{;Z5y@ǀ]>uCH!0s ٯ}[ҍp/j 㚾nE]OwX ,MߊT<ҧR+ZBTƗUI [Ahh4< 5Y~:D8; j VD>E,Jh >4(;ݠ H@9y~Eɞw,K-LTՇ P9ʱ-z`8@*Ԋs9TU΢R5J**:o"ZkL@3W) @ fſwr噯7N@X¨WZ4H<'Ku+ zHe`jD9к(ߐ(^5 ᴹݧ|PܚAlLk=i s 1 ^u 3tbNcօgJI(/,?SMzMSδsl>3RFw(9m03,;?t nሾ,Rv_\K}.)Tf}|MlR`][)Ǔ?i:WiZq·pϊʰϏ?qbS??'&e~Uݨg;|\dp( 6~eT$>~p,BkBOP:.v =]PVoM7yAqLjþ+ØS\p&}+ڴ7 5޹Z O~M۾MSOgTF qp޹@w&~ڸy(dbIGkNjü8]vjf?q*_~`{  ~In~8@*$NBqn>Gh:|u(biw7wEI7djt>x}g@1S\#d/CAMѣ@**iǽG%b}٫щu5+!-QEAT]d׋Zk$ -0<K] C4 pU qrtL47y%UrPei:_!kʠkN}n0tWr|G}igr(s%7Ի4=_Syʴ_ᐳڜvw5hy Kiݝb:? C (J~[k>_8K؅V|ubF/}fW>?)ΆݱkF):U܀ٌeLg _p:F&OK#oX+sL#e뭱|3s>9CBo\ `ݯYzx~/žxO>#r_Ȕ;mo]xv)a)rJrOlhqsgfᬸ׆Ž.jͭTب۫z N+]4IK3{꜂`MZ{:<dz̵Idk }~WI§̃A$GvOSR 8Qȇf4-0!I&dKt?c]Bnp&al; Bzy8$8Rn7ϫ-~J 1O]>-6ݥI$VS X̺p^g|{)^;r.0 .˱wr`ӂK TI~ u\mj|J_fC/Zσ{`A7u}B~kS>3覼EO"gK]h\1nx~ JS8ХԦy Z!{%A/{n*, <~ UxH` M+=2+C4 d!2d ):{H1ڇN֡&h-P:NgJೠ54&&h)3Etơ9k$N zQ+G)u~Q-)g1'b-7em|x)W"FI/w?Nƞ]ZHqo K=f:.pUicU-]344^qME{Um~xf>7Ń-p{vhW$_6?4z>5zkq@?7c*u롭b8fyӻ,u]ZLb볢j$͟}Qf^V:wi(X_, d-Ye^?J|6)=ߘP?MK֑X]C3ܐg'C\mkQ}$ [M"gF=H.qV/WX ƣJ׬M@* PT] !S[$+d%cQӴ#_N u=( @ɍQJ; 85TdAad2ܵ^bb^ԣ̤]VGIFԢSήEktld|BQATNDSh5O%x{" :ni_{ϞbEG4g'!{>}>i9Yx-1=(={ۉe\A ]*oPGThή2 #2ꗡ/jp|d{e$n?^E6I-hNA2gRuuG{_jCO5g.\>v]KРo<>\0+&_W9TFcFI-ؕNiOwIE*Kka!"-kQڂ9c{l#jޫon/vK?9΅IҺ+RcB}n OQD#t3ެ~]e< &W}^)l?0\a:>on>}yX{| .GiN;DA"?V+'i;,NlOK%b8q,w7{uJ@}|nڴ{n!OK7>Y d2sk*M8mOGqH_L=$F&]I}-ey\53ڦc? SJi9S֦$aICOggSLm`  Y<ͱ>?jR Bh|ܡk 6~W4'7ȖQʰؿdJ _*ZuJM_&iF3eVn}kEdV@jׁߖ6|KJN@zWPE,߂Sdfd`R 4VGh&h> [&ܳV*Pyu}BA8Nz/=^IM8' yՊNh$d rTx,}lc92:)qW"?SG'<@E%I%pUV۩n}yQݽZ{Gr͏tv Cm:(Jʤ#]J6{j=mya=3RjZuy>x!cS7WkNYL@1j@7 EVF Iq,M϶SmSWSX ٭5F%z08.r)^^$1Ǽ] @VS%!iAtGN{h/"Y9{\ٳk'pJܴ5lHGo[:xj$s;ϓ[ꛚ}BN1Vw&ZM%̊4{5L |SRr:Qً{XrjH|wc6SΓ6~T W2"FsxqaKt*nGSj_jyOW#nSȟW5-n LO567a;GG 4Ԟjyt(,\$(7}Or]6Qn1_1,x)}1|+S /Ġ$B,zagbUJ[lKM}8qg鰸MFy 3n-7-Ӄ.GUuȭ*Z|r&Ȼ| <.N%"'9| n>B.=E [lbcJ4K-o+dO} W^EO*Nw8: a(z }ʱ}27_FtTb\OՏ1Gn'ȟJr{@Zkũ p@ 8T*ddYىY Cڨzg^J>%k^@S}vkgk:$PJwl}ߗHz( ZSABbX:'GBkz{¼ﵤkҗ|2: ׅgae tk/&x*>͞18Dy=AIQ$6nelB޼_爑F(nrf|aAs\<~σw?zf+5OU{ZE*"@9sBMsG 3YOdR\Gf] &'M%,f(n3鋹N 5gmSC5^Q xS7Coܑ6[ϏvJpu=Oy*^`V}頳(Z}&d7G/]J7;KvMQD0Ǽ}{52m4$y,֜QmYg1Ҥu׌U\<5&/-V-kWEE&6'ReuZׯUWiB @\+KK ґxhQ[/K&;"OaF\NlpV{;Qx>yJ|_t*\hXVZrFvDu@ׂ*}* Uƍu¤5uOD؇Hm%[qVySC3zF:vIvx f.jYA(pHwz UԹfEyh\>!kV tR멵vEBUH2|J+u A|,2W[{p۽.wGVhdI1n @adhn1>/@ *bggD녵?*4ġw'(/ȕVúkZFkcVgN[obn\Þ!:ޤ 'SӚ}c>ݪտ|۲j&+'g'&jzH8.U(j/2n]6vmi>y?[׉~.J0[ G{Ns_U}|L9ŽV:8:p/q8ߕ`ݑw P[Թ&+\ ]፯{@YIKXQᡧ?` x"t/>)K5e>Tn8q냏evwMso*ݩϿ|(j?ۉQjs,l@^j"{@ψ +<:w 3rCo\)Z߱{-g<9IuN}-ju_&ey"ϧ';fD=@nU$UbN-\:|Gg}HU+>_&Kg&0d|Ou?*0Q yVB"E:dEeG*wdRA'2ϚUZƳtRC.Nvuok2F<Kuo7RsZ'|-;T<*j@g痝lk$` <ܰDHSk}CrJ0aĽ)s/D_"Q풁?ǝ8_ジ2]_N aL䢻#IF2c|+1ܞo1wn̤^ϷPf^pSrLou)zWwOĚW{>tQƵYrtߴ\$5 }77SFbpЖ2'SNuЅk({ic֯ɩ jzͩY(;㝻lv_`uUN-Camz /0vl.wvzPxۚvcO zn7Ȋ; xp[W6_xc<Ց_g_wr\'٩|hgEs=TŠBtێljBVϬd]3' WٶzӘ^-9-"վk͉ aGYǙ Ɩp: K/pE7"ƑT^}nqEO+f1}Ac:旪/rE慚jJWUQPY|!?hGV7Ya5{G { }[i^?#VnË/dn~OM PϾ:c_cwcj-;/3vKY7=yլl<,jV%F{jP~ Ls)p}HѺCIidߵ˛c>3{ZSk*TY'g:sIUZe>g>KNjT=gZ9őԺRR*&'.UsINKQ9?ΐI(wFm9jPC謁>5g*]P+LQP+Amk)\ THjA;@dԙ(0?W)F,OypǮE!re Lbǯgg'yQjZ~U;lj UVyuOw϶ރ4{5мTJnQ@q i_x]g-g9"fݻ'Tl^ PpKIm7q[? I_$>Lw/xR*fT\g"{?'(jmF1ϽpӠ}qǦG+H{-W%g'RU'^f`G)K"`jIrHLunU{WQχq2%c[!@;J|4v[,~^slWZi89yuh?̺[ycdfe &.~R5MdY.`vAL^&7wmȮ[ OggS@Lm`ۈG@ޜFF-yW&yj֓nW|,D˾(Yn*G߻FTSf)/ >=j.7~Xh[-զϻ,|ħ'=X%yuHf;|J?uiJ,͞?ogY=3#o𜆿oyߘyk>~%{Jy/S;U Q$B DPi|UJcw辞7|ӎsEROP;j&ZXNeUJ=Og":Чd&`{x,L~\2ǗT)u1ѫn]oV_[7vyK{ڽ_sǻk!7OM93;s՗=޷ˑx:Oh2\3,; + ŻՕ{3Č%ςwPک2et}zd.=}T:9DPHbOXN=guyu{98JB:@h?n0 # tLt4JR/w]/>gj}fmpA_3;+LGj(Kqhޞ 5'GvYn.~km06hVyCf=C:}¦ziٲqGwKM=׳Tb\s pUIgT(p'JeFiORˠX !F*b3v<{tvino㻣OCix?n R" TFVT΃,k ˹KmQgpM\{_肄\0tgvEVZv{%vyi\p"Wڜ}i>{q8+%/tk9ػ0:w}߹3~yOwi⛉&l=9 5cg6~p`׌St9?OsY 8z0,K^V4~ד:0؇Ne) F4V%yllnVs~qxQ ^ǯvS~dz~.su31?z=/}l ׺vڹ]Yn9:Vx8ϫ>POfc"񵣚*֐j%\$3KŽ]L*wl/݇x :&ᙾTkeq}ϫg1ogz9f+>ʓЂ R7.i{Wxuץל/I%1:>sUo\1%f~j⺎$tuSk#ϯqxH*=1eڇ.MjPUF?~Rrt쮪ڽ/*ܭk :~~$%F'pfuֱ嗫8w/_Ak{O'q%xfu3Q=VG;7k $4n5;ps]nf5 \/?_x#d^\oF7~\NW*>?\I Ceʌ';4kMC9hVpTUi=i'K }?^|C![#N!P›oyK^N3OEHV<+33JK腳^!VuyNJEU8Cq@%r^hI|nϙ*vM@]ihz@ЬEBm]S9 ITǨ ;g&ukˉ5/JtMRtSJlӷs;ǃ{ןLy+/nuӽ?k VvV䔊ǽJPgH⨃dݸ8Yխ~@^BGw6:-#XR44\|xEz!(S=YAR?i8v83{}>g: ~G*UR*k&y5ϋ=J%Up2B펜tPp8T8^I~pnT)wL?ǜ3ڋp6@xC(-ue%pKUib?fe_XI? }&#]C0sȴgpn~ema%;x%Vy(jm͚QyM-?aOmr-~搛l]Aɪ[vǗ79L[Gi W^kɰUp/#Hk;e+\8:򘪀IK+ε&{|}Xo4'"aq ^쯫~կew_x>d߂ k9J Pj=CrFK C>mG3t'-rt]ϭ凖 'hPza-+p cr / .T=})nxƅ=Z![aOɮj<=>S;}q54w#kp:_ѝeŮعx=kmK3dv/LgքWێ#?84"ܯ$*jVS%ƨsbzyf>8#bm5b}Z|>xΐ'7%?(OggS@Lm`\ǯ ;G*e1q_ӓBϏ#Fˢ: 0z ϩ.:".ɿHDr]U;ck8)qKojhnM5H9\n#@_s}4hZA?|W['.(:qzgj<똹YsoJ9b;@ t.P@P@ũмA C{Y]?ÑxݕJ ([?J EzA qh*E+P!jr(8] ./gJV`'RbVJō4 Up.RWbCG#4 dnD)* Cт>drOOVʐz}9}х HH8mjbfoF0TeHnqp6{NƞlVƊ^۹.o@@eY+uZP!ֽ4;Es7OOo:3 W; r%2"AɆ2kOZhD ,_}Or$ᴐ읕^CZx)S=iaR*T}~mܬc;C=9ӷ]#k6>'*_Йyݧʝ+Abjّԙݳ/u_yA]tv4/Lo\+dnM)$5uNqL$_\K2*6 ?&{* q`+KX'}UUo7N:K++zݥ]oڥeR=w[~^\?̾ѿΦBNfGΟ>oʓujnʍ$|67&TLeixW%AnKn?׷x)Y*t磯`(^t>9lʯ[P3)-W*#6\NR=Plh~<=2w:Ɠ}?ln}8 ՞)QbfO+=T>-Dpk~/ەKKe*`; Yq󉗬;&VcpZ ZhًV*@@ZJV~9dW# `÷IVTz Ez`K{>ЋҨ>z]/}qZPNJW `@HP:$թ@;{ꄔM(Jz J 6W-*) C;Ԉ=Twshn }$ky,hV]Pf]#w]ަ3{,?pS8[l\)O=>HɃ qֆ ej}r8K;Gh)~i)^2uywsvl{T}5&RGY/ߵyS 3<Ņ]uJ@)>`CRЦ.i r|{>8,(0>dr}D 2=y?)\@/us|K$s4G5&sjk@N^oô|pUoG?zLA]%-.?5f/ zK⏪|Mƶ }دAeU Bu`~R4ɱ>ERjùh(k(tG%Zo7|Te2g[eeUYR}'_ SpCoZj\hX;M7z}d3 ߃-&KKiׁS!ı] >撰u#|:a`\}Ux跑vrƐqDnA3vSN[Pr/:Wh wPPM}C-r=(^a6Z=CW6j(и{q6oo z g:۔Z$)w@P#51tdSIAftLĿĆ(Q(LEDXY̳1<|6TͦFԽ+փ$q27˴ܝ@k$4uy$?4p)fE9)[tU)?ў ECM`!Md*&{Qm1his)g=yd\hFpd> ;66[W.Ss ڸxrHJ?F$BƤ1$Z=tslo5>_~7MvESO$"?y H&IRg(A`5 *kڱ(Sj~;{M]~;`ohG=;uWTsf] ;yTJPZ Uq<k;-V>JA)9f=QcGCMMݞ>]7qظa{VkEhQMv!)U&*鿻%5+It~ Wsc_ +4- 73)%}Z ? ).跖6:;omdZ>){qH>t/D&4vK/3gzΜYjuOqDWCV"Ii4'_}ƫoxmE};xϐ՚(v\ {n6Ov7㸲8~I<2ϴF wPKi2%ANhoD_z]*c^Yh-yO>;&g\|Ϟu/7Zi:?Vfn:yxJ" G-սK PQ 3AihAS%!^[t9 ¯*Pգ kWj#׮@D?{dT :5P@RT&щԹR#9xsZ/$+RvF?S[ 5ב T-Sz{ ᳯ,s*iMHTN=PPDqHdBoUz=HJNN}n3hmlO55 ih.nboTf{JbVHjy3@hOIU=!5cXpuۚҳwͅIldgf8AwKn@K|aMp^>$.7Z|)7e;L&"b#(Yy˫U)^yڼstu0 ~pD-});_/qi5q U_0Ixҍսʃ=v rFtP(P( )u'c*dtG֑|Xwdd N%) @/~G*0/vY ~Lڟ4bT>;׊ Js'?xWe+Jg:|1_٪GZv?XL2W=gwC՛(ʙv7`phcIzǽ;wwXae äDZ0$|W%) BgBjm "*᧸!b8I6yY8G'#^,BqI_*bتksk$Fl ț R8?)bw5TϷ"4o47vGM~'o\^Jp#ʀ ijko*i5s\/( Аަ3~8 EGNev fϙ4X>>09t3DتIo(;গ*ӟ+./U M5bՖ؎ii ?jQ2]|m+p"y% _^lC'=`Cs7}_{-#^ 5]*OggS@-Lm`|Jdsx?@h2N̽Ӿw5.cB?EN@ub? ~cs5/#> } I䇭?"JXwR4 ;nQ?D{L]'g&z@T@*J.^ $8 O#O|^jn` ql%"x*%S; x:4 p}Q),ghɆA-|c_J@Z<詎#RHvm)_Ӓ=oQ8P>tӪayaf~w܂Z roRR)ZĨ:c;?KG0eUaϩ1kM*tWVbP'T{kL-W pNH xՓ4K\j<Jj"F@^@s4GNw(Z(JYHD5PwQSRL[S$G Sk Q}HrT -=CMO@RBtw"67bKҒvDذpcv,>g]'r'&KBFOӒn_!K֌ = }ߝSjE2Oĸ~_SZd蹻e8/'mn}wou_]c_3q&l_L\ѲܶIs.JG5Pk-P6k˝"Y?XILgoVZ.Utiw.vF5k|}6juC$Ԟ$jUtS{>X>"&a<NN}pfW~V:ͤ*x5-o,p\yLzހ76ؖo9Q߽y&b[iωuxHܱ,]$`MxJ]T< Rxإ^zMZ{ծhZ:|EgB5+ٹ@!IS[QZw} '~RS赒JuxP;D.翟帏@KG`r^?>R(:QҨE_\ [q%V>RU(8MPQT[Ct5 T ڵ/{ _?Dg޿ڵ܊Z(ϩ/rT@V@`E?byJz}I1]} MIDL4ZS':;1'A-80WѠ!*=~NeOD%-h4ˉlrIj7Qׇ -2rIIZw#uLOOR0.ʽ'k,xYQk/A&ge[w.'%j$M6 >n>Xh'X|)nM1dž ,aj004.pգ[M#nW,SoI4OW8RYW{ESsT]`}{ T'kܼ1re,βk+va&x/9 o6鞏jڦŕ&ʊeKĉm q⥼[r%tbSg8f?/v/F}CÌf=A^{/:`u\tcxEs<6(k%ѯ7\GJS̥s}+VWb{ {4Eݜg1Cj%NㅧM6M߆1"~{5'W` W%ԐDoV\L6Ǘt?hr \uSk|8N=~ /F_R{NEÝ"f~73j+oxumf:կ[nU5Y}8TCKһ;]ظKٯi?wNz_5^:|*!PA$T@xgI&[ W*GU1XAGbfk#qy.1fvg17UbpMb|}lCGD*'ZW7D806ǜf}uU_V+rҭDA+TP(uBPqD]?vQ BܝJѳd栯}*P"͜+jP\wD*]]ߑW34]ٙۿ?/OݵGZHtp6T:yըдS!TCvu&Z#֐ZY&IGtdӕ$NvOZԤ@@\VV@eB$zĎ01<$=[1% UR{6iY=*R ,ӆV2aL;\ye)m`;u'}(եs6X_ٺN~MwԾ_w:;/I_-vw+ #iΒZ79p^c3 YLZ~J,o Ik#Q/ d%U]pAR &fIx[Kc]c)#ݨYÇĝ(PwL\6FlUC(2#{nHݥmC˯~u>'UfB%R4/EW4(_Y~8sъ1@FgO5{GSjp6HI?i1r[jTK8ۏ~8fpt]s\t ۟+7 87 B'慃#@cAJu9P4ha;>WH8%"olh|e: M4Wg7yKPL/:J)5fu迧SPZ#$|@RF#gk2@l^ *:N@P?|}O7NH #)*!Z+ηRcݫ(8IKT-jj".N3P%<Ht(w*кw%# ]^fa*ٸ! (dNI~nc9g_o3:mN+8|3vH|!m'7M5SCӘMܸ_> C:73nE/Tb 4EF+vanT+>m1RFKkʅś] oYnЇV[ %|F6P ||73c\n=*Q+Z<ʠZR /݌r'{Y9.XA_N]Sٿ+_x|G #Mi(s>|R>1Dž4ڕL>(&imwW%Wv{1mx~xͣf3>k]χч\KJlfUPN q`?CM|]F[#ͩRnw0Q#yJ*HۗvC}}Q7{a=I&aPvEVG-qȸ¾~|>;|zJR[cdxgXp~D^{[%-TT>,Qs$8\#t166 6Q*%i{ /jQqzZvIY/e&OggS@ALm`WY(BKM.W&jq ĈBލ61=K7wN5kٿ후Z'ۺF톬FFިPYv^/jy@UV>[pd; G| *EtSϧjV tC"8)kazϣ0&/jY h=QklKpPh&R52}̑tр B3猫j}>wýOͤO HB/i9̩'#ۛ? X{ų~סny8NՊS_d&e0U=oxN]UrGetFAZYC+۟ qkyM}̅[pjZKSp7= "ӕ2D$E2Eoh2n_^ɳ]Iz ; rDj*LD'F5P`YokTBR!Q(gp m4U;dMk56#RG$4(y~ G+IB9b֮T}L*I S8ι}G\}]g_W qJ\6H,kYvn7x:_|R j;a`i>pn+Ri37PDZ-^|F`[vC)kh';k7umEa XO9^ D2jȮE\]X{O;s JAbܵ6E-R 'X{Z?gy=X[1"B]#A]eq"˚BSZ~V_5N[Yk2.Lol *Zg$f/,T+u~r<( 99-iB.c77Jd~Ǘyȋ2rܿB5"sH|I.4[VvPYQrԀcγDoDB&Oq0i8i&C0hŇy ]Yrԏdk@Q{x2;N𙖇JtMywV|9/;N:׼S7U'=ˣ`qr >1g@w}hgwl 82࿭aYgYeNbpYyW5kBӌ=a?܋rN]\QoQǕﺉ 﫫T@8RO,EePozYP/80٫ TZ<ι/c?O` <Oy'٣@ɛE@瀦r 'z6g0"DN!т00''O~n&'?ٔe?ᔺr^h58L|WRj-1ah u{Ґu .Tw]^9S {ؕ}?GD?AäלM}.nHn ӳ Ra֔ {ΣLu0ίݝgho:۞Z*q[*ذjM>v?l{l|mF-5% :T1oj ^j|п@|g@Gʿ#v*2zCU/ًHDAVP {@D2KӮĬw2̵%dk4nς\8mOFhȩ[.ۨES@{WpWf'Ěw͎xӪ( k#s0zmyLbm7 cl#sbӬ?|XXj 䧖]n.qRݪ =m?mJzc0k%vӔ2zUX3yړmOD{'at+K'ǏYS ߵfxVp8GaP()gG\ zK\/50DuyPlNw/OIE}5jbw%8-6v^wݴRDڐ֯+Jdq$IO[~qdn1Vo9:({-VJgUDUGf%5o[t2YH" nCg-Ǽ_F]bۗ >:{_z}s3Ƚ=ǿ&o7n/0P鍊ovp61 1ԡ\WIY|!2M2w+Am1ԧfq醬JZ}.oM:Ƿ͝pg]խ~sR{;/*SP= NThb{I4~~4Uh54 4'(F0LuUCnѧEϸt%NJE-Ӳg֡MQC,Z3G4JhFs=둾>Ms-`-ZPi'pf; WW|nw|K>ME\/2+VuY%AIOu'0eTyTB2T.*L$H-fWG67vDI-[Fha5驮#ZpDL ~IElT:HƭO)c,"aO&y٪%޿+BUjtQf1~ [.:"wk< /%'`+hOVR|χt%\iJ*?hͯ֜5bRUrZ/ס7z|TMc{paJh/֑ILL/7ӭnkVh357oMFt /y>A )Kv+ 0D޺1izڻ9ݟ.ҫ y\' g]ޒ0f2΄eήFq5-ϳnS]!v5b$lu2A|REP \,[CBuq~@$Ud:ݮou|gEW6I=WZ+}L gO KsΈ޶쩵(cZC4b=jS5p|G߽9.*Ə D\3 Z9jẋtL5Hȱ= NtC0|9Nwi *bRHwBCS̋-)KqV]}.QdC+Yz-HԈ~2+DA8Sӑ% r=q(pZJ.Dtc E@^j9Bu/РZE1uzIBAp (&yTm+t|U2jH7XX}P3e0{~oեА7<1dZB͵§G6DԆsk5j(G. yz~FїKzڟ'(W[,w篛uzJy ,J-i |e,t uH}ՕxJ< fŕ!Ѽ[O05};Uƈ#\Nbo+8\vLj|Asϧ$zSZ8N|N_?c_7`Uvq]Pt6b>!l},)PV}Ph$|.y:oCD,1mSDwdT JdXQu+>]Y}B >Vz];OBݔIUv w#vi*q%qؚtgs/w%Ҳ?ˋwp6%cIKEF\WOO4'겹p[bUu>>f}5rHFJ5:U 3hZ[UIZӉep_>d2)Lt]u5+Q+K?}3c1%(`]Ux̔+]ίsY m/\h- v9k_շVU;s^=u黧^[arY 6듗< >/nR[AWUgϐ!ƪphJMQ)D>zW;S$B8=mVP|WMQU#A;Cw(G#J?MZ/KK칥W3wjo[ >;7(0 &O tZrChy{l.>cBwfZi!tҧhB2<_JM>oEӮ'rm(|Euʹ׊ `ܺmm<[][еw7exmtZԳ):XZ_FGoqe6ڡAU7n GzZG/lf QMqh3GWILr 3z^ySfw~K -Upo:T scn&tV~N x)k!mCt֦siM[S ʍzuJ6x*;z掩DNɜ 9(CďjjL ?L ouy}L^פCDpؾWЯ¯* /MFmT吣3{kRХ&߶7깱hBnt/?1Uq:ypiU‘}8k:\ #C-pzJ"V~Gqz0V*0AHiUXk=Iڮ< Hp"j**͎ Wv'UZe=L6N4Ul9nʽ|ym az=*^]??1î]Fjk~7,% g'$.Q>R,˛F'pg}KLj in(l *4oE*cgU7^IFꋨCk(|\ce"6ɲ=w-_=& jv}yIA붿{}[U5zԻC2w$::~P3wfPP3^]^;~Iu:C>hZ+ P߹n²?QOWϼ5`'>dNu j9l8x8٢^H&ỌMM"Q-'  E^\pI k\g0W7-A& dr~*3 \_zcz J5t6Fgő[:^{8Y(dE]}< ȶ&U cI:~KB{Y(xxh-r]|&qAkMm$sZ$R_F;>"P߂G\ kBթ )}cϬoaΥA-q>_{I"8k=Cu6Bz\1 ['N5ӓN#Q:JZ%SP*Ah/rޫ ]$%@SIPs-OG],q왨ZNxW'㢩T9?6M,(48Ȉ١KׂA[c/H)kDm^* H"VPbz] O2 2Nճ#5K>=̥yB3MLS~NsL]Â~-Osw@$2Ű~ݲz2νGt\r BJ |̀9n2_ޝfjbjY@ҭ*{-pR*xVmȓexyA-LZϼˆyI4ݣQѣeKPtcUݚ`޿<`?,{mUix^yN qϳoFS;woRW:!yrZ(܁(w;_ߴ6e] ~ɽYD's܃[{5<_Y[&;On -ɇ; |w!KQnF+hĭGTfFf"_[V6:.*F2. LD?ȇlSG*7JK\?~ֺRZk;gfx6l?^t 9Œ== 5WΏ_|b w HH}3{1YH$mNץ~nx=spLAd׊X# oJ֟hWȃ( @T BAj%Po4{184kB!mx:S]X~YQSg'di;!$dZSQσ;ktבt^?jȂYDTi/G&DN[G|iA$!Ijt&i:I]4aY9+n"]H'*bQaZɣ:'U;f'?r ȲfB'Af WRS$r;TpkYUrȮA59cJsV"┹z|^EhD@7MQ(C G-L *^b،SwǦIA~z!O- q&vk&].PZ.74bذc3͛mL*\x{<-?z_`w4q,Eϟ;Bܹ[gS)9*0E&sTꝧ,$/^Z7nȆ.ˇ0yUƸ6щS{o4c`0 Ou([z'isO{ւN⟝FG}ǟ,u{#i/jPx:aKRw&oP\O'_ ۿuf-ܓМ0\axX\+XOu[7Ŋ$ fzMsR֫q;7IYdԽ5&xI\cf]Ghw)- /"myM5ExK.w |G 1 ?&f5FU|D L zqRhfq;t¯qA6(L "^Zo^jt~x5QX8Fo:k_x&d7F.UGP6Vqp+ZPW6+Q|  _f%<.m}>-''q/QuQhƉfWK~ao3? 6gL%,``jwY=bx}-j򷔩ؼ*'D_w:8WL>ؾ?~Z0;TN׊ƙl QS"nQ't]]kȅ:o$z/+ɧIʺ9[7][f9<{ܯz9>|cոPjrܽQ}~Ze~9ܒA@ͤTU 7ĊD]vO z/wWlVh \/]rPKZsYfuyF>ДSC|SQ:{f=c\C]L kMsMgQ{[iuX{M6ѝE "3@84^SC :\Ht 4B5ڔ/}B0ծQe͢R])"Z;\QQMT"FԤ6GѨI.VfHރ= ʴWq~%e뗶/KRSw!mzRw{7٫]k(m ;wy*3WhD#',M )鹄jw6UǭV]YGMt|XM}WGU~2OCs}}ax+aOa-<:a, s%:=dfW!~oUۍ7k$U}Fmו䱽Hd%mjv=ϯGE8Zs/1ZڭsgܶwmNAvzQ9x\l0B^Fvq&?([]/ iZ;(9E2+ab`zu' fz0Lxp1o#e=MpP>WPIP3fO/FL߰.>iӴaǮ d,=K3<yp1B N>u^!%\Z-z=4D`n*zk7M K2M3U)P茒BQ;ZkYq2D0Yqڃ!ԑS{j DF җ ̺IJzJ.&YluTɔYΞt9hӣǑ]/Jwg4}Qگ )Kfx΁{Rd*z9:]V);#SDZWDWUC%7! UG&.K$R2ɀMSZB+>Qu!rf%>*Wӫp"'d~G_kO3w`Kk͞6TkW5cxLiwxrozpݸhuT2 wuֺz_=M^_LY.JasoG9_8b|,xӣ{K{m8rJ+.-I s"?̆ɠI<",6oZ?I>]n~6L^<o盟.4v!(jpfOs޹l `9B$x?P qmiVwRөc5>pzQЕqKS{1GMχ'D )u]k:~z PE-D _R9ģ}ڻ['ژ)k!䜆wW+ yXݾ+gā,Yxԉy`>KVՔSRy~"ʛ7Q2(|+=n $@)=Z!*!$6_NPAq9ZfժuLI]ZzuF@M."l݇%GE;Ok QDkYwh>VGrV!'(U(`yF }Þ/ 7UK֧ \  "XX_p}ˡ{TNT]KDŕΟ&Ƒϻ]XB{QrY'^>%oF/ӇKCsfɾqL*pIi%YXq9*k's"/$Wy\+GB'1=byOLTn%>$v;qY1t|(uQd*T6іLb!'׍OiUW5^\)mϗjmp~A `}FgMDDr O_ u[KA.G\:WqG }|&jCi)gev-8GxZ|e .rqyD~ cEm0OA><S }ܸվlɇqd_MK>h]n:FGj(أGss/u"yĚZ?1ϰeFg#c@a6t$RrҙSBxOm9֙9#so&Ӹ;b j rguS/sh-FK%߇Cn"&M}w:u[@ I@P3~ DO55+q>9Ժ@u&78V&;Bl]Əw%ynw]ɨ=4}gPD%Wpɏ6U(wmRwMsqjrVQ7P\Q2%^p Ki'2:q; xR&zGX$#"eg~E \ *w|X4dn0Y wI҈IEpWYokύla\c.2j1gQk )y!73uKCJ]3lFN%q x[gl;h'I]^'ݗea-Z>6ILTg"w=OR#y!{luXWDYxq.g0k}*4E"Z_yc켲S>*V>%BC}[ƪ~| еzd[J7I6|SaSR>r,8 _&>e[m\p/,zU, _?]0/ji{zP!V|gUN~5z]ިw2yinC@180.?Z__daA9~ROinh+9t -6@Y5sf<,_+qc)0hx}OggS@}Lm`"J ^Y* \3 >/ Z2~##[U#u :嫼M[2 1IZ۟DZ_th)1q圩SĎrǫ{T/o^HT~XTe-'ӣ9<%KzJ׬2zN,͞/E"}Tb:QN5k4ӼЄ5淗bI.̏gN 3R_ctP2ؚ 3QI  Ѕe)"U5VF[Qڏ\L⠤SKXMoÖqr ĺ/U5D% '>V ܱ> Z-* hzw[-zLǝ٪^y#ZuoħvjjMEȩ6%DĮHcQ|6or{JL^T1.N}3lvI{\) =*Q_Bg@ B6Mޘ{& NV`CmǮ^=5m-˱ iX>Nc4/~iՇ4?SP w3/f" ȯ_2+DlĞ{©I2 JKԚۙpVH`qpwke>^2 jzj{GhR|R 5R\fr= j1ӂ ҠѺ̳V4]5ٺQ8pF=;2_[%Eɩ&A 误i<3t@OLR8E6r'a IE:SE˓NMD=|Ǟ_lNNZ5$~C\rH*/,@9l/s?Zz.+X뒛jeo]gJW8JB+pm7;6~h|R]ۮ7Qw1x[gVVU9H.!mV_Y٬׬kr~/O%"j+)ÅUWN~6|j -_n*V3Ȅ ^8:JpEȉ"l1%&5\'4+ vڨe{T ;6G_^euXQThjmH\r,3p yKvtM.Ҿ2(`2\l~h arBo)W&ahL']Sdsʶ*3 }O+Zր+ w/0ǚ-׹?_a™l7nG-VZ_NW\'lPdS݀(&ni4dRYM !\$nU:AцtXÕhOQR5y1~[;b_v o\jV6 Jn%Y3Ec^Y* j?LGhkO\ύM}8T<1%T:LCQrdcJ C+dt־qYGmMG|SJ `X uK3(WjQhAo *4@E@YJjv0n"]lzOv@%`;OcI3Vs%(iH\,kuOt>vɹ6MzrT8 TUЌD2Z!O;*~Wj4Ӎz)ͩNT1iP\]>3춑M9Bʸ8 j.'fc~qTR|["#^3 iVVmޝMl<9xyī>'N47!q}tԳi;&XA#ؔ'ƙa [FڸS~d"9쒖+ N &vQ-!{k qqԊxvOjx?s4|#?|]_hEM_39U\|Pn> 7Rюsu-8V]鮈`=t糒7Yf U:IJ]Ci} $ 7"隕#h|&5#FLP ]@"(\&Jp Kp!tۦ!;mO!!wxgatVwbR^1aKҨ~+P{k(PS|_)2U^5'y?]ݯo =_& :0\uw(e8.F_*mq]GjI?O۾;{8i_F~f>ל6+ft:yی+>Y@K#1C`3J?ͱ_ϟh?]p᧷ -*( nfPu#'HΞqW vcOݽ'URi2D|x 9T{3߄H 2kN%=(^{EdHMAYF%,ֿsT @dԳlT9%#YzQU@^@<-yMP( \v>v)u*d֨TcNUP%=m%Z[^$eVyi<uKKL2DV'B=JYT9UkS5EJ$ХT'bE}Pע"hq-DŽ"_s52h]H )Xׂ@#USst=4^ -bmS3]# wS^3}C ڂ<3';) $L17m뱭j+zS)SZmbaR;O?4X0X*bCu'm}j81ݧ?Gɶt{eYN+{a3":,VCW~Qcn햝Kxd-E dy˾C'ng>:@WOʛ8W'?n9Lwg,{>XG$ 9Wř&oh2Vo{3]O+tA?O^qrmhoiKh =.tXX'׾$0\ Ɛ|:˾efsYӶ]w6Rv Ljxn[C8E/jaom,Z™ـĴ1OggS@Lm`#G|^isRU-y&-p#=QQv/xX,5+՗^ޱqeavn/Wc*=ƶZŬ[q'OyWk1WMYm_I]*='xf7-ŵq5Kς0'k>'ǫW:vJmN-ʙpFFfI9&&_2SQY>\r&_4'}6t "E7Gg ;[iuWdʡ h;]{+z u_*BA !#X_,otV 5N,N*B^Qc1>!z~N*\O+6Y% E}ᶐo=sٖ1OAdgvBukzoxj;k&73ZwD+HސE o8[*֢gUxM@-+9 9Q%2 M`D]VEj7G33w-rN䄐trq/ 鞽Ezz1tge8C2sٰB@w\ZLTQO7?]qZ^{E}ahn⣷J-9h,yt˧E`F>!k>987zw)vvwKfeoݑc@ls޺]agxpW ˏ s4YҺN)"h5pZ3JB8X9vÊ`8&#3]ΎZj]߾%|ǖ_K.q)3ʐ$JKXS\v.9\nnJ\~LMo|v6c=Wx# xgMx$&yUw~'z<{DтǘDjm+w=_hvҔ' k^!J7ߘ61]q~__cLCF y22]P^J&]Y=%99I<*3P+B}zÓTZ C DTʜ6gϾ)oB2Ih53{&Qp9s64}_u\f2*[M -#錹snஔw-iJ}ͩx|9І S;u\+"0YFͼJ1nfZ[˨\BPAij$SkTs73{ R(?h3SMlUDcO}H֘Hߥۡ!v]C$JkOtU=uw&=;=rf" ZYJ}DړCeGɖ2{L* JF*0l&#)~ pٱ}RTSg14=y?oU}6x=~)Bm4Z#CVB-0IcvDgDaҲe~5<͗&U݂-/ҺQyKf=iȂOia t87xDYm, ۠8k&vS/pHѯLue|RpϿS_ 9Cr/{#bwx?57oRT&(Ԡ^{8=EӤn)*]I=?eC4bIOP?smˁ>G{GrM==:tEq"OݕI* x̝ PR=+IGOAEںjV ^(qr-BLp7-9rTʟS/oUŧf.f ɰFwk{\Cy/$ݷrkN]j L̽E_nGǹX, sXW߽ZHeHQz5 =[%,x'ԽV꺟jFqZtVbr/N;Sڄ|5| BdYW-n[93//̫gTnm\9@ӽ"ut=÷ӳrο3zcz7H1Χ_SEd0`S06#*3gkߌ|yvВuX̛qxNkZ(DXYɷ\E'{Z'y4^O#3fO', CÓ״D?d&r_Ύc9ڭ Ѣ6g9T%Z&铓@O7GM%bJ'LW'&ps@9nmp]B$Ԫq<)4 AY+`y/f?S Tq(tzZOo5L'@\Ej-t!3"JT ]Ո,v+gd/0#QR-Y^z7[. 5>k=7И^OT؞;C~ܫZعק(XFOggS@Lm`$:~y DŽ89r`58_XZ;&s^-pǀ)WmdͶW*WLlߛЈ8CֳPiξ_Bb{9n}~'<`{D D #:B]}kZ[.?$JdP"<8ܛQmn-͹(} OM2{ 8MmV{['Q%)~Fͳ, -k`mLLRu'lXy|],/[u2K۹&Iyו4aq=ٛbJ=߹͗ӞNѦܗ z;\87 ѥ߷ fsk؁Q3S&}uT.iJ_"[n A_wϦ0W{d`}+`]ުL7-cElHUȴ;Q}kDaV)\YFJN@{lS%Ʃuj+Q=? Ȇ仼=qg{~onӁZ{~*U~Ni<}3K6;D֫=? N_qgW>J` F]Z'itp&6^,Aس(g14[eY^}I[r>Eo<<93y̠d׻դ<XABƉZ7X8Rٓw3lI!V^oP3B>WọcԱ:3Q>JJS3Q C(ڣģ%A]jLTQ[s4UOu[O |'shzӭmNT@&*NqTQjڪk:q*{zE$r0? ] qr]֏J!QȪH #i١YjVXj f%5)\E%fEs UiOtoDk9{lz՘燈J;_. <n"k9]I*$0l #?zNr#Z{3*/``Gw{Ksf.[MWh N-Ǯϟ?Ě7x&Gt[q ͐T>e*71cтXe]unitڹncR~_Dq9ʕN-5w̉?m߭l5w] F2͔i_Ffï(>NekYϑe3NUfuFV?꘯9fn_33 Ԫ)ZQ=1YU܏#o?6D*Sk~z"NKsѫVEg*BC<$0}-9&I{Vx_W+|XJzvl@ut:s3m@v).E}+%?o9c[9Ip|<[j?{GȘAڍOsa.ot9?^p'gXK-_rkl1(2l \+n=x|'Ӭ\_`:lԟMk9C~܂?P9 >ᮙ|t܊zoO8ܪ X,DET!m6J=5  C;c/G t[!s5{NaN"s8gT8KGM'RS螂"aynC^n"o~ϩRYDkA:'N)@~kVaRGO i(<^3K AZi!WNi+_ݫ0doQk~2Z* dDթErҲ(Bq?dewf?H6^Gw9N@6NqH.`,v3xξݱq$߿S8Wv=wd":Hn}<+n _cXwQeUŌFgV}\Mo:M zǛSk9rWۓnjX]u>SarOY-F}x,12kjA1}=;ox;ڏvN0~HC1˄8t4,eq51>rUta.RswݚMKEJ\-}S64]pw7kl hS\Ճަj{[^jvmb-RO옪{<>ad@&MbӑzHUjw6Ozv`B[~Eܞ1sW?6iyrO+ٿ 8I?ӄ7Ǟ؜}58k7F*sDzd{U\_{G/ >[?d½+:(ypsts2 W7J'\9} x809|Uǚ GS6mGz-鐙|:i/^Yޟ=/mmNWXmm^5ݯ>_Y|^)܂jI@v_ܿ빗|"7 ͏ `VQRYE` m4Aʴ^~cQAxAD?5`i=o"Z<*L={m_j3Ds{O@]&JCQH}M6pPT9y:nuRvu*TpZ)HNT&`WoHz֏%*ⰱ.ap, Aޡk]{ Oh#3εp29rj !)u棩8N"9~gn%4$;@["4YeMwk4ѓS$%S-,1zFUP~}ʪL]:"@%us\Mq{i{덑Z1gL<+U{ : BvO|>\TGJ=S.B"a w"8 "H3 W=T2^6W=٢yjC%w XŗNi+1R.~HΪՕo[+I\rr=~d>N}]wRpa'yŇɓƻY\O&kc`<֙K/),KΤwLPq6+5&VqJwx}CXl;3dݜr6k|]=>&u6=ݨI#]]01+/. 8L0$0~?7npiG烍q)dEҚG+VO !uUI VnGwWx{6WV^nƞJ޹CB.ju6%8ޓH!˷룋4B7\ėxtn#|MУU o }.3PncOVR?K; ?}r2̼m޹>Ncu\U  ͡ZI?P+>@\giibP+oBCJ)e= :վ$}'y?ˣRh}P U댠u.zڠkBM21J(0VAA/H/!>ZD"ZXGp1[v>k;TtR*Shvkׯ0_c?]y>N1Q;Cl">Qˋ;F !% zE &TvmUґ.ZcفS1:/,Q;qK#.;qa) ^ܤCUn(,:ܞy*|pY[=TS27P(0+<=ji6텆js'7%>Y]r{iوo% Ϛm/0|^!X, ZzPbe83KաQ`MΏWP6&}[ hW7]̀Zk\u9*' _cq[w1Gm(*W&F}RN!SVY먷iayOpߧ}_;鳷K;zƻqA{{Q-ļ~vHMg:,i7]a;}F(Y@}G<佲*C>^졔ĴZ96]oUގ3o5'>d_A _zt!.o5M诽_ Sݭ^YgN_~xDJV+cVvN%|zC #5!?&Laǟwo]\٥]v4;ْEhWSVNv0slUg-ޱIpmokZhlaRM{uj6 _>&U|PWe\o%2OggS@Lm`%b  п@AP+ / ~j#e-*$f@h4:`+֐76+PSe-qP! x8WZ5$xeD5mL_k$\Ԭ6w~dzκKe- a (4N:9u gL*5NBMsDeWF`0U(jꢵBTIW 䤒iYc氓Iׇ}чLmَ~DUN8&TYv4Ӹ8ѫ]9DV9 =5ő.3γ]lg"S瑏G ?~-ZT'rDI-!E@c{?ק&O+[~ s)n]yf\r_lؔ7o . 3r] `r >{ _ '}' j%[qkrd(޸ 2<=:k" \T GT SP\]`NW 4ԩIг jl͸Qή *kukkOVԡznxH4Ҏ&-dJ;1U`괮 9zH,>EN!OP,[/qJ8>t Q頫+ 4;_DMDb&::NZY#θu^J6vZ2YEHN$5 _C"GYB*3{QWmM$*jQo ]ZKX{r&dFw%tjt*W4$J@$M!Cϴ>Yboɡh 00,v6gt祌7e}h?FM):W*|_-BWt{JcNec\>(aqorn3`~=~kSu nJt/мv鷈{'Uycs=`Z"TW&'_ȋ@Ugq7\;+QaT@ 2a oOSjnoT_-j&[ݻooJW#&;)(_&d]TeD9g o 1obc8yc^?f~sן{) n7qm6Op B!qk-1EdSz\iu2z+>,9@8tU7'kaMIikL(3 -%ܚK 5iRW*RH͗Z=6ʤs59'ڇ*dX:c7riDIH 9D$Pԩs wp$=Eݡpu//*srv5*l2DtEnTpDMS`^:e||oӏp=~pT#Lgk ȤRDb`Yc_wL5OP"wM Y.xJBPT>?Ug#dNGZۓ aFVQ0tJbd;cg8+DV;Vτ]9rċ;mZႦ̯s@jĪ{|b;G~#ubͽ+ENR={爜>w[y5^;ُ"tT7B]\*\B^RŨiLlzw,a=eU"}iyd͹i~T~FFE36F; |PRovi@QE8bV[=~dϣyddPI&s]^B9T O,}G8k>Q$e, 癶wOE H۫WqgaNUI+CX k&dV} $lP?[-ow֟1΢i#% ?@}tj#[66eLD*JtC T^:3cqQ$%{ I'B7JrémSP% ]g|<__/NF֌\3$ʏi>J$6N _֖DA23 ԣk﫠( Gusv5rE4Z{Z˷iA~Ɨ(kPT޵'E)V'Eu~YQ8Dywr\C`ȑ4fRH ֪Įb?Gg;7&**^ px׏/Jn`_r{wr>Y d2ژZD;Ɋr庱S=!?'h7ΈT@k\L*j|^KsH*Hcӣ^™pY殅*K$EkM!9KD%@5(vqyعZk*YLC_(*N sV's&Ϙ«ڎK?rL I큺}|i痓GW~פeBDtU:hxQVv*$PY<_>x=#eޅDasSzK`t笝Y'<=ϙ*"R5FJ)͗ Qx>TvSя_u5<_M<=$$3cM5l$ $K&F*^ ֐߽IsQZ-,JQUuFKOnzIqcx;B׏M,'xSYbyp.^&$k9p7FkM>]QstrD"Dmea|];N|<́n^|&qӏJDV\@Nt\sNpI\?y$*0ȸ+X&_L^5G@ d3ӞvOɮf^)MK)sF-'Ew0;naU u{\O'h^n;x+VJ-s:Ɔa„J+:/^{ۣ΂ٛu[SaW: Wqj))X㦃]X>lVYrىk[no7VeXnTYt\OggS@Lm`&L[n|PCďj&ެn GGNc5Vޠ}*kϩHطwĩ@n[D4!.}Y8 R-ۿ9G s7uۅ."~vẋGM("L} ya8BG6IU2D"j? Ғj&w$0 v?«BTZTGjWZ8MfLߑ{jd)59<'PR^kR{3u**,;Z9E$>s 4%)g"";o?0eu ,|9hRdW"gqIwO|?亟EKG=ݢ:2J .Rd;>)*V?koJr_qڛ2^fL~LB9<Ѷˬz'Ry)i.vy~ {[.m2gW1 F4!"\.1O)<>_Z5 9\1'Y:k.Ƴ0+vi %x',s ?K0\~?YQ2fR-fv {?; kI^~{S =*mfG YQkǗvG*bd=-d \ RX+~/N@~rT}=r}Lv4niX`cgCô߲YPQ痞9߷ D(ةruC_R ~=z֙M3)bY}(:gj쾐'`N[ ,5ۯc_nG ^:| IjJ`ÈOɍU  ʸU+AnI y W D×*rV.'t>z~#Zֳ~DAVDv:qjbZg]-5*_l ]ɓsE*A-$c/EHs`~:(N E@ X@{ hHEdUrCmɗt:hOqFUe4f,FKI8t#dT2DNeP*;NR$#mi]ZXԐSjBduDif^5Uudjjg$@3 &I?k@1Ӳ4(*SILg;ZYՠF ul|ɠU0zݟe'Ge&n|ycwˑ\aHqf4tㄑ^Uh Bf]'gJX-o*2ǪKXk2̆S_&PlԺ ~E_#eLszȲr^:_C[7?ziaig@| *\OѴۍW>NGce57},_OboǾnOkݏwqͺ~c15R/^ѹүg H3h]O e[۞4 ^Κ,w?J>VZ^d DSJ/w8(!^n<:KD⦉:?&P/#c5=8.0Lz.+8T\{#LNOE q qo\ϣ']%UOD[m\'V\c&}{_lEs9eNJ4~;Ӻ*VhaD 1M)~y׶Vm}Qg˾n:S_18vy1Рdclq4h9h4u-`@SRO'ߦoՈgXu5ҹ[Uj`Op\qDr c]ol듈;Rsk&~v2xd>ZQתj<>jvgΙ~ߋR63q"lIVdlk"SVB>>yjܯhye95[dTS^ߴnpu/ -wkJjw}zN/VGtܟ MN2KIz^ʁ\^t8 閞IޣcGؿwfu_p6Z64Qwf.Z|a '>&]Z<&oѻ̈́ulfnZ+:笛7A"Vr6g8b3"xFGEXZ#7ITs!5 2$ m#Q$̈Zĩ7Sg*pP=VlBÄn-?@SFBEY''Z$ЭdRaM:N+j@M2nqD?Husx99VքLР)/tE%Fuj~S%YSZ^k3dՠ&˹zkWKeDHDPAg+q@t뽚`g3I4Cf; 0S)cX;zt `4?s{>M&@AY*0nNxuu2=X\2w$81qY) Nse;S>RչoA}?>W_|$u6RIעG~J}RXo4F$MEUE8SX7Wd^ΰ-|1sMTN4ox '5>iuԜzv֖_vs4R?Ee}Ž:j.8zsTR,`S]ЩynWUUi4nx|M0n/dgqA] >Sሴ+P'Ae6n~#B(kb-ɭ)3?5^$ݕtAcrxl/坧 Ί  @zhUh !}*L>ZQ1ewn{WRjshCyg_"JHONZqPN'( @'C|LCH)RS%rHNTR O\5td 3KDhf+jwF"y i#8:uJodA @F $@@[[Zۉͻo0MەTNbC|X.;ͶdY'M,hO͐V;C7J;2f ^k}_1E1=zU=>HV_z:5CӴs̿^[-Iw <j|͡$?A+h2z­ܡop$]5܎ * wſ/ͪ$ڒ ZťR~$y:ϥ{k%]*Lgp[ OըG׳uiFXFcj3$I6 #ax- gٌp668g"\?.7JEGRA#U5!g*TDm!ȞO 2Cn>ORЙTi tjw@y?_hMfOZI:}uobY2CP*S?a'kк3 Ciw7u2z-O>;ei#zxzPzo+9ܾIWٟ h 4#qmy +7Z8nE1 { =uGvxk, wKGM2gsA֤hOCc:u.-{]J>ON{sH䦂{ג;^1du\ce5)ƽEhl:Kάt!žz1k/rޮ@w ߾+,eE ܍t8.b&E4G9_wX{ﳾ>Ԁ!Ɠ /CMe\9ue k o֤铔*ʼnM:uOx_&aACn1sIFn!aKиՁӫ&_璕cg wޯ3ؔ3o6mK Q~ c\`3нW`nn~}i|͡P4B9l) _hhn`.k \rGSoEU{^ L+^)S'UD+j{[{th% E[mr ۷A( rʄ8ƍ,g'wcHu $o i-j+ެ&=h"?)sγl Cgm-#k:R}rIJvkFk4+HKLVĉ`ݱΪU2itW^PD 3芜ݯSQ]hCAmIJꄬ:i dFC͎>Z[B "ME 9f2"D%fPf#!#IU.I)8?m3 vS _[i3W(}mA Vi&w}$5ΥR_*@5kTێҞ)Wmc)M=K u7kl9xA5D7Wns=Tؿ36or{D787E/r0 d?'NҞ۹ "_˧"Y7cm_ fٔimHdrܾ O*JL)y}m+>d tQ/7W| g)}fW.A@Sk!W7`\k[zr--ʽ߅\0z:*Q$3^C<%S?z5Ѓ-tMD& 7&$kV?'2.sI"eRX1i/NUU9ƗB%0Ba|6ӣ4tx~kweʴ鱧uL溼Znj>l[ӭEWs8!Z -"d1eRA7d;Ϧj<;j'95{x^@w츴屐%~ZK3ˀxAf\),C(ɬ ]#|uNklG=Wkۢ Tw!_7KHhcvxۏ-*g9#3}$!X !fC`8E@):H(saaχ:Pk7y'tQ+s$ԥ!8])DSS>RSͩ)+*o/Ǭ,^WN 'p`CP%!p$Rsm=t T"uBJ#ڕoZu*S ֠Qwe}S) 5MqJ8vydɘz6ѳUI~ D.+o%g~:#"D'al(Y|͡iB( -ڋ(-e&$*rO@E|loV4S6T}VJDE^<QȣIӁNQGb4( V:XϾhu 1[vǁ"#BJ~{ipD 'ȇk8F"u׊jST9hdMDKGgh_F-{T۔N}]K|B Eu\"&"Ta@;BAwȚNwHV82Оgyuջ%|Bw$@FXZ^b޵ݍ|W,z,v5kh>}cөSv1x&mvotj MͦP85Q}*>RY݃q{M(2=+]eWÙDy?]9kKjXC#mV /YCL{A@o/_y uvG{Z͕RU.,8Xj4B96* f=6jprWE 埲宇38^Y|͎Pbnf'uog,ѹ5m٧9' /o8ws;6e[}[ @,{%ƣRs{Yue d4BI!ZQCkkpUM ~0DY:+Ngw%fIھg]U0 ߋO;C:jOTfBCU$@vKaoJқF,,>j^No`zA\ls@{lߑ[E)>4V;.Wun, w&X=*r[ڼVAŢ+OggS@Lm`(}FuZafWbյ\oSz`<9AU :U#`bҥ~\cΣBH˸k]"muI-@q;UI4U[æڊSH7^!t 5 x6TiU5Y[ 2 *Ԩ4]ykދn)ܷ{σDK글Q UPpdV@^^⴬=BNC80; qQEs}o_~q{]gbasVT$R#\̒h8#8**@רdg 0hT^{̈́"0"T:h$C 8޲멡GXkfASlWBfA{(d.em0{c{3SM'Ȝa>'[dFKׯ u yk`7ht9L:r-A8zUkn(9 cl&&d~ݿ#nTj^4r1_-G%}=ݻVϿrUqC!4WC6VU@q51[QُW4wȁcv^62 #v. +ƨk|s0mT^'S!qyyn?ś[KYا &Ykpʋ ls@U˛Wi3ouyIwd bK1(z,cէ~|u,/[H]m(Z{}5<)?D=쳅K 6P9(t6X4},&݁[*9|P$C?)4hqe>"sa(&vUA]FN5~xRorWr8HW4G! י]ugOC4v񩄜*δ! AYA h. e*Um#ehASj @f G$fH4oK|Be"'\YyZzq"r >(B-nh\7q Pҭ+V =juY[) JjU!SՁyY-l˔3ڭT+D0vggJsPHFU<_r霅kū1&wy,sxV4N1vS/)x02v3#S@~©ۤZwAN7J+ԏߟ5pߑs@bobjW`TfaN d0OݓyP=M^'O>ͭwÛ≋S2`7<̭ã҅_0mbkq;{&~ 7TS&j ޚP㷹7ՙwJVN˩jdjX}~uo˻B?->2Q⠦ { 2裶CcϠx/ݖ.w~9Ϲ|ۢ4˻qv̏f!Sޘއt)m1_:´5]x@.a@uEj>:|RhZ z@} ThWکnuFu襪u|+Yp<>,t4 57S3hdMdv u^!ykVtY;:JYTXG ]ŪS5Vz643DcБGR07^!Uj4!j"<)@Z67FRC:C93߻lϧZ#>t iZDV8PT[+!%-CMN ?  @SiG[͕X*ެ(84nQ}}ǖ 񤤷0B*3[{SfSi'fJ=.ߍT7Kn@-Ohb0kZq-o4 _Z]*zskxY5 BbWO[пz#gĺ]~ZYVV֕?up't#SVM5ݘwv"O_n$y#T{+Xtx*0-Ux), 쿥NMH%iTJf+t>GBşkxٕY4t(M i#5y@so N˟"댱}.gV.Ia5Mms?hBnV}g|i 8;=ه^.~wk.EJ,$ ͮr>*rk$]j[e]⼇9sn$;8^r#owDz$D7$Ϊ{Fhd盨{ΩGT@s;D\_eFE6h|h6IYQYJJu!AAiiWQ< Si!sdtm QD5q4*s d?{S3@3O:HREEF|bPHWrnܫrAJUAu3j·"oKA*@}̈Ui ` @Im9vXR44W].u r72b=> sHY@u3'tP+u}'wWDϮOo> Q>Pz+{:16?u1&2sk"cy}YM6' cj)Z\N>Kx$Vmv 5.Ԥ2kpυj ̼D[tky~Vee2ga>]fzX^982kTDZ$IBuJkO{|cI~OĶtQ/<;DcooE}4BF'T=^E>͕*KB)˕j#soNo~ֱ=U؉,3+b0s7-W`N^& 9kW4mӴ/ɮg{ua ~E&d"6z\ )5^j?vv\nA⦟%S"PW&qidZhckV0w 0p~]z.gw(ELІOku߉\GwV:hU3yiқՓlND8-5R+{NӍ08Mz9<")`TcM%F_byj-v=U'F:=)ت HڧI<!>z-F?7?E.$ci[!ϫ?ucT@W=|OggS@ Lm`)!>: Ԕ?o}|nTjR?^]M=OǴGAz9 1CW @Eդ$QHhQCvnE S L*;"gc#tUk@q}H4$= OfmԆ+TGq38:;Bfj/ '?_D<8G9ǯߡQL5jhxj1Oo -U|-:59+I޹8ZԤnKhQBԚHNkShRJG9}Y+~NYlh!0_*B%鳡k"aO|!UU&Р&Ps{}K7Scy5C5Wa8oe ][v+n=*ܨ2G-OXh?ukRQ~mJ]ݎwsJ<?ܽ]7;Mjɍ ^~9aM:o*>?)4^!ZmֆuKOo`?O]!׍|:;gJDFiI͙m :ݏI*PKID7=5 "rpi6 5vuorJua47[- U.lO<^- 9kǓL}X3L1&T$#>NkZ:]%-ܼ[,ɺԵte.#ԕGTzr̎O}r׽L+BintYopf(ړv璚.ݤz.$ѰQlĴ5ڌP̢/EKJZlp{DTpx6>i\I[M_{{rw8Lm+޼i%/Ц`op_i:T&95P 44wK~LYͷDKqK˼Y9Hk!5k@kW( oЅpC~ސɡ9Ԩ\h u$\; *DJҋzT&dB^Qy #ҫRK@Z音=V==tBJ׮%tV1)x$h&z8!"T{AU8*5Q4,Ԭth;O]+dfP)sreqxJ55n:]ɣ[UwB5<)Hxǝ|ªIpa]wX:"eǠL@wĪVV6Iܨ/Pg:ȸpdԣIA"'c` Brҹh`2oն+äc5++O?sc'+W3-,71 ]Ã_9=go7,7:y<&{`fUć`ww5;D2 ҽ_AA+{;q 4k9n &5 kĮ [ 64F0RMNh4({4%,;~*!FĊ9F_F ƮZ<'mqs<| ZRzvu3ja1S+~U5V ~)|_ΟY\S7?L0v2`R\?|NPD~V'*}w.!*%&[HgQ+X$ {p~^b1ZuuhATI~4p%s(ߍ ] W>u*dHq tt%}aCR!X2E>n"PUUM`9Z#"jp 2UW"iᚢ~wz*Cu?ϒgǥG1e R2SĈB?%КtDD!JߡqռO*4;sԀ4qD$[rE&2o؇f8P2=?Gu[='_*#Z=(u:e|!S@E%PX{F?I(pv0dsaF#gspQ ؆Qn .Bt/|@FK:҈\P =$/FԤl<䢒KjUt6ſȠ靺7UO ;xES˯V_Fͮ5(gӀu~=YWI!nhA#7FysjLT(WQN)姝MޥnQ_ "KMSJx:Slk/OS/9eszrΫdj7Ӻ}ڥ~p \Jh1ǓcqiqjKFirMM!8oBBu'gk npRozj|Tym:DA/dH;Z.g7 "GGq'<~aނq0xÔC|i䲾TMXf[*^ Ԏt ?? '`dzS)7hwYW Pސ:&%:֋wC9t_>/oPWBq**Zy /[+Ս9}iu.g> UTF[QZ5pAjd-jpݽιN$~8_*g Pٟ{*P#JuЮ|UhR`DjR:Ms k3K~LP2ZeS*;\$$4dSe 9yf:침J肚@$\9T 25W": "%sI8LT) 4W}>Oz0P?W{`˞QK;FNxzy̏瓖\oD{7DYg]hǁ`̱ubBMh7,F࿭}b|`,,q/Xת='PKgSϯ[w+ٓnս' j381>Wno]jjmpD4@g;iW̍N{(KQof | | e'e>(ߏ8>f&\ 8jRdڝsO60l٢ݥyV<t?`D=M O|f1vb X3S>MƋc?rkj}8ub*?Ov8ghř߶ȪwH_w/.QWpq%F(**yy$aSKfnܭ*[h 2{pt[[ŸkuI`Q#Eڒ*siByoe# WMdcX/&?-PEϻzT!t7&@PWOggS@Lm`*):Ggj0N'x`7 m- 1}.Κn|%b*?=E՞)>Q4#7đK=8Rk0HH8wH-ՁS+D+!SZrQ%R+s DӤEgeJG# &sS.:Uֹe׳sg魕#9 T$&gSpnſΘSAqѡ^ LRxN Wo>N8 T .ƕeo)P?vNbD/̱{yBC{n,}TYe ]$s=Cy*.^zhD}7>F4^BOw 4?r91&8#^k7n> @i]RZIʃ忸#>hb~![-yQ=}w`gv[6oo|ژj3?*, ~[z O9XRٿ k~)^dfbX11%g4J<@j׏C3 q&+HI / _o)?jх?[ xݤ=ƅ]sS6R=];sxyhbx[Oq<09(?G+5d\/ǯZbs!g9YtO\,巙IE_w]ڮܑXs#KQޮ<_:e20U~Κi>>*8?6R C(kOmRPqnGV]|/Oݙr>{h`7qo`jWYV sE~6s)}3')ucp:>Z U/8~ZK*1ՂWPCga#Bѷ*l- txo@[щ&WN1Sqto!b֤8Lx__> u&TF"4IQ>,\MT8! Y9vǩN*rIYIG_EӾXqR#J.JqCSET{?QRzHVQNaq7rEӌyjEW^'pC-@zjD)kʐtbDDUA#yQr(,I|~#]USf- 3vz!O fP;.7+jW~cC柁ˊ=j%U) ߃p% _ns~Ș- 1 ȧ_d4t}R?+*F7~ҠqS=QwQmC#ڕU<=6*0uq{As9^>Wx7y1#NC&tׂ_uqP=zށO<|=5c-\{Bo[_=$^QB" .xg.x:JUridf TW%~튕Kзgݧq<{n?`r;;hmNs_lCeӧX_3AƤߝ; forYTx`l+z#P9B6vV ޔa{9V|mQc&UgvbZtK]3R/Wy_p Drgu^?賁:ޢހZ>P&s{=6\o!_YF|ȧu/֐px\H3be͋UAṕ}j2C'[?I{ .Jˢ!:NW/&]/ ))Niji;NڪS6eqn)LYH㌮K]6^+N Pg\fq{DFlף|3Z"r 6l_;T? qk ]Oc۽p͏~>O~`\ͭl7?^.ag ѥ)G]N':UEЃzucHhA>]alN k^Ճ ^doQ'Ɛ*ˬ <7+L3鵻DjVT1].v8ܽ 9lyH}>|cY4잓Tʈ$`7>j9 *LHTߐϿ8ɼcKbnݵ$<ysuf}Ad׷㼾Ϥ:.9/ 22r ގW*a?Uez{1 {_@'Ԇ;k~o ty^&޼ )5'Og Dbr/7@ H_yUAUtNud}>ÙWK>2UC^ϘTU>-B*%-YJMJ #ꀲC@vDz9yա(;>hWzSEO5{/}-*g/+ZErlԈj7NZ7v:N}Ac:^P['J+ZТV!ثtP3TvJc)Tv*Jg_Sukڤht,:; CN:ի.H$Nktnzuʧy{3AwȯмIP7@!(:;|Yo'rOjk+^ ؼv b[[Kml F_W׌o=I߯ )ˠW.e+;~ѽp bK7͏C@SqB?|؝^hn7uA;fylf> ^G_1r I.;q T*^< :N!nWb'ʬ܃luAy#Ƃbpdo% UJۜf=N+km;79qn՝$PA(DZ]zEmKj'.4yt'=Uw}IV>_K7SzbjwQ9r`L*E=`vbY}={نΝa-Z7'ɹ+u#0Hd6#`7Q7~"bv M>N\QC^1弫٩ \f]Η{LLٙt ;'}ꓫxV<0zJUirT{Tk:a'O\j ֒r+y^ qn)܏ ~ҿIa~׈ O=sxG2Dn@vOqNt6-5̩qt2qÑSO.%w&*p>뎳Kx*2eg) vS!jtcr5>48hр"gQyDE9BFR%wP]VALdZ?NQ3R(g;ubֿr퓨thJNTkhV1 zU<܊W"zvp֣+-h~8U}^*8Lڽ]A C^w~ N"sp(H|/ t ~͚A':g~bRSÿ[$Z2.WvqdX^rta~eF@?+?&saQLP`G KVfR7^kׅ3  }TP*4" %{n4sYok^yR:cUlk*$$CJO4N&JG5gBXН!,K&D~~Q6ԊFh/90eHFQM8!VKj=c*Qq?*16zf"ox-۾o9YĮ+{ 8,n]J!˸~=5Vްp2fMU\PN?bi Re5ތ[x!XtWef7[@c-仾d\_Щ{>8,Du/|:d t5{%H[ ~^ßuݗX1:x6{9e"\xI3?7nϩOع'hoGzßO1nQBFB|1?}XTls:y+OggS@-Lm`+Ԑ {¿ 17FP號/흮K-$seY8Wa[wgmL6d-:=NZxo\3"b>c*??p6~q.)%&hȭ&ڹ @ײU%^gZcGGD#ɻjz4 cEU/ 5Ȕ -.y[]-hMJ ~u-שK=R֫FUZ"5l4BOKSqδ8Eժ5OK\E#kHwPHBGDT2kQٵy7skuAph<+__x[7C;Δ; +g E Z"kpX}KҫS3 h=f4D QQ*>Iߥv5_tU _xP`rVvo\ĴhᅞLovThaςnBىu)h;KoE3ݭJ[x)b)M1 :?Il;ja[1n4/f2Op4j~\=ZpC `Ը<>u[+ǾuںZ/t^WFEnP) '(t<G6',v|nLRl'yX"=Oy)|a$W+ouPb O¡'^PmY)pEr>8gc N}vlǣƤ&GƊu1?ne[PL^=N- 0\yjOnW>e|~;6m1VT>c>H:{-BE}7 U.w67}|Nˏ{:]Zݞ ϱKV\gպ+ϝQt|@?qM1.ǿ{zaTY# nrdl~{j&; U/PP+B [?E_W; ׆"a$(tHN-#uq>A&ТZcx$_e6ΉAaJYY3$dJDvMm'Ah 2*4fȪI0V'E8Ch|#ǯ*@NyTPTG;s]أ.#cj&kmv7y!3Te&4P*)4S}69Ewke*j, ݡ)/Iv4$:9tJjW'ީ=iTΞ V1h"l*Ξt>2" N=[ө'N:eя%s snE}+(fgrqu(MK$d.gW GRVR?Lp?źȞ/y& !vr`=d<& kToMBג0+s&޳ Xv z1E.wxh@w [d9"L6YUu~v૱ݡ,+\ZIFcU?]_Y{M.E&1Q8Vӳ{-oN ȚusR\wpoc]G퐞᫨Cj8cbrZSYɧyVޅ֥Jlca$`&{1:;t9L5:crrշ-ߛZx{kk ^ɧFۇ$Ѭt]OdQ`e5昸z]}gPӫ8v7+EKRfĕKalL!_2b< emD'@{0ےk%N@q\!G7o%$Q iEu"봁Kxn7@2i}jZ u pnzTX>iJ++%ikOqjכTp F '}f ~')8h8w)q}|6MtDE-̂#m.HDTzr<ɬW%ΞЃCCT̵j8KaN:f!ҩd:Ԗ^:NMR壡ziJv$ d}NQ4PGT"pקBdxgT#+]L^3B=׮M"C(AZ;gv.>KL{t#rW$ SLgcj;1<ىMf0#78f }5nxaJɧ4LrcƓC#A`4EG\&ӫކZ_؆>usESo5o՘[o =uT՟#8Waug[tn#|w|+JzwGڂ?2pJv~LL"z#؜_r] _dVCHaW҈ -KAh-v6^}ubl!_vؑ+܈#UFer&f 5LӒ%(LeqtH4M~#k>.cђk}aU`/O:uw'`狢s,hs@1}sOnOʇ/Po/{E3[hMcc.؛@'PQ+*]aiU'Υz_GLJ1D*ºHw6kSښ5y ekԳ15[ΛEs'wD FPeײ`z<;tozl7޺^Z%6p} GOW.ۂmнoWRr>?f7JGX(~ʿzV}&b;w'E ?6hSm컠)&>),c"'ۄ'2Vz:"d[ 4aZP׍s&TJit2nzcNdˠOggS@=Lm`,TK{1#+{_"Z8P@.yms7_R$SOޮ@DZ?z >8 +WQG,۹JWK갾|^1$FEDPoN4{!QJu@|ε6E*; uƹ+t$ ~**Qt!t`y{7acN87?{DS.sD*2EѽFCk<0Efu̽ʑ FI=JPNۊȗ8ߵB, }fYH>UkeBЉ,4h!)(:kO1~ҭ7`v:t$fZ*5r.JUi,-Zfj)wQ2`3e,YGUÞ\̜y1x+ݽ13Y?ndE~@洼0{hbXo-CA' Y"x0Ӡ= Zȹw$x*}c@ckVܳLI#a#jLU\l3=?(׶@8E{a{g|5$Vg;I|}]M\1*]6~mܕ S]}9:siK]c1"yIo17˛Ds(׊w^xl?dߠC–vpP^A 8[ԖG|4xB|p8ϢH*7W j|w4[({ݫoք)єZqd~Mxמ^5 }ZI2{NIɢ}_*:HwM$_QtpqT#LG]NސTZduo?uSLn082Dm&CVJtW=j{Tq~vlj]qP-=zSThaI+NhMH,ORMΓ#\MEkՈ~j<jK8SYjE:_S%Z3JoHdΊ0QvpB瞫tdGV5WP 5ȝH q{]gգuLLr'6u4\]IzWV-ſQ;>*\%U?|AS7 { t66KMƸ\>UХiTew9|{R,Sze,҆КCQ;'̥ _bbYv-vow!%zNuBV]}'Ⱥ^BD%ydBе 8OS@B#pf`mߵ|t%fjDSt{u)UD2#,CSD{h9HC_'##`̵V1^\R'9d"/Ea3SDMuFJPX8nU 2:rGAUFU9A"+¯; S̷ 0|.W`~%:*x}VEWɧˡ'J2(v6,W9l\`4UcÉy5ae]@Rϫf6\ xR)t>{o[  ⼭R>yʯ9-96|>1 t^Od>p:3=`{`sR`br2LSa + ԗ3MRuSV%f&ىG.DdGƜCA+ڊc.v$i4iV^]lrgBa+k셨jvUkB| uDUr4YkF'"dBɮ]^R;ZP/'Gh$9йuO}]ǟAdurrDFѼr#ukhO#9̃Q-, :TL~ҳ]?Y|%ʥP|+8T\W4Qy5.Jx̗h:=>dh6aĨQoouOvKkQǮv|Yqq|+𚴮5m&(*K5sӸ^)*7huseX k0k?'n{9G^'cn852M}F]ΰ-Fln]LCG74JebZK%h>*vp'[ƾ_k8FOX{3qHQ0ޔs /HK.N1r+VUNg8LSpIxɴ*FQsg!{y-]$>O1Wdζz3D?knx¹φ'R˕K͗'Qi좵ʮȄ!KrT0Ǖ^ $%i -OԓjJ1QӝHgJS 2*pHRʅ.tue圣B'-̬;> K%z^+r:Wq'! WN MZ8jv3GENT*MGNVT&L}{2nivumBJsf[wt<O3'oz$Q=x:!(݋1c[zVҺn7sa;>-g:XWimϟ뼱LB&^m[0%>QTfG_pG_S:(ع:Z`,UH>=|*Aqow/B|=Fh(諛րw#Gs+@eܡ;+aw5fkVcvkl`_^~|BxCg;VeO^_%'ӻ$>ؙ*8do=$q+x&_9)ţ% kT*@Qg-Wfy5e0ߏ9k?ĒBɤԴ=Fǣz Vh \rI7y=ߒkF C,vЇQ,7~xcE1q<7[_|Ҿ:6)/EW6潕.-CLnEG9E{|Rӭp) qN4YS2JLH~sԟ!u|E>Fv9xs `^ *>%k˹7}heUOjoQT~r0 5C!/PԊWǞ~;|$ԩ^4lw)xtKkp=8Q<πڎ_}T5}Lq,RٽNz,oBS'iп(Hp6_2dh' \53$Aviٕ @*p2Ik_G;t툽YZ=:T%YʑTJVoQ41L$!3%$}*TT~4N֬tm@:i)T_򠿜 ErNV'WZEJd3ˤ:AI/k2[*:Fף~QQ]ԄyOs;95ΉUQjF3gUrM2'D5$:L_C=QP:գFPVڬnT FFLzmKN Q>}?P' ,D*j)onlLcǔCz,B ^-g2z?K.Y]ֲ>c$ʌhԅ07/,[V4!<OݳC褁юn4W6%p`."ad.bZw$;7#?' /@ǩLkTr>=^K ut0iLCy'{E+WvIR;_S{Ji: .{_& iWyuEl #|L]3h+B1ϛK+T^_M9$2×U]G=R vZYJ{#+򗵗/'ra\f1X?iSy5 PƖX d.O}vƉwj&M ')y-I9ݜ4i/K@]ǗS'md~FXc(N ԗnlnG|>Vw);?pdӖFA[/6S+^mmR~ 2c<1 9quDKkZc:da"{:3AU+n|RdÅA" )\k!Mog]R)C5V]pZ!k*yS X* (Z8_n}Z dmZG\PKhtE5ըJ|%Rg=*s}C>sbcZklڄW:$9ډvw7QɤPj7]kLq<%.D"r/ pĜj TC*35ZRQ j͹CjID: ʵRpJCJfLvn+䮨tQf^l3Cz>*:+EV73\v @8DxV傈[ޠʅ9cjR5-;n_ǐpZѭv13߷zYQ>FZ׊4EBTw~_WX[fc7-%nhp?5#+Yd%HN~j͜}j};d֯Fz*0#-gnb}j߄N'k[WW.m`zsH[#;ex%$fN]H;H?vRUޫS[ty*iW҄zZǮҧp]X8/? U<92`,0r*|Smg)r9=yÔ?(џd_ #R o|D'K~}P³'Uq~: ᾟz]>*CB&o].g\0b@]bTTBऌWE p=a88,T"N=Sk?w\)=o&yi :Uf͵^|AP+R阚''8w!PЋPm^oljGf>veHw0`bW^@v;u*Y7DM|xݝ <$@*{yҁ{Sʐ0{/-~~Si?-]~̣TYqpJs6$$WsN4g \t^v=p"]8i Zz}vgP㞪?ϭh]:qg2"TF=2o3` %7YN_??D=Si?vgC̩ LyRewĴ`Sjs«5r8.,pc!vAcy*&gYπv^I%8~utiFfZ>22S_MQCYP@6L,._ƾxѫgg*,nE䱂n(LFM/5:ڊ\RiR~BFrҦ 6WJ&}n<hr V*._Lޝq:36^X& /j>N",1 7c/1~ֹ|m7]5Vi+Ӿm樽{ݙڰicx^m…[,Z|t _c_+9^D{I[Ost0zl{U7Ng`.}d* Y|;hWqu*Ʀ=yXk@=I͜ ʭ>d9ʔxJ7 P͗#9F9]nE$zsT- fãYxrҕ._8dCHGI~io Y >/xw~:] >nVLgj~tT^zn|>Uw'}ߏX_ qCt-OpAXlq7+lCD9ٰ1}$yaBE߯oP_,̲Hy_pۜ/Ān}c>8Tx= `ZpX?"#Eiv[oƵf{P<7ԆW3S;'?l2jp~8:7Aɾ>?g\" np$Owr?_At:CĽ(8J4\sD;5Q/t Y!B֧DW"O: gJz鎂 Bm<x$ABV FM@4nfj^k"NCk]t%t㌜\EUW//Gz T)"8_o}|~_$:uϤ\i#Fsj@hxdBS}h'>"t@p5E6ݬ*8@!1LUOsX5f6ϖ{:qbM>U~/Y. ЎCfO? ^ \E=AY={yؓpo~VONG>N~08NԵd*Pb_:X_8rns8$/uzz> o/ [Y{A^=b@Tk:˃wrpLI BvHN,9< +FFϙgP@|ʒi_8ʴ9_L_/RgY3)W[(/wKN}Mhe<-"=:Xϫx`rF>m%[pyMP'09 ]?2擡TyJ3kBv%d6@8_%_O䇐n%cc^Lx>3AcXlٲH^jjݿI;}͢Q?WCxݾt_)`fQoC؟7Ӟ-k)s^'h HﯚǏ4u&|ҁ΅л>VTcIer|k[Q s}0|&ٮG}uAqC;xο?pS߽*Dld ~_6=_(jnjsY;}sJ-(ǵuB>tEQDU#_QLUDȓ]2Wj_ܶOJtWvP=%TB,'̍;xPpdd8`#'ôi;덷jXPos\oURVzANK78zk8Z7P͘h@`DP8 Ze 3gUM  ś`opF i3מ~ RRO4#!.~7eP]. 2.ˤWN2r:tThޢZY7cc+E{1֗S}jZo?|֋Ч+r$wduz:FD{2鼻[h X8 B_ɛ9S2|œA䙼 n N5{̧&O \4S0-D"Ñې>?7Z۰UiB/%j}{ػ_+wg,h@Use}:I t6*-N|^NE9BɝU5 Hhvݴɶey%8A ֻ^s-.-a?p^=[+T^tmjWEaP_Ox5TdŽ?mj+v}I۲ۺ|t~/\g: PEI)[[:a@9w=2㐞0Imu}r^>$o*:`4/+)(hBBSBHZ'S5UũKg)M~ؐ t3)A;(G8J j^p"H\RY.9}-2NP}N k,1mcQjpH~ Tt68}I,GM!L`_pu Ty]HDWZxN4vM%מ5 6A˜Yբ[oE\.YSAN˧ۚjh%Wn@k }|Onb]PWދ/lUG/y%(\.r羗v~ɳyB*F9xYכfO d;|l`k]|`wztDۤTHr?gSLZ0K9WRuiKOE]Β/+ߢz̉~|I&n)Wu0>` ؼA)CF[V?ed8l)XCn{ ^_PP+6~|tVVBϸ:cUg:Hn4Ћr(bmC *7zPQ jϿ ;HGgQg%HNPDjU[qT:Ѓ!SZ3jZavğPPܻfd #FwEfT\vmfiJft%)"ώʕAbCk% ݱLM | MOHBީY^Bnys:\^? vI U>gt*\_70ǻd_z7]B-v<-u3Z;髕?Mg&0au=;c ջn>v`w{q <*(S^.)3`8ֱ xP>G/RagΙAGɤPO2uqDjk9 _;S,ۍ 03^ ABוv$qx'1@\r% hęr@\8KԮ|$CB$<s! M/Q#}^|}I--Y8@?YBj)CgNOι*4@y*&xJ*QFHWqjw  z+mc&+tH:/Q32Jm݃:8L#x5gGwt߆1{1Gm 4O)IsHm) >Z$j8#ݗΑJH: H$#+X;j+~vi:]ߥ|gDK +3x?%;MޓzmU&#e2vEƷ]3Yc -K4]KReץYqxԍ[N xAv)v. frW3. ݸs8LwXP(e~Ռhed^Fע/-Udzt7}xjے;ػFVgab7>R4I㣯b˸ .> sLOS:aϹܜr[b^/ )pTTp`]Ac2HJ>TwC[N o9hT\|]d|3w{2gn7ldҦka̧lI# :j6@d΃&3{= ]G.k6W>>د v`娸s?Yc. D%<؈-}َwD=ȝfw\{,|Ty1낡nqw ,$ҥLs?neڇϬן"z6ϫu0dɷh)M_uq:sKxπqn/eͦ:6p;0Օ!0b}။d˞&j> ~Co/ԆpO%f/S{Ιr)#ry`;5f }_#+nUM/:w8"ڣxurS@V Z̵IЯYdpuZu~l""BzXD }Q46#EsRL suMLG?vַ<[_L. gК.{Ʃ\TMr8voZ|ٳtnw u+H+:qўle'wwzrrsZ/hs9xwpd*Iͮ Gr6 I߮@Q!}]9!5[ZtDw] hLdٰHdTBVD)"*Yz~Xuos JVػ# Z"m!f+"`. q8@3M",2A%MN!kFT<9&%B{yjHݓKvCݩgqPG^`QrA+V:]{b% ozjޏ/wӟ>6ףH\&(Ѓ?QHzka pLG g8yeɲcR)i΍wM-rN|+c 'Q76KJ bEo6O pV.n|>>rRT&I(TX+^7i9p &շ4LDk'p;0UjmFhDbS3MFhzihO]'jRSlaF|S2~rkyCW L.\g/b.ب9,%s9\*D2 9@t ثlamW+X6dZ!ܮ&ʲҡe|?1v,y~w{OhƁ0Z[1dZF .tHONdvsB׎ij @{yCL3Qe^0΋|{ϲp^!ǯp>a8EkP7-==;̇p">שA5iu&a oŝ^Q2y?ZsZCYi.ֽvJB6dSUFZ {Q둂TS-"hūN?3Nv̭JΣr ELTh2}o4R$r橢xٴ'GF>&OeNM4U*{fQ|m$kWNؙV:$&֬3RТ߻BZ@e֮N$c;pb"}2Gi`0#Hb"W")tDF ِ]4i ZQߞl©ܖZ=9L%Lͮ;s湧y쿇;T6'MCj1hRږ?4ٍ?}{IvG뢭4Oxa~ٞkg0Cg3;b0e+KSW/\G? F~ 5YB^Q1&t\B}(Dm ;7]-sS*ia}]~)Rrc~\7cf4Z ػ&RF8g4{sirRy1ǖbu*ɇcj Jdq(Ջ)w/u _pw<i6g֔]=DKHL ǹ{ 8JwQ7܎we7L;?R"FQj+ƫnֱn"KϷmdIst=k,$bG?n~wD/> 5'VpNᛀƯۈ.iO?Gw'@>8;ˈ0e5;VY K5La^ /)@MZ.4}RH#hľ9lNPlO)D>Qk6]rQI͵j!52coΒP pU;k?R\hvY1e2 f42M} f!kSxZR- ;h"FȎKKNQniDI}tbӱsֹDfChW묚.Y$NIz}只/NLG#{3"7Ne'vVGH=5Gz4U)ҠBgw8U{^^j㟚5;BNg ?M&|aH53uxWV3lT*ӿ!q!`6ü9uMfVVՖ26FV[QwklO% PbKM O2]m}}Qz8*-=k!]TJ^\k{PW[ĝGXہBJz iW 4M!R'-l҃!4' YY7bd]۬tָ涼{[S>_v?<LJ`xBnj*Ă/n]eL_^$M}^$?fjBxTf:;阣:E!- ̙ΜN3ḳRI+oQ"ΐ!5k\NNA*b4]\իeԌ]":vPk+OtchPsLg /ك*zPyRꊮO=>F;YPZWJhSkgVA-bߙ]*@̊żU)Њrp *E֙ySݡ@hZ=@EL^Cd oo%2߸Id@58e@nגn)NS Hܪ0ۊ8-|{K+ 6ݞsz|-u&ꎶ.)!uHˌjEяi' OxR5% *{ C{FU;62.;oYjU j6BX77Z5Ϝl0@]1!$'op1wS:~?>峉Al3"{8XXYW=De[^ riݗzw;|_n(EP sm9t=NRWIY}^Յ Ǫhg{q~imuK)E ڕÈ`@wlJc .gI5a-sR߷V*-[3ڒ$YEK+ 2_M&j .r{2R ~5tV.p7Kٺ~]!h/+Pg.aQ/obnᐞ<ֲ/KpqnV{jm_jեc@/umYcNJ($›$

z4Z4dÄ$,dJ.ٻ) Ri(ͣB=59kPEԺsoP1_8hkSO&U(BџkK1s4(0Gm=!{u|:O)汸/= bJ~ky9e[{$ |S(N׮sB&tR Ys9*~w@R8CJKdH\jA e)-9 Eo~lzlRX*<2 Pw^\ x穷)&'j+"p%g.jX_G3 g2-󗢅k|\| ɣHrsTV= 9e$%q՗ zӷyj4Nbn Ϟ[ͦEF@1~ LKS5g? .y3`y9媁9#*91`-$ XK']G8^Ҵ\N# wyQw4N{e$?wWVј]f :fKzuiGiQ}_]]3c" ؜y_CK9uHK ><)vRi7yn=3yV}6[PNŒuZRxJC/n3rb5 WlXl9_x ]My>柹ҾSl.},QSuJ[E謹| =4Zp5vLSYLu~,h~NY%޲zH Nw; P.otSp ]fZwv{7eP;0Ԇ u4GK+fF #\q6 ]wЗ_8=gw#L7 X+cyD79PQ!n|^R_!RG'j:A<8$NDĢN:GBgh;A8"8jgsF]nC%H6&p!c7?YDgx:I{~&Ń(d>k9┻~j6UƙQ7=se p DV.J}g[-y[ٳNJ*nuNrFe3۟S[U *y'c%zy)n E6ytn0fğZ+~]eX>EN/\eSk8C0Z&[+ld3›}5A o{}g8X-f;U?gWLlWc+-Gz-ڣv1u]J}OZHbouhEEQ"Dthyzri>%,Nvc+9?N&shuo3~f;oV\o?2Ԫٯɇ|KX2{خX C_^&;.G'4M8rJ'G[ !!!浚mͼNKɻ;YrO8afܻ}+r|3m$@AO zy䂻Cq6z+?! ߁5o'B,hW Y#ٳp pkƲej@gh)]NoE}QEBSјZu;EEzLy4REQ?Q#R}8Z#EKgY{ZTRSIcS=<[Yo;CM\&,rUD̚ Dw~d?Pth)2鲈!~5txFR\O!QT)$u"~}TxF?yg_-~|ڳJj;)gE! :StljNl r ihQoOS(hA% H.0!7jAjyZgeͨ93'߬35DҖmAS%^Mܼ:9`u٣+Y僽.Z .SƻkDUnG_zrz^k %| (g4/ﻷ 9 6JY=OGVәӮj0@惰.!LڑӀѾ=%)wq,=Ǽx~~ vTdx:ߪ:V8j!FW! \3v6W ѭY5F¦ +lj߿^κ݃.5 mחsѴ ˎ8wEn{G7:.e#*_AT2tOYe֕|Xn(KW X.K 0I_ P lzÌ8սX( > }]D~5Z^pyb k {";rt*SU1z}2jݟ3Z|.>w{ )Xnov2jï33;W';bvDM40$Lo l8YjcI-rHshrVv|0糵 Gއ:K:Wrc␧-ȸ\%gr3Ehp^T)*AE #Fh@{WQUgmagj1׊(pEC{<|K^ѵ[j01$(BrʉZT-qj O?ՙ& TuU@L"qȡ1Q#-'gl_tf(vs2@_Ԋ s| j=F4tH(joji$IHslϨX*wαkʯyÔ^a:\i}[׊{A1 w5 3# S-g;zٯζW9m%Wquƿ25Hvmox:S你Oc|Bi-7s.0ź[>K#!4Tg"5XM߸'tK-"vV #؎4ϟ-__sD^VVLLfd-Y_-si<7S.I)([Gu:0#*$V3A^'غH.Ggm'R#qT^gm {dA+EyxxO֪or{=#y38r vfu6Je|EZwu =֠~+vK]M~[~*xk(LHS͘k:E(q%6ΚQm"ſɢ[. rk j\YD=*tSYI~ѳNt2Z{r}EMS"w>P\$éTȧ eStR0/Y/MԢ"9]c\IuZ$>(E((mkgBT9jg;DF]/_q>"kO-",暀G<ԓ_nyl3*UrTuZBvY+INY^"(j40JS+Ubv0qdK"8RcJ88]R:ѻ9<~Rt]ܤf6)iiQ!o!<CR RW3Ӻ\7a\cB*Q0<[83 [K:~{(슺vPuc, уejc,uFg#M&N oo5lBg~̵ǮQ!@>.naLLZʊ EP /}mړ}#ϳyxn &h,m4Ώϼlo⋱TJz2x'sR/k7eyr= "1ѨJsoEo;7wfg`|8oA< W WV!mR?[;<-sj$ȷnLPYvu胉e`Q6#.rBŶzTsnyjdFGȾeA'xK$035_2OkƁY0W_̕W_%}J%*ڮ\FdY3քBpլ~l1]7 `a65/v9Sry6E[7* )wCIm[3[1 Gs1"r;XVFޮX:~ت OggS@Lm`2HW➆|qp o|C[/㘩\9gvd^VڭT(+:4S ]Ǡ?өMߗzBh]CT*<ǁBf7p+NgD jWxқʶ"{*ESڨ EUA<8\|+?tO  RK\7xpl$E5G Q!BYJȹۃ'=OS׶L\x%# 紽6y$̍(&x^] M>JAp[&y^v4 әYqѴ~)WNJE:X ͙heWRqv6q` {_D@\J$ڳL½}F }C?@T&vӿjkas>Ε%$Λ{Aո^vΤN )Y]V~ f@5pS9ys^t(9>F5X4ZR0 U=So2^] ?LfC\TBkՊ32tXkCK3G赎 Y =th,6"9'K[(<*Ln0OF\*Ro/;lK|`0Դ1wG-߼:WReM{);6F,IϷ`Hsז8Ott(. v$oE[F\W+ݾ |Iq,F^rR} z+'toyEuH'#|cFZ 񪎓-G }T˫(zuhz/=}\s?SRD rA0>>f­y+U&פ;Z \%?9/4$ G&r[$Wnj4EÀ{YLFT:LBN~}Q  r'wwҫZdlYeV^!Clnl܎r[Io</߆θ"&>9$+}/*IҍJ)swPR-6%fozZqQ+m4'oS9W%[K׬n55~&-.odwɒ]ПOOtc0gr8O"Ѣs|C#no5-8ŝNzwӨ6-87P']ET/845s?+qҋwD-ܣx;~(5{2Z}-z"IPKP+GD)/f1hDvgͩ2[?<ljYm>EWʫg}*&i-T\>wwcKZ;b8PyXum 67mFTOhhMׇ3#jݳC}(j/rW$Mhz,4? 5mqw-QU*YȮI1*#_~+b6'z1q="WNǁΞiN֕=6n3z?rŎnV/tMQcTb%|1 ^u*~ )7"ч~N>%G`_[wOwA)sveS膯{֛oSUgRFhP\!̓VZZ~}J`mTktȭJSYpeIT&_[ztnp?LoXn[Z/@v{h{G6=yjh=͆y祶5zyi1hHIDOwH!Ukkist'Whj2&c{Lug@T苀)5@egP+$J OSkR㒊ThBtB"w[}=ڕF+rڵi2kh*%{Lo +S(pbB r=zafqŔI&!bW<YW/ա+Z֩G=5ЏWgC:H:R@V+D;z5T"U tz"q`nZԹq30 p5S|DLCZP'iK.1xأf ĤƳ,/H'/~ւ)ڀˊk hF}36vw&>pTn,WOv;5As aJ- Kfy-N L/Ž-'ڶ?^iv;Dת:jz)+TDž<|X}i,VؒW'xCr֕>[ǻ.(}Pso,Pn''Eg9-6`H5|?Z_B*Uʛb腑ɩHgeW4zV W0g%OUET+{H@-bꘑDHS5`^321:s?ׇ;3T k(]e1L}ϥ6bzT̟WTW4h2Zkv !&/ ]t^T@rP^CϺjC!G ;RC8s:|Ű4QݸYr̠xCס:&_**p"HA,)/0GрHU*0 :G lYCtMMi0(A $>KKPYo&.|NanQIڣǛܨPI Xr^:c^EiMcztL诫u'ktcѷBQ~Z\$ԑ׆҃ꬶ:[q%Ye./->9|ݎ{)XX߅xXf%V۸+\_ǽbjD5拣yg_c%7y~ zT1HDaXnFx'jýmjX11Y.l~!ȯ)D9 ҭ|U1p+U{v&2^  gSBHcuDH8nbzk6X[ޅcSkOggS@Lm`3Nv' ;j#g'{D r48d*kB:S!dIex=@X`GmӢ̇BNzH󀣧D\Jfd 7s pȾv9ςSLZI&2o dQ!tCts=8BPK2{|FurH6I-rEƅ>7tԠ"~NHgܻZ#@UE7=e@@t .]91M.m"gU(Ntwx3"S4|/ &ذey>yǨې,sWss)%C&[ۗ2LzkhCgqi9 gKKɋfi'Ӯ''WKF]ʬ/}<ɉmDf)r*Y3qYcՈ&w# {Qd. Z 1n#3D qtg.ŜvA2*2-;(0<&^1@ۻNcTֿY/,8X:']5ٗAuGr-8QIBlh|ʵs~Z[m*.v?\<i~5,xu/+~h&{uLFŤ3Wp[;VJ^U29!6Yq餌H=}p;o 7Wk:xwzR~sZ<9rt zZ5QNoٴ׃ɕOn4zie yZ{֫㉛/}fG?{|Mm4_k>K6i;.ޮ{q{.^:Q0h *v{' 9fj#o/qKB;#W \@GW$ZQV'̀3 $Ủz\oQK3sm>Uwe.dz?lJhQA$'NuppqC:b!iIT 2AO{7,U-I̝8Zdh#JtRBTj]Y Je_xn*Zc<$G4_ '"ז٤=[@2,mjfxqe򻳟+0uu-X+3:f=4]p7sʺ81EK7  Qy*Ҭ ǵX=݄ap4 Mf36ޞ8\6`]J?/Z׻p-WF%踷]sU|`:I]=Hp*y2x?h- Ry1^Khf]olEnm?~3~[QwBW%wp9&s5w1!n-XP~vkjõS>rԆW詐VEjVfGА8BAz*=ȲOt<=+T=jɄί!S&Ux53]FD81p9!!9sUS\5ZgJNtR2l<}NsG&;1{ۧFfL)ا8I.vG+m]\O3B.s='[>XTLJuTyТro>OyAĘCW4jN8c󼼙0Hmy׊>.%<-tPM^#0橏I19tm9sZ$( թ1nǸ?؞=-(2[٬ 0$}ݰx 13OݭK3Pt>BdLm8`w,V&}O߂5דYxL, ]WQn~˓"'1heNhW;)FNqAQq4l&qAqEqlg^t ayK%hEج+DֵohOV;ܯVh>Xݖ?(FnR݁G&gbJ .X3L>?DC7ǔA۬e~e<*۝YH!ZѵM}"3%h{ʆ+Mv|#gۗ䫇Rs/3Gh&xw:e*`Ãozut?'8~^C ٓ2dsrWHl\`dߪprF}1v%mZJm. sX lmh9f+\I^3Ӯ~i+S_nU87uAJ/n[K7\wj^~GG[,5p7&~v{?cV7`>@92`ԟg)xt8`5^|7gN=, ȚWBᖹ:I.sbJ #a&f&WFI/B  T$ P5v'<&wpuGr ;:>dN'VE}\*SV0Ѻ'"tWNtSxsRISŞd*55X's>@A dQ+8вrNڒj0׮rRCJ82Ho)Ϻ_^?[{EHi= fk._\4ܖ㾞~$&5{Z}TzrWsF"Ȩe)}7 2p`@4c*eTvVbMOjdIf)GeSsrwb/ZhiiF[ʜY߸\HXW6P,9U`Pƹ3reG0\=€rʨ1Oأğ.[8 >b=2kr&ZNgVat'C "Vv>[ÛѾnU Yw 7'ݝ'M5i+hpB_$dbUdFh·='^}4xԉ[/?WZ|6YSۄ;T9-$~F4&J0>'~{ӏ63zObԻgYK{;VQBoXU 7ZXw)4#7{K~waDZ"m%r]k^+NOe@ѧ?V'l'H|jSuÉXxpT-?or_1~-5,Lbv9K;Q}4Nuޣg,'I8*p5]wK4g']ZڹN/ *v7XԊmp6rM@OL3 ̢+Ot܎}墻A ~Br:y=OԉxJPi:)c;rޚjΧH DG*U%B!P yIS8nQt Rgͼ/=}n3Q N([ k?AsL)Zs-;Ô䑇Pd:$SH*D; :5/"DZ=[LăEy<݇4Xc/@{CmA{BDkTf:fM#O]~9t^5=?s޶ sU+{O>Tg@:]tMfÈ~svZ?HQ*}~#YS I ܟB;֊sB^ړ޷ ㌉Opܺ ͦIJkh ?pS$zm \.ɔt T+unKd*n>wk$oB%5d›:=З5w.?onr%_U"&H~ AK.t9Ȇe#`{G ^JE~{{%lROggS@Lm`4C(N^v{;yAs6v`wԆ _IZk>8C;u3wy>;D#q79ҒA-wv}< -O]pd1׊ GYTNV~FsT*A<":G!EPi05)GA}ǓxD36U֏ W2R?MD SP*A6Ȳ:i<re1ŔxQ!guaw3P^3S-~8ZK~SҵQ5xiVBډj֧RՁ=@k38!ACVmvR" oz1 @옢6;U3qXOrt NCNDr|:, cy"ԥF"uW Vmԭ+u\åS^saK>[\85tp{Z9ϨC%gh18R[6۫ {x{9h`Ю.(-LQhH<׌.jp[Ψd ogqu.YRzerF>wݖnb×|G])WEr lEF׾qŌTic9PzιzKo[Ü SG4eQM"5zc;<>uwq.^h1n^kߚBg|"JTkmB"iա<^rZQQW/ƕ)s.ј*oإ}2xEވ:QJ8=4Qk*T= { dUhS"U"T~T[5ݜ}B*BŪ[^;^oU}KGZw۷ϫ G_5 ;ύnlk @oc ^{^m\k5{/H6Jͣ !-FPAIڐvFx4?Y3?H48BL  8 (:IZ~2jYL\dx0w*; O)8N\"(PQ*3f]/UN2"=y>q8KsxoClzuQVy U@U' qun2O)nawC SW$Amq8\͒齷}z:{$t"Cz#>%SCjEѹx$=r:ZM0u# kߏȴ7 BAoZZzz{zKaVM$20]v{ڒƛy5JBq~0slj"df'@Q蟝Uĸ!9E"[g+&78\HiO?>=CRDZm0㑯Z]O___~UOafΜZ{׎AP/?<' ;sb2S"M'n-PBxڡ8}?8bE$)Pk  <2^f5~{jE.Bx6?ދc0_<},͡mZx ە re{M<\ ~v7嘂6LýH6q9hR : } he܌~ »..tu-ed[P@}kEZ;lW\@v :&nJ*Cp : _/"2$, ٯze\*1EwZ!. urPT*;׏sLo2n6\<7]2ncpV;{͆>u4LĩM:UpBv=5႐:s]ϾQ9!+}B6y*s:Rk N/dDwԨ$ROVd=S$,ls?Otq!kS=ʿyh_eݢas}K#TQ@IE%!ek8NV_ph/Y!i܀N+VZ q6/(}l@ҭ U'7*GȱnEpV\%WɄvOFF" w~Vo8_ej"r4 EEܜ0!N37!5g] u{u$)}ܻu_.Hkz@ߛƗY4 ԽۿJM囯Cۗc=l,bCo~f<$ڏ<|џ;2|N졺l/bhnUk}Qz/W]?X=X#⤙y%6nR0I~E-"{Kqēf5XpC'.,\c4;;u[gW'T=ڛ9 Ӫjqi帪s-[WgRq|7>uF}Qrls%e&bk8E1H?T6n4U[ո)vJnox^j# œ1z{,I:{PP{UJ^UMb? VߔD!pR['HG^kUQNGC)8og-#8I·+)$l1/nǁ :tqh <͚ ([#1Hi&\0>|,о".8rصFAs2A9zf Fx<_1奞%YKQ=ϧUhP)Mg~CPiE0p."H_8~*Iwq=\ZK;<T3 {YG]iEQ9[N;JL/jwLDֱF4.6pTT^ʳl3˄TLi?(}3PmH臏6&SvLJk {7M+pc3zÝqZarZhUL+ S6I!˂t|?oڀ'ϓ {O||NpZx֋_TV*Vb?*.l?{_θuCū8[G4콱]ǔ\ljf4 dcQ% grA~ @o4I=gX!n{J|쥡ц4g=꒍tDd L/z4?=tT^CÝ+i?%Gϯl%+sMۿM-h~3cʢctGnroRhRwdr.~v{"0Pc6* *;_QN'xKS/B2JEwړ~';2rLH}4@ M{!AjH^q8Iʋ)tD9 EN}cwsܥ=J⸍>:DR(k~}O.Ͽsp*8AwpB dB{nsȘ8^N1MS{{VGRJ*Ӌ Ygv+S39薝Pir'J-Uk-^/Q@I;PhLJ32[|="NA]nyȨ]Q)w/*$}tZ>YޘG06}}{`rB fZ00ijd A nJ{MMY& \R ºdKƯ"QhM>6ov%FaA`}ؚoF |߷k"6x չ yPQ|pOggSJLm`5KOvf7h8Pݾjj# "V=J+$o:y(ڔ5eod.RHz z84$Y'+絵/Z#괾ʽn\D]4O=:$#~>Sw}էH5Kƒ Y:y:~vzTg(پs>y_fev=S<^FC4(t?(~.}d?fS|PzJR*JmjJծJ֪IPD<}YPA5=Wy. 5~؂bc~eJ>=B P%k_*ԓZ髃u"G|"?;C*yNʶhӛ ٛG/q *U @Gƶb D!޿ kM;ZI)٭_Uc{Q=MS G= o(#ͻ?_ )n :O1#YDe9Gs%ҋ+l\/Ijf{1I TLZpH^sF$.?2)/gEe0姂u՚]8õA7_`ò/lL1Va)u}؍:9^3UGw66=)ɧzaA}9F%{-r"1kKÿ[f~.ß…ħ1V\v>#{?]]0¿3qv~Aj<9:\﷩+i+5YfAS핆8ar:إtN6N?9rk,]̸Q}<>38ԆX5OQ-T6&DU٣Jvi?`+(&8NeɈc=I/sG"~TΪBJ-NQTwhATQyިLx|Q=-SoR/Q>wpL3+*dty Ip><yԈ*3P[x_?I n]*Xȃ3%6V+тIĤT u}msبy=T뭗g9M~y03*),Q7ش7wl97BQ#]~ǘV|օ{uyۛBL_`n|ggLU%N#ՕEAabG7ɿ6gmw·4:V }lmR4m$#O&=ʺW׸M8uP*^*;z^gv;bPGŭw)i{Ƣ[P7x%5Kdol!g}wxʄu&:=2_mޙYWġM̞)~%Ǧ_@:_Ĝ=F^+riYY 7ε egR<ߗVL+\&tox1|yP^_Wu{2>So<}mf.pSU,Tv]疢bP3UWy-)RoCt!N.ʣo;F1yY_"ӻ݋i; n/YmT}v>٭e|v{ѱJn u _+?{&RR GT TGˋq[+rM5ul4RWլ@@%qBt|DY4ckH~|>PhfWfkq@R}oC~JSQ4NtqH8:JEB.}Pdz<>jl液:?~kCCzA6^H߿<O7=Qh qTjdSA)S/ ՞~$FW^+O׮"ЀD~TCP'(YJ69sϥ~;v'NG_A%c}(ysmՔ;^ {e8];bBcƼ0N/]z/?wD$:̮ ؒV_O:nO/br {A{Ly1l >\"{/+E7PŘ89BQoᕸXv}Ҋ۳ldP8nap`,1.}UcUn{?y=,^4<|#t2v >0Ɍ&9@UBՊIOo=S.cNspb^JT0gf ȝW7ʻ ،Sth7?_ r^"f_ z^UoG^og @[{xYn?(A䚃2@'6.Ĝby^vLvv7AmDm#TZnԥؿuNH%qsJg tcwL/rJN&05k2{ sRDs3 8fAdF${7.ON *ߞnj]u,)|!>i:K=,M.n%LHʖt0{hk>={깊G Ԋ;hA OyUa[YF304ċ|3o3_ BiBjmmm.-nG;IHM?/T蝚tJbLyx?fOM$.b֪lOs_sB&Bh!U Jh=j6*o-nl?'ö2-DH@kmXvy7l njP>~?rmH)(: WXh$M q0)!<]{ KmOOvewfok޹&¹p]^/f E^ݫ}PtkKDOnE)]mi_NQ19^ɿ guQa!^ JPQm(_f>-k\"L+Oߞ\-+(LGyyֱB-oϪUOL)y-[,:θoޫ`OV |o6n]c[kxbWh z>;,ٟRImpf[j||VwN)LZg3}}W %+0g_U^D|Gޭcwz`ydz=)(}C )N(H A*dduHTp0>_+g;M陞O#Ȥݺ/^|M]$Ѓ+HG;xa!Ac ۛ:?. ɸl|'}3PlS;s߼.Os-Ǝ֝6ecKlzymѪˮ~jUw6YE=Unzxex>b/ /N #c+zz;L>[IVg$sw)!KC{ r7/g[J*^}yj=b^(Y+t[kGn kyXvo wwl/G㾌PLvN&8̳{x'~0{ֽUAdNѷaqYIy3P,nBrR="o7VxyԣȽ2jb̩ wV]u*h2_ukui-media/ukui-volume-control-applet-qt/data/sounds/audio.json0000664000175000017500000000125015170054730023617 0ustar fengfeng{ "copyormove-succeed": "operation-file", "devconnection-break": "device-removed", "devconnection-failed": "device-added-failed", "devconnection-succeed": "device-added", "dialog-complete": "complete", "dialog-error": "dialog-error", "dialog-warning": "dialog-warning", "general-notification": "notification-general", "low-power": "battery-low", "operation-not-supported": "operation-unsupported", "photo": "screen-capture", "power-connected": "power-plug", "recycle-bin-clear": "trash-empty", "screen": "screen-record", "special-notification": "notification-special", "audio-volume-change": "audio-volume-change" } ukui-media/ukui-volume-control-applet-qt/data/sounds/startup.ogg0000664000175000017500000113740115170054730024034 0ustar fengfengOggS M^3vorbisDmOggS MTdRSvorbis,Xiph.Org libVorbis I 20150105 (⛄⛄⛄⛄)ENCODER=libsndfilevorbis+BCV1L ŀАU`$)fI)(yHI)0c1c1c 4d( Ij9g'r9iN8 Q9 &cnkn)% Y@H!RH!b!b!r!r * 2 L2餓N:騣:(B -JL1Vc]|s9s9s BCV BdB!R)r 2ȀАU GI˱$O,Q53ESTMUUUUu]Wvevuv}Y[}Y[؅]aaaa}}} 4d #9)"9d ")Ifjihm˲,˲ iiiiiiifYeYeYeYeYeYeYeYeYeYeYeYeY@h*@@qq$ER$r, Y@R,r4Gs4s@BDFHJLNPRT> "9@@OggS@ MvSZr8;4g p Ʒ?a=k<Gx:ztsOSeOSVFܽ&ߋ8B^YNku"*|ldqTy82z'Ƭx^fM`Ap~AAel_ZCΈ{p~&e2ڃ{ (Mw( p 2CǍ{_舮]pS<$ffzSӫGS>xGj2Ee5b21>z.5Șf3w%'^;"KF!gkU[O$<{h9gLYס5n.BϵL&GF;nxzJxNr1#Gdq^Ig~{097g"g㽪ޣǗ]#򬝐xUgW{v+󭒹JEh;*GJ;Iui)q>I*TvZO'u$)9̭O'TZJԬWTeV)>5jKawgI$hK-ʝ~<*tC%Rj!|9*-M(AMZy RB6"*F0; $gvʨީUB2$% /p}zG[~'bjuB*=47{˼TQP4W 1ބ=O o'+hw`V4[i_)V}cQ˻߷`d|c_#e)`}w?vi \tM~I)[Os|ωU9!;)Qtg 類ԌzO}^F#us[X**O6aO+) yΤ.XzVR^\X=I:'Ĭ2$g$Ǚ@!QrUBޟ㈋O.b]daG&GZ?vE/q 6r:[MrȆ%guC,tX/d ~1S&mI6FtUWP{C7Yuu૓ipssc_300uY^g3Ѧ'Sˢ`*ޓ{ny4NRd-#ZIe|$;0i}|4WJtվ?{"DZ*uB׌5L)N'NIJJ{{7rkvxYqdֹ?>;8Kk649NA-EYjgŝj|2hWQEj@ԟWZ2NȐ5GwqJ\;9.5JN褢5kNP{fq@ r"tʤMf~ M]RC{]kv:TdS!dAǤ8a>~RI"pd ^.zPg'fe=+TSȩYVV3I\=r&3r~w7'_#Z |Ɇ:$S8؅tω=V:+<ZDp~iET*¡ y]1])YzC=#Fb|$joaRܤ3BF zO X1<ܤs-BE 9bAG=Kmfwȕ;R _m=uotILMd(4U{PRf#*.Ia՟:<\ly\љ𨞖Xj*\uc1$6s= ޛ ^RVn5z.aXd UNB;Ɯ=[겜/xc޺v\AU1Kun* m__ȷYogﲚk5 o9|7@is5ڮ:̦FWoF"BHMR7nBۓ>)~-WsQu8gZ^m #yǽն=<(w9l~-^^q|p&;w*.ރ{ {BjcID*3]όh1Sdr^C!]H݉\)]R'g FJe=/e/%_JLz4$]T*.㤨<䔨?vVI@gOMݱL]55o[::j\2U}4D24wX,psMCGh㻫6SqUҸBwW M-~YL5]$̎'H萩U3й^|tkC׆܇wҵ;Z>2sGwߧ L%j3qlsy<1u&Nkj8':@毐k#$Z˝-v}B*ח;p,}]Ћ<ˎ;ϐ#ȸ\dЭNXf z=㚩W"ݟ:fn/W[E*i\0>?lTIh)<3 -Aѻ+h콽C(O0&Z* >DS\:Y n髢eInB$w?fR1t=g"y3n}n,gGs6q<5d` u7M{"bY=1I](tF+ zҕW']/!-Pm0jev>zn2얚 ;+1U=ry?RQ߼kc)Il ?PGm1ȿKPj&'O޽d> 5څ>$JC{vBKIpgDLl]QG_׶Dz*dYb]RK߻+ágجRX@˂o?5v:q` >_Dzx">[2kxr1 f@o JrXuɁsԯ#p::ffKRψsLjR?'۰d`VtӢ2ʊ ct\6 qu}w~IJ,9j9=g[G(磋Ohmc#)YB|WXFd֓{?I wOH 4pdY9!@QiV[ӧw< 9GU_b69lrz5RTt0uɳCkqG };Vjx;*kDrɊ&U-ydhxI=Ky\'i[W:4.P5h5 3xM=E(H&6ezvzPսGtGM'kTbϞߝU=jҐvOdR^3v/tFkڪo"CR,UZ묽㡭Up|pT:z9u ^FUB[&'Ԟs\~gxչQDW|R[R(J2YnݲwXd'up0Md/^\# 0f]'z.'YMX6p굧JOUWdfWb@_ze?Qu'l>F=|6ƞ{(XƖ7ѱM,5G{zcbWe,ۑBu-291+cQ8}K>A%΋$x{yWKymS̿sw>._t1lOggS@ M< zR606 *`Owq9o'ɸrFdS7;wi7˯Hqtޢ]&a $| 5ęA;mT8~!Ǫ'_,}aKuL,SW_Kn4aAF]2%KAeʋ,Ö[R|>Tݽ["7R|tuV3]9zop!my5Eh55˾."Q0݌.zkӁg+\|}&.19{Pg{7'W:05bV~5b:_.W!LXWedoHS7{ϯh}~h4^5ooSއ2?t+sɺ1e-ed~g. Snrj<;iOv~Q!Mo$oMyHTέa|t׮|:8>Y5^ݷۯۯ_ڭd̋[̿j pO)/~$Eco/1n=wo+2_'ZVkwu: <Z -o83~Xcdef .yuᅕ9)?hœRBQE\Z Ne jYo"%>>/%tq J]\|cNMR83!5!UJ^w?=iRZZwT]:/AWS34FqQp S*7;%"9*];k8DqR'\*0vc&+Eh|F֓D5{Qi`gUS32NT@甚թ= I)~ Au ﱷteSk<]:ݵ")BR$e*ZTfsNg&y9>^zOUg"JALB<ϚE>!砯sإGa` ڝ?#ÈT0S#" OBe;~IN~V^0F}SA/rvBtۊcWWK6X%#lik5ng$FCU ju^/k+bfMZ)Yw|MWI 1|wS_Y.*}]Se5 P}www5d1|Yw/Pa9I_IJS4]nҠ=5B{~]/z{Vq^wt1>4?y5r*.FST|G3os0?m/K;\e6nH*պt۟ynܳ8s'iѬz9s55F5u6"kҲcGBiDod۱7 P  .]TL|~sw7yG jttF^&4?*, /1d}NM4j-{+Xn`iyF`ҟs7M[v5ۋ$hjx~v@a!kkF׺Q$3 \b8ȑH$4-WUeAnB?tEF(d-]nh^*P>'x6TI긱vHON\+]8]T"Nrv=A o?K_%"ԩ}^jH%.d k:krQ{q\)\G[ fieY'^D;a8+S8ԏ&IȖ"VS3xp,<EZfעu.|sfH8Ԩq1^}gRL[>'ߙܣy^KD)ρ{-9__<Νls=L} Q;{o G嚱& , S*ɖN5^MN9]#'}\J=+6'맖iBk^B'מUr[p㚷\?C!SH_֖uPNgXMCfT&ķ m{djS]iBAGtp7z(B{Nl`Ÿ6\@t&n%ܜG-\uKm:OiL2: х,C=Q_%0Qfl|އoBvh٫?zM˃4pFv?嗰u*W\3+` }ژqcZ둦(@?{6AIrvpCp^-MS_gAq|ZTsѫcͶ졷Qgy $k%NzWƒF)M'g{* o S4BnWkCR꫞ziHK3c"x)f.XJYS *aL'nS'38 N(>Q~oƅK'Je={e/5|6{+c̹Y)\VwbמĒÑ)Y,6 ~jT-KͶ\?~.x>rlp~c-b[ L/ۖRJ9+x3݊lqm+8JeNHY즂GU? BH}5*x} M6 jip+B3(8Zd-Tꉈ8y١Zʛ6U4/WUDk!n=Gur[|[)Ych޲⻧8.H"' <nih?9@ Zeog/s(>_}݅oDz?P]Ȍ7/zyԿQ?>a3Ś O&~w $7+|h PBoO ̌]*zә4j1ژW}vYF]nc<5}77n≗ Ή+:Z|FzY8IDw,D4 EUvI}8Ky= \n"iȚ@Qc]K6Q 5!|= 3rL6-$h_ n2菔0kvEbXϔ/Wenz"k{LU{{0Jwz;ؽ&91M׼\_rÈ3yrb_b-+ϡ6'Aϭ^fUu-" ޢogV1=\3J®':|ޥpmYa{9S~=͵,ϸz{mukj|R+5\HvFOggS@, M(xĢ#}5W10l2D̏C~dkԚ).."r3!2i=w9@[ 2!G/'zu{yD(S(.ɮ/“DzS7؛[(A׊4._?n^3R1-t[UwWwf5!#qg_ANnx>j1=rCCǞU_߷ܻ9mQu1k{SE'I$I&4mi{Cyshk7&pKt{ӦΝgqo|R%~7+)h!YOϡ}s6!N%d wWS$aͪ t[wgYoEjzGܸrT 笼f<'T9n" ̼4.X 19Kv.Z{7|T7 y#1fDu^8[ ٛW^;ߗd}w^sprmRRK]?Tr w"~{̇Ec5Y:9!TDkpRlX^muYV&<1+D7>l粵Ʀ}@W_UjC6>߿6"[RT A?B]̊f5ͣo WR[VkRo%Œ< zw+q0|*T {wxr?GXNg888ؒ>|,LsF“ I J kny*9Q,k^GLgv֒˸ZjO4l H/v}Gm|W~j;to_vkOgo!^kx4!w۩k0"{%rJd-zAN¾E[z7[)27ۍ*ݗQH⟚XˋJ@s8v!}6U!i; u|r z#פ9q_nQV)*̛;= X"~0c9]!?1l pAcmbIJFOm22Hh[ hu}yu޲ PrԌ;1.pݵVGzdn@x+9@oz~@PrQSzy6\RS. 3<@#$[IyʏqYY3{MDZ Wo>4g=7ijq6yclg5wo22\qؔ#:}hp~9SȔv RqM7%C> ->^Gl,ÙU,nfƤzH%Mɮ0d)s߬KO{i_S h7^o1wB|D@zg}uhEɫΦNW&귐2,J4, å#$hLktnߗ\3ġ}qPIIeuWSu j<>Szv^##) |hf`QZg: C2,B4@CΪEO)0~4htw忡鬎7 ⫳C?Y :uiIƦ=| >=čY+ٛ.%_^cl| M7Mc6n^//w賳~_Y|`~L<,)"zݎߟ{UGz\5 ۴;eS{[§p9n|m<-9$%:<7yVm(UCu{fyRV, ix^ @ӴK,P]b?~]ecnCGt^زprpv&wǻbk`_/FZP%׊Z|>aRi?[D~\a]Fԙ9aD G>X nZAS+Qh< pbUՂj~ЀCp co$n׌ ibQ}-d F|Jp@ezw  rxs UzR&DE]g퓔m`v$&ݷvOВYs:+tvuaOس)jF:v4'{6Tʸ=p󿝽~J:Ѧlc,D+Eḡ+XK;*#D@wT #qL|eit?T>ra)/2&w["2;޽kTb3ޱB9ԬԞjd`hݛ|ri c`1,36تU|ua@d e=M2`@WO0} (_PYnϽU?^-YmMOt*w*vd6wLc* GxJ.qVܠI A.WH䍣iu}_=YQ3SͿUV%/2Cg/J{vNi;QZ + ?/>ԧ`Vb=V'W|wZ?as]@ 1K66$^mA_9hW{)>s:EԋBM$xt_/> ߷ Z+`^ͯP7U[K?b< /5Ok;#W׽[\|Z?ooXP p=paʖ-) y=0mc<6G !~|V`Bn@$դhCpgR?9SK!^< @n. |0 0fx̦TA*Nh*5jTPA&G+zh'8zɕ-OGg >P#.]|}u*j }=4u@#93|8H;YX8[AV3SST; S+: }Gi^P  _JDTM"7_ U@`G(c P& ={*_!` zKSxܽ!m<D7ZCHm't,ο=5D;1u<kFs;@g;dRD` >&eIEκN6z.e_0[(1Hs}Wt~ΩEOijdWcϐmK/tj=miB$Rg?kL.bTAsF8`h8~t đjiZxP۲Ƅ 9 Ioyj/ժTR*[|:nyݫ3X#@K6=xh(l3Ҽ!-@/ dK(ċ(c~bsg}׆?ɕfU#-b OggS@@ M=um )ZJ~Ǚv[ni 'ss-UaRo@!2o_0ƾ@( BA" LYv@zDJ|瀀W|l\3@xO(ݼh` D oI o@P$30%9Fhj桥MAwSP$ )Ҡ '%О?$HeS){Ж)5O _pC"7*SVx=.w9#]xUƦe\`M_=z' :oS$ð@0;.?`;8W1m`sK9"N="|gJ?2D7n6^`m0!X5s!K cpc\@_g@RICWctzt d#UV1]யKNDx܆QenmQO75RR:b}Fl/Ii8hj}^`_`oGI{-4G&x8-؈N+}Lì.X&F&XO-样NA/Jh~mPjз8Ƌh:~¤YlZCnLu;ҵ'ulnowG Qte3$F,/ ʨk'nWeѶ-~?#/X'Kmϟ20J18l#`Yׁ~S#[+YVB@-+gKgFٹXM_Vqv5И@'?/Q軁@?MEsՒ..G픮,Nlb3b(S:n__c[26 5p:Va,-.BP @5/{88AMT> j&fPq]Ua}f#hJjIfz>%5f4/!EP`=y=}/F8*X[dC5Y/]DBv7@ J d)pu:}{$Uډ5#1Peu6}z!{]¾4ӴGp/Uد:mʄ6XNm#Wt2Y;˜D~PEVpxΞ1%^$KDp_wcՎf.tP347?]](JΙi+DQTQP5ihJ #!YjNh? քD/( A e%#sn2%,=>&޻pJ{̟v{׻L7{\Mz!AI`e<^e:&o&ӽYd3}T2oy޷Ug:n\wk[ּTWZuMl?[{9c aZ5PD!rώHkvkJ}C3%Ϣ-QV|dsIHŪ{lTGл4K9Qdz|?i([I@wZh. BY)(,~7& #ŖXMmǏэ >3bU wus.X}̽d?v.p$|T^ۢ50os#wr S_SEM?'\PǑw0 8*DwC *"RQYzԚR 8z@@t#lÃVdV.y@J@ o'Br# $ևZ5g2 SJOrR~EW>cTG"{r*jQάom!C0sX:nߍM[܎c]:`EDԛ>נ\Y>M`jL9N93\Pyg6~r{gٵoY7WKo?d}^\䡥zVۭ?bk!_+ ʆ^೏ 3 Fi\ydBxݮjWں)-npZ2d\[Xr^`= e %ԇ4J ģYض5$ I?:3Hhx#BeXDm38nE(WkזfրJk[:ve4aO>nVDc!VOj7νw 䍾Kq2}_ϽY%/֏ӈ&9|4VM&or]cݙ.dXzOKgXrlH@?|O J[c"K\rnK?=un둽3Vj̰v*S+]qoZf)*,[OaW ; y̑&sۆS3,]Q:*o_-L@\ʫ0wm?g'o 2(/rPP2rb N 4T͌ _ڨqiYDb:112~=PG0ZdSqs=kkl *޸{=a>2#>DޠG0bHOWߣ/ Ru^+//x02,gpJęJ@wjGˢ8Qh-*;dS8%E+F~h BTɞlG/{i">r;=@`fFykF5= 01~-MJ-´_RQ;{-My-T>'L>^Q`8C?UhyӲgJ߻$)(Guj>G?ّR#igl_>h֎ P84A|c{fus|hO *:6?Dlůڢ-uwYz!#:hw@|GοU}l.Bޝ=Nos\ZlQ6F}%\xf߹˔+2/ ,;\d??V/h $cZ՗;1Q<ט5XV q6l|PV 8"]9ոN? "U?}uJz~<×_wtFbG_3nz*O}pUDN?jW%KMHbuviV眯UzQ\:mgx?x#⓹^ϫ+;swBFk3Lf]^G1ކk<!&-HQMj$1QG tc4ԙkZ."6dT8!QT2ьGs}=Ɵito!náp`"J1>~ A&f2rϤWhwIEsM7UBndH* TZ=R?f}.mAusq:UZ&0`Mrt+Z w4rx"AzWT{ɞ_G>xa!^ൢvJfmܩ(t!(ݜ$Jh D@ qWE+ ` Q;dc&F =)T#ڳEƋVuZU<3=Vv7+4zf1y-,V D[cr}纋)ԫBΰ-]Q0Ji߷c[}g~՚^Zοy]_VMn+7X|)oyɹk,\ yX#o';&/V-Md&$mit?w/$DwYL7VwcgL }aQͲ|M߮l 6E?!b9O&6 r?6Uer,rOwr_ERFôfΜSAK͞򜦀e`=}q$Ov=Q}S/)WOggSN M1(Tu[ïl@kc>k !"H1FqO }YM @CRI`߻j)L%ƧmQ22>pA#'%.馂9[*f"~IقȓEr$(}/'f8IO ѐ g;{YwLJu%wuFTtSz泷:VevRS_K2C>) ϬhwvJp/s0|CBX 4OK(]Rgl}_9: Ёj{95ש,C޽_j&cӒ»A=q~¯>h૔|꬟/푕Gi Lyce;nPk(g2J^ѤD=8j@Ƨ.{ ]𐚎<(KSg,K3]0&SENYo=5Ǔ$ɀ5M.z%Ӕm]Vq;ħ6:Z!Ϝw|j{Uj{{ZYuww|MO߃~\3Oq>rIt:mn8jkq}C_u)i -__#LuAǨd{(ba]j!8jeUzy)GcxNaN~'iz=V)y+^y[qAJB|\]{®&rC^^c94 v,QE1߿jmbbu.vLA}'󧱂 bb'W8k}lBBNھ~ykMO}<`K6l]|:%Зoe_|}v\o/4n٨?-{eo,D}[rj^SZN}eJݹl涩P~/LQgqEB!ZVo7`h 0l?&٘<裩D*(>,˦6rk{l%2]s9ޛ{07F=1#RYKFɿydǜ]C9Ç$qmnw ! >/ZN\^GֽRWc(\t:y!T?{^.{nTFzGw+,k){ QyqW~zR>,/|U{?b5&p5að_T(P>_j{e}$ÅWHM?]2')-m^poߍ^IrO~7kjq$ږ;,8F^|_emn='9߫7-3_"\4 &1;.y~y;HZ2wх>bW ٿzxw'_T\,&E4JB걂tTj}g z˒F61ķFuv?$Eꁚ{Sg=Яq>?yM}uO[^jD1lg]܅Ԙ9/^Nd#j |Rm5$WbO}.?됉}u2?5ϼĎU|I9\q- UU ԝ`iڹ%Z?eҬ 7%5^cUBΞk?΁1ݺ8~3~2s+)4aM+j>' ̡RЏQ˃%5&MlvwkӾ[}H~Fv/'7j;Pxǯ{ <u]h=UN(е*kwq-Uekx+## zi4>@qadkPvEC)y=}>ڵ/ hTD }t?bAދɷH? JA::i>vj3[К~6 vYoNMR=t<6%"ň}!w>zBlUVڗv8Pm&9|=yhfT4֝.6Q*EU8^_9-RAF<{*fEƋX h81?{}ap.qtr+;{4,C!pyΓfX_κ*YL\dԫ/᷽_#5h|*|+\qHloWxke79F_Vt`6Wh#VFs׫c@~]QBYYi5_,;VJ&y/$\P.qx=(ivo%\Hg r|7'eO8ۦlN6'έK/!^ ,JlX/",H_Z Aۀ(D~C#; {7μyϰ #znn GhToDlM6z{k*7 >5 *Kgr1Af WϬUj| "c,B_c #H k+cck;7`͑(hl7˽ܮ0/@۵?&}I_NJe}u~r~˱},eޮ=߮w9_s~FY}ǐkfC snaCX4>7nl{/7LٹVj~ٝ9- .^:`xewDZ8U3Sk >J> ܂#]QO 03豁\akD!3ՓSx PQP3;US%@-&Ak!.LZmEbv}.UC0k2Q*/zs+0(XAq&i sb#Zf9>̭7Ugȹ=S@7M?:?Op.3j8 $˷D @<=E\Mw8S׸fcF5@zGbZm"ܘYE,?lh}2^6Zz[$yMU>wnDH'@]~0Q@`)@Nik|iX5{Y)^C$N[ru36~ $ B" SV$pde)No͋|w{(Ѿt}.[mX^ ym 8QR7[)\@LFy'yVڮSĞ)߃E9WUs/Y̪+gr3$Bd!sL0. 66`*.l}+sگsbx¥\6Gr݁;)oӟ۽/]yI#'9j|8eæiU ?35HcM& AzM ]%:w~{Dr?o2^qY(13&*Un5FR$ݽ=\Vu3-]d6'f܅oZGW5!$IKl6'ZcB曪Hi}{v_NYyӢ{e9^s߾>Q wTPv%LFf٭POggS^ MX{=@o#s0k껀R m9 ZGSӠ 4=l&ϑNCT 纬]v@g;F'\' yA֯K͞޷!] kZUz;hV5h:żgҢH$38_$RƻO,QAs|(U"JdJC!t{٥nyIGH2$w.Ы1PJL֏h4S3.xrf  3h}JV..?*OП9S|J_S3ӭ=VcxA)ȴmZͩ=W_~Os]*Yůru, >u]3IRv.U- 8J>B2tE屩 qI(dm/I@y_D=T4_Ӛ>dkY+ʩz"Y0=JqƝ[[=Cyq_y,@/pbٻ#|ZrgzM6]@+*b@VY]`אg 䧺V @De7PU@E('@z{7 ( /ʯOloZk`/T]3HPBi6@opjR$<Ӿv=`6Q]@kD+#b~hpm9"_ jjЙ317#uI*Xw?W|JxbtA٪1fɍ}߅Kd$lGcKSct*q$t -_Ԩef&)0}]\r%yu_?*0~.ϏS|$Y;dX>Sڳ@~'5K7`3ۛzxo)$Lm/I!4a\p6z^W!ΥOb3m5%|+)>94S9LcV爉H4Ga `H ~os&R=h@ #ųɞ6̆cDdRTgU'?ׯ*ڲLvw3u5inZw皐l9QkIj:ܒ\WO\df9%ry#; !r;Xpobv6ҵJ/C/P"_f!{긧~ϸ>/?v][_ E{ޓw _|']tvj:(Xl:8O/>}0?Yeg=:l-ߙbp'- y uQ95w%Im5vYӡlSDQ)|8*v1Ig״v/Wo3.IEU2e<z$Q|kېǧ!r081.8CUwǗI^EC9|un{|h~[E @1<ϙ۟jyoꯁuayߵ_~Z|=w+{#Ӷl❨Es'5]@TpV/ AG8?%Jv%: 4F.z`uf@G?wJ:D$~jd GmAZw&;7Ѐq *Z$ `g 6lkȏfg CEhNM6ǣSB$̨i1唏q ygB( MERu4/fw {~e)]$2ThU5T1gp/tg|!)vv:@ d ELl$O3 j@ɐd0chZH/l̾n۫_~bdרms6k_ihSfCxpg .^45Z t[$0\ E$oHTɠ0{]ZƚۊV|T5˶o0!)^M`.Jc?lFd\Qƣ:5#B]/u]4# rK]{m0DW9+.&kq<^"7~w6'я߅˳ՏDL `/ DL'6vt#JobMl2{ӻ (tW3**4]KO1Ѹ$x}<6֡V1_zր eJ5jy1 "PvA AT(J$UUD&;=bF"0*fUaa{3P%?ʡ0~|95.ϩ(Zfz3H}Sd45Yz?c>TinyCN@0n J j M͙~e$k5v xSX|'C|;i @qpCe[ݿ3 >STr(I4d[GspsKAY I?7/αڛ[8Ɗa2@mA /[E[,ԏy>02IA?oP,'S 43nY~qV Rh2Mt`JRL+DE&4,rbUouݷ'J[E*m BYZ{mI+Ǣh%vQ֭bN@fcsplu{TF w2ULN.\ubJh9{q"v4Zz'l;^1U|^f;tDǀ^4G.ck%PCp.v;+-N_RZ ZsUӥFϋc [q) \k!Дr#FdfڸFsvCn۵R4#}ehE4n{9la^'>D!}e}3Sz%.sΌKf|P!^]r-+2ـ+6oA6Orn JV˶1&s(ƮC-OggSp M֍Ʒ{3F9d/3x ǂDQg4}.8jhO Ulٻ% #UzTqa_QBT}{ސOÄC6[~~|-%jjsjL} (+ʐY@ FJN0WQxeRAQ\9̸jYf^_ Fl3ƈJQ*tfY106O\|'D tz#Gc4@CM;)*{*5?&7Ax O4S.Zkw(S QkWNEBu ) 'vK0('eX`DsWhVW.7,4 ^ojK##l;<_fcZA9JY=~|gkmxgf8 2+.:zEw)?Ìb Gžw~on9FZOP.3'ӗVf}s1&_(lj.Lؕ.iM@B0D[*t b"j8!b92Hf*IuXJ"MkP. r)Uc9[AQp| #e )Zv?j)pm^|Ԯ-_pu  +;9ldoƫQ:^ٯcO6m֧ͭ$-eqS&w,<:¤)  ?xmy[~Z@Q 94PҎN,ɇfj Mg֟ns>2cv-?R1* 9\1^0v뺭9SK{Tp@@Qs7`VѾVؕJ-}e1ެp~E㱺Z>]pTx.xc5nCz%o}>ƌR4Ktlhӵn~n/VA: >YvRD)1]R# #>=v2ɇq S 8) -# `{o V ըcrJn9|Y7N" J|Z U p mogeoCXt rf;^Kul?s7BfZ||wMƅr=ZqZJ9d9L|rٷ=^Dd֭ׄ"9ϋ5"ϞodlwGNII@jo_*RR1X5s>,}LJ`1s2K&B4-́ 5NJCJ4zM)r] 4q-:Z/kw+}!.!S,SAjaιGZ8?OJ9 ;RXfRÝ.tR46Zuc]Z/629o>U2zO}%\Yz|Ηh>{Z2={КONK&\"1 i) L9㝴HHkS֜؉V^>^߯!ٯjƱ/x5\.CvN)R˳ܤ5q!eANW~yΆ nĔ!lc6lvZ_~ n XsNӁ[~3yUW½l3{}D;-e<=]z5.t Z}a~nX ..d;ZBePvp⨷\SiaMʢB3eNHe5*=Ɍξ_^NawH=kTQk/mU)5$$QsU.2=fg)箵R (| W=n@ҩJwK^>3["47@KRUC Z_=Х7jRt D(")$#F?)nYZjN{C9z@ Mg.G($2 O(elC9}dCŸ^-݄CT}-CO'ш1_ћwYn^2!f L:y/3>ed&[2A=!v\z \ l 3_} $7s!-{& oe8ޯ?8ܤrd5SWoǒ˵&ѓkwGE| | %:t{ z0!dcׄY>ٶ@F +,i==ɶܬ<&m4泍 %C٨)!O丕`3k 6m2EEpvH_~(m4|J~=:5x"5W<;Te >7'x2wh潷.qx#&O#NHfHOYU>KۤߋW겯W?A̭~[Vae8!#X0v۰:kzN%1]zijj[u[/fc pZEnMWM~mV.-~73mn&4IWҋqY?=>C9l~΍7}v!Jᱽ>sc>Շur^Fàz:ˇ|(i6g7<92$ ;52:7ԫn;1XNwy!YQG Z]݉D6nQ}#`j8߆MW1Y呺7{{|L/OW W֛ԏ}{3't3Q)|X9,ȑ<}{ߧmGl*wϱd7JtOدF BDk|WUsbF1R[p4cy^- ڌ2F,pT_LѽU:H$%+㇬D@0 t;mNPN `QqLSͦkҍ}Z22WfZ~QpӽF%ѤST==wn&Z.YDZ]l1_YJ]c eX]E'2lg5uNў;*jw'gX*LxWq{Hz9H{yvM dכvПOyΗz :r~gW>fyS\tE e7ڻa7?]{^5MMq7j js<~}C׌܋w:*.o"rסXxv q7LQ~{}c]*s>Յƥd6wZ]~suϚ .XY˟6*I'5^`Ae?[ȯl2: 0Rc!0mOggS M Ҁ[5,<)<?<:w=!@I|-?k,,bFzև{zտ?:sz)=nBjR?o:t9tSpdu69z[)h! j9W|ZTV[@uGp-6Ea`lq' VC#~C ΝW禅>fQEvL\΁VPvyF|t WqFt(00 /uF(pHI28J8<ߞ.@yf`ʐ8>6oM8^{#/V%6jdha]~\2f_(g,@3c|WMoLxf~~P]mAh[VB>}ʑl8<ñR6n+mC` t}5_wM^lOz+(gG𭁎 Qt>A@C9V1Gډ SNjP4`艊Naѱi@K7t0K.v=h,oIv@S/,d"4"a Q&zhӵ֫@ ݻ1Zq+nrs.{o .>&y7g߆ŒԬSd ϜI o :#7=KxjSgo8>e>V>{RFy.q\Se5 "q}n>5&%E.go}wlVskSk⪘tsn?")f?tߛLa%EmS/}mHzb| F|ՖJң9.l0|v*nV{uc3Я&x%.%~v;}Ob b{\5bLRNK$/_ 4J!`m^J֯Bt$W8w h2Z@t;@(Ϸ3hMP:ܐ Td<]$q?=,vu&FQp-ETeQYl٭K1#Y'Qr|1TJ3GEJ[RJaf1bv>*jQC+AvmM8))@µ  K*NӧO2!4\b$2Éݧo,0igWLϪb]xHܟ8.ME4f|p{>fx&I'isl ٿcɽ40.6 0)Lڣ>_,X[|M#,qio#Ru;5 )y`C.`9R5>%Z%h'3XF5Ο^?{H W =h=fZyH␣!&bV Wjxd&6[e"3'UbI'G7.DrxV/=n_FXY[_]0nwcRޥ{\tuzk(˯w♶&+2q׍fy+uPSr~*v[:OmYIvQ0iouTT0lk*R_(akvXxSMu*ˤ&#;zcK{S*K۴)Qb 9I.i7viR~!Y.RdGlv*)s&Nj~~rdJR' iZC)^\g> I>)RʑE ߵM:Z䯁I;؞ah3mG0V` jC~Y[L͈7=_]ֽ,{c!z/_~L eQ#g *(ޏwKV~F4nԦ9bm$:~ʱ#׵VkI y|EUj|i`g+M9*17+ pJ ė (ݧ ;1 JNsytZ`sP-b4CDyy\(ff`KS8zP,p! D@Ԩ^2V?k@39"Fihb@Ej1)7'@2$><cJnva7 -g|AD71?\2}I4SB\FKAäo~&K;r "FpII{aceF`6}[×r]8H 'xm K'ҵZC?Db#U6~!>z@M Ta75+%Kh7y vA qj҅З]OFij)x ac<~«Z'VD2MǯS(}Ց' S^#T rw05v7T\z1 fNs̍ZƝ`h؞^ cV¨;<**q\-E}fR:6[V<=ZM=Cs8=K`&xھ.5@vSQ3PUğXMnvm?l{֪R-s{<VN?H/5 G:g%y[z]yl}![26+'reXIx {-ά昙ZO#om֌A]5ZmA; W-O 9UBջo0 z=q5yw,N~?w($N4߶?G.8'OⲨzȲ(w>]}1.}ff(هFLI^pҬDl ;KZ@i- $пRQZPC cvaxrh2%*8=@ iP%#RsMѿ'Ҏ$?}qEY,k_-`1^Ԫsi˵@ Jo?zQDY֥@=uhBl$rhEUVl=CkǗUkS[`Zc'ۊoE~" |‘j+Q;{5I] 5EE,4ʡEa]\g= ´*A0z2V"qf2fi pP1u JO/ >RZL7{)oq,\sp+ ,N䒾keC})ikでӒH-}7y_lq{h*)j~Y2Vu>羼DD5'󕘪w绾iM˪6BcpcU;v'׸r9OggS M *Ɂ072;6} O'.8I-*5V6%$_7{ Ttpp >pC'=~jm?yLn_KԬCBk]#!x,n!e{@U\ۓ -UzFyԽ6XDFsܒk;Vmt4(@1W31@~2/ ۞p/0; L Ӯ¡OhUX5k`Ys{q~[KT 9DcFyˋ\h鉵B"^qϖ 3!#*ӌ4KpQ=O۷ɸThib{B)C`8i}FyLeciBez*Mc)Chš 7pZV݀uK1'CW* aHS l=r%o6Pt1B b }676ӊ!kljzGTWwKFxvg?G)os#ݑZK嘎WM/U'Du1zF~ ^|6D#wJ.nb~bYEԑ+"PsN=6p= wHW|H@V6$jWx+$/m\46˱]%D7wo-}~[Q\|tpa[6o9ECMkF &F.nH6Z7Mg ~OťᾹ/;J“ǂi䍁 zs gǽUK0>Ny='v2 ]zw~TϋuY$yrOrnjXKh?!}hS(HuѯuGPw;ޓ"<=҉Qo͊g]Ɇ rHRͼI% UHk6׼a Ych?o̶ِ~ӯ9jj5Js7;'A.xzjJxgeS7/+)\Yb]A7d,uXzF umAOt ",Tޒ)Iކ![:?[8uzc^[+]t0L@#|Z>MT ^^b+ Q" ~d /{E@6֦])1VlL5LkMΊhUY :G6 5_C'(0 UUܘje׺_kW^|ufw '%6Tf\|'1qiEoL/YUO ]t"0 t-&"].&i15{;< pi@trRZA827-$oh[5. A?;VIlyǕ[ p̒ 8M3qQZHԎw8fs}L j"^KQfăqmMK+raka[sM  yxgE%Z` <.{ڳvS;<1<9y_9wXaǨN50ƇgW2p!D_9U iJ :#x:1e1ےe`:) ã'e3ȦҼ9L:<3'@I7%`z:e!s-K#21$)JRu\75w!Tq6c/}SOj&2n_Lh:7VrNW<}**ν;X{Yo:.U.9'K7]c}zې]'9|Q;\m)f}jEVZ7aw"W:]׏%p-su֜kKbو0FM~[&W-$%6$M५gu^n. Q:4ۗF*jlr8,girl_Wգ?in>UIGN\ dM2 ~R^ۉ[Z cOލ<RF(M)t3`ZǛntko@W*BAg髓*=vPf>ˤ u>#qҪW~EDy`iQPT(&@VDQD,)saZP<VgS1(fTꧦDWC4'x]]m-񂞩\_*Q̔i9 ddk2g:S@Co^fEMP@s*R:[Dv ((IxwUx}m+5d4$ qk =dg&/vхnT碑gN[wUD e#+.6/@BUnź"q흹&nn}j= j9$&X\a|Vl#0|szJVR9vf NR%YqNRIDbU*SmNe%=xo[Wm\rgO[eݩ<.iVǐy6MPwh-HO!)&OJ;R &^88ac+<כ,On|OI >N/ěTL ;Ea?$dw>^G?>m#?s/t||og7gu-wi{?5ZϚ7:䡡{=VzD`{~"xK%JRDlC 9͈EY&e4>*h Wϯԭi 6 W 95^TJvj_&4i~8=SoMO5ǧپƵPNKBtGTxk>m?ܒS.Pv)WQIN0[>tet0YG]?9*NvHzWs#(M%l8}ٱ/4 @#Bx] /-}/l ;_#C=\qb@`L)J`2Yh:O.Cf9Rr5kR%Jۻd>D؍ISB~V P1Ox߶k/PDr|i>?ϮG#wN]ٓlwۃ:,W9s[H4VrqggtnxބlA[sڵ#*.WA9m?}zgt vugR~v`a_ud~6"M"9TU3?|P@'nx]'gs0N0妨A JU=t:Zr'⣕ij;SUvKk$Ts$Yj\qo?Pd:1GWmzaOGheTwwSkjTdJoҵ9r/}zn#_l)(qh]M?MqӘ 0΂Q |ER /쓅[~n'T}࿳P;Né B~pѥ7xJ~/0``Qmccvh6ܶd;IG8/__E+|ӲzuRױ?Z]Y-\GVTb0 h߳E6׆ٍbJ[i2xn(*@/lv!HE Za GAWNk!sdM".헷tOBm6ȼLHqz/kM7 鈤82F/[$ℷCev^myt0[<ŒrkMk,aبRŬKn3\a3։"q1PX_lyXy9'4݀Xmppa=@=~~ӎcVZ~G!'[5{ݍ8nrb|)` Wꑅ^*xYsݼ8?U"o/vAONX#YN4\o؉pn")>[~%_q0}Ͳce)n)`J󳄮 ]_?/2#Bnۏj77?" {̷8eKQSI`E3}*Ϊɗdr)(zM[(u+_{qqp3][\&:/BL)7Ðvj~咬_k&:8O#]X,%S\Զ$K$yKVa[##ttgWPtR`5*(s5J_W_\0 )|J.DF4Mlk0w6S>~z hཫ_{ ?j6@;:6~ 'l`LësJTDӠ*qaVla*-9%Z'a^\S[QlP)JjGed-jYH<)Fa:_x>N3}ϿZك__{@250$Q ]S7Xdݜsuڷ\Y:qj.#K|պ7Jw {ybaտ*e$F~6}.8r@]wA))[vl q;Y1.5V8V7c8'\}+:LQ,2H`G˷dVMs\.cc:K[m4ϗuUf}X])kAh_Y{I͈w;'jǒ2b$A3x5^,]uyn&  {wp+WD  )GǀKB wp?Va<H(2AECVh~S[UTsz%tBU⨱H TP*[M8*)EI@xs2c~sbMGMNX1tsId@=W $z6vϑGtY!f\wT),{2=|R]"xAp".%ѢFM$~F(UIbΖ T 6u}^lZz{D5r6h^ 'e)gq60[}mӂBdA׭(OggS@ M 4U|))kzhwYWV_DۏS".}zC!.~3Q\;C@F %yW T~EmQiO*?tDgJpENpHqmW @ E@؀vz(*{D"R50snZ':-Q9k9*ZJ@A+Hܳ-ݻJ\>g]M}/7Lݒ2: D$ ]F"+P2$0 %^X@'bz'950@wAԑȡ hA:..xE &|)3\ugIBa93pÁ j-@F}INt˦tayq]Cof2 toy:\qB Ew4 SwR+⠱4! `C~_ H %0ܐxm]|W֢7WUL1tB^j C(y _;%v K6ΆBSWВ W~Wn溢٨0є>M7rT[oT{ɹ(& S?s$3-I?N;^ <3(CDCt88PX_Å@1SilŁӯ-i C'ޑ zhw8(wq *Zw ʳ}+ \]m/y`,E0T#(f#!+y$wӣh9DGԤ)08tV]~~G."^ D, 8K1"Ҫ5}@Ѥa:DT,Bq?P P2uTda,gwõ}i-WɈ>]ިimMv}lEO4~Hh!-o(6Jr-`Fa h F㷳sAnj:,-Hzi~7@Djˇkg_~pޝQ"{^(0$>KQ&2J,FD|idNyL#Ű͔K~xۨ `Hv=8/VN]+72e|c. 5cj)Kacj3+sK%%XV~`/a?g ez _j&>;GEsqjVM:(v(;QR}Mcvz]smocWQ"}:.;8͔^"i\&?Vi6Ǎφ`F8]YhCl2J c.,*#}1eRʾm)?P8^ 35nkx`1 -p-* /_l~>N "cb$]yֿA!?\`:4T䨴:[-R_ @&፧Ӓ^t{z_Py=O VD#cH$ 椠5 L\r [" V+UC&VG bPtQw8ii]i@5)8d~pᅭ0~s](Sk= ק2 0dyI1@ve:$I`k}Bi7РSE;86)i{Vʞ_7}u_(E 㟳a?xw(`P;fX@_$S.~ߵWg}&rA@~Nk/N?DI}x cPY?w?F{Y_@eW/o6%p{~FbuLPkT@2x Y1_t}n)d)55h^iХM6H(hmV(*`;zmCLJk`%J,"e}o5&Cp־HfVEf,vh@hmfU}f3j>9.7 o<c͜è0} K*3uj/`un:x뜫*Ԕ˛,oeM\dJhTƋ_[zM,2 ж؜  ]h9`02yyzQ칯Ϲ7]Z?X8=&3!0o ^ȏ[pH Й[z*j` TЩNWE9ISD+!hO= WTjyx^К_~Xž^ݿ2H8 x>-@]uJ`El5̛ tJ;|8x.$JDY e& 7؝ƀRz'/̊YT$TC'$ITw\l-W}ʕW i**J@d$0+kȲZ[翋>Gt4xW" Qg'@ boàt/$Htzzj]kLo;!h7zLخ#؟Vt̾{r41nhrVpYz2흨RC7"5\djӊLSQ bcq{jׯH#JE_ѷKS6N3dY,51ƘJGޮNOP< $lsF:rԪy9 NBk(Φ=\f>%h=y5{{9Y\ (!tH%Ky75}p9B mfӢsjdO_NR<[fMA-dw 'J8#'uUN?tz9ln>їE_:,@@ -盢lsYq]6>{H=H;5˷jO›RNÓ64Z|gȫscK+\d_伶wS,zjEA| <3(1-g,-QT`XXȲw.L1,?v[/[A pyz׹~z&PZvڴ*::T@b}p﵍%lg-z׳uC?ul/(4ү/#ZC/S~U١FQp}} %;8D壵>.]7jyG6RbUˊhwA3 C{jXAUJ&+ ayU~eıڴZ* 2%4WUj)R+s9䤈@&(!uysr>@ߑ9JH` V;MT7l}Wk_X1#'7\)r'o_OW^'՜4]l4U܌pfCNu_zJ&oO'bD_7d7H-8|o+'z0لHrXdnu̢ߤ!K@?F_C]"AXNS 4UN[MÄ"z&Br#[#C3~V6jp"TEZR'5fOkD)p1BUtzj,\Sl4]oQN( aDT8m̽qj]uk~O앲m\X%sΩ*ikT8O KǠ/&1 Vy=0?.oBme~ً?NAb톪4d*tJ\4bjIx~uzW3$#oǣ^v=Oz4{bJMW<]ͻ@<4WlNT-&Z>}2x8ڢ8t{ Q`%3y5;c<mc`Vgnm`M؉fuw|>sv]NCڜ$~VE\oeڔ{~Kk}yh? G번=ƫGKs=n7K]Us64f9dU?9?qP/ߟH>Yefje0`_~0ؖy5 eu({OggS@ M L +|]oA9-Jf00Y*]ǟ >]Ҹ8Ju~Bꐻ/xDJQ$kҫkERzk\H/zD|iTUTk gzc_2$#y5ܺwF<%擊x1d5+W>7 5zg-5?ZL?˫~>DG:Os2X=FܬV| a.UaKy+TuX_;Wlۋ8]Ob/xa<3k>-ԣ `vTG9&t;fcW [!ȉ /trgDc{s#NW2&$N]O6؞*k,?}erk*y?::V9ӭOq:FC+T H 7I'5ڍ0:c+gF.jq< PZOQY[#v`|.T~_l qU?gCoPХ-d YGVaztouyf׍5|Z7OϝHZ&ykԊLfhV>| X8fr#}/,aq$_*lMfߑiE]^?Mk&9{! Ȧ=T^n->pl ۋ8 \O ~14+flev3'_-IgHh_pj|.p!򧵴RKz:xDP`z-k͸LJm{ҭr.UM20Y_ÐCezṰ{v/ܚ×nӊ5T'fct` "mi}p{~s\l~}uyxm;o}l^"v~ת^qOGUZ-OB+<_.6+2'(A]XGv&x{H۫@s#Qt\b63 +q֧XϬdtǤl6]r}V﯃~Q?B?to~??Q_n{MmoqqU>Օۭ>4v.ɮ8gݻ%ȍ%fBiU 2lq%-^wV^ty_OMbu LaL}L^up6}/ǴkԾtL2/Vvu?d1oE<1 hXԋ' ̜onHbƓf#ImUbNWz;!LWi4+9|xqɦ}h'|ŊZcvx} ȵ_ۅ/t𺞃ppNsʨ16 dlEK*2YמV Sk;:P۪~_g|zpk?NQ(8i{I r]`@\|,2/KlI:@@Yʧṡv'Y'qC;):1z:nVgY[k֮]oZ8B]vAgS' *U P*PQ8@@_:ϋ̼. f#s{Ld8T[P'e.7>;}4cn~n7>y?N%X)Ia[ߊ g7 Tᬜ~%Fظ4;4F@nq^AT 2Foy5%ocWcùDqr|+ݶʯRHIvp͢ԩo-VJ)x#'gv_VgSG,S<|axv0f|/__P.|,Ǐ5RkAeꕜ?eu=}K 4/`v+ 55$ƋgtJL{ջɦL-U.k;=~O-6LỶwťBU׺%, ؛đջ~W6NtTF2.[o.<ʫµ VW}ʹgg/?| {z]4`3Iije5t_W}.5b.^ig3՟Z;B$8iIsu6=} Lɔ!a4- p {\~{'~:2Z_^ݡ+??moD2gѴOouݼGqf47_/-s뇍߷~Rs.ocfX}fϫ{yϲNaN2voo醙j977ؾRbik/`z+nظqS΁ P@"Yܢ9/i)~ GV3.ԋGTE?땁ȼ? 9K./huND -{K`EZ' (o궂*D }+iY+d ӟ=& B 96ha >>86htB먥ϓB:`5-#3G U>R8=Wg]D[KM~^?mEo's`{kQI'nXfh&GV ,$@Py&ՎY /*͸3>{Q<#rr5ѭ߰Ax~5y7~U+0\(Z./J1h5d10Xmt!7RoXSk <[r(5p4݋v]MCdaSLXgz_u`" Bxkt2he8X)G'05d׎:פ!:VoHDШ8>ʲMtM{U5I'\ٸQĕϿ6B{o`CLMX8vJ^@!gjw}"[@J3F'&)s!҇otS+_lע2 h_Xw ]lqS4zg c** <[jo+*tW '((]Rjd:M 4S8'40TPA D(,鮀$-/ì:t]jtQJGGbٷbvQ؂* E!? $='S.-{{p?q/P5H67r^7/* BnF$ћ2m` /8GU΅pt0.p}P "L+MyK]`VD)lqgiv~Vp* Z8WHufW۳RC"sQy< )mvɻVdnEiaf=C`<Ǥۍh&&5-@xc'X5KyAq ;\^e WWSFɀ j1v-l;3`}o0gLLuzrk qv_=ܶ$p[ A!CRJ0 բ-61YxfJ;Y }+6;>y^mFaU hcNXe sc_5k5gJ R'p?~MA/;ge97VAXh0dyD{3Cɼ<% J] b=O _3CbV[nia0@/| -'ۢBZ_19ޔI'WF'Q\쪥[8*rSu[ucsV,՘EY{FI ͘|29MZƩL6?{yVO=99qҴ]UZqZNˇP(ޜ(jT?igCX j!;i1c!6|>OTtds^R}/K(95Z#4jbo'qK>b賺( mԓM%b:$5$ !ݱ sU#*(F]s@+ŸdJC^g=n#*%4kZ )d/|zmcfI0`]*Y~1.W׳ks"8~_ Wu;)g5bgF] _L4&w Ŝu}~LTD*>-yVm1Zt1r H1F⼝0 "Ԗ S^6$F(BF~e؈.ցbnLmƀ_~=QL8uQI} UG3k7֧Xp:߻AZEL/n]>"3k-{zCS;Gj~#k:cSwyZ~uWﺠ.g "}B<Ҭ?}ð~<u2A;=\^EٌG) U)Цkm\PTC!XDrr7[f{zJ᪃F"}[-c!X'I!Tf{m8 \F<D"-&[ZrkFa*zUɘ_n|g:s?ַ9xgv5*jT*KJzϳWh6j:/_ΘF#ʹc*ugSJIlʬyX7XWL.'EMG͗y]%"M))3>jMf+޵{J2zzL7:enͥ|6O3#9so4qV[U=w;Gp6Gz(jW׬]~;@7|6Vy3p.܂Kn;SxKZgGTEz("n6 c w:t_ǀ8]Dm.hCA*&"jBmcN{=@D #d[Bm;ݘfV{z ,<y_)3)D@I@ppU$8 "+x098wOX[*P,^UjR bl="yL<(nϊbbaLQ` R3:,eP:F!FK|WiGAr&emR:6:"-姾6.7K#$e5z?޵;=.>_0Ӊh><#0,pF Igu rQI\y7 5}tepeu#寉]sS0󮟑KJv0F-]5͢u-mjxPPm \)▓NEX@1"E΂ʢ?s#rV0,46qg?UkV C3+4Z炃r Tx^" N <6 "0trP:U]!*mSu&\r^{Ȝ#>nsnL;ua'Cvn#?NyS=7UmS"Y͘"Dzũy}5ۛU|bgmȽ.S_>]Tw/oCSՇc QtIvG_đTRMeg1 U1BJtG*tY& 6ǁvH`yL(=ŭwYi&_^N*^.l* <=FLl2.zMaxSE'fF8R}d;6۸'Z]P@3Q`G%$>k1AfoOqvPni-zXq',SB>7[E@͗ p1Q2`>gJ䕙Q\_{8Ɋ(ssPcI-^֎"fBy:h",/mf?\!u g.pNMcH%9.={L+ꆚ+M2vjf:@›@R֝zrU^Nʤ~F̘OܚˏM7iP[S-f˟^3pwTaw2ǝMt떓#J.̲ kmS{svV:OD]3ŗL[n'v븴G3aTP.ݑ@3,ib d>ܹG5̦Vn\^yx;2%7:=IJ=Ǔ㼝pԽ $9'UW_WNCyedTgKRBe:ʹ4*r,'H2i=b&RMظ\ȖlMf캷 8I"HCs|3nT~bm<}-f#Qc%zsmzȇAwRR01hb'""iZ% ޜi' P|.YtGvb#pW镬b7;G"F0XddwӰGT#A⁁T7ĬqiBh몷"IW]PwTZ lU?{5Pg+=tOD_w)11ڿh+Wr#omgJJrmBQC^@DUĬYp>"ĩC LX8nc4sߓߏHW`Z(5H<$l`)y zc]ZS}bDC`>2kIת`3cLʯM}.|pvWӣDl"AN shHbCo?/v=iXs_wջlVcO"0?ꐨ 1RPwfA9J+Vp*#__Hȁ,Y ϑtt#OVkq+C&Cu{G֪y@Rhg2qJqLΓ'`'nj҉OLO%(6wV[M$<[^LV#h9]lKfV_EFao!2H!܋[SМc#%ϫ_:}*Nw G*vPC>'.;o:++>%~ΜQd:h+)JĶ5-0Uwh*??W!;k vUnħȳilPtZ8ʚϛ$\/9KV3B΁^RdEɦO۔ކv֥rQ7*e(P6sbT4w!߭ߗb~~:$i $k OoSDJF@fUu\D&ebV%ҺfKjӢ#M#Ś 2Y;VC+p`KhQAdOggS@ MQ@( %黑>$Lw*ϔ LCT4Vt@7(ֽf4ϺwAQ!i8G-Z07 tu~>²[H-(* ytcq~QчH q pjn2īS25opT?j;:?}5%y@3F(p 0ڪZS9׌{}eC ? P0RK9NWb:QqX`&d2 eDx*Z9 `|޴[aQ#G֚Djr"^|k'gں4`'EQL8/%e6Lb. ;"]Ar୰+Cz. xi/0Wե:,۽wAξ Hb=^at::Z3\숏M~̓$.h&@)s{X ^;,Zvk\سA#eUDMϬHj(F3#@v?ʊ|O+ HP`L9F]ap$܉Ŵ^ma,$E&:d+q ۉwp{PǃT~{"/.MT og@_Qo!{:=,u!QE<0R6f˦Kj;D;07f#婼R= dR4w| `f~Hykq?A@> 1>s먨P+pUA]"}]#'Xqu_kϣ?QjJY6~g[IvWm_d_[ Ka;c[Ϗ|DjGgu~έ|}⡴/#޶DW{.[r(흕Td˪:!UEo*.3<.XDxOO\-WNk!bccSw:[|>`e0-3_9 $\RVLC2Յ}A})valÒx^:htc97f}ݲW'׍ a^/%S4dEë?,M>{NݛM:3",Ub8 ӮRS{zf?YrY$GF2.yW7iz*Dρj6ˇw5y|y ,Ti+~ \T A*x'urICX k'"јiWtJ0<]|Gd҆OMl?I֌TצT~EEWI>D1)o_;T@vZhU(Ԣ߬aS|mj+ /ԩV]| (3RX@U?|ZLﯜbz*BogeYnYDt!hADz@LkDBO+םۭ+dMco3"YL[#=֫MДjӧ1t=AkuY$qӏ?$Sj}w>IݵwG@E`L]e1}zEfDtvU1HM9qdVJ r+dr[ m#[]TNn? ?)"I&5a&r|QyaSP,=|{$t1__"L~!lQe˧7Zڽ95<>Qi R ikˌ{ PijO̿4%,V r/{,l,?y55rM#u&,R%~fG=&=0"$ {z]gy@_N7Ϗތ j`p)OBofI faʭ|zD tS1I3=W__%"{^gZ'kYl{xN+2rR. \W*.]5+)2.kZԸz@o_-Ք*|O`ۑīR>P< rjV$@ck+a{R((kzTcg_?M(ZB[m9x" "` ޻@{jͺ;˜缧ETRTU"p< 08zP[Uĩ:ϙxQr:?CW!u}KkWwmThpl<9LxigC$D45i;1'E};'HtaC6<(c=y.0U)Q u[bSUܡ!4̉xZyK!`h"!qz4)jw/ÃŅپط2T1OtA^B3tcxTyX1.RdoR1/mc{c%{MĆc;mi,>=KN &;jgOqqv-(><j[{&ՃIJQ5D1,NrYklA yUGb}Ν蕞#G)wʭ/'!g[v{cr}zy-fj,{WĊn ن!62t:|V%o( !e>~::Pq(Q˓st \w*.P;[?`Attv麸w_fиh/U 1ZYD yEt^2(@@h>ڂl/ (۸(9R4VقnOMuW1E,ҹOӘN!oϷ䳋N)؞H#*z?ƈPIìgcCC@vxN?RJ09sQpڥ!ȫΑNvjW<-9hF].RAD w[dE8l!0uw5dUb( kq~hE:L@ںҜBKۈc^@3@4H+iS~NTTRu):u=G 8>אdJN H\q}[x ӄIF!Ҭ;!闷o`[ZĥG n瘝%265Nj^aD=a.02U~UqKD#4 _\5k<\?gM*MM G/*-n1jHQ= ΑzpG1W 9}E}szmLRI 7uM!c,WiDц9)OF: ŔRfQPv5 u{'\P7qX C{JGr ,2YLNW(-`[|k17%UK!mZE„Ŝ>C2bn¤/rn6oYewxx$LF{O{\rK^v치(_yvnosƺL:^WZjY(Sڒ__?dӵ}D A'52-e WD؊`ۉg,!6]uXO0/I6e-4~˧"WwTpPTj':Ώ2D,97)vE%}n(ΦRP_]ZUȢi'* ;SUTE('/4G6TN4 zYZH4٭Ow"ݏ1XT1>[Ȗi\V~,3o>k !4Y0gKZ9dY(܏5i%O Xt4S:}6~ی ?"`<,D" *E}sK*\J8lJx趨yXUHcw=լ|{o*ڰ M@ndA5'ACS}v-#w>"=o-K=*b(1=jyZџ%?y,xSPm"6j6ߛ_zz  =LG/P{)n=hΧsrΌpT>cvw2{JKi0k[5dO>V> eM/>5jM`n,ru0aSs -Q S=Ǵ;%kshKZ鲆DʆƑHVL=^5'FM25'T|gU߷圚tٸxɠfV ˻Oa?D8'%ǜ,( Z۽QXOp'b]Y9MG|xLf&/S)Эu]L½IIrWRxXÿ6/6߱FWl^ѽ>Ө^l=kը#`; ~.oĆYf'+dHwKwI\J"FfΟ;pǵƤw1`[Ɯ^[w_4!;phTAEp~6Be>r , Sh^AZUETԡ燧 >bMLK$t{/毶FDDЫHs~ukT@Jf!""ݗߚBZ nGn(R/Q =<GcLsdڐDrDE,YދG;ʜǟ;׎O.(I:##5@jkRܜكq eo5!DhB:Rʄi7 8rU+> ͠iRkψ`cjn_Uv>@+_gY;cvYZy}z2K 쇿 )p>Rre'J9u'BʐJ>ˁ<N"ҎYd/c\rm艦 ,ZdPbx݀iyVnn]Ar\"x`TGkO8d8U01+{Uwm<֪"\hݰ6;9qAn36=6my][avۑ/oy-eGs5&p^tr}rӻ˭}pSu^Ϩ"zo=q)E J(D4GԘ:\·nlZ!4Iu(+{+S[ud (U)<-8O"*hYU+zُϿ v TPhRe* 8YUjvNCw ]#M;u(ZRDA@>Ĝ>L3}3c;#Bg%HFZ89Z)/ "^ESFQ&P%)0R?E98̣y30=ڽiÛ[Xx/mO(y2C6Ul˲j硞w0};p2gDŽ&x6MOggS M n\Ȅ~} oBV闋OT?R9dջkWw@*Yc-hcSdX?9+ ?#4*"ib@&*W<$RLP5^d4Dq=}4W#EV=F[qJ,n+Tk!sϳ螜YM­5Ų޷k`ߒٔ5M^tV$W[k/k5U+=)ls ¨39 h35zKڎl'7,3/7w Ǘ)GΞ7MUs W~hEӈxWu]6'gZ!`|EwC 7kHK[t-k,M |37n6oLt>y‰q;/7ς`Oyx誷3e</ҳɯkg;LU;@n=|RJ]?Oe/s_Ƣ]"~?Z/ Y# Ue?cCs;#^:.&Z˼r`)DF'zI]o~?v|`]:u/v T )ׯZ&^Bi}MϷZ+ga{9_,7_?u>T{^͗4io,5LzlI\S_p#]2eG:,}k$F:ݘϥyG7g5Wɹt9t5S3 4z }Ipz(=uqсw_Ksï~1Vn^zM|={O%JrfL,)Z[2t&roy[.1}x~OgxB묣[St&-[inՑc%˕P^Vʪʡ(~ z}39 tc5?7ǥn,ވ0qvŭ [KUU9K)DTnaGe"uu-6=ojJ| ͍IEj|j#|W-͇>F1o^T!~>'VW킻lyvGVʝ(@Dฬb @A@K7 OKaĭR a荌<{u9S9>Ls,$D+]S8d>&Qdc!q~"6/"ӗS`JaGWZkjSCej',ibϨ-X˸&D[g n]7GH^ABhtB`%(wW}ݨy<e&F/b{)9=tn>?Ywa3Q}lȔU /fK$GMlzTϰIx&<DPBCfRAPۣbJO=AܑZ럌Chϓ|=fbpۺ@^˜%&VK7Z*.yWdNb̥yu}V5߭, cL2b(j?~;n>K3cy2j:/]Pe 界cxnC z+V@2C즪a8}k7Ī9 +G׈*HorOosgw']@!hYHiXW%V kcΠR6Prj3ixP16JΥÆnW7Hn=YU75eiSh4 Za>uhry{5QdMJcI]Ca{x D?/ E[j 3VrxgJpDRǯ`oE ~G{icϯ>خ в0򈜡/s9ik'm՜d<T2΂sͩ `6[,h;ۿÞ$bZO7-םMc%Vk=YMİfӢV;X-QVAlQ8xIi>=V87=C>]fO:tM<E4k'ԕC7/d'=\HEO<7:j*̟87gPIG= -E4!n;*j34W-Z)nc |Te.:dzN?+YS_7W>3ra}3M8Y|n_:Y\yӊ<^|^MxS`aZXg/k[OyUpsn1wڽ7vr|ȁOVIF1y{e{ xxֵΠ\YO$Q-vs7\߰#n c\3|mz՚בj.u難7yH{ ,ˆO5B-H9Uϼ"gWP~~8わkT{(NSy;'*F(yK}wr>,[IS_Ju5Vո${ɷ~L\~CX`}_=Gi+ߖ[czw_--5Nq#kxM3_%y f} gy[1ܵ1߃*rÃ}0+l9CD׷/]}yӟ9'tzVkĞGg:54n=>|j7|UPo/;y},+ٝP]nYcN}w}_> #31LśnR%{kWZ];zv5ߏWmSDy7+}Vڃ;a:KFϟqsy-Iёwhter~!lTje3禒2FNiRoݓ=UAI>*Wn,\yU})~5ܲ$GLip1a0敯SEMZEi\IT^'}֏Q<&U $ǽYeb.Aznõo9V|~ג~w{_Ej~dqgK'uEڙjJǑ"(r-$[K|{#[i[)~]߭J㙖',營JJ=dwFJl0읏!Zލ?M̼U%P,uuxݷr"qU#Y%j@}O~5I秡1^߇3_j<|޵c6 }|IaNus^WVgw5tt@ǵkrx{ϧsկq1gDEۉOR椽wO a6ioQ]ppNAm.jsh|k;XNw, I!zBӹfWŏ3?ɧԭϾEw?kԻnt!^'Eoyx79#E][ӯ/7_6I*V\pZݹ*WޢNkR]g&BӞ0{(2H ;٩Y >@&_@wL_]jSXeA/ģB_?'#:9F}=:k{xx'5~ ΋UdI+K\TTp?y604a *J; Ik}}wڭGfm̒O,2!!BKmPiދ;yrfz>|g@_4 ܢ8 6/IHx0i#]fWSdW;҇V2ulm~GuiY6p<[3;?Ib}@('ÃfPD3=iWʞZFɍpQQ YUxrX@_0# A^baC,a !?Ǒ<(' ([gjk27e[:M^ݵOEtRk%|7Yi٢.țoԏZ/G'Bn>UNtO;wA9N+/noɝ SDUi  &Z } q%jySaKuŭHj?ElO꿶S{'lοmt-KggSiܳffh_b{a s']& .r]YkmVWwٙ$a `A~"kۯOy v:oZnOR;?YW7VNg<st@3J=)W;%2ANg|s;[5t=+klߨNðp+S_$Ͻۥ>an=4`-W󼨓{xtwz=J-T,a3}>j2Ѩ}uœ+x+C!m^*;5I5wꙟUMwyu'_\~!9쁌+Ne=~2~iݐ8pFR̒n`CCΘ=7>zĻyEG7 W?w?,w\וwMKbׄ` G Ӵ]wqv%|:붷|zJy< a@WODqZ.vU׊7-_U^O|d7THOxI]Qt/ p_ڵ뭯_e^z~Z@=8KI?>Mu)|閾g9u\΢]s4l&̈J!7W~ÛpPI[ j|t~˙ɸ󌋕KkEltiӸuD ]|#= 6S-u\_ 9G?de|8g5͂oO5sM!9]{fϯוl6p'͌w2Zv ~VxxA22"Z$"Ǥȏ+2nC-6O.uzK#'o1 "ok5j~rclK;pFj:_*9Xq@rUK@,+TO!I@ "*$J$A#0:=( `>>}j8أuIm.rs\Ӭ>ZEkV#mn>~{9տiL\O=&yCw8p_E?jo*!㬌um/{; z:TW/p,ƍ`ΧG({;+zqg )³> G&dTΤ"-4<l9xђ\GR#Ñ[IZ'-R J?za=@H`[*JdjOi*>\ZT* `:lfbjnk=y{MI|\oY@g˥v]H q\1wUP_+`@-xcAP D@⚅G~X_0n>Ґlgww,p`~ail';6zMMaIRzqy֍ E\֏tI-!˹^pxZ6~͔S1ި}'^{UJxC5)#~3LtXsooqrGw\ӨK[E:CA_L6S&~wǎmaf:*CVG{~I ,naI>:6D`Q&N9=!_hab$-Xh(MQ'~H#ff51Dٛ  QdviwA@6#kJJ Dh܌fYs5>]dW>_\[QQi\W;5ބĆ̜ ОkvqZQ#Y ʒuE6v@Y<0$eIL3-II@8I:ޚ0!Wĝ&s40Oԍ:~x QB 'sȱ)h{n sHA{{9e>C*b] Y}ӣ>Cv<~Nh(LpzT~sM9E7U^fa .lNj[lfQ+xmF蘿͎MIA:V0G2l ,J@SݗY;M=[Ū*- v]X&M IDIpA{UZ';\$07LEkzι*Q@09@`SxlE:YBnvwO[}X| έ]+ ƴ$vũKp3g]^Js2rmF9۟5?Wt"Sxm Ӹ.9S4q{%[h4MO_y|=uf$IݯOu^8Uq!EEm'ڟ&)ssG)|@teecl @Yr> e{c9 娄d2`:vpz&xRls۰M>)mLB*6[JV"yh E括 Lucvšgx6 eĪ~s~ )@Ҩ7oJh CնdjO[fGs @8vC r @$c  ϊP X//"/u[}IQTο֋&@u~>[7 zp6QKrs^^1^l^E*`( A_ۓjo/йLBnn\lt_mjxFf=VOdڑ떎4`sߧ8!F)B`b!&~6z ?G!k.eOU0a@}˯Eo\wdMowل8޺).z?{̾XrA\_@$L/Z>OiaeFga֫;:6.Bµ6zE`@kjRWRn@TX=Uʢ vĹ@>YsUpRZ00sk#,\I`_{i&󨎆i40?/u[4@@hX;?+\:0%G :0@A\}P_<%8 ڗb'ܭDFvs9X{ gZF~sJ,!dr]fkHW 1@ pX]\Qw*d%]E9T(.uY{MW2Ro7䙻72l* ]|Եڦh+.d-˘6Jwinԣ&Ub^(d*KJ?+1hX Ifm،"ٷ8p8"BmÆ$  %x]mrm+0l}P @[vܝ(4@ո5 n/Ip4YKI8|$(vxPy} : Po]Ky:h]w7@,UB#@x]5C#%A)w,HòƉrυHѧFY%A"޾=Y+-V}n?DEQ7"J׮j|>:"mv>~.]XLͬD3$ 9ysgϨƀVc!vWDh`X~āuaZHq!9 .%,sih+t~_G׌0&}~_mơ\ ([ӹ>cJiPKZϮyͫRԯװt>sIݟDwQiƪI?KA$.<Dh XjvPȲO1jAz1a떡Βo3],XSLhw/ћ B+ Ńxs2F(mlG>U !6~ t#9:pR=Qޗ6LTs/c 20(#'ܜDV%xKJ?Xa. <YZ)Ս'AE Gh}EJ >ădqG}H$oyپ)1gXA|4e,ƹXrQekcƏW|ywmR/_9:Ϻ)`BH.TW9N61LgZ>-=貨ltPxd:ǧqrޕ8 yl<~,` 0ْnl 3QX )<-* h? 4Xb}Vs]@KIx7I"W\h侸r /9qS;o.Hz-Y TX${\Da&#'P|-˜_TZkAGl}uBԺ`v_.T@,] [tpFT!ً/[OV}d[xN7YcYyGó<پz[TU]FF^yyʧa+`rAV +4qz$ kƕ'8 xp pp XMM:=4j_~D˘w-LJ&rlk;l p&{hkS3Q[Q`*[=OVFd:6{_xRV Մ@۝"w"< rp1iz`ү?Jru${iɪ(Q"'8,YV%A+g\_Q꽙m}cw>":+{Z'Χbk>ԥ}bIPR6YPƌ:c{vH@[Zf'u@=7 [_4/]̔o;Z^r'ƃ:Od.?ǛY;f'_xG͋v"v.;nwLu(/?ra.8߄6]J|cE1鈬-:OMmۜ&6&OtJF<|pe`fw0}îS1:h Ah[xyɩ'Ԑ QA"'] =Ӓܗ쓐zݤ3) _uu W\/Dɗc~?rC;W<7❮$@c$V,zZ3_ŴL'n-by†z'F f _i:[eFF(2 9o6[l7bVZ "fc/m`6yLԢ%K"`ܘ:Lq7ggʖ]wã~CFMY3١mV4Cfd'~-G5J2uy&=c].]}( vcd'Y0}Sqaሞn5nitr57kkHOggS6 MѤKdδɻl%bvW"4ĺ(!n e`(+g/|ٖ_7>+¬}OvR#BۊEϞُ*|#hG:߻-Z u}|{̾_.'eЏg%UէdfXهwO\ܭ9$sBScܻW-{X\-,j֩A* @ltBFrOx9EARA^6aJujDginŌGJR~Vх)[Vn^/ U:X\add{<[+FL5cʚ@b޻a'IR6~_ @[N855Dh$ oOI'vu)d",^ޜޔv6ZuܽIZ9c|`ۍ4ՏܜXl5y.4>"ql+%!bQPǬ=ҕԕQaԇblUT+1XRԘ1r?6W2ݡ*Dn0Z] YR-~5uQ7LojX.u(c5k؄N'isnx_m:×lgTlҴWWޏ{ʧ4Ǔiί-o ~/e0)]S;oMbmN|վfl3/VP%ceNr fYtE ߕSҪoC]GOcVg7TPs-Pn!>OY} j%V]ӼTUrLsi8r ?r9<aP.ai7:4cO]Y~]汾O㾱l,jJ/לř^<. | 8U9bmgZrWK^࿩,:֓nSHS:p|3f~~׎eBx<̯3m/9x<יŮ}~f {6nTR.-l-j[z ߣR*E.Oo~ՉO!SAe,m ? n~|]N&_V9>GϝEj׫^ԦOExH_w0Oza9|@0[hUP}7>p[* t | %ҿTµʴmo_Ƹ^c?Mzn>qOW<ϫXsX"S\ske%>yWITR;XhekQ=؛n cDZ4 ;G+yGmt)f.۾̪ZȒQDjM*f?.wy/7K|̀\ zH{kr{ٿ}|b~|U{F_~ 77_]Wwmz )fw~o}<=>?b溯Ȫg2y\ ޖ^Ⱥ,Srt͒ѿw uޒ-o7GFf3WjMgxg v̏Vn>5l̀nhYtwҳc~]4ƈwOk)NfOw7?9;.WuP=%iGx|un۷|lx>1< =pY.m;otv1סUapJH\GDH2pl6UTJ^o,ws8o} Ȩ4NdRM'2Yx9._B{FSi8g]z]Lp~bZMcU%B]P H6./*ͱt&|Ӎ/Ull(,k~4"/|H:~*>"ZX<߮Fh%X}T^d!EX ?C ѡl{x駾vsL`*g!>\8AUcp^ރz+M7 ;?V le#Ɇ9ez# j,4{O_:%O'E?WT\Ukw7TEOct Pk1Rc?1fOr4ғ6T?z<}֥N#wA)WM5 k\7#%ڥ( ~"tEf\zr$tU;'T._?Z;<8M-kU/Ѷ&I1>OhwVgV!=< .5zD>{lo@.='ɿ76_26-/H{UϜ]HІ<is_ VٳЅ׋=9;KGi+ /qҜ{ {A (v# u6>F"z} =/UӫD=j2S >9(:-%Zy4k\ 4b&L'qp; I)|-7m=kHl<()w5$sn}vx>ΦYe|@TƳ=b1e6~'+Sad?릲Q& =gmzsBj6 OmjPLk'7u6x$˓ٹWoؒ>g1׭eni@NY+l\i.$=@m\}gam]2+RtoH@($Y|&ȪcЋ3q@KX.wdݕDvx9md~UF*tur7L4fjkcԙ#]9,'6پϯW esNգP&||Q5qWGpqD#l}b\`!/d۠I[PYne\|b_|M+ZGr܂NkGUm4k46<խ&?C]+>Dsez$bw'd`_G0(f׿[KZ$I -}0iY@Mgf1  ,S Q"Y$6J|z~k;c^J"yp] ϰ9hv}֧S\w繪~OLqm6G=~ևY>_s<Czz %ƫVYK3-tBfC՗: zylU~*['BU`qLpG?`"|@&%b5"y/vݯ&fj\7ֻEFp!tP̪7>ID~Da+*b  hȚ:zkZ:WQ|==8JC9)a-ֹ#U._yuM6gC@_T/DZaZmQ9GќQ"=c*}?* ><>7M5TAADVuTPL樊"P~"E9 t1%?V@{|dtL\=l%@u@ȘCz<fYw% ل0? ն}ޝezu ;5v{@Ah3,{sMĩɔ>njX)m&ܺ(b\|: ^Q5ی=pV,AzGx$dqlV0{PblWʁhȒ΁~?4=h}v_]dꨓ]V}%*6[ϻ{ ֬l#Z)}庪*#M gU[ JާG2;&heTE}d4;-(t|TEyKɅq%K(Q 7۟;!KYMuIJD%T֯@Wʵ탗woN-)?zkpN᪏ !W:]M^o8^y+jG *P:r5~a93F?h|Rۓ-*RuqB{YH/+s^D147n]*N35"#.%>h;+a;)fM^rRvǮVfYa7ݩ>G4y"/ f] QH˾,15b8,?7neMݝVYg;=ݝT9 =UJ- > <3Ll>%bZM{Æ"H4(4VV<K6EѲΒ3 [*3!0D&>5ſ3'Q"<_fVϾ=/mF靊jֹx!eYAQD){jW3(^Y^@P:!yLXy~=I+E(h}B_Z3tYܐSNuU`m1sή!*EĐxZqjX(W^ƤHUSQS#3%"&@W_/"8$sT@YDS5EDDVɉ{FK^U7{bkܒSGoEWD}A4+:|]fvwz="P)~XMףx 6M£5}13&(a2?PM+ ߅/+[8E"b.NSu0n[6KY˥K7oUx0<- Sq;-~إ3qU~qb\9~c{G`qfUqĖOisi1GuI?!#AGꢍ $1 8Lv> 6s $B(#ĦtKPUe:ۼ +:j[ <[u7i~.ۡꁦxHCL%p_|8aﷁգK%2e ~bPv568_Q6_|a}Yp+Y[,E i(D opն0^"osփEYU/[ȰMVbb߄wu ޖqq^Ԙovm|>CQYj}|dU+K~#B·DHQ ׊>:[5`tU齟PGmcͷdG%Ad0W](`lغr)TظCq0iԏJեٳJk[ O:SN"( S"/ys5Cjv֋˱%"d`x:I`(@vy(Ty#PS*KU@uKϓzT13V: /cDW PHJ:yّ, uRA-Z߭h !2)jժEx8NtڑC/y_&ruW?֪lEy XD }U;n !Z/ǐ(45ueidõսXoԽkLx'gZE.Dݕ7g Mu\ăm{j_ q>,?~-x̦l=|&2:5%ĝKYs \5mġ:qTwI*X6iv|ԓ 6%a? ui`\OWxn\s2UH?58Ϧn|en4 /*7Rot8$wj *t=tuQm|L:K 8$59Vr_Hۡ~>VҦK5!ML/ǻ ADPc@*琏U^'AcWs7w%-BgXIaHu;4|wƆPWƧw۽ˡX"k3hc{c6F⛥TmF7(ݽ?m97^uH(5]bu_OggS@V MlTz8F-f:xF!TҫX#6 e7嗛xmx^tg9$ pmC\ihUl/٣5y<[Mtnzt?ԕg/j JDo|Ӡ&W&z Yy+:ԒVpIDblFϋZ*",* I,/qrY_"-"{oEtO?pbcZy?/&xm]lCE gQCWq:4O~|xP=骄L*T)q,&jĺV0R.PEFp!bjf5W?mUϭuD12|b"b^{3n+i|oV5mwXn'S;#bocB XGSJ9svI%ni{Xf;2Q Acv? H%23$ Oyj,K mt()̸0]ߺss-UU.Xy_C$j!qt@ED VPQ?M0S]SSPiY4Z;BF缋FNDGATWAe6ST@UG˘ G(DQ̳bb,ċf2cTPU}߫ n/!˸N `*) DQE+~]F6C8Iz2iX:^`ԏ`_j\Gg?ӝt3gg)HFJD}"߹JmT{uqh]z.l'"JLkjzgVxX$}Ʉz#=8nf>JƯ˩e:BQNpXS;cNqu]_z!ƨ8fNd糾oV<5\ؼV~-l IV7-wGSB#şG|+b3Yc_`1H Ks16Kmw3햣0 텃nuF/vqWozaC֊z $ߕp^wS5je0Jg|#n`Xd8UF5Z8-nu:Q{8p/5tf:/cp;~\8 $9ΙFwjTZx5h+w[Sߪ72*.5zP緎jS@Z;SEe_ى&JdIϹia5:_ chvtqZZNZ=O&"  z15g-d?r)b9T8TLKGĨԩ܋Inm͘+?' gIh_ExG{t<񺗚yFٰ=Su?``9Z4[*/}n=%5^>t\*(S&>e<8w˽*Hg?d~mW h"kF0kS7X9HM5lnM qfPH~".zSHz4N^vd5\OhY0Z"27Xv{V@N*<O|yڎԉp[P~wx;j68EͲ?%༆osP"ƥz?EPzk PI(cN՘>&,_s}ӟGWMe 8A" f齐fzn s导hQZ}]֘x+W9;ڦ5r?9Q^4g^4#,U.\ζ)%?v'{4Oɋߟ>C=Zoװks" 66sHMzm9|$s|ΣgdoZ1?57͸LaAP+.T{i<.5뻈ԭ7kA<8\B3G'w bjQȻz|q_>Qi\Y=2-G'3= rؙ&.M_j?I2cGe(yNX&S:Oѵgrwjh]q H%RhTsxB* ocd(DpXom\Q?=O~*r|h `@ Ȥw2eJۇ5=5-gqƼe)ŹpIN5=5w"DGT_$zҫ. u;R?VyT?ﲩv~ٟ&:ًv|yT[[ꤧ8}5h[m͊B|YϏVv:iXY^ :ܟiFՍ|f=d)Bw@p86Ie˴hnȽk҄*18BF Wwt`$a0llU9S:H$jPԘ'UzAM?߆lՎ(?xӆA;0$_&YxVNY*Z*RܥezFB?m4#:՗r}TҌ|ni}s/GǭkS/&|2X3c_ܶ]:7>yݪ7U7?'uw뿎ֿ&TٕL*doG[e~vzeƕf&4 QZM9ЮwV+h!%?=e0`]7՚Um/ҴtӟpOggS@j M7%d \) 4(%*嬰+f_1M~v> DA] pMJ[0o1d'b!҅}}0ZWn6QS&"Pݜy;eyXa BD5-0IYhvYQoYu,r?3eP_K#~hHs̏[hmxSYLdmtĵ3l65ݰ.Rgh@udY4M S$ScDpJ$6U7&Wqy3$pl(}n"h%SFv;idYJc^a4yϯ~a%nlau ^0Ⳃ'1n1<4fzt#өsxF#ˮVQ;=>JnT]{~,~w袋  4eȭZ \4T9?mnADM)=#؝xW\kf Rm  4~rq@&}Sϫn϶tE#o^1o:p TNƒf$ B:LRy(חDBjEsg%Z(j+4l˹9TOLH@$;d\kuB8 cBQՃ<;`WQbv]56n%`"@[T C jbqG饎1ԋ b\+ "LZ78T/G9QKWAWE[(*=i5BU|8*c 0\cJ.ƪu͈yA0$"$tcK’zadOO'v:Q7G#Ob9./=Ji&n oV˵5dW+mVH ٭k͟eJ|R[]|h9l3?[| 96n0fٕǻطdCʺ40o֣r*x>܎ m{h4^Z}NtوTz[*d//Mm>3~U-=ڨ2Y8N,YDGT9~@=HRͪ逭 FjSqU>&lzKS@(ldp5 0( )n f]C4% kh k{,J܏1qVY,NZX[a =ِ /sJc]3V<msźY֤`w06M\H,ZAdam^y&!wX43bB/f ~\ ӯדE[I13R0g,zX.(\Mcs?麖l}4WuAt@ӏݣ;o[ %㪦4o,ec ܭ}“CbPA =v҂En2~eOxQc QA.ܰ f(" b]?޵?S+ `/ds7;L;\Aev eAȒwv]G:dGJ5=J-v1ݬ9*pjAD A)p(WNp{I5%};8c &~vZAТM5Jy5E)bԵZE՜uI>{W=OՓT]b)*-8$ENj'ga2 X SVSAl%Ch!c`TbJ 7)u @eٙKftg z PYd@Lq1;ڨ.VBu \5x# %!|nj8q]p4a W͎pLjN=Fr} \DƅQǠJ$^]{DHe,v]mϽFg#[;Xkv^,썪l3f-)Cmx>Co&-|F,`Ur*9urL$YȕFV~ơ s9xϡSV1Yp,qQgeL~ɲ%l7D9K PvV+;-RȑP~Lߓ>>R$RE8+.R^&.Fa 3UϩJka,M-t/q@mvZp Y׉ˮ㤕޸T#P4SlGs[=H*kܽtbF%"9Yb#dT`$38؀U`emu8 "ά3,p`;VhE,^]nԭ"%M>Mrr&$uk#HTѵ;nD8o]qFo9nں@rG\6tǖ+-\3-~hFkQi&ʢ6w6_vW՗[KK;ʳ_5yɵwJTB!cLj&1+8*ZQ5!e }(éZnЫ£uDB6ujfh @J'rjoq̹$ YJSAHٟd?@ yr] ®88+3.PUUtZExi* RpUCj\1tSTc[UN8]mF>J}>;lf2&kæ ۋlwtcei +7#¢/v3"KZ6wk0p;D+?Jr^\ H^vhTg>OoZtq6.>ݕUrӯ"ήyKd׍ ,>إU\bX }KEPDufTP_ǚu愯cJXR菚Bξ uSf`_乙NZ1q*f;TXZk_K-Ƣ)q,=70@5Hx.t.1N[@Ud5bUƺLr=-lܻm%;c` E6T]уn2y2K7[q nVABoB[>[[Čӽ>^?JdK{\:.J̮7b%,mjpjJϵ 6fNo1I;$EЅB|).,d)V%϶3'Zݣl5αs\%([ x{[!eNhu*Iah+}D.IfƦ7g~Tt4J岉uSXkS糴[ ir{GNڬϺK(wp\#@ ĝ5fEՓ%Gc]-U+7z/hbDz'Umm/:W:'oyDBw#[՜K<DMb0D:>} dN[ 1K8즂jdU'-({ċKesDWq[%Eò5J#߻wߘYeQכ:CʆH1Am(;M߭|u,;S !ޗhL0$_sVd<8M%|hiUȘ~u.1}dU uvK^Z])&t<C͋˘xxS\0u~ӂ]DmEʸ'xiGEBɬ@elˀIoBX:,Ɍq._YVtmUjnİ|x*k3.s)hGc"eaectC?y֊ybC[9Zٞ\7g~~(I2B8^~cL_"m׻/^m@ mR3#>n]zJ&IH 5>ڴx[seLŬV'0}dabha5Kr8=ss5Jm맰n4t Q҆D_%+ źrtcpB_|5XG$,s!LhҀq8Zƀtmܫ^#y{OggSz M =>\mz}h L{iOLnp8e_ފ~ۈxlc9^*[ 4PO|J+lR}RYV N=jK`;4A4N$:}n8&pBc]ApD D\ۮ5"c&/o= (@k,_vլ+TjyfaɫToS/ X6"yl E2]PCbTv@&"Ŵ"  @кܱRvZ䠷+^k 㔟/8bt@61LUWL*rXUu&7HO-CVGu#C-^T8cܻjj%viaHt>#x?pWp@7or|~]]}8,&ߞWVja̟ »h{"ӦR&nwzPO)칭M,kD>&׃RO/_Vl{ҤS{˱X2Yt5SKNܱw1bS!Қ7.W|igԶszsbn$u"y?ϻϒ/c#zcAp»08ȥ4E e?(1M N?zuCa49F7yBZ2^|-ypHYQ-3J tlM,*:u3hq>5PnFIpvV/?OjȔe!#d|@^)SB>'~0@1"ui`J|m:lxgV 'x׏Cy 8k&U/`oRb|J1~VghT !iA!jdp羬3K`Dl6[8S׵~AnWQKnn(RZU0y B7sRDL-%˪ ȀjZA%:z2QD 'ULy좎r@7q=2jHWI`T? }it"\AUʃ")*I93/񺣉{f3{K2ক8EUJQ_Yֈo& Va%t2*I=1[qϼ#=ӞuBebU< .{@H]7Uv^5n\r UC]r\PgϿ֠c̴ ?%%=hBtxr2w¿wG*ZmDp3%?ES=j#'ۺ\T,_W9]8 ̊1/RuK|*$M')U:Z!fLBe,*o}doN>!u}zN :}t&_m*&j:[Ό?81 %1M+N ffv庣^(#&+cPԲ垻˖2'# f4\J}y'- fI9;ݘd.RqqqeVV|eEh@|Rj2 .)\LΈivV3y1_\Ahl#ugR Ȭ5 MuR%sQ@2 oM, -h ʜ?rPV58/+_ܻ_L7#~lZU_'[=JvD۾ɑIqDDsn^Z3vvv{>rM$7ǣk=lQFn^cO6v_Fy[8wޖ)O-' "<+; RoJI{:UQDЦqքB*“f@3V1TWtefDbei!R|h`bSLAi(K@aTԹ:Vqҏ#k۽xDwc2 utPQ1DA9?cL񧊊 (,VmkiFdYs5 ͫ>]sDC5tIj$`g}Nr;?}i|J !.fJRZ)CtY,3 ^$ =. <]yq!)u=fCFO׾z\[7QwiDɨ<7+.[%n?ѸlG_8ȃJMf_- ?sP{&_КrP.5@j񵾉2?pк߷Szd=l֗LWeR:eC!&~:nH+u݆sz]#g41˂M~cLD+ `~z|M ?`1d[# Am Mc"[*}=Z֪.VEmSEW<[{g:=.a;(>Әen,Y/wYj>y]7Uxl*?D96OVkHdMn2*Wmݵէd7~ ,Y-@\8/lFsqo]N#r6CyEkd'Vai{vjeJ3T@G@ϕm9qW~45W]&s_N-uY8M K"?cК"(qnOOW~l G|8 S >Y^uk~s\h\i\0jujVֿ|}E|K܇`0/)݂1]»UY niM!\r{DңeKs!Nc_9Qiܛ^>>5iSZM g )d7VlIXN*̛Us1g0^n5|QTV"Ǐ7O,=Q`%sEkzPnO~:Ư.o?6|g^˽׾Ѣfw&d&)Ə+Y._[ A<q9vwHm|fࣅ'\sBٷ'o0EUR+-]}u)p_]{ P>lE,dyztLM?c%cN_Eë6 )(jEl%)G' 0n>M>M=L;W«~c#ǥ<[޺>\)QeԓÎS~8b}d17|ԙuGJbhydZ1g6x~pf{Rd=2Vo< qtvnJg}+"BBwooׇ7H%)BV!,!"z;~y͑Q؉#&y}ZUNMTޭsytӋkayrߝN]>e ZN%Z/v2UF`_tt?"jPS[Ԛ ։Mt?SNQY5zzM˺)%ljhҜcKM>^OןZ9O/r6}m<Lb"[l{O(:kEwajgkǾhqS=D21Kf>D|?ӧ$0>F29[4si}MuT>zE9\67W{^v˟uyu,xϕZD|8y0w"{n8Xn.x馾WWSnyxMOM/jHDJg:__v=\N\ċ½TSIvU'zg4|sWR~1?G?d3o hk^}ӟOl*Čx^%95u_T:p#%A&UO֫unu 5oj}O.:l7r]e~Gw}<5QRӸꮱ3=xl__w{SJUhČe4Jn?aϗo\ ~ұnoc)9c0\Hk,b)Ӛ:^3[%W\&7[y$1[]߻14<W(l[ntl1)Wrk=wLSu8}$M(k(F2![_h'99۠5e^Dm3ښQEBn25ZRsAnrxq{dL_F4}i?yǴzKwLU= saKQXnX-et_ Ź@X|c/M֮f6هe>zeƺ͹pq1{[>i58õΐSmps#GzP=DӇ}[L@7`*og~o$Vg_yo_z[7or0=_e[[zTIͮ(ȷ3=û6zy洍ߡ-bbZ*ZZ{Ԇ`7J=r4%.hxsJ=TtF'RQF;wȷJCNvݖfέuW~JܺoR3N{ 8[ U# ikj_<=xmOSv(y}{=Q. lrNIw2[l;ÿS:opRIE޷_<EF-~0.{a/o"yo_)?E}v%׾?nSz$|jzq7yLĝARy*n|| dCWlF/߇^\[緗[ޟ茯1SG~e=q0 K:a'5r01?cTHy@`:Twvaz1ѦL[@0+tr>y;:3O~nvNʩM&ߠan[V#VJyRjZȌsU-z%Kx%Z|vCԸk{&NQ^ߚy>vG>t m6t7y`k6fbyA<䬹П+t -*\1x9>p)@jy,M6sL8Ğڰg{Zi$@(W'wa!CR{_[YNyCʻFV]T(/'崯?yѫPZ֮TcQN}yR ¡*0h+ˡ/K5W_H2J[?7ڝ/!ra<׳_9_S4lCqjVg6 niS)W|;#?Xu;P/-zc[6{1/׵,S<׼ۜ u0c'8=/ZT/}q~@a@ޏv~H?j2د#Ey/kݾbr=WzQL^׵O\(%쫶ub9\2l4$l!YƿA`a^pm;B-_>_XRXzo{'r~xr s 뉏##ZnJc^P+oBc+:يTf/R"iMM/s0[6\=%2_Y+}*0dj!<Ʌ$uOggS M^j&-t^5o?YY~z|>7V7b}ݱeTrFO[A1xoL%80-;5%CsVBzʞ5tk˛2FoZMD?0j>LILEX+a%U2K)jyfw+} | = WjlzEw7mɢoӓ.:Fu_Xxꝲ=*0cl{5zC+{B*bDR>JjU>}._*Td х" }F̅]|Q]#w̦,Fow`1U C pۻŃ:][^iחr|Y$8O^q, =ζTa&mQ~%pzJtt ɫ).qTggA|0R*2Yd?0+58V˲? ,nl9L@Zݽ>}Oί~q~:[}.r_tkC&M-;T[I?]7X̾gmpEk̼!rRz Zu8 *\Џ0Rug9}Du@N3C:|ipNU@: vx ,׳:v'}հ?IkƬNî{UT;pfNR 2'7T(u/Dk3NVre34{݌QbB/k/u=Q 7y _9u=o];ݺ|9~GeOd“3ǟ҆YL/:~y>p}Wc]"cVnϴYF_.Ϫt|mzG8kyRRr H~ ^<,M -?l#;pܡ`"&*䇀ݗGL3|"<šZ"^\zespSgMnp#]n \H*$Fm3yt6▌G:?ޙ VoK5<7#\͗_>m7zAj&Ij0C'W5$k7MV%%ۄ6U"Hr%Њ(֚WO׸~Z;[@{H) @{4EWu~,;r d?lìPDҦG^ YE0>kb@]%QRjГV*匱,1' ܡ"Rp6%{ ETѿaxh بVkh<,*H ;+,/UP R"?󍩪4VA (S~ s_||EtcXg&MhyY/QnUdtœ:u ͫIPGtg53L8jq]̤?ҝY|Fvs$l/Ly~G})„Q7S PRkxIRf-e3L]\ͧha>}ٟ -TQNIr8uViP,ń*z@|_Zn(RxamAێJD]]P-#*M<᫞`ۻdѿ~,G i]NUXUtV$̑DIc[Id: zIn8uPk/}(+ CXZ>'5$B5JºTΛa j;$8Dylv8ͻ>_FCw?&*w]w]뒚]W$߰3=FQV&gujw+;.DWTh FǤ>=Q>7mZ{N:Z=^?7v; ~xNR/=E t?"VXޞ#r: `6vA EPFi@lAUϤDH -t= ǬW?un0LQ t Y/_"[mȪ\Z;|сg[4 F-"j U b\LJ] ,@( -{qDk斳W2*"2Kf'I_>nU qy)HIxp$b2сt%!ǡSQtJ jf#]p2{O/VbT *'1G~TQ s=v,ykn+٧TEU~\d!]FN[dM (建/Q sש|O猾?xL!\uBFgXhw ;y)6.զaIξVX5X6T6ˌ}EW#qE_McP=k#:55/̛-OnJ.jbNhx̤8? (?q!t˭<&4JD+6O3\#=ˆSqDXžUG-y5m՞.S=uzj4%]te͞$5Q:sC~Z/&B{9M$K7G3k QK(g:+:b|T0Siqda,Pq{& YWthduou7jbz>*Oho ݎ-ApJP%u]@BJ˟=U8e_8 qN"w Ū21?^TPD0"N}Uyh;sS!PZgZѦ~=)ePp3%մ]UTНE]EPAnىK'TeDzy7x||;BjV}~.QAPw2tj"v'zvdn(ʾܶv2"QzỎc=%E9M2dٷR7${}bf}QL߭B2Ur:ߡ€ͅoݰ׵ fR_r߱O-9R-3;5lf@Vsd՗~o\\\Lh չ!eQ >*OŒ,11tTDGC5k`k~`O NV'mrVf}LlɄBpTu\qb&*oP_[EhgqLp 85] 򫪸Ir50J2hA$IfI%Je?a.4:#Uَ"#65t;4`e1??Ѹ@לòCرG4ٍr˿ՅAU5Nj L9hp&.L!# z>u}s7"{ywtn]VЪ jRrтUFnYqO( 2/l'V:T}K [#Sx 0 KmfRN?pߩi(gIIqZ3xگQخ8`SA 6 ~LLzǧ׻Qg5ߢ늙]X?tK[ lT@ WĽ{oeעAK'O{5ġȏ7%$u $7ee:⧇^WJy@pjDK@|R%86j{"*K71y @HKᨪJ^i.QU]* .$DX>ʕ*cϧ[F;Xӌ6-+;[UC|'A0;%6tPKI ~i CI]<3 w 9l*l˄ݑ k\#$Ͳ9={enwA(~ϮD\u_[}BUQ| ܙB;2^h<ԩ#V-&oΩbK R%]OE#xSo1Z+Gq}L fukjGlyr3PeAHj^gyQ\=9G[b%~cCS.VoEsNxĩ뾿֙E>[V(]cHܙrRS#x1nt_JX(MoF!M_2x&R}=be%˳ZvZRvhX]KB"k) ok8fˀ\}n{}˚wGyǭ-:L*TY!ېQtcn`P(vTbt@7PyzQ6llx%A3 'rM=U{Vwh2pf|g+bv6rgz:??<:l]svϾַi52),mnۈCh_N66[}\?|x P;tok>VݔkzUU*dFHه붞_/Ѱi LQh.YKjTt#;4D}AQI/A%nm*.vM]|FLf.9}HX}j"Q1EI5M''$#5yxU).ܿ%qRRNoc>DEregqC+82O9-1k I4 "`NWMJqS5cjS jɣ) wZ>bWKи߉_|<_|ha^7l9hҡ$0Bԩ(Om^OQfuU >yr_y0`gSciwx2882r/&%5U<=x>]{]WJ4p~_v7ԫCv9~{٭:S-wggsyI gM80\;S6w6Y(kUqB˶Qei؍iٷnyOQ~gu]}q>{?u~R[S64owK}E^OpdjķeկZدsWgz5eMmj=Jd32&5P8= O]gk\Z͋\WHr^n_u\dW=6и^SG7fo3O*^ٷ[Rd=  T8Ra”vj!x#]NZT_sqdÄ:i\NvP`$]@ܵ8!;FT7ecc^V:̣F }W|E-钆}߷S|oFK T^7 (1>xzhj\yUk^TߒmC,KA,ZY2sL=t/Zo1N罸 x~yBaW =̞}Ul/2 O>:=WtqKׅ6S}ɢ7͇Q~[|5ϟc1G[oQu=E4M3lβ,E,7v)S0vts^iމ+%YR:H.׵ ˜]3Xi,>J}&ZzbE$?rI>tz\'X@REkY/>[/޿|jRx?{ĿR!Ny{m5c%hNwohe͕Ҟy\sտTnn}_g~Z&BE{_)6]t]T{(ռT g%ޝ'3b%eiד.7:~n/}kH(bOH J -ۍ:{ ~İWv?tw_vό DuN<@52~wIɊR7rq똢l0+uJB6Ng7j a{{̞+%RwpsYQv )=~.x%:< K1RW"ܰ0JF=`~qYn<" X]ʅ~^\F~ܾ74M=R8_5G !m @#xn:^G'7|HzIX"pF2LʘVXcVy??=B^QNV LS`E<$nX_ٓu+}yw]WV#N%,HyyvSk[/qdB[x띅z^~ dEտ;42sZ֨l?GZOhܥ1%5a2[=i׷}znlyZz_7Tsv^s-M7ǾMO~MJMgmiMS~ w淄QHYyYGwOsu;㖺p#ۏR =4K' Wzجx?Rj NDKum~NZ5aw{J:]U&)ݛޖ<;<66l]>ן>OggS@ M5J0n:X0elցAXc_䔻<'v\15!Nd<9ʰ^7zjW;,_}Bv|mH @4h`A9bwz'F7`` |e`Jn h5FCh@k6g^_}H_ܸ|#@ݮw_ݯ ѫ\_8>:>TI~79u[}V+&xn*ewAjؤ|.hs~,;M7Cs ?A7;F :~JFJ.@Ln 7-}P(M{5c`prLyˢ'_uk"~ \b.'^Y%)$sfbw՜$[>AwT{7x?ί/܈ .L Q\?]yF`A_@yܱ'V#/_%迓?79NLw^O̓QⴕɃ'#[1 t{&?@B](ٰ (>hD"sMo*fK?:2O/\/ b9gd SU7Rl3iSDWn*Kn?M>9+X[3Ոv3Gs>+HyVp;%mR^OΣlT̐v@6`JEqg'פ o1<$o8^nO%1SkQ>"нx{Z*wpI6X.{hieK"!wqFŊ!2|LAraˆ'q@.{os>xD[;ݔm d^{Rt׽[}ߔJ+nG(qT /ƀ1+ϒgYs)ޭ-xf_Sq$j>iEdlqewb~fLV=[@G/"wt@ˇs*R J`)86HP |iBzܿieUm O4fbxAhs  +YkZ!߬)`@#["OS @$UY1ZZσOٕ LuoZ|$R4gRme>5obghge.\=Bnt0pHrQYfW$&fe>YnDŽL \_okYޖ*\ydx !!J.U6Is-^իNW[gv]Ub|_cӞݏk*9u5gjcLӤu=Fh*4<|#41b*F (EKm=+:R9.]TU$5Ol j* q;huN@.2->3MH H=ha!^"T"JGBW-Vw9Il&<ޢpksqT0o_ @{l3_%P 0_ 4nM оn$weB[Pd5Z$^ĿY })O-"FW/­>i{;= !vvS0},w?iKUzLn*Ft(gcI?<@=nt3_^[n'v؉Qͫ3WudƎ=Qn&Jꯐdkuػb"Y1|%}(B; Rmr G`X4BSW.ƙי 'Db3LO5 ) 27)kUАƀ1w8(pZ7^GdwY"d w!o @]hd I"ĶmC.l݂sNf"ⷕ/m?ޘHO1Qǘk:E]LO*+. I=u[8n(v#@%&a"; ߦL"mXY>)^K59dQ1@>m$A_ O׆ܫŪpŢ O #^ANYH#Nd[En>a @ϳAd88OwΓDWB.3R;K'({P҆c:;eC-o.1sbȬHDU1 Gb6ː*e8ʱl2"g]ZCa8l*난WhroNH&C}tcy$} epIP$Tp6r鹘@4o(OK1[q08Ve;˯ߐNޔ=Q@mUOggS@ MT)) RXђJY uG7F?R9; fB͛|)49?[e{%c; +m[?\'`ݬǿf \?TޜNlEe$;j}w-og/±V $\O!t\>_7eHzFS`azO fۨ6+[L-5:vFz) GhkC=ӧA-{l?zf%<`ЮYu[|;uM­vw9!HⱥW~ UcFQk8 @!"բya8JGʰ4l߳BNP~qgO?o4h$%$"3{kǰva+`v5k^Q7͛:b30l.xksOL3%5㝦nK,mhyWq }XO.Yٜu咺lX38' GsO6iQCMn| >p5-c3?. /^x3_=ҎF)!btz@sS3*":>0*`y1Kp@qfb17?@c..z .@CZ}}-_s[c4D[~] ޑgE Yal#fGZ!e:뙇pl sܣ^7 +?,YYCQplxWPodE*@Ѹ*a`9|QTPaZ Z ^vENLNvoq#NLD|-wDa|H0JH(@e9WQ|LK\K֞N:?bo~.KH{Hfԗ%SVG|Dٴ |w^Eq =1[8C.ۅMċW֥ހyINj5|¼A~]#.1Zp+٫֌ϩn;W_~cs;>`ljhBoKb3yh@R[Oiס{g0bn)?H"^wYM*4{v`^l-۰;)PQfmDPr8Bj4GDvȤ}RDұ[jJDLM^rD@0[v0`;$,kl +aNsDv` y&v8a9K=V Unog F\*~]RGrp))_ֆ9kUȨ“HKA CNɵd)*ϴ{j2$Ƚk`Jo}ɐP28yej Tʝ)p lT &T#!QeŪN4\^^$"7P𼥦W;.m7,'ޕ1ujzj_CEzwpL[Og~~ɇI$17We]{Ʋ$JW.qEUx%p!rѝJ͖z_UMks3R`uԾQ“ϟ9˥D? T~]l ja[fusSKwB(a0/>f~%x9q+w M`|I&z9W%@<~.mF0d4KCHD V`bcq:1HM%&7υ+ؽ%% kp&$ w.  ޙ{۟WcMXYc eˁ/16kT|w r1Qb4̀"iT }d}h SlZ!4GS"շز/3dq5DL!W|hZ1ֻ>1xMlc7zWWBdi~7g 3N 0 jo8s2VqM>Q޼HG/6ufo(o5_x62U$3'>K™?=Yyaq~ _"*kdGAw w 'jͥD7U$0Qxҏrnv7fB^6tg[vҎf h-U.D`&ԪYQDME_yoU?CD؁YA?J/ W0gT| `0iEAI!( g(iTqWz*2ީX 1MQ(pR6C$OI!*[((k -#地U6a U^c~mj)t`tgN_~<[b 1pXV#hDr `16ZA]D7ޢfFrw sv?bcdFWq$8!;qwj/8źo=.Eo(u8@IwtW(Nm{?L7ۓxsI#7`{qgsjKa9_9o~U~Lպo\#ry9B*_*O+,as̻r7Բn4t ޸dYWO@h6ЫE/y$œ\6quް 5W=x^+kVn)l#C172wD5釽nw.PZ7n$[+'vt4 ~ Lh\ ͈0K!杰HD7. p554αoZT.EVYyK pZYcg~ ʥa[6s>C}AU=b6P$ ?u٦(Kd++Zբ 0bnFD$ ȵ_. \h*7hiI]eŽ/HXQzA%AU~mն?~Y` Cl{tU ݜʵpB<ɚYߪܲ_%ņ)XgQvJ `Jd} i!3mdeyigڭWeiꚷ6m;z{{> 4ͅnb+p]x4v.w@ UV v$OggS@ M54+@pB#QV 6F:pSpwy_ܯ1$ N1y`_YnVTBτnIڟ'j(oj8By*O"s8yc`,Yש:Vy@=O)$ 6٬()Z@Ր+{e@ϔTNg`?khWwMÝ h8#JW9$>`NjH]Z0<{T > Tt'Y+GT  Ps40DM-jo8)A,*}"lޚ鸙+p嗮M&5 :zHNޟ23򞹑wx:w\Mo2YWW Z}<zz.䫜?N1J<ĕpv)I Ǹ/:TfCHEK`- ,l\Pk2.A!TBuSBN[۩ fVͰ0H\0VӰyZ^e}e]4lO+rViH10$k.O?Cl.\$H`}9e ٿGv4q<.|r)&;t>՛4"(ejwKʒO|$Jwi﹵ׯU$RHk`SO*>,XoF`!%negsSҚ(eV_.+̌;&\C\"B l$\_sߘ][bwqhxUiqy>Qӯ)Α*]x_ G?.$SȀ ^\Gl wxrPTLߖ8XN,N2V @3ZP\ ci^1,جV*uӤ3Q@I]]5MXwmՏՐw=)?òf Z5p|(F|[A wkpt @p|IߢAӞ_TRLV*;*RO{ ?>[ :^_DR> ëT4/@;ztBGVtgꂪ: {Π|!Ƌ!\͠!rzVĜa'U=3ᱪi5Dy_m9^ڒǥu*I@3ۜ?+&_SI%BIP#jĖK4s?ނO<B`(cSuq.-W)4R45*`q#\ @K/?ͫmvC[N፟,^j0'xnn7˩`?c5Wc=t)(6[!(JvA#-^ {=$[e͍Y.Iuxu@T ~Z)uÑYU HmsPhَW@ao$ʎGƀB &9TNM̰="^JH! LS $ң{DBeuR^c){ g鹠kGi.!ewޑqO-.H6j S/Z@ .¯H>F %!$~)ka;_%Id–邱|'bZ&y+>g rgV{Nl 1XzJ6*5 &rbّCeh."Y|tpo]_4fyD q<^ܟwUY$yO}VRJ8ftk9_!,[坿U [XjGdW\PX+t'+u笎[5ZD6#ptq}g X:: 壞q`}_G~jQ7wy$Zo*UEW.ҋ1ۋ QyBf@=2z C*S#U`:*ء} H0 t@tEu0{?xo.FPYYqF߮ Eh׋r̢*ٞHpPvX'(CGYq6d/b |}ެ",>q@(׊C55a|;ê7>D9퓌5=i܋;39҃qmTqx6=-=NYi.8xc5~?eBLm _W^RgQʶӥIab`&%jkMV>1_2 ~E[d,#LdGt :47Ccc#D=.4V\Ȫ2`<g%?l-W?GJd@L ΙۙU**aċJR7iZr6gNMס%kBw|q?FH) DDDxZH88'<S`,u&zO (OcD nNYI* Ps kHQQ>{V{_1y/b!-Tz#1TET7Np̲EOd5!p@ HWZda8.o$eZw 60cnp6`DWQ!Jg\V2huHCh,,-z:;kҽe>[clyoDG;D)"Ĉ}KGn(O: p+y^~BZv{YŲK'+UIؠQ .Ӓ#A(.$Uv뎿DՁ8,v6)%#߄\K3RgS)Ąkw:pzRX.X}yX![ui6oOp,ƚbԤ2܉'ֈ1JiSiuIVzh͔67`G{M;']@,"}{/ϣ=&NlOggS M z>l3q H|$6[\ Yy d\e)uՂvRs WdϪKʇFTχ<="PGUZkUv6jH|{_>[|V;!bFZ-+_al*3<xuL!seoS{&|W5e#|` DĢVEhgur lɏ`.]יKD18D:8Eg*M,fg @@LEw,Rnbq_{hup=r(#b1,*㬊J.փ SYNJUԨzq4[Kݑ4aIɿBOQD2I( 4Bi\|P+\&ҩ~/@>Ŀ3nɩ֯QPxVgi):k9$f |A͸96{`]?W&t&#7ZaO 꼗} ^+:k̮8}3~ymlfF[S~PO]1.|)[^n]}ţK#CKN ě1qyv,VrvsҞ^RB(&D.WIBVN4]Ƨ9-ܩo.ŬvsxATj5IWPiɷkdϦro}%贯bg>.Ss K/܎ddhVopOu\26p 5+ ~:Alz8'xWy_ε/,"ΩB ރY+G Y+şǜj][=\I'Xʀ1N%FJg9=pu;t D4%HLi:V`_~^ٺ9-vU%U*JXbE+%9(DNBwtdYJ tF-;V}[s7ϹF9 pI] `8K<]vrQҠ<>] {fxWYѬkγ2n/S5uE177EmMy]N ^+FJєt\qh_ jeZi~O[(iu% țtDGnw BP&@wPSLCUntɊ:Z>| J$Ɔ*@!Ȑӏg/qDZ+Et[ǯ%3LYAqzxkof'3T!F2$""%a@T;RK QE,\g/pⓩ0D(DQɪ?LP6$:PDUKQPU ͚)bPП{XV zEeU`CcPerEZt$xCD>}4QlKu ON~Y6ٲ/>Qy*ix?C9K%/'qvXT[]6bVWA젖sv /wfiI1EjmD+Q0Sbj47']DfG!SBh{=l 0~3Y+ ߣpg7{xr~훖%ؑ 3J׉Xq1?]!WCܟɉ,8ȩkx9cO4&4 #Zuo۟#ĽwPeA3$ut-CGCU { ߏwZXY1wKܹh]eӥBٜ:q]peTv1[Hp"{WڣGūs~.Y-+yOtTrrwVd`WtO$c*<{329G!z;.-UE?2 SK3u[ kLݪ0_F˳ >yYx4yD)Jr2g.v*SӴ nrwL\F- y|&ZQwpY|CJ4_[BrJݪ`I~xk#p}+rM҈Ee&YĂ Ŵʊiu0$@~0~*I4[O`_cqƛ"nFaP z<6.{U͸5, =,Hw1AnͿ7ꮧXtޟTSD>+}5/MZ^艣؅r i6li:Ħ.ekNf?I:jXi2dDͦ!R-y,9 >>*S, KVGߦH6FS4ݯ,p]5W,_7e;&rSs7h]XռFۦt=nkkV% b &-Tjk \ DSZsF칼bj$Y躋@SCs펞@.f &I5qp$ EjDkn{ϳOggS M ttӿˌFO݀}B*]~9OvK_"e;͇hbZZDPbXmvu6ӂ! H|ŮiCNJ:r}?No"Z9`F `I@=r5|n^Hx齕We\6߸,ߎ~cZmFU|-,\b8RFLLs1XR(L# găDLa@N ne6o r8 u+_+af.r9,aAZ$.W<\")Mr6)15̈w5ƿJH@gD]dӕ'ay߼;h/)Kwe.^$MjF]D}Y BiJ(FԧB|ffR>|,r#JByPvdZSYtTvI $.VϽ]^Ћq)yśy&_g5\8rX&K1Oƞ;Ew-iVb!)U+Y'4ɯIBE.$:PwXuQnUӇĐe.ˢƒ Hq~.Ŋ{ptUl(ITs(MR?O5ϴ?4^tuO}'M֜{VݣO|-I۔YgL;a.c.S}OcYlzT.ݩVJmהMFπcaO+쵓*DYj^^|YVY>LTcGY~+pvUԊ#VÉw}Ĺ3.~>u5Z.kE3?J뻚Iw)WDl}698dh.y Y#pMX3T'C@ /tlUNK׏-:-ћ*|6ҳc빏1/u>91>ӒFs>Qk[]D{mTp1;WgX̻U~5Ì9y:ƎIR*YaOd '~%U3;ui t HnyOr,E>ۭkr:SA&~8kÕ),dfzԭ(S1ծ/5@ wɟzVyO+ c~hJb Pgwz/FW8SڞV޵YjTJTӹ3t哻֥ONK, P)Լc3p}p^w_F/ٖ'53h7A,m٣9zzB+yv+UgƓJW i~_;!v= +z|'[몑Q[o"x^$Ay9:kj<ϩߔC]p=%o"}"c\$TH,.r:^&0L@|B)^ޝ?jgCֿ,ǽ'NGVo1 wAEW B3(Sޞqu5dMqVN⿼ӸtOmCQ @*ԙ3r3"?г΢.],-vg$ 4ͰޡmjٛZm~εa9~} ݿ̍yɭ@I۝1.N=ýKrf̦HWQ]W|RO&9ږ]8(S,f홫k xw(Y$Q)LƿCb t}BmQפK4TߖL;zi̇LJnQ~vN:ṎL嘸K >HV@-wr {{z >?cSKC7%vS.tKt쮷?[V <f+ +<56_./ݵՎ~#OoV 'V|uَ~_%ԫVmtb}!ZZ=>4=֞B$Y{y!s^qm7{ԉWס}B!ixM\x#P?ypD.!*3z?eV1QѠ)U{Oԏv}5 4"ab]Fٰ5Jo-yߜ4oG1ﵮ^>yR?i5yNס^qm_}ȾmVfjDOoؤo[ou1!0qD8/Ӄ?9.Dgdb,] TpB3nʖ_dsLMβ287lL#J(qx=?}ã/iƣ[~_mn_Wy5sΜ?~mj{>z\:SE|ggZJc3g0td:x\ĴQQrj5OtvC公&9#զ+/MM8AEa뻞UYpF$=~ȥ[#I46y_m=o|eE8~/q1b>drOĹp>Xehqf eڽ(ipwLby!91/>{Zvǎ9eD*-jeߠlfLeJMط,w%ͶEbi4 ӋZÛBk}`۫'gF/(?E$;_ZWwrI!)~|.׍oV (IڟKwէ{ۙ Pg- 󖊵iE\-JrO/CNbM!JPWݗԲJ?G2<C}nIC#A9lvngOggS@ M!ĸɸĿD"j|ş[䧆/ =yi|g/IS'kK۷X#OugF̓D"+Mo*)J'E1_\F@;ѝ :(iLnOCr:azzU-Αc<zŠлNh|c_ڇ[*qV"dCV6& vGH+9c*mnl"f/TnۇӶӟ8nQq迻kʒreFdN*k#b{aG"5Ik`^Bi7g24u !;i; ̯[wq=zC*x>%5 ֤/-T@8ݕ {$*_/Td+Z8voYsido̞m:?VvBKdk- }Ims&dqi/^ h~&L.s6=,W/wS%o#U+ XrB#l&,rmOG/U8w7Eu)NN}믖T"jDoXhApȿF[`]֢/+] ?o/6 ; H+WEKZESZ; O?9|b8d@].rsѫgHqi8= #J%&nrz=*rYE|+f+~DisSYT=u}8?Ȭz,Ƚ働~ hN^Qs^^Țz{7=wowp:c)-;&[d~U*2LaqֿAW.4v#{GdK~nL\+M7x+VՑb`>'EA e8B8xr{~8,[5jz{ameޭKhGb=+i}k Z)0~*g2Hn"RxW;`TBv|8 tճ}\s罡oe52b!UXB ˟҈@e n>k3j;> F3+[RT5ʪkө}bcQSƘ<ޣ>4w(wxD/vnH>;l8W_+׏.˭xX9_3_ - vy @ތ(u@$OCbFGrf=0jDZqƿhk?*,U8zJY;"l5@Z}vs2G4s}г%6SBR'gХ-7[h}ܡ)ץ~Sja{g1|XB|hXṾOv g5XAkV3zFyϥ{_xz`jhR;+OAywSpq-C,7"9:X* B>@"vϮz@-Kی1cZ]}ws颌-ݖ!L勁IlRcUճGM4ugd_ Qq7\gm$*NK>ȴo-n\u|uMdZ%?Q{V<VW1M&ۋ,̏lEi0oTM{,Ҹ594N2wm{;.Z˲dTH]xF˺Jm<gwxv->_>L7"S())<{8.%u<Vg9<Q!ţ'9󎣮*O 7쪞jûT2^GAacFi!>0?ϒR([.ߋF-=@vN[{eܻ8 6)cs|pv-OQ~tR[o£cfywX+r!Z 2Gz~4^d(2TIaEl&P+7{ iAcЩ@B wmFP8&(JdWR+AJ\ugE壊s 5mܣ=NOuOi64ύNA!? ;M-M60v-dxtTY;w6Yz=ax 7 0KG cc0OjzxxUnԦ}d/*8dr˂mfhax4x9X} /SUozlt<;˽ӱLIe44W?XhE̴kl SGs,Z2"vuc5MQDA EiwЯ5j6-ƪ,, 7IUl^/|5Sk.GVYc ʆXnR 8~ 225a(u&Nc0ʌl\- l~8rz)uhMeG~^?,yR n铠-$!pǜno;GIqos:[}^ĈMn˸)e\ơ޴y7ť?~|OnkUǺ;omsH_ fu޼06aL&\.9uWfwmvc)L:'/}>#?UHL]n|mw=gu 5MgI_Ֆt bu427@N? '('#i~U ~]|x+SYUL(>v{̕{ }akÿz[qzI]K` o+*0.:zqM t6\4aw P^bf>hi8ݪ (t6 ]؛c>␦H[`R/ Nؗ E!CϽ~M@ cSTuP0-) @"_@ fçFf?j~Iu Y6Ռ'z[㞤1.Vy Pv3hh3?H?˷CJ Qޖ'``+ݠ%t=>=lf-d7$wW5/ u!6FP L!!y=4!g4Ca9;3{4Ȳ+8@H3BWai18u ʼnj@eE=^:$') [ކ-ƈ萺JC~&\2q7̌\`'W&1MIrP~cT3Y- 6}á.v,֣W&[/a&/m{6QAockU}cgR:xX9|~0-4k|~ާ|OW5ڵ"Ǘu?I0A} ]<-YԅMq>TdƱiXw޽.]\ecH11WnDEY>m}8"q/.9r ufOggS@ M"4"9=x;=Fl;|ƨ$t}JG5T|w=x=lHnd֓Wx0 .@|>gI8qshHO`b|ސ&ZޭF$Ti?3ˍ 5Cб5 "??7g/9R-( P!*辡}(+"(/̒s#uOj>:$=7ZuOsXx<>zW-{<;qO/.IFdN$$ezfء]o}~6J,T6DR_BV(Á`lּvd._vd}O;[Y/@ָ5 c{g $T {{O!}ww|jֻ\Vi+ln`CbX @fx` iԪrd֓:RGP\Ti䅌W]_AwK(Yﴭ۶T(1TY""؊M'"`S' O{u~7vG4%yNgKfz`sZ2$).f6Yݯ\#yݓpL_=bA7VȧA{+bni#h>G|&Wg+-کNl9:q㿕,&rX=iY4s A>~N)JbwGua=[ߪ<\׾_| ol4PtǩuTn3CUoy5*}}!9f:@%m_yH)m8^N˩aDGq-&|"/`Jy斷]uv$[xțX"2:{9}m{RO*9c$x'C7*G,cx#qFC>qm >F[6,֙ѵjfk_ L(/JT'~`L\/d0BV# &&hH3E]mb C0 -6K{\D@jH_yP p w;mYW."ZӈR~n ~e.M5pEp>GEm9ן^O+ga)}O+|o k)G&3\B+ $#刿lԲ{E8.GGsٻȌ~~QYbЪ|mi "vZ]IW{ۃ峵ZVٟK#|cKQ@]H * < ]gAD` ya&B8YIHZ/Ȣ{O5V̭ \'IPg㣚ܙA2ZÑ9<><6hӰ򲽦t.YR&@$"Hm% 2!ճNHHd So?  PԐ袺wSӓ 3+-?ek<x619VoOX~)AҋWm&tT3=@y[.fߢF'[ױVie]^wL5kPmx ve촷G#Աt"[{yAuz4o O#MM`VAa X*kndos6yuԣZef LYO;uN/[|U&}_qqytE)+Xwryͷ1f@,'cc6of'W\N0 cU+xOviDKR3uȒms_/ԷE?)}nmߨME 8&u\_y|4ףx~]3ۖ{垛-oy9͚u'ЏtEI LpdzHtvS&7gP/paZgƶׯG^ۭ3?T I3;kJEЖrR̬Qwc; Z>u䋈v.<4^)@cmkX/eM?"D>QgӮv5Cϳ\3uIR@T˦ y K тppG?! Mܖ.ґ.θES,v[ mqvQMGZf/i@<"CgV=/9ɮ/GF X=$,8tN}Ak-q(n^eG%[@C'KͨW PL}f/Fkel~dz-xvƏ>9?oܘ 7tP"V z6E ۽^ ƎcY6L;TՕ x[q򞠣]uVk/VLp5J:ZaKެaK剂SV2ڳܬ9'|j;gӤ ^Sή *8`*R$\,ƀf9XRoG?Ld0]yK8 ]oʯm xu %TYdNԒ6FMr5cF7;g iW2}kǎUICMלN"kɽDhm5nsN[e\+I:t8jNdzAid6oug/= G!GK"~N%Y~U كZ<Ǔy'>d?Չ2f!MY|jo/af&:˯;zԏt^9{k2eaEcY¹}nc=ֿo%VY}r>2$mp+%*̵mb, _O)9SxՔ]YHm3~ [GEރx`J)5%\قٻkx)? Ԩ; 2[QCyi[$lv%} "AO V|9~\(S&V{h7ϨM$h";.q. YG:i`w|<,2=#u/RZեպ~U=jijY$: e{YUj}>/ 8@HK}җT@'hC_!*:21 \þl+"gjdT tgNT/9yM<.]EKn9hC> 1E`\CA~qfZb` @$=z!2H<ݫW bf1-h;tI-=7hQFu,4;`ӝbBs(qye VEE6-f&xփ\V #:{bC(U.ϗ! W4iO5b8&pF PPx;պ mùwܷb^֫4+׽ڝFp1DK(MUv=ag01LSUUF&{Tmkuyg#y^D2|PN;yK <7r7 WI`%փ=\R{YϤOttĎ}бCZ'~}KSŪ|:MR߲ DyTm<]exwf֚?6y@m mAы:/$p0Sna҂\0X.(gة?JwOe5~]?CaB'KeRl0HY/GB}}ee \{8 p > f `X턼*O}%Z)IA5)W*I+-n5o=nGq58U n[)D6y30_UJeߎr)ms J"{ iVDvm2@"J()Bv5DZ\y+QqL;WQ Ζ׷u 0\?20Oլ5`-o>,/rQB*R+B]gX_׍*U^ND+ZR_+VL+jЉ @:L”N֮ KĴ]@0ETFyOG&CoXzKF*nE_ХZnWD_$ Xۉiץ 6 ' 'h,d*;fGHVk՛`WS>QJ7e*3ڰ5l ,1{CxUu;LAK Gw2EkCA,y&yDJAǍ<\/5HWcOW3%~eYn-2:.xl}լQ;$)7 k~/%NowWj|R@vFk 25h&3kd|YRxYN\-7HkU<ndpHZcecoWҽ`ߛkٲQ4J/FYy'C2z8$kjUឍBH"ށK2Xd@0 6`ٲQȧD1^/|pڜ"C]/xT?^<_'\0-v 1qd\5熂$]g%ۂJHcg ӟՖ!jce65@Ҝ~(ٛRN%Ԉ3%Yui{DtO|Lz{;#LcI.f5]{b?e(7;W "Q r3*p{ܗ>.\Wx` v< vùMK Q_ATu9GE^T~ht]oT65 * [  js|n4[o hؑ?(Y*dJEZ5{<$\ɥ~Nѹ^'EQ8כCD GWԡRFe73(Ox*?H WY yz < [Aq ήN@qJӵhIʓZ8p*/zm|TܝeA|(NLi(J즪G6?V,rWʕ5g`y gKvsPETuSwF!۹"K_ i}"8;̵$9$03>R.1'V%eMH];ynw$gvKtx'4l/bo/u Kal\R}ӵtDؒfW1.nBǴѳW1o3՚+;0Tk 7;žX)5i&"Z.Yxa?/_ow<,:Yp8kiiIN.we\XWD*ND*-T& Qԉ/*0@ZN~+ue\o*B~a3d(Ա[a4ѢM^mbL9jҁ!T[xrzl7BxOoGmK qCݥDb\cph'˱#T 69X^w~{۹9B\ 9_VxaZOۏtx٢ZBSO ђR5}UWl}uXOh6?\>J7cխ1඄P{z0mT5wwh%c/E[ꇖ--O0[6M4އq\ pܮ]GuDu`Uҧwprƴ^,Z@Q\ăىd:(F~, tb.K} =Z}T> u,^#~s,g?&*kelp[ ]֓'6~ݫ%Ur5;ĪPqQbY{LuU|Z͇ݺ~xL04ARЭ|>Y) Ǿdu.W&Z܆Ucc\nzs1|eZٖ9f Gwo(Nv}!HY{LQ@zPy{Q)` E]z+X],ѹX"d>&ayѲE~N[ݡB:|q1Z56o`=FSdgVOggS@7 M$'02#>*W ܟ>8@(b%5=Ed'; Jj qV6N g_W, DC^a1U'W7U_tpRTuU%n":I?hpd5\3>/i * *ߚ#@Qό:Mi (Vp׿j'T#)j.Nί*0׆}/=us"Z1L1h*@S#*ʅ=Q]rVh׀ ߊ@@hDH S% mLY"ꇻqC) mO{xY$R]A5>h]裡",!u-{)}Fܗ+_VZݯ?uvy*_C(eu27~hkVZ¢6f\nܳ vyC)!-(m H/U&i,M{f&vuZ\;sX6879 =12_+o6P|apL"H$ozjI/.˧!( &n ag}~b FUlL@۟Ǻ]}#ꁍ>$$؁hhb B/2;gY$!)ovn澧~t`2ȯ<X!/?ިT+-SYj Lm(>7,Nެ߉%2nXmG$be7yR^⎟匔ȁ4o8U4f˥'ש<)KYVjq=)"q{%ʿ{g!ʵnRk98AJߺ67[N7xNKV͙06RAri?n,[<%`/+}XIuDQ;UeH ɾ랬*CQzٌS5N+]\jF]ioiyxO}1ĭ,!/# ks?'2Hn ٝ>*<3={!4x`FdTA:B*>! !]4l huҥ~nB#G}W%+2_{ 5:ǽ4B_jJ:TvtܟpD @W>U֬&'g.d&IZ:bN:{}U:ǔPs .tHT\ìe4+ĠGbj$g"Fd\V'cELu 43zG"sх٠m߲3jLYf5D$jS gQMVWr5ǞVރޭ:c? gs/T}~>?JC1oFZm khBxֳ`W_ۣnKy&=64K)gG0wZ)'?`u6m#ݓ]_qf'|&.ҷRJIc2~Fdܸ.Lk#o{xHdtߞBlRaI,sU{[)-DA!67O+8T #433vmTER~o^t>fټ$RlI%xkʽ Ŕ\a" /6!1h,-Ux:d1a',u6foz^[:ԪGi>JT_B}ݪs$w͓W-Yx <>Ofk _ٛb2N7m|JIa};f;Ʌz3z^ć`>hMS'-Kv.98ژc j䢌vWXOfG+EddYlG wWJ, kžnaBΥ37?wty S:,# N+ k]['ߣz5mIw"}Ѿpe66S0ʥ)sMmi(xz,Hz< :ϸ"u5N-wO-"ᄾW&>wP(jMxꟾvdzTϞn.l.o[=Ҏm-3;]ΒOըh9VḈe$Z/_{;Y]вxF|EAdx!!JK;tgU_EAQQ-^ XEAU*Rb:UWbQ@ "nbTU h ld 0k=eͨkj -<|հwݯ32e"sI ֟]Yr8<+9Ϡ yn *(ɚ@ <EF* ;JY#/פfVHHPI$ӧ xn1' nȽǞQ p9cXX^؇v:V؆xϴ:uŪDrov3+ݚ[n[z滐j T Ub$"nS;uYƩ:8kOQ.n>?ï-GV)Л4TMG׺W0"PY5?nYx]Bz@bas)fM9ZsF f'Ic!};xp<,*I763P\|FVuXf œ+"OQT̯?TB_.3z`5\ekc~Zb.|rJW!H/.Y.|J"Z K=?jɖC<) Ӽnٙd`FsNcuZJs)+M2Iz:|X)K ȟ^6LCezߴN}U i̦۪-hp+@[B_|?BܱjP͐~y%~fB?kF=w:I{#//ƅ/U<_X;MWksᆿ<{\Hm:ߑ<81j<Wtĭ2\9y/N#Kz8ܮJė ãՀa,s\[|lRs51ǚ!˝;ftS 4lX)zY(<(G3\<>me_"&ՉD$[Q Lev]e$*bCWXk ^fp'X9~2.RL$&<=R/&c1UYC19kMw/mj͚SZ^gmh.72&sL[ԙW""t !Lˈ4A۬S]^SPk PG'|b쬵nUz{soߓӵ`9CDQs#53b!D[K"<_#?B0-0DEEG)""-"Ҳ4 =;"A*9噓8uXz)U9oEMbOAs0 zTRw 36)}8T+`iNEs7Ξn[8 } LrVd#U M")y鉞t S3"dfZ g#M@ $CBWho*YܦXI:*^jwLM}uR>ƎsTM0\ %DX^|Tmx?UX] ݾ?1KOp2<>#}O{~-'\[ J'Nn3BQڸmD>RNR%RthUg6c/*4X Pʶ__eCR zzڬ髀D ]hWOA| їV:gZ WحNey/SvY~njڛ,!Bx\" Uug(_wtZ ^Ah\Fdj )=BG*{rOlYkٜEl&}uJ.9ExOɅX>Dl͛~\uT2^w>9ZɚzL !?h%ZlZe5Ԭ]T%MU:F"򩝀O_A Y׳Rv'R3D)o:RzJTJGUQy5 Q<5Z,6ST4ԹR+#?O'pBpԸv7Dkٌ*/|*3 *L٬ ^,Yo6q'pj5[v!Oz% "*vu"C9"E˜늨Hhg. dHf@\"^׷ѷ>].,q0=∤ Y͞ol?Xb[w}~zs[6C[qoj"jԠط*Zۖ;=l)6D &'j|ca!=`In; 7|uDDFt{D z\;@;G}4 0IeB+[Y1pZ%Q "&=Eθ,vs2㪑̚3nb)367~iY^~ w𢩬d >UYظ jF|Z;W{-kMW=>qSUq‹IꯚS:)$} dQ,$e_uٽ?eFMƞUy.x +Qy KT*~TW'T y̙oףSEtnXAR^ Y,ߊ}>Պ5JIG,$|xz|2ȚMk,+)C")z_z'.Ϫ*;`Q!NݯYu:z7{B'_*pa//A!~Y5S4mJ}/{L%y\6 c.v فʊADX4i#g {T<`*{rZ#*Ŕ]KTĐFWJ͌piMr[y+[>-&dRBs @]g !u@H .)ާRK{Sb"]?½,v-AnP7JUl\rכp;n9Xϭs%{J;* 61_k̙>wP暲qۦCa;q V:#yV+ePQ1|#6tA+k~gsgO<D9+;<ݱ‰\wE!10gP5|Trv"4t.w;!$JDZiaƤnBn,H{8-͍Z>߬l iRzO)HzOV$/03i(Jk6jMk \ c5ѩ0H-'Iwpy,ylVLA6sj?~WGŝ05QbeX~p; *渶2p"o;Y,ÕkԠݭf Ѭ=I7WW̱דM]ŨT k !ŘIkg~ᱏZW l\ k|6h/>L|\·c<\0^ԧ֛p? 4ldOggS@W M&"C5!$$ܹG?NA>`⨄n) Ɓȕi |/C<>RNtC oglƼؾⶳvt)2[3#+%ǟ|ȘO1Z BWO = ٠fVֺ^*tE҇ MIUdUNx_c,FR)FΗn*.U01;חbsG:" ຨS@REjC-hc7Ǣy$;m]ܥ[amYY|^?N-,M5AnaFPE}]2 0SwE`F=brXܲp WIZz?AK]66y.wֿx7⁶L\nV}V1ڕ-}Ny{N6T(_b a/~n}=E]\jI-*#?K5>ѿf{>ݥW7a5uyг%B$DŽu!jo#[4ש7%(533>qBW#˴ 7sO6w-jìסWVVu\wԴ ݫۛ?X];qOZ[i,Xsc[vG]о0aH/|v|lxYlFPdH>܂GtV#znW/V 1iGSѷ>@'}HYI^h/|~Mx#UҊFDu"wo}jYVԩ-r]&d_>-HW'ٮ3pPgiEj1Q:D6m8gZ"CĻV~:Pu&JT8Hv*G *f 40G[EHHMysu:3Eu_ :b3pbIr`le"n -)C.ٓ=y3?`]vNRh\yF"=nz1RgfbNMn_7)Rї=EAcITl}YlJ`ٕ;y]fwRx'S_YbClH9Lp:*+uF91r&4'U5BwQ+r·^6Hύ;sm:Ƭ]xv>&XRU3qH!Dqj()o9MD{?rՅj>}=y= ck=|% GeJρ5S* gYV/]|cՅiBh-JᷬLus9*,:iFKL7a52@i|H!?&?VIsyʶﳫͼfXت9J3^9WaLĮƒ{|%H3k6E1!]?qEkNmԡ[vgTP{Kw5׏  U^8Hec4Gs^áUNS8;r o& ZfPQ^뤀ʴa~" z2./+GLznؚc> 5PQ(L<*=_'_>'yUNG5;0C&d&:se{RQ-?䀧SN:Vr{y@3?`.asKTCXJѕbedIBsYjS,C3A6M٫?$[2cMc6^KWưP%R*Ϩݔq닔ZE43:(GwLaƲoc2uӤ+GE* );-t3bLUGa`nT.u2M H)E8HxX:ՍIƽ)>w#IKrUKYpj%9D(eD hk{>XWFCDE C5Ԭsy %#DE#pޢuP1LGMu҂RETksPD#AtfIkjJ3ܓ`1;/(֘ DUTe)b-+ݪgsi10o^gDcҦVf2CD,Eu̢Ua9)0duЏ,Zu+~ɀ(0T8EbzdWMAd&8GA& )3=Cj]Fu2T{F<1[=)_~}ʩlU,oϚs/΢7yh"ZcP޷C:9*^FW߻SgQb@6jHu"|&\.ov&9l{匬qP ćvU3&7<5b;;ivkj~ KSXWں+;2`WEhvؐVGxJt+8\ӕ˷9E?^fvDsN8OƐ2"^md UCdY +:WFtӸ8~xbst9;h7fQ9 .&YOH&`r "QQS3ܔ[f[π?'v-8N*d?|U}_ib lh1Y#ɘ2U k ծB{q^5M}A޼mp-amWC?Fڤ\{^>?Y)x)PIvSJ$7}v]}*+IV3c2tꮷYo` po w#f WRMgk.ȤO4Sp6}( ?ߧZnx˖ޫ Zf(`S&>TUE5bMCxRU6.+]QA%Tѓd;Uʹh_7ADJH4k_g r8[d"& H,kT<*2< AE{-#JzFN =eԺ=SP@^uk q4@ʖt)1sEN Fܫ!ّʙk5% g]c⬪'""Sp#x;9Ha@iёq?h0h((jDn am_GߞD;seu3s+NǸas& -6Gˉl7%RĴtsjG(ߴqS!oy]R:(+ԼZS5X47k:VFݑu0QtY4R,(kxrzs3;ŨZ"-R:e6d^YԏxkopߙqA0-p%wG#Ll]dx|>v}T>)TCNP v^K}&}slSx\:N km쾫聑R4"*'_=]}EiS͖z]Y:|ޝ GR[q`ΘRAE$ *\3Mzk;fD쀗/HUATp 3E+rcH D9u?B) &йfG.T/_WJ1񈈸<3")J}~"V{?Ԕ^>"{z#0@!g#(X}~ ݈#-҆j|T)uJQ|9*}/g '[rc}.c֠b*=g:t1BCm?HbSB?3/&Fd\r8u1FÄx h nu?N]µG ƥo`x3浅M9c!΍wD fB𰣮aoWITk_nuȚfQp$#&hʯO!rJx8Mft>μg5Zo2vث3 gr+' .|r U?DxK&SS}M aAC> 3W˰&.=Rg/1F,Tv'eh(-4鐟q!jC+kab}ki%YGg*Ӑjz6֏f"`]a,,=0Ww\2AAfgϳvl?LF,/W+{S9(r vkkAQ6kJ` i_1nx8A(i7ƻ(߭K/WߘkצsT_8=Sؑz>?uz,fl{;8Q5EDxj2"DI*Q)\wZbQ ޿x֙N{' %eu kf~ ڗۉ$, D~Ne`HW:5o&փڀk(V%Mzdӯ~'[ '>jaDt5UY58ꀢ@`fWaŤ(/yi"58e UA>-|!56{QVW%ZDD\(iѓϸpxϪf\ǑҞ#8[kvo *m܍bRywjq }doA9i ڛ><$fS>&%ˆ&F͸?rp? #ҍ7m#DM_nn˵x]6w'n2Vr7$r?}/ nQqܿk"GXʢ:nR`OЂѯW_7rqىcq1abN.Uho*l:ّk}'ur~} ]NNqV[)8I:}'Nojcݟ!Vl)Sğ!c~5ho$ d*u~3a|{#Jέz?-Aq3SA,mBssdohYYƦf?Կ>uW X/}>e'׼L)UL ~=ol 7P{8njxh/- 4.3vf" y<=lADaʏs*CL 8鈹^D>f2 z:"8˿#=pRū05ǜ8m{ɯ@u&gBֿ.i#;T W\y-M_pCϏ?ikf=%|~~3_0ɆG.T4w^n⚿URט.گXȏ}z»A(XHuST9yz䦭{;ԧԸīJNkA^**OԈ]v|;qʂ/ϛY%2~R٘S)Di Ɠ쫽gHsOߗmLOggSv M(T\ü9Y(&)ܭLhPU?]*<{\5'(R-`t$s6 Tʇ L~hѭ-O 5}ZAf 'gQһ8|NF\7WUAlVy^.Ƶ2^΍ ufSWT(WNJu j-8zn?mQZCQRwQV@ VSI3^O?U2f2k^utTkp*@UuDPvj7q=ekʫ NQP/`UWD@>]AJ2u~ƣu1GpEϏY&1R**K:@MXg|pLn T]q1 ӥ#Iu(qV,r y|A,,AOI྇.lqZgqoPiGϕ2>9yl ۡ^\(k\s1]p2Ju5ѝ:>8yBjWfBi¹/,!C*h~H;f%uVV6Y(:hBw.x޼|3)6OJyq7W+qX  "J^+ZSbf}zO;}WxF+:m]b%eDSXVLʑ<&)Jڹz\PLgNt1k/Gy:&e'M&e~r=3#.VGUS҅+Pa%q/!_N.~z9fѴ ^J%L+ytyIno': nm_RerumC>P_t -,σeLzSWpNZ}vsDd0ZVb̕d5cݍ-|8+Cd85PKo62Uˍ'Fx6/3[|n/H/7os#Z>ߟ!NU^:!kQI';=ժ t=?Q(}鰄%)(L UyS*U"ͤv[fЗt~-b(8}TDi1EMt F "Ru;j ֗vްʠ{J(F^rԹ0wO|q&ۦڻ2͆WtZYkʼnU6*^t@=w3E*z ]Z?g]? 7߶S !l;G~d1N #D|;Hg3 UZd[y㊣~l0; p8vJg}5*Ok}y o) k, bȐNU_jtf/!^ m#n}lgӟAxW|A=ypMՔn4rkhw8P$? >X{w*&Lͅ if8tvt+IaX;NroٹBˍV:oS`D 8Fs"LNRjB^=#3lP<'jïCNmbm/M5G\C+{g9B= <#\G dnJjv4c۶H8s;H3WO܇y_OkW<~|].Y40^VyM7*SyՍW[&ƿxV%@'S{uŖ eނ/R5ƺŪRIU',=X=G)ŅM-xh)gZO^/ǯயЋϯ C5VsKlQ=ԸY7;xTܮC7[SgyyVcpu*{{FTm?CUZwR[% ɞEt+JhqN):cR} /xXq3jѪW8gw]/7 hj8?[WIy'mezIYx19ڥe_>+S%sxvheߩEL]o齅*`rs4<BAM޲ @[u?~&)cᦆZZPq_ML/iM__/5[|wcZN_ø 0}yR~ҲO߯]FQxqtQ=PH㗓;hqs>}}?OZT9Y%` ٷY.WgG9]7xף03qހ{^ZUη0eކT ±}YSg_uoͧcokV=eWK3qq$]1W^VI+ujp 刺0UsJ\XT:E"oϣT<#rX~c[]~[ջb49"˘pR%I*9Ƅ'`L/~ލt{{ ܭ׾6pƷeևxv<ԣ\%To?ΟGqnms9iB2nJ=3b>pf1wD ~k>z7g Om|)&7{ɫ G9oy>3<`\p?I:ϼ(*=_nT==:6wZ)"V?k\p\'=L?{4jkd̚fkĠ?gh}:a v542fq㱄qx! 1wSNiu Hl3N}]?8oK?rxKި9곋G~ԔWy=DHY݈9{%Vm[̥"ɚݽ7- WN,߹,μtI/{=TXJ7w~z8v6džkly]"19i3W SJ$nPl8~tjG~,ϿY6c͌K2>_}{yv|~Xoҵ-`Lb?ӍRuuK}y\Auv\?彪$]; 5rN~#wGWxzNTXmϦ5΍~l7_|^?7/;i}Xu{|֦m֦o3G'a?ˈ;dqZ[)C¶wR'>'iݵO>)]y*68[pJʙ83cvU ?Ѭnx%Rd -k3//z;,63Az/mTG%m1x_^t0ԻS18:R,2_;7SCԂ6<\dGqb=3iEr*ܠ[oUK׊.|:_JZ W.ǃ(3rÔFs9Mc5[DLu^!ݓ>eIZHG* &3.5ʾ^WƊ\#>lllX'⼹^{Mhyi/n6onaGO3V}Xw{m3)og/o湾O'yDN2j"}qzGilY͊ҺLoe|8Bkǟ>/Uj63~չ`D6U*GUZ?'Ƿ5:(%ZSO\Wu@AE}>UWT^M R[^Q]7h?/MF|EDMzu_p^4[S3o RET |[ 7M3}B*ЏmhH=eO6.c/ˈo䭔?},:YHQ-C5= h6JDz vCE"/@T,x%"3T4|0HZ\gsbh=>ɯ;;]AIeǎuH2?{YazƕcN˷1uŠ[P/g E/NZa?Q{\k=~?_-S.uWˍ׃y~,46gkƬ?uILowP-o9D0ꞫO OjӐKJEQ.ʝp}G]OчV6D u_x~?}wl5xOȶB2:i%~ԱtNӓio'7-[4b%Ӓ%<*Juec k$WsIn R g|~`]Υ~`3(MZ7&M<\W7~XvR<:F/=oK8[!2?MgȬQ&~i<ϸ-);2Ӭ "f!Iuz[լ0ScrP4݊w[صL | 1Bvj;$^~IU<=I\\3,-'[58.B%ah>MC:ꁂ NT'"4"n,r\ǔKqzeRWibFVIGڙxn3+O pPe'LbwdvثFUDNu8G7eJ<#,%yy?~Ic}yI(8hgg4FGXYV}P|mUۼY c{@PnNj~Y^e$z- a *޴44 sSՃƉ >&{}a" d;_WLIiab>'?MNQ2㺽4O(5ϕkvǒYL!K+$>,[S'ﭷ.=#ׅu߼Oy]%:qT3Ή\XS1Gwyڑ?yÊ«>/&݌ݎoM 1{-beqUY2T⾞C> [\%FDܼM|zEiNjrr~Y 6J a9noN{ m݄)ԩUK@?覿sMZ1f=r$SL fZ5jU$9NWȫGFĬt_j秒 &)Jt*܏I-QqES E e嶈ϪẢ "J*59Uu "#]|ޕ4[@2 "Ma3Uј]k  G3j-!Kfup b @{k^CXm(cK9>cRZkr b|o y14jS~:1mX GE! M8 #kn\iNj}S JhMhv/ {d*SNRt[O|f'}e 8L^;w=l6ےvj8c%mg՘;Q~Bt藩Q|Iy '}TЉFI:񫫟W 4*xIu)K%syTX{ JoQ-_g27f҆bD`{doO4rb:!; +ӻdž/-c7@bz@{ܨi?4"m2꨼)i&QkNGr#օ*~0>,xHg=uwq澂`5 9Ir!x‰syI:| kLD-(M5aM|Fg u?ձCGH\s-n|p5m{?s/aYȓiX<HlḳuyBMxpÌ,R1wzbaH}rCUxfuq D3F``Fl f[m=WsQPcs,m L~Y(lpOfjd<*߮V}45|έ*;#1 DuMGFjz8M# 8 #]_k5穦HqjuqZEU CU@vf׸LJ;#"* Et-`ʷ+|/_ʩIȨ,KaWc1 M:Ek1u4YŐqԜ&C:-堨^8%NG6$W)Zq}AB 6'{d9+BAOW?Bz$]H;HA;j2>PɜbLǕIfV%iHJ@Jǝ:YKF&u4\*8w>/1s[CM`.1. ]/~Uɦ)٨f?>MVq7tp/aFLNb7!"+&8VJ%?wR5_#̊gwrZݕbuAX8ӪKe~1o [/j $f6q]1Ifr] |JZG|ҿܟ/*yi}d>KzQ u~6R{$~/u밐v\[燍[˓u/f 1-*Sd|\޵ng {Vm#c~DJu;b+=פV⹯ۑwfe_oN9=#F\vz2RNٱkZxǙD7p뢳lPxəY\wf4&x+{Y0_d/ l@薝ZY<+[|DaƨBz"_FHM@[8vtCy9Vdu(QW9Q ȽIq 5z|jh֘JK}}:)H~,8-l" )F[+a3|@MC YvEK#L՚^M=J̤"*Yr7/5_~ڟ9Hí6I `JjJl .,T'c>ѪWj}xi GZMgvX] N5SUA[(@#]*uw2ugԮ*_\7J3\,&kB/)Gf>ͱ)6IH$|rd͠՛ّا'o(ݐDe@}8ê:j*۩t{^3?#m&SC]BTl_ʛʳ~)SLNYL 0S|Tl^/Әq|B}kZP5ݭl }mt7cL8˧Ep{f{OSkn[ua8b]@nSޤ֯9M'"bMO]2"#_EwƓt0חD~|b?_zp6RƱ'hwW2KZ;qXyeNnpqǣO|94hqO|6`5 g-NG`i?a](Q[RJ4ErG5߈IYGW3#IVS' S!gW>YWpG%ܭY7濺=Ew3c bcFGCWfs8j`k}g (~/zJs=}=aN,孑?l{65nX&Ar%V+XҸZOu}(PNx$dEtL4SiltConpl:.U> &~Y a||O@|,M,_~qq#Lk_[Jj_8u󽏣>oB# 8+DO۝L{~q"'rvMlVh<>ZyY~nZpIp -o/0f`Vz=?_r$JٴbDŸgO't|'=2F/f7jQ@NsԸw |ވ`2^:D:P(zSʱ[bkmP>#Sᩪ~:WWH鉣oZEEРb˃DOc>6bc4`܇/6ߌQE,w9T׌`N^Y\3bjx+jTl퟿ޖSOo'8 ' j}EUTDuXZA7<ȬYU5 E(fTdnOM}t=nEgWr.YBр(} =hw|řQ0V})^>^έNa 33ӅImf*3 P^|CnPqE qh6Bu+s$hwv_i ,ä~exά|yN@hV[Rm6N"կ[U?>8r GQzxШc*e;J**]e YX~l(T,[5֖mجU8uSrBX}C}bn x]4G ~Ywj MVwo ^W#HesȌt0!׾_<GFJ{_8bHDG4sj1d =Fv@kK=|RG_S4dH՗)y"ӵ [ *ƠBwHCG!jKRA@աWs_߲dCzGĻፌp881/׷n~2HF;|iD™6Vw]z\D%v"|4U$NE@SfRQG8OU)}_u7@9r2193URaܒ fN˛R%r?(^}bAgT* i=ZE<?HD.M SτCwp &`]m&Țsl ə_:GVOf"IiVk`:NvPN#8sŧE=+i78Bk ZaI8y+1RoUG O}&wO<&f=႟qI3꘧,t~cW{QΫ.l^{>hu}?Et F Ǩ6Ѩ|0 e]&I<.v71bl7޿\TTнq %^Mu|8t&+ZW?$\!tkIq-V摐+&'7#~}qU G9Y <&TN5ıR;)u 5J`jF?sDŽ;ޡʼ*=oYWocsW3]Lu=Wl3%+;+ҟ6XMAz[5Jݿq \ ":WT-52TdOBSd2nRW&z!H(IZ9u~UC8M-k}y{\yzlʒq&OggS@ M+ ~Y\Wf@jܳ\ XoVѩS]Z&M9v'`3/T9h>];;~VIQ;;Z!W9wAˈLC=}eTޠRuJ?vtweo$Ĝ](^NUa[S ׻"~qgu}L<3Ox*js XtR>:ˀj]V3_6:f-DrڂJn$ǕS5,hEͪm|uDEJ։]kWk){]: cgpП\42<.w h= iSaYf5k2SiKk)HQY~c.1T !Z*3j(e{|W(u]:zX4M$ Ej7Cw׀h6rGIzĪ>WVhk+ʇ|tkIѰݯ2P(!̢8MS|DŽGR}xiUjgpH2B)ieLٻ8:4tJ%[e1 I*N̨n|Zcx_w\t+e]˘Y֩qqzҝA 7lJE .l#2(XVwO]X5^OeE.rXO3͛yyNYKqЪ8Kzz}W /ObsZVf]&!O 쯂ɠ\@;_MB+6`.%fG*z"=]UiUiY„'pЌulRR%Ӱ:0ԒNe5þĢlEWc]׬> VyÑǼC,K(*7]wD2 =iKjMx`YQ"ϺH<4gfgǡ]]泻Ԙ>#>yE?.+:l'+-LV:l~TFڵG/>`6;lZ)Tf\K&^I\G'p&8o/5#g];Y" YÀUbr/n=%xhOrkV&J}dG 24 7WT~-@\S]GCf_kw7DU[Clj&Gp*ym\yϰa;jr"tZADq=ZTbs| ,GXRA<teLDQc 9OJ鍈2-RKst8R4Pj*l*/RC*(!eD`HɸRUAH)*?3]UnX1lCΡj( ٗ&]޹$"Z엡/+Yr0?MD\\PY+u*K˻J9A.pYJ88].ꭲ{ȿ]#ݾp Ks%3VE,Pム=$1=(""G띊+GK1]R͸r|5<ӌ> 3`)OqrC8UaΜ* ΉJe5ͪM}F]8wuXV_ckJD 752D#Y~X^owZ_~\+ڂ"0 *1L L()B5.~B;s*tQ<7Dx U םl5QIS}qW>SJe;tjT-X *`*׊|Ytbk7A!欇BI[W kR,KgtQ<֙ N:8٦e>i"1kJg:4R9a՝^eLCpb-kۍaY䑦=]!򳿼|*]5hF쇒i`ϿQ~13ZQH@C ޏ/= [֫p9Ei~ѮLU`!h.-]5rE^-iPإ ;k_ ?wCKtd~yۛ+Gt9W<jW%W GFg_MM96KB y_?uu4Sr#_z:4V^e߽Ooj"*[y"t8Whk3<@2_X^I1p󱳷K'N3ڛ*A~R^Iu:|*=ί<(i=]αhGiN"4\LsL ?@ģJulwOU&9T{UZ}f2*p/1Ɔ5k]HDdpv4 o+[N|~^$fBKaqx+~EAt4|xW3I?ɹ79P =A$be\Wv&͟.tMʊ,qO }-\̂HH/!`20GXwŘN(x&gۤ.~ ^HBg&bN_ci)I\{V}%jW@?yyլ&e )X!Ycmpi\HdLBtiY ]j b*?.OUcR0r2yL\"ER?q${},ۏ0gyYX-g唁)q3ud./*NpdMV@ƾ}E΢a bwuk\D{QAեY]"4u2Ŵck/nU5sa~|Pq|52}T!5]H`!x1łfb Ҫ[uq+2F~/=GqUQ).h,rݳOV#di?gf?{J¿cf#eERX6v*͔\RcuVgw^F^枦qG_~z5/o$4mweWz:w;2hm[u4!, &uH׻byUAs~\y.tg(Z (Nb- ./X5{ Gma!zglR!dsmﰲZO*lVI7 ϽU˟vcm/TbNǵG[S=:_pၫ Nlhz\K 4~zFh6t5a$Z!"_h MO*-U95\xi>ӞG"R,=xʅ%FMo. \WƼt2#^̓QIl틯ɧ{lAs:ǭiL,l wL)wmK"mΪd+ 7DJFghnA7 ݊ )_rOggS@ M,yY\['09g-u#$cZ"} i!`T欈{Rkz[G?Twԧq -%x=}wnj γCAٺWS3՚kHz5Hx( ZvPq ՓV/ Ǐu<QX*z[rO>8FGF+NGix|GxMLj@ ؒҪs1xhy,<}zX *n C墻O3CҤ.S&@w@wk5@=9RZ"k I* nOUAPRE 1PGD)4QsJ(en. D缠'm)f, ڊ_~XK0PpZ]Y@v~80PI0TLFW@DϾyb@rT;&ޓU\٬,=suN/D6/UO2#zы`4W['".bz_9``ma*vexQًڽZl5%*:a|zQ\u"vNjI}s>QCm97|0$=C/GM_K>=*OuWu^`G-+kȇܬɴnwd#%AlT%T͇|5ԙ& vOyzW2 pR6( yxtht"X:?g *9i}vy@>]$ hއ:e_;+-#j?΁aT@pkEߛ\;? Ac=Ew%GX1!Y\{"þqo; 1,X`ֹ 'y6qZ+'dif|նܜ\g-u􏞳 \^,?ʃ QD! *b{eVKU|.Bn3# Tɘ53.zl:j "B ' rezOQ9b멖qVK< (2Q=P 6Y1C,ԕ~㩈J5` Pq@#Rx#:# gƭ|= "::>֩ƁcoՁޛќ5ђfGJ3W)StT v%:yL(]] 1Wꣻ"S_AQ**6ҝ"*yٿEUTEQ$Wxvdh7"#nOk\CV|"?uDt;:3ZH&Cp&QD]=B#eQ]-Z:)N~-j H[&oAVIA5%р'Z1"KBP#v#G1:|MɽX,~:JgFOB:"S+d{$*kNَ?!ሞѼS#PsZhTg$O8b(dߑD<@DQ& |YDks^,N2%)?#2 qtfw(.J% [Dݱ1#GZwP<Q]& 6E:ye~C}s뺷ўwjpgwswX?[VXBEJZcDŽ,/d:[jw;*}rX" ۯ7k~w?$SmoT-?3ZD9\ TA"/s)?måWE ̼L:Ӌ8_T|y8B+ebY%.SK:ƌZGZۦ^ ^?N5-_vJuf Wgk.YQ=M;7%CÅ]sݫF̓op#~YEp8DBSS]|&20-* PXP>~u7_SNt>q6-B+}}ރ}fAaTO}hY>o.mC= > z{4*,>~7(Gt].MHdz~R!rP*_Yue(pr߿_s tÒ}>3>Gs6,ὺ$Xkg/g~6 וBvQeV`>Y\W ޸G p__=18V+,8M-:4׹c^pcSG7U0V֥:𯾶=*ɲVDգ"T^yj;UwF @TH7AJmTUpTYB[͑ܳ-o1(Q%۩S pv| YWg\l T\R֢VEJZ TLw`Zx*P*$>/ѩL<(Adan@]pHr@E)31_mv?aDHLswTWK;?Ufq?9~=#5Lu>rs\Xt]j8?vgeRnaĜ-܈un27ϴdzGF;4IN߬& ~iLQ.gV-`qBEt?C܊jrVpzWjvH%v`lKCEF֭9^$dzܢ@/?Ƚ.m&jv-_,=:!^@7T1JT"K2,dSP ZI]*W.@ʮs}Z0+*`oZST_cQ,KB{o0 jj{juYuSDeLn"tyPqfsNW(x:mxY3P@HtgyüJq'D Q*h\+H-3hkm 5}ysl5F^PI765k89{YL `[0$~{ wi 'nE<2iWL]Ti5>OӏuBȶzoں= z q_kk>9qPuYQ [աS͝ XtD"I 6sLS*Cy2c8o[ 4y|$:5Cw zUNSsGކm^bin|r3$?__MyKqEbfl]^?ygn&;̥G{D.w[~yX^bmZ}(=>1Q (`"ÄǪ獗^x-\YyN1߄;R0Ӧo/#q>svkޚXz.xMM+ΊF|ZBY9+<3MA(|#^ҀVbS3p"<} Jz 4-. ;l7UɐHY] h[Q1n} $ ixV4˴gṞl>Z2aP 6txI׾D2HU!tMWo㒣hDHjiD i.Te@!єlOggS@ M- ~i~ S"ZN\zVlb0P@CzNEʽ?ZU:D Lu+Rk*EGw'* *X5:e5=TI ) lZ;ZtzK]NmGnDay\1yix[/gᶘM⚺ZKksCKS>ScOa6˕_o'y}ڲ|")Յ].j Xc=s m[i~F5Ub|׋m=iTh~)Lۖ^z}88WgzV?A"KSަG,R9wxG*%(Jj smDa{j&bj=HW|f0m[Gj7^O苐=}m,f]_z'wnʓ:3޹NէDPEC Ȫ!S4Ṙ)h"HrMad}H3(.aY) #٭5w'ۡ[PIX$wźMGo]Yu}_BοC$( mkn?-`]%_-q5S/Bȟ>'~~o.Kxǖh~]#`.EW}4_ Row $W= Xrh?bj矫DӞLRtB Ѓx_ VW[+gU2YSkx[f"J# ?!sNWJL_sGDs>xc9X';Lb񨟓'YV 4\ޚ_zj;QC3]+mtHۻavvPݤ:_pZBٿZo<_ +Bn^Jؙ~8l?רm*^cVE-NσfӈUϚ2QѪXQ),'Ė[aS03 B,RCa! r)^Wo胭duVPޫ:ZWkYe(z"UVECgu_ޏMKñKV}YP@TS)p5`7x$yJSKb+z=뽮2|X(T&@pyD/G"f4/#a5Bqd\U=h(_.q%bqmQ'ᛩ>56dL*:i~yR@8BHE玓Rz-\>7:3 Z%3Zia-RUWKCzV' Inpɱ"P ^8JhXTcv!\yvqRЧ_ 7-A-(z;@tRgg,a:hQj"( LH! :Z{ޏwINq]CUF4D*@WSk>#~ ּpZN õv 鹉8F޵a[ah̫-wy*ӟzQhq|+1Sl7*FpP_ 2w_LLO$Af)rj׃ 0[/xnc|.뇬|Jk6R7wE80%tPm'312Kf<'yU*Pn~86]E;u=i\Div֖a/hdqstM}d \QKC&j4W@="~@_bǟ{ A㻸+ L7l/i͐X^O(CyX-'{w}cvu*zXZ-_6zW K-_=z(OmC~!Y5~~3 ܮ+5Ԗ;$)^6If6I[EJ~7q:?4ڏ\3lŧIj|unApOggS@ M.KUR$9OQ'mX]O6n@XE!<C_(N@VAw1}C]JЭ8wx'r@ևh=LܨZd juj)T"TG5w΀| CМG@7'k7 }@>IŭFvP'2<Bϵp:ܒ|6}Y*WuV )~$R+@FՏN9M)8:c<@v[ZEjJ)1AŁԬSg GfB@WWg.+fQ~z8NB@H&9n9~7lh@QID=R.] I`8_,>i.,cvl Uo뉀w]o{N#*+ۤZ[$u-k-sP{>4p#ᯣnLbü3?3x93V.^:W>{c Tǥ>מnVٵ1~PR.k%BŸ?UfQ CRB.r[ D7@m%ZI$Q"1fB'KkfGzg׎&՞9gtI<|ά8=Woq$Ȯ$I- ڊlowFp>5^cұaL-0q)şR_*jٱ~;ھ&(tuہKr*Mw5'zT\,/) :g&OhgG T?+U"U+|=(fJ{R_ Q3;s3=^ GG/brx6P$xskjJm]V~a{8=?  /'3B6^[ϔ/%D_hw%}T]./fԹkU?{;w_ĉ@z_g)\ݯޟ|"y'b<.?;hxa%տ_,иY/6KBNQUlwõ ӟ*9|AI<,rN{uLG֓{> .yioA9yC7Ux12zZ`oDe~}ÝŒ՞8 °g?5i%bz>kXOc[7)j7J,F:]f{#L0Zq§f`ypa{ n+42F#] iOQw"B.!|iDp]Wq|I3f:LQ'FfޜsYX%O۹#Q2ώBVs\ET|4]ٟ;Cus@׺$8;O .f+_bG:z[^GRD$;/c'B_>I{;rk3>TQ>l%a<{2khԩvFtGECUnᤓ|RjMBP볤H&8SkVƞѪSm3հ)& #29Qy]*Q)n0 j^m~^wWǴhd=殿:1=h//H'&=T6}wXU YYYVQ <9Pu:~8)EїzڳN_nkkΞ8h?PFT_,J>Wmph}U[VM@?""pZVv-Q,?v9|+K+jws:;ݻKz?6^{uNe)3W(xwSX'S"*)|\P2auI ElJ;e>]EbjH{+]UhV;q[q!x '|G64iZY;&y|稵@OQrpdY\53jdJ}_Z)kPՓDgGD v?CRCJr<5T!K2:I&%yr~(BfB~Ϗ@kLM80g*g\\zr@7HnSwWRzTPy`5&Sv$?#2YRQ՚9(8AK@ӃZ["E hbL+Up"#Ͼk iQ;?{ EfQ9.UpstAUCBH>O&ϟT}:,,wal)qCb!PRgQryV7[1{F دb_/+} WH(F"M/W؎yQ/C;o=8]^oqߨY7w>[ev[__q.zOggS@ M/*ypSgi[: i;Jŭ'Ol){*S36]Am|QٳMNt _Kj_7oЛ<;5AIw]s@[y8qwə'C*>tǑFAzj_'8Dw 3}[BL]”R3S$'>/>]"cq'$Z@16gV /VȌwj̎ RᬹܸANU˵U\]8%S龤ׄeuЮH%sUTTuͣR' ~-Q&i?2xe̲~v19IQA4Ψ/$# *4T?"*^QAQl"܍z!Eև,JŠCmP;,홵Sm1>Hq̜7=(tűMF| oXvA5~sժ糕9l}:$nE2(g?v1jp,0B^ÊIw ɉL"?qrԥi+=<|Zܣ$ٷ ڑIvBs{ssʥ/ib73APNMysP0zcBn 6 R p/snp?_x y 0Ncq݅E  n\E-tJvy QS3pf.X,d3=>cR=+]^t3mk,nIlĂIXISb tGQ0]޺lBz8<$&;WzFIB=A םl,+?GyYuwAH% ݟU*[?LJHaCzTxwuѬPE;hU}Pȹq<8n;hmfDh Wtf^^=]`{RsBB™HDQs%*!DݤPDFniCDl|۷Gc:FNE#Fĵm\:wt!ZɟtYSwO*F Ugܦt'>KlM?7ƌ=~m#~kǤ:|Yߞ+it_;.yj0&_mF~py.Bsp?8N^B=ϚM"o[eVN6['Tfh!.ՇxpV81_o` ԥFFwR\:8Ű=6Uor1F[k)@W{~g[G71uՑhv|lPu㶱O9op]V?! t [u)q^;9Nջ؟]V>j%8=c{o.{٢P}|ڇ9D-nvEn7̔\֞kK1% \:]H[[Cu N  ep8.Ey٤Z^?wP';Gzb}jRap]h[8F&Џǵf)fĻ1?B*#ߐ]DEsiwƯTw} H#5Ԙ ȘS9Vr ;kM_[CmI'vc#C!o;yT>@Th Ej4s$䪵VA]Ъ@5Rj] RB=I9:JR)pjFLGι(4wT=t+59];11;J]>;ds͚(ߡ+8Y:n)y T8 WkGJBTvBZ+t<*#)PFj_kD/k{{Ww},z  w}| ` ٣S ~!&ߧnСgo1vI9hX'n um d0.rT.9wo0=JK5$ِ-j%&Y|]c]8^7ȸ5 وj^U`|+k*P#Oog(`؛uZ}\<<*<&˓#_ 2SƼ%z;צSa$WTQvF1nwK~r>>hR4WϮBz&V̳93&n(dQts׋?p3GnGnVex;sFx-r 7n*X 6ۭwM ). u\j}9eFB]ɧT)ן *ି~Wdg9dg 'Hw>jgut"zWq5)\&/k^͏' vTvk[p%q' l׬!u_gsùDkfP h꧅ @ E]:y=˥Nb\]=¤)GvS=!7Mq92@8|RU1UW\_KGJ;KY-P|.3'D *A8NIrN*#qQ㈨NZZNwpE 8MB׊NH%gzQT R'zP0F#ׁB/ǯ9d4'p\$H7 MķA9)G7Χ$֕{X]Z]bw度'v֗}c:,}eiDS]^}_ݟ-k6;c>ooR?LAo:6\Ү=io}ϤY]{5kf.a~<ʒ6 VGUPjL=7:e*Hz] X PU ʎLYϰ#N zȣoL_=F&.'IDvuϻ\;? 8,joOGNyw*Nlv;Ne1yոЦ\y+4w۫]avY?[RažBz}{m[+FmDԇf9UX)ϜxGOҤ. ]⾣B+{ T_P[q o]v@WU1?g/A|K+|&asPxao;f>t֊.D:͕7ČNE@ձ.{T [ xs'J !El_WzI T6>$/?jًkyԧ'%uJ~%B'Z/SEhਵooJQ/ɗL]SAg^ܝR!qf2Jp]ޛI'6㵡_N "59" ]eF 2敏 w%Ӣ#Y0Uv=~&c+^סyBq#\$Ʃ?~#z9hT.s.m87:#fWj.9Æ /ks£'eJ_bUlkU5zFw%s]lZ""(楪J{L8- ?j}t6+VHpd@t}SmI:_~gc]2?;/ۚO3IR'-vkDLw~[sGp4xn(_K(_-^J#%KRWS`;~on|?{%<"S}٬<)e$.oLrѤH3 :^?Qa8VjL$_;鉾lZ-+ܒ0c?vYeYm'xL9'F#$ 7\y]~='Q?BSZzg4xlMS sWj_:wSZ#p\OsYV]glVY4g̟){%B(A@n܍ܞF?k6Nyd93;XdL&ٌpb;#DPk4z>LQ1}λXGv{4w:TE)٩uR6YZ]šS*qVT1?_]Tc6ZYB#>w'y<_5pPT@L3 oւEv1xb Ox!c|-#3[)A<]u*MO]f8z"Z9MN\4ѫ8:HL1_˻5hq!"B#5S"7rʐyPq2g:eSWi3Zf*Z? āZWWzVw^LW:)NbdD_+yRԮD 8 GV3:9jHVvD+3hmruG>J><σ0;xu^{; 55+q:Rjj-XDf}ӣ-?#sQxY" ]QW^Ra;=7Heu-n|YƚIu%[ɵx}6KX*g i  iy-=4{E[K{uKk.726>^;7cREYq7jǯGgAB4Cy'm*s6wwU g9̕W^y; Gĺ=w9/b<@xX|iKM^[ﺷ6̭G_d4L_goӋ0M5+<ÂkhW+ku\fBϑUfeQhIi=$Q(]&Ngs,.^֌x8uq ݮQIk֏ksH3ꜟ;ԧ6RDg 6_RwGjJSG:<+(h#ztFQ9 !qhًYu;i~ 5EovuXe}!E Zw6FPt?YG_}#sRi>p -{,-)ix?Iq>wZǷ8m5t>hwg7yNy~?ôyݯg7aw3ÙG6τۖ&1޿5Ү{ַa n2# {։Ozsy5'7z|b/=7 gӧ'vTǰj#j&/tӦ-67dq5B14!L\s2ޯM> <7dդIk|r[!'jDӯ1/˿kI wg^0S1#<ɋj$Dub)sEÜƟ?=-y3Ua.HM~~tpl r/;qT]T%S=?̟oH?ovs3%ϵoI~~QOx恿N .=KA1'|w7c; N1zYw#ѿ첚X77jvSMyľ J_Sф rKBhljHy(sg#*gƢ*E[N9~;r]W\r}MG| o?>K/砺..@th'W_]+“BHL5Sr$&j:wJqIitZ(5"j4ӯ7Ž$w'n9N3G"G&SA!DփR|hjJwl5amYU䖭Q+@hp8JqKys5:M;dLPOJ0&[ޚ A>-|GBvIɅ%& =d&*!]o/?9MpZ[$+KґΛ߬a!m ?zL;:bඪSzbXyu7w"vq OxZՐ#dh~Kh_p&鱞g.Iѩ&ԓ꾿ތ1.,Kr[Z ȊR]Gܜ{7ת\Ƽ^*㬻Ma` ,eMzh/2y+cfu=x#\vv)?SZ/AXݥ{3i5]lJ8_ԮlS)&w Xg &-:x&|h\YOYA)=G.y/-;Coy';Y\RRc}=rfm=)}C7Y#kl-\㢦DSMdt|^2_ fGȎSQAeM(6^F1!L<9İ:Q}<>VA`NQiVڸ.Fޠ莸|>ǽ18@5}><@>1u(ԊJ >;P>lv+pjgTY샯&7 v!~vT 3i |x gup"nP,b2I]SNm]^̊wP|")ǡAPULA-$>d;8tF~p?tUnՔ RkiupN \kJdN!H܇ٍ꧵,=mQ@!#EUBQ6t nGRYJ@AkuEJȣ[~돮P 5JӎhqV  RGRiߴ=wD#s?;9™U`$t{,}2 ]7m"Dkt 9GC*,OD1)qe+8,e:E$?)Q hf'L_TSI UUcϤ6銔`V |WQ--Wn78dξ41O՞g.f7[)('=pJ>}*.x#Lc>w"#qJyhQГ wet "8%^iU^@M`$0nF"U(p^6Qpuc^7dʺl)PAJ& pu3;F K6,{=t{T^o/#χT"]z1u֙m%2m }=T104Wɯ~/[/[ p<\x;]Αjwrfnߍ#V^T+8;Ϛ;PRIg1aQw1O@ɹY ջq 6 Φy\M|׶kɊ%3'u,gl#?!7۳%:_s+23jˆ%W#dnl?ϱ4 {[r~li0kp0@:;FOr{Д/b́SjS2>?Q$:\~t"WPKon iU'ba[(}r nN$JuGPIEk=T4ͤtrxkgh=ΜUvpy!sHU[ID6 8w&BNP'EJpxB8ڳ$&!#uH!7rȞ(2תSؓ'ƨ4E}"jVD9cG֩?Rv*H*T45R3Sc=[n݋yC5U;$8>2fJ>86Mk';aīS=ӕqʤ\=95&v/+Dl77]̳̍RiuR5ԧDYBe#mR%gkNRelHQ毯\zpsҿԝ+ <p-g;(c?Wn^/kS>S6ݞ {,U_+&zXcs2 e3)150ÿBc5h q5TRPpP~t}|IVI <,v̢@:u/Iyg{'~H}f.Z4֟ML+2EϷE'ڋ)!>q=ۄqrVw]o^+1KQk eN |;8,>·A | ƇͩKu'ai g'ɅkViV)o)=LL-lε2"NG MeDnm J7a`كV\qDn&G5dX\웸կgK&Bik7{o@|EQOԉQΓiCD9qˌ97N&Y_.|Ϡu_+@)VP]btUlc\۪Nt9|*WvJ{ \$07Cʸ1{!&~%mhpn2( IebXN'Ay1KPOggS@ M2Uj3jiާە ;MP"I׏/OsVW Q!7Sk;,g)tz4J|KSL4?g^IFp$ 7~B,W- =MdPC>ZYU׫!~:cԣl0I油TC99yqd"z:cD+ H|7RB&|uQhɿZ2:_9+PPiv_KO/2G-* AU*LǤFv%iPu'RZ5n#E#cΧ9B,\CΜ\h==F﫣IBWyՍǻr Wj!DkrI~e%l'8-k3NkӼqY}ڋ:é^/ꕮ|:U-~_ݓا1ۉ~Eaw~2 xeQqlVsyr=cx5vx"fx}T$uIt#}wOԪRMJs|!!1~;qJn5DYt^UiS1~*O*~kƚ^~ 'IƋ8ҥ<:- 7Y1Wl_lx|l?]zjFUiIct)yWGKG ;68̷WYKtr$`uF*殭$WBrpq3s})}2]>'*;DYfLG+MWEMVR k.˽4cr.'pv^k^X>9Mo9NeY[yu/4m.LJ(K]}\6Rdz՝koqtk/ p蠨N^g!29/pYR\āN;Y%_{9#ijeޕ1uOf=[JVvbN#izeA|pRN˜/|WלU;J|*B%*I;A_Z4Zi' +ߋ hQ;YX>E=pqG48L&gnz@]SL, c|G酬yLLbҗOmdRGO`qs))Է7  t Wy G45>օomg]@ VsC7DnR37"s j[/҅~5z~xfg<_0v/w'좯O# ]_>b ֈr:yяWkpks2l*R~t\kcE0$ŋk3^sμ,2(s/&&u~̇$(Ra߿hɷwh<tK킴-aUm$TlP~0l^l;wQ`ӑ]}BQnKb0ͽSƕ-WEB /ģqwa[<ڂKrڠUk9m abuKa=mjO6y2G2[3k29>-?VLvMTB`_X:\ ~YDn< ʼntÁ>իP YC,~ r寯@oEoT}E*쯇ǜR0Z^@}X4P .>CZ[ɧV].{०{c9w?rgs[WרVϛ%$[* j-a*^Qlٿ.^GAE}]O %ׇ&v>3'UJK1ܬPZV|O{~*4ͫ5Aoa:2֛_/qʭ8rb˓ Z5ZL43ۮ &ȟgJ w?r3c1آPU{5!D zJIH$= +ͱt+sՀAKqW87;ڑ8osZA#/?gT}k՘U&QƹRf%QwJzꭑŽTo5q'◰[;n~/IN%;ž#q s=|g'Φ~5]y*J@͈ǣg-X fy8ZWB9Qln wfCރhĩdG2\&o#IVɄcg%%ꃯ fh;Vjg<V%x|pV w֯:Bov*osyyb%N-cco߾}Vey(L6>Põ̯zi^W^Ab p؈ *MNpĔSNsmA J;$O})wrd?ť dZ SyTTέ8@+|D9M*L4YnIA>52bw A FG@hͬu>eu=x3 =qxx]VdBxd5ǹGwd<*ӏVu|5 /QbZ'u?:$2u"E!=C3"q{;^w4~y"JN4[q3HuޟmfyWfҊ)d^b}kqW^|5|!K iQVĴFn@9Ztorr6UAMĕ?qUjZ󭼕.ot5cMeCC^g&OwS+i Lm9ty7TԗR} }(+յ^lσ~bfB ǵ4۹Uw ;KΓF=?c͓}8f>SqJbҮ=)O[w}O e;jXDr٘#Usl+#\A/TdX@Iꇩtt:Cpܛp{ꨞ^[go( ~g a{e|-/suX.qιTfa'=kR7ؽd4?Ze}R`Ob"7]Bi*8bm9vUVO.[zM0m;+ 6gaԡ8mOggS@) M3ɰޗ՟j4g:&8Đk_ _>(8NB$ρ?,Z9w߿9eF$݆wD;M=>vu_>ߡK׷7UHEAZ | @)QErh`Iӽ9@X9$Z_3ҩBfS*݈j{85OD[WO?r1/fVx2qUiE.TOrD}}R fZDGzHּZ ҧv}N)C)ut#.c&Fe4 @,P=ep ZnvlUNa \vVYy"ϷΈ:\~=?!wgQ.s6,Bpo1A%o:1M?R}p'*1ha`= ~tGAuVz7"+xM7YG2OK訕VC~ #ceiȡOnwUFʇ.`ߪ|xŝ] p>JoߟmrB@43w||Ht}d{t`tGﳵ#T/HOnU'# T~ p U:r0NjlsYa;FH0v)8'o3#)6W|Vlp/gKVN׮qfuyŬ]Ɩ_%C c|Qy-Zf?̙/#vg zQ,'n{c-VK1~] H;gĆX?N.yv9f{Y9ԋ|7W[_ WhR/˹/ƟG)7 ߵ>_{U2S3zB|ҁwtXkؾ@y"(xUJ@;U ţ^AԽ84U#IG 8ޙH"υ#%ҪZjvH)*EFtgK^D@5MJk5-IWcS^)~'TsK(]M Tkڟ~MhHշ![ϨTk=DiYs9L}$v \sqO+:.i ,.ݕT|Q-k {+N+2{q{> B%(!{1?yܑ?ԏ0I'9..`wnyU7CꞢ8@fhՠҏ0GOENO>n2 Y)e@'WGO"oo $o1!?ԕok[ksA5yEmWCGw}Nv3g}:]k2|ŲfZ֨Wndfo+Y>씧N uiQ^t*t/(cd6^ft4uGpjӚH5x>!0t[Wjz@\*4z`ik1*mBʬcXP߉Z䡔0ݹuyߊ 3dDkE]}Nyw|yxp稟Pz"a<\S\p{{% E좚pDiy,1n9;]mDD^44s r_i=}j4'Мۂ]X_)$s-Vg}O=0ϹdxonLDRӹ |챜7V}NRT4>ercb Pdz!ûLWo'iKfT> nftrW:N V#d.:kxwēo.qQHK M5զMޒwZ^5oiKV_) (ej.~_9 `9ܚNwRE;ԃp??^2;&Oke(]GfV࡯QP[G!|PB#Be5X Z9@߭ Huꭑ@K@v }V6dqJ!zT胊(MqRfW<٬sfju k""pv[P8F?R2Zk^TƣѢ\ Z)(td/5.:Qp}T8AE#HC*ϸϧzEY8 DQ88N@FUQ^؋ H N2W gHD:dJ?<$IWT^H QtRd+[Uȣ &NWM1@y~U=^ 8q:BW#Vpmc"v[.2NI.hIjUDlvD_`AZ_R~"d4=R}FK8_h2qq)aK"ٹo#=f;Qkɢ|eo.G3Rw麇KiUc[θ f`C\iC8y7j!= OJJJ+2#-G9ۘ? zFC8Pǎ"]XxG="j4N~U1vU57nBi& 㨭BZC_?Llb~}ʹrI?y}r(D:%tq]eL)@KC؊7jm1SM:,L-a# 5mߍԤqU_'zs =NpC!?޵no ISX]ozz~ri7f:_A /pՒqxtXöi˃F3zƹfBHſJ$GjG8x.~7]Ll%8kzr|TpE*?Ry{y.Nf|zkӞ2![}3º vw=HL2Ztчbdy$ZL@ۿ[!0B$vWakr>YmkV7r_³V{ҏgXDg}}=O/m5~Mj+Ϧ=[s[CҠ/Bz qm_L[F#b኏ OggS@9 M43 [՟ `<ܪJ@ћWu<';2 yt9A8@-ZL".(9%>w<>R݁nuPYx0 Ne>E&KS7UIOhP# >+EǩټvH= U8N#܂Jřwm:4(]>B; }2#Btދ!'֞9"N s=$zf%E̩!S,Źp@n{fЀpHVb^5`edM*t" ꐣ^"(ϖW,5):n bF;렳(2!F@Pp4`1 -Q`LeY0Oݎ!biVmE4 =}Qz(ו㰓Y) x\FFLEQmp uٟx )uRt\E:?u&E2O?\2>6gUW 'Ui # LrFL|6Gά{r>e~LmߧQ!V7n{^#Xj'1k=hɬU8R gC_0`kx]&~Ynw7Ma3ܢx8&22}e~^]ϸNv?3X$uaW1R[+UԙO4WQe`؀!UD0 H7N 9m(l];Kj+nďȇ$mie8 ^%uY\~e(]tԵѫ_5]sp%޸))b.+MŻ!U:ڕ*UqwT6?yNQo|2^jOt+ı$񿣺26(?:킡i3rn Y~]Hǜ?zFpz1 ڹ@KrC{͝[riy^+"a 6=?N8 6~GF30F#Dy=wl=V{JP`@0w,6Ov{-mI+/!1z$.&xOnkRZZ:`xvWP8w7 osl'P&1Q>[4MOV,Pw;VS㟿O'2̵_GDuk{<̇TeۻF=g]b>av͘k]v*G]QNPpv;;؟YqԔԲP]td)V|[JQT9k:Z湋ʨӏyW>K=EǓ~LA6x>j(ĕ-$s (H(/5_ύv&E M#iN G(dĺyZ^HwL]݄_t1Gt!L W~UI7"rUצּ wsqv2er!SM=ߎSS<»YmS;J0M #(~}w~6@rQjlV-6Q%qp=vV c㸆*YįeLm10Ӿo{0ͨooI~ 1>,a2!YOgHr,2p͔o% RrLĸk] EyU܄Q6 _܏tLw:-߸/dϠ=.zTo}<_܏C׳\1W%s=|࢕&S6vhڝ>gf똠fXNGGI޵ȿ; x`UKmb˙. uSs9n1SÒ^mG,U\עU^IuE7MYyj el;'N6⺁XcuU[ʫE:cs =EըCre*qtNГZtzxZdrpbV]z;1CIK)Z' Ki.RL/rU^`8㬫'c|vK*gnݪG}q^)6 [c! ޠ[^@(>[toD)[h qN* w}^cW}F 9{*]?sW?9j G8N{7w0,,@p.0K!jRͲ651f7EN'!rIOtmlQwp9v֊]힯WB6ɴԟ :17 HSksmT(;H%BfSkcQU:_O͵P.:ㆠ ';UHU²VzԖ@eʲyTI ɵW'FVN$9|#ɇDֵJ:8N .!9eԜ r- pȈUe2.z@mꒇҵ׆G|XS\?Y;A s:r}͂2/ Am͏?6;hӵ$9'WXi֟}t}#/|np8fp^Eտ]W<U:SiB^;h&;V޸0u<' _/WK ywkGN=D[{}R4y %dkQY @Yw?*Z J-EAjj@K؛JzU]7jTa>čCBjv'yH*Hd#ԗ# A{dAD KUV~"QMߤQ1=iGS.Ig8p3Ό81-JeAgf!#y)bnh֜8T=z "[/;V):Cz҄#DLvvrH!HGRF S5R4JHWɧyo~ytq;}LF'8qB;bw#ωX=|I?eQ ‘H fYgzgqv3x2PDFWB%T$2?$h9J I5`$^\tS`+jN8}O1=egC~9\kU}Ţk;MqsU1vdNs=>F_j6qO-1 qękGV$F8X8Uvej0y\֍Gvp&{I=i}xKx R?.$:\`{']~0hbQT5>Avܯ$ͭ!%n㺾2U$E;$nzȗoFJ_zo,; K"m {S¼di 5BЦhTQ0>m i&~Z;RHO(%5]Y{_vs*s. ߬D/'W`lxgd&Jmԭӡ)| ֣n Vj0rZ#?QxA}1Eѓuǘ8{7ŷ$6|רS) yI&rt[=hޟ&ↅ.x Bf} D޴+G}՚5~~r *L{ w[OVpR۬xDٿ8~ol̬O-o7wa*c=O}=f>vyG KgWÛ*׬ߛ-Dog(>ږߩbZ?oN0Ym/zB[(xW88l7}֮ w~H2K'B,8֞jguDtq֌Ju?uQ4A'WL6px%R(ա3G%~$!}׺ Qywٹ&|C5kdҡ#4Z{9%#8:\!f0 1dt݅[p NyUZl=???SkDӺКFvƱD Yj0k33$ [2t%jn?NCJvÙ7璼г6DMBbh IPo!b\>LOP wa<51r{eUȩnOdd;vL8yKv&9E%$Ƥsϛ H[̐[ʡ#*+^F(ۣ !ry:D߀<׵toW+`sa w>[j%_? wB}4>bTїX./a ;}ĊUBdYԻYMn9gP9H&!&\hXusv]Cb+{qo~r-]?7~ ;Dpޱmk2s4DY;;3Nu~qj5!#qZw E:Vyb߻_oT֖,L糗7f>m&hB* p6ؓlܧ j’/O<ʭГp0 j䨃LOTlةT4" Ж=컞fd<{p^ 1=A zq2B[0P:s_^3Y pOW'۬AT$) ug%-fZTq%KQCDsS:t,Ջ~R?]8&>e"'ɯ|U=jG<E:szT"S;S ='4h=*Y.oUb\yyt"˪9[yAES':Re7y uw~R;yVMة'8CLtK:3IR: :ǓC} xCjSN+i}-Ug;$$5=aS~] lxlezj#~&.׻:q盺+Aΐܞes?yx*㎎ZW`Ա=sk_ 7?7ðv#3w!=*9gqHiv7qR|*gDOLbڇ*A3ǿZ[k?1b!ZpIR4v_Re26kݛL`o2C&j͞ S~"c ɬ0Y\[UVZ`HjgO"W,}m}S!k^4v' şBSR[)l-.)>Lzbz/^ p^+rdw{i&忷2TǻW DU xWǑpYtXֽ0"^jb 퐵nyQE):?'td=䩑su2q=ߒ1ˡDG/pܺh(95i, Gqſ+'ӿ\\QVi.!gUCMpu p(fTjTRHvJj%sMuOFC^\j, `'k֚tJBe~MFYZMקJ3gcBB#{<F?;$Uá`b>QbhdfŞϧ|*G(P*Όh&QįΡ{58[lD?GFv_nGj5 h*~(IHdmD 9s>GJO+sKΗ..{$pFU<_Χ?ävy?}zɻ\]-LTlrż])&T[h,pNN1RC%]IgXh'Ch1M^7ݣKZ{ʿ5RRguQGttԺch}+6I-α,S.\M(*0??Y5 NB_6>9 kgB>M;r$;6Y|¯ﮤ0r/뙽K?G©=ˌ}"(rq\͞y]Qr WFB_?7=_įm$ H|Tk)bм,xۛT)8A!ׇ<T%G*(zA޾1ht;]#0O1oUa0їtrLDa#}iis5&/O c#d o8pSQq|IQG]muPYo߼~qF7kn4]Ak6EMGs/[m'G1;·ߒg\uSrY%!~S̆ᢱ?O0-9 OggS@Y M6k ՟쭰Жמּ IS[]L;Bt2Ǚ"ѵk@W{>~6 ]NZ;GU֚Wᤜ5U>ɢqi>08}iET؋:;dζw8ƳcēAJwzT TM\,{ν쪕am1Iۃy)bjQ5K7>j4u"+,_yfC"Ơo0%)_';$+z/P <ꝱwKXLB\~C)7}Mb2Tc(>tP}ss<3u>c7I@zn;gQڢƧ9?W^7yhVsPKam&G˲+wO9K!yۖxS2S T&5hWYyw"A `§E1głm H[fiUU;Tp5o}ݏF26aG9Y `:^GU%dAhbHe@%@ UD:P׬Jp]%?L5'3 9݃KSZ"zr2Ti2VydNىșLYŨf+2F о_^i!}S1B8hv5(8um*9q|IzQ۱>I:eHP{kC{LLw!B{F+Dhdo9 t\ZTBr4\U-{+Tw&E RAy9A4c?psbrx&fD?`?ųG}Qx䑐=g)l2a"]- 48] {ōB;DCc%ߏæqUwUвx=y:KߎOwR H4r]Tѱ{w5a9%/oqN98a;x(q:YCo7Шհ}yYU>ƙ`3Jn= }?*9VVk4Az(] c~NWo!~&OSĎ$EM泯*s VLUKٹ0$x?~< T+K}[rچ}K>nݵ\6N7zA@iMf.sӟm 4'PN'oc[e}=ȫ?;, FAQ9?;3naW1MȋlW+a+b%%K:g!}W)G:alAK L&,^nx ^{ҒOlcAeO7r~XZ6W({aSv(7+5'4Q"}zv[7/%~U4'z.rdXqi6Gܸ~p8 #r~%#y&M-Vq>W8céNtw਄wTujJz >H"PE!TjVpxgd"/8p:SOs^v{\+Lڵսv{M-I>*= Q#ѡ3N'0Ϛ Pzh6_sje:zPu. BiE5nA@"Wio/ ?\$C֎b?;gν!сC2_ȟGMݡ(&@hu( PT٠'MM^bCJɛu-T=FǕFb!DV)QOCctƛ{0G6?;5ym]Yog0c!k +qXKUJy*rzof|vjNwpTPrae4}ڬsuy4>VMKTzyNz{{Du9k WsΊkRalӳ=cĤ=_{~Bk8vwm5qc㻒+cݵਿ˘Ym,,CEHl7Rb뤾Js‰Mц1")v0lvRꮥ!e*̟*JA—l5|m.؜C) z&=bZ[Cq1#O1s!)ZmUOEfuKs_6EīN;lƓq댊uLx?eoQl#ʑ3?ɚ[Qufh8j<[mT=4`H< Ggi1K\=@8gx{|_ZϿ b"8ƩY@z7L{^o=]u׮Zt}(Y7^sYwėڿOxj Ŕn OoY4f[ԯTKd&,E"3ɚ ?yʹx1k'>+fS}ˬ. k!oESԤ~_֙S \`}S@M:߹2K|tgWACuf D7UO]Ȋ.OᐲJDZcni=j Yī CO`!kVe]SDsƿ&z6BhԀ 8S>(w/SLȘg:E2tkgy\{u<=n} st"sP0N|8)!<$f:8'4+HSzs}>1]6f[}S2+s6^.wf6iq!w>QTȘS齏D;:M6Hr "$[,zz?0=qޮCwMT h#!c:L(Y׊ joe\|:%S 00KaU'y)oM@g-N,bWt+krVJyc wʒǩ%FQٔs({|=l.FQbkHRR"I}^#'T]Ö"w/mq{vwgy畎ed%Ǎ& yR:x(1؝7ev<'Sx륂6v;gGEmzU6&bapp5õ'O3R{sN%/&FZ$.$P^Ow`T yRnۃśGaRϦicAVDXy+fe^4wuWn['uyab܉<8/2r>\XgXx`/[Txw`gӞ].>Rxd;2t~IZZd ܓuWg3ZF4eJ4kRkLU:GFtgZ|h]w9Mr@xP\B^[&qD/O~KawSnS'ީS׉}9G +_~=F7?غkGṠtFvT9VڹDy :F:ёgWXDW~+r"Fb2IG2ȧv8u9AdJ r OF̥ϋwz>tn_Um5;oq,U|$@fmOĸW>1?x36Y%o"r ib/_bnմF7i^>Y1nbisxV3~~V|ރ#jpxs fU~wd2]DJ9{%rq컮 _?'yxn?x}Ş4ߨ*wd>3jL;T5$*Ҭ\e [ߐfggIrskUcxpѳw.[5AYe{N >XwrD<"'o:V ˑNh!J-K1_ƣR/k*n8N)$sq(:7B i]ϧPvWֳ |>O7]z[bWNS*$ks.p~EGҘI+ i!{zMZS#ڌIF4z><)pѻ}P3w@ K% #<.b"%_qĒ͓QOm :e\ ֱiq$hshjRA&2>[τPc1jğ^~AozsU!߷8G{4\?^ &uR%)H{)A6qYIEFV 4$3:wepz hʤ練J^u:HGi +zM":E' HjeybYXHMD58kP+$+1NeMy<sM&pJuƦCt@8Z+HjMjn  q Å]/~z9 \ӢfItl:}㾁6XO;yߝ F.M~yVIT{'I9 -7a_'9:DO{0˃wb,_l??gkX:r.mF^H2{z; qV ̦.3fv]o5N{l֛͡W|wnf9 %p1|z 'H>)aÔ,GQzar讑s}HjvܧiJ%zl_\y+4 m3AߕJRc[9 2j= 'a֤㣑J4U$.by}XnGeXL8$&PQ7zURͰTX|gU{q͢C_d_dȈ?ݔDpq-r??3ɹtrQ3rqn%}sd צ 4aڞ"$mz^D߄Bض.̆ї/$ ޖۉr ;Pݧpa[>~'烍ZpLپ`b/?Ƀ‰hwJ*OvTh22ηpW Q)1c \?{P.N 0%zO385vXf.YF_39w4ԟECtJi41{]Dg#YO̥F fTIR e]:VeG^yYV"zwWfAC'֦{! SBEdǿZIJ)&l9jJSԟgWsJ{j(_#qYd¡kPv&5sUT 59#(:QwdYÌNu!y6b}q">[yONFיH948NFF`^P 5db)޼#JT߼P|&*u[x~~ (];4+ s%5ݖJ969v=NOE FY~OIm_bp? ~̥zo$77Rd |_lkftt'^˺Oʼn.ygu/;!Nxw{rɵ'hh`.ʚ\#F +jktYɥ=kTz+90+~h䐘.bbnoO & 1ZaL/9xwC^{{UB~1H}ַ7BsOstɑr[Rgd47+?ˈ@w;)-VSR-<,kӁgcgI94{m6566$p* Aׁgz/; HfcqV;GoMg0YrΟ~ײn1zeBu 2?Eki\ʫYOYǧZ%n0Z-fXk(zLt1S/|W3>+oM;^@abg!p<^>:̳<poµBo=hJ:y9ADj'mȻ({/NG Ya9[28֍I֬8L;P?$@{ҚDQ甐93|:p/G7ePkb0)őz+Ac Ͳrjӄ} iY*s]9~~{Jhg򨁣s>XoVm{-Ma(55Xag[)Ri5 N5A&"/VvWZ|QCxܯ*r#hnWyHPT2Km<sWx >-8.dTme=jHc"ɀmv٬޸̩nߑ`6gE"oY7;:1]y(ȴhK6ɏ:XPowr)&o4g6+<1x_~ZT OggS@y M8򼽸|~w 7ZOS9 ~Dir=+h؆X+1;II),Z(ˤA[UdS;Vg9YB=QMq`^Lmu(.ʰ҈ ))9$uvHNrW:Σ !7o9t.hyЭ-w| $55j*"4*e) %d.3|~M8Ht˛uN8J"kŬ!SN5gzHYQ~-?ݝkR}/ q:^3wU@e &a1"WrS`h(y]Qu?}qz&P@j̤N|vs|0Z~/EtzE;c؎ý>:8Np᳝񞝼GmOL-'uGZm0MwFWM0>NzS7#װ~~T|~9UjE'yͬo11wQX!ǂr6qa~Ș ßh̫?4 _|iFkoh}CwvsU6Vn T⧕r߾q7ҁO[UW2FUVQj}Xw ̶ LHi) nM׶qa]z `."y&\waZ-ԅh`$ Jk~B%'MrW]y$t_elת>"-)>oo]w ̴' ȥcLt޽8-5N#AwUլʵ`p?/gV'(ix8WLTAGrk] 2F0~Vl9+_D)7-&=M#&e3;+i{ULYk:bQzHVMKT(B&47ԏnEx!)1}(0jJ]X8Vyzz`6~立mYli\U˩5ǚM?1q-2vZaK."gZ/P& ~}$: ;t{t:T;L#[_ҙpV, W:K}mQYG+5~g:ioU*^d=oF~J @vڣs=>~oƟ#.M1F:ܳI5kv7*75 AB4LfMTmY'skgO%.:S"JL@jȻd5iYRoe{|]STDdJ;4Ae8wA{+[AuBԨ!+3)͐ԖȈJfsթHYu8fC2MȑSubL1q9!Eto y:_:6HL+687 4!/o%ǖێ MԨ78Nl7$JU-,h!c~/ 9Ǎ/]rl>8dC`j>v +q7kӧs{!%O;ђc {2]\Ps~ 1U?N%;A[`^0+"CU;ux" =t]2S瓩q©|)oF, wzVgRYgͷ(imKB.x3Ϭt>^Ax uUb)@W7D]6_~7yb24h<5BOYCں_jJ{T]TYJֱV=\.I vK \T]#0Ư/LgݳO!^ =V}zKZrBᗴי2{ *8F(c'V]~@i=0ytR|9Vg 8%ݬ!]?/ya/xΙ!YCuNx"u4ppY!YiW)"V!kr) 4y?j;|t@8KA@򵂦8콆H∐'q޳J~%I!;}UR+RdJLҢRUZFj:y=R'+ӓ7YJ_Sj]nG(tZőu $J|D6k%5cRt?8ŽɩZElqйGuvLBbvS%sq 7M|OӑNDPJ<ڑJ "sa\ң728P> LѐiR4~C7FLMf|c+9ߨԿ]|!Ci#+}Fq#4=!%J`r;R^z5bWO (Q =\YN-Z}k7`g妀݂ü4(A7e]*1aߗ(`3ыUf cef&F_xfzB r; 9`8JJ LN`'}i6|.+T6ʅW2JOw@>HmU ?=<Ӱ+> sy~ýݳx T oյ2ۧ{z#BէoZv߂l{=S7 j"7OkNSH].AyHV%P(_i7lh!=Y(o[x.Ъ76Odnv/Yblmߖ1A!qxOu#V'wLVMT6ZT }z/%ڸe+8wgV6=>_}nUn_Ï"VWTg(#PM<^&?*@_GV9%&i7!gh: gIz~[zg/{68V? ,O[2ىSh՜LS iꪮ4SHX 3' x8rj{ {ch_} 뚠|XV._S/%7rE.OggS@ M9pT$([^ b;uk:=O5^O8wGG@ >A;; (?2J(EWq) 3KoyDEX'iHԇ+no'E"95z](Tb8Zj|7o,ٺT_ R2kmZߕ('4SluWO$P?$J#"3yPSNSZ-q&yiS!#54 wy!%dj-4**:jZ4 Z=];n 2UM?ND2GT ԪԈ֜C:x\c|͝.;~4MRI' HяNh ٣ĩ3tD@}s΃ ^ }#>2ǃ&!BS҈nA'pb8_v8o.n*)ϕKmm|К.46lSSWM%0.a'n R=DLk&aW5l>Ji]ʵ~5}lVԅ> ջKZo]S[}\ϱ"%˨֭j`uвgޭz F[ޘUr˺ 8u-*o=lpA2<}0Tb6:5 ׼ؐaW~BoA{2[|_2ŷt>`Δ@)EG_,]L mwtpY@< 8{Q@ 5AubBO#ԞwIoM@XxUH[5qqC2ܨ!Px+z(2nE$BD]aGBW\ 5IqE@A#oA*ʕfN^>ϢcV&Z$eN]jI:EU4lR(+h.$u+-!3k&'j̤r ȚKf$!'^qHLK}>h~7yB"*tՠL'|TG*Ec':e%t^D2P""栿Oz)*Z(qOEN u.IAnLWʇaHxSծ}\:M0wQngaNaQ*ȺK8kGy]޷I}Y{Hcf1kc~ԩAE,z%w3MF>^❹菡^_1k%湱ػmӏo|u:)^3Hk[h{jzl]'[j?&`鏮w~]<}p|^ansYz?a҂Q*pOVfYDJc Bu l話w,z~}*ږez`SkKU%mUKug.d0*i{ox={mr@˧UXQQ[gυIƲakomAmEE|ܴ9# T+?J4>[̕dLf(GJot[J tTGpPC?]_iw3#jMTK;N9 Ng.X,}MBkOJg_ E)"Nk@:˓8t9"ǥuSz*@Hm/Bڗ2u6@U'B9NSkj$]Ͼ 4S\ٙsjJk89!̓zn "jx1w|JwkkGƪ$FGՉYU#+G2!N;= C zfiC)U?{?Ist\(=w2Q=n;>?c?r(f(M <1+,*55"PQLI#YDD4q$QꎐhaA"ЧgroJkr;Nh@}]p @` v?Շ>|WwE' ,~o0v, $S&ҧ)|_aA10_*3@sA[h ").n~D+b k8voI}z/ZU 1ٵGhZ G Gg#w}XfP3=s ~Ѹ/ a._(o7]1T/#eb(QeتMhMzR=-[|Juaxky`_DI}O_ݟú7q],kVv֕MpT>vӳ~qF/H oTM*?bg^_ݭ'魙}I .rNhJSAG6֥+B"Uj~ r;Iܴu~m~x|E"A]W;-4&iOsgi+͞$AFDx~<MWͽM5W|Z:ǻJZ@3TE"?S~:?mdo$N <_ FHa?r7cDZN_Ҫy`z} SWo>7:G#XxzwL}7h=o{?x944GjNpRunu^wlU:~ޅ;rUWnAþ ~wwJ$_d/סx^n{mgT,t綔?[z%Y$Ś {V\ ǧZY0EMЙ:3Ijϣ/uVV^u<̿6cS1ш\v\Sb%'npچk}KZ_ v.41ϒ-~Fɦc3dčl{P0FuH?LQ8+>HrTfw&_|DSK By}D)oq?:ƌi0!y I4cy~ܳozR?'[QG7XC̶7YGJQ|ymC(pf>9i&k4TJReb/~ T?[:,{skBZt/MB.ϟҖ9hWj=l6#mcugk4uF-ֶý75 lPFUR8POggS@ M:cޅ[w'd5#5 M$X~ )ԂYw  l|{88ph){t;)IZ}}4Ԙb<ի-#yzZ3*tU,檍|ljBE?#TTYififM=IhO} 0uL24){WY˝VLBb)V- 4;?p3U3qԢ@qyc-']d1 ړioBY#ZV&;Ué:K찦HR!Z8#%5[I6]YY;j"5+t}CkKF8#w` yiFR݉R#Eg=!#sכq㸏~jd9\1O5yڧz?cS5؋y||u$=;QW_~ŧYp!qNڰm2G7$"^^u]0x4#R˕UNٸS>,;3.kwt/mufA$Aq`3&4UDlV7hngqlWG|h1[85*7+$պ1R*2e86*46R,nǑ hV jfM螣RN.8菱`4^# 6e.҅pbbK⧒_zp lrs?Ϙ~EC\ėE[?8y'ͪ 3WHFtF{ۣ>nuR7rmn#Jd'qrkқyq}✿_dAn;a7` GɴpI~:-&p>k$B ,-*swFPŵG-OݛB*gu F2g#rc(Eҙ8-s:)Hsǒ` ̋Ӽ34G^\Y͚xxS219M>:P|[N2;'J$UbDSƂR( Fֱr06yla/O9-(SvyhC0}`Ӂ|zr~U |it6gtLi/"-⅄~wY3+aU pn?S@&/$LPc{,fm 5SJ;VTv Q"RQkZ5aeFߚ*=*'DHyDC9)!JUoq軏By'u˩q-Q-ꤪLPocE3!N^zL%s~s-sZGϬUҭUg&!гy,r~{}@H2ZǙR*;hJm$NDVqP'89Uɫ5Zjթ[VjEEiOU9 >)Ձ̜N^5* "B)}9ϓm?x/2뗙5":(>HK' > Z\)ﺔ*nCyŬNSsr s7rst!,wlNWpՉ=5kׄs%82 NAsBMVӱބ 'UDw=XQ)IE۽m/$ζGϿ>v8Il{Nsl8rѰ`5T1 9xzNV 0|ȩF f צ:Ww9ؿ>UmpNBkُҜh_P~܇ѓ}~tሒs\K!x].83:ڭ_E=t]N`7qǐ/u&frɯ [G pk]6+.GYDΧv^!@GYڬ 8LU9:TTx?Z(VEb~!1#)wkGA̯ 9 D=hU?W䝇gQ.uTS"_*5b?sfGq:G5Ae5[G?J6HYB>?9#i9*T3Y[!n^oCk~7G3Rij0Ե{;NOʩzs]{ίWvs]κuTeO2h[TG^eVX3pts$҂C!>SbВSSZsܣsC'`*P/ZjUcI鋀EgfilwNȶLHҷÜC;D4kLcQ,W(xU;j?te4_35m–3-_2.8wk!Q\7Eϙ i tk(lC<<=ef+C |oN_qkXPY=FDD v>KXUp?K܀]rϴ pc-%S=\ǩ:j)(G'QƩ\N"9jS -$A7P@gh@ d\EvvNuɦf7y 禶DJ(\ZSV٠ůa Z5`CWQ҇=h Q&!jTh⣛8 YL@20ɓL}%ie(%L?`G\$~b9I|{Oèݖ><*-Jd锚Dݮ;Ptaic&|^R@t[{qŒi^2O\<;OqKܜ@*L^bѭ"Brתjw7S#tswm)ÅӇ)OF`Hx>|(!N˓o~xM]nʕJyкk! OLr^>ZjaO}^G!-`s6^>'X1;\n&سpE6*9oߪD|8],1gШenl7`0.额+i ^Ոs4 }ǯG=³ÛrX&lյMVrUkUPNeApRdqF S668GI+)橞*7I;EK^V=8iq~jߪ?sy"9_Su2)k |2;+ 70Σ^b8COK&]K&eف;S R ^SYuqjJ'I>4L:EQ_ɦN+G8TTѵkMjo '뿆蜪U%JL@) 5jjQDNνx5Q{BsHZ82;[ݮ%1j5*TթNcX-DJ>)Qz螿vrVxGL}yh_?觠!*)y$-@3&AF0,oRɿ+$D;yvz@@@1rv*)d-Q?hFB PN׳X/g)tHu\MDˠ3 U-:le8 [I0.TF;_򿗢'Wa~ETٸMՅ4v3;{B~Q>fUW@,o >8*}^=L UۇC/R a:Lz UTmj=5ZdrF9{)?Mr-#6!1?vf^.Yv{&!݅O<% ϧxHyWV'}$ƴ 泶59/U};y6ߠ{@|rQ*.OA3&|`Kcub}OfƂvYW?-̏ L5lԿ3-\iKviuWvיIY'R>9}^WVz*xNJ_/"E(CcɾƷ -]}6_~{\ vWݯ |OYYZ%;)WoVvv?jNE]֨Χx=~ם/|ZKoѿu?l}kjkvz~&'ǫLFJ_iI$OhpF7-R[+ܡC<f^{R^w>! * x܀|}U` $~hMhVy8sQռ/.YZ@D:Hu}U؍NvzPKfrtѯ?/jtVXkuqǤ)˚(z]tӋ:;,*ݎMC[k=j#L$%IaE1y?[멿XX̧>{ςcԏޛ"` Z+ ~;dHFvGҎ)?D&;ۑڟ*{5k;!! s#Wk{ՑU3Z8hzjj$vхZ9Hv~mN;HU~Bzmup>?{G?께:ӈQ?3GƔE;TkfH')FN)5qO5?Ș8~AKϤ BvEʼn9|\Lh/T^h wЈHw<42AN P\!{fT4Ĥb4ʎZ::;5| /\eoU2\9dB7?g9|LslTQ0a4I7%?D{an˻z!Dpꢅr٦V.ъtt%J:VRjxE PvFx[M(qY! 8RPBI]h,k=ֆ8o3H ++iʵ5'SgFr?ɍrt>KyTr%+hn3Lo?HF96\{Z%W½LsզnEM6? qٺUPq*߁w0rĘYMi;Kk7sϷ,0Xu tr3n| še2qondƴ J#eZac3bM&:Lr֠'*=1~0%:k>Z%| IX~{0`RFB]OggS@ M<xޅ;gLkM3sOdS+%VPݭoWwP,8w_pdXbj\:gSrx8QKE`nBu9k4T-D^G 'L9jVJ~gq59AP!;MfCj3K/ 5JL gx*ThY(@ҝ5[C 5*Iխ}d9K: 5k=:%yS!ԞU#k]\=<SvPG 3QGCgᵃF6hurSV`7箽wu8FM5EGL'PA'4ǗԢȇ胿'%?ژCy-ԢL0?8#bׯwhY]kǯg6T?d>jFF2E$PxZ>9ojmWl:]̪q`thބ$5fو$ToL>b&@]Z~9՜w7?7MZCõY,<ܑ|=.{1+VJz6^[WJP0CnQ=@rF=HMp~ 3${jGYާtQ7 <gC9Vcr][#v;F(OT#xp,[ںSjdFQ'SYw " Q_dvfyj&dWӳNEX\j@AAןd ͮB팸$;=G꣤O5Օ^㫤F-@dzLFtiuR ZVp+qe=BwF?(~=u_T*ts4l^JN=6+7;^aBS"zp/m;~yOhC$Ps#=CwyG ܏WP[[@z{Z%qXf+]1OOwsY͛U9s?N@z]<Z9=N'/'/*H#py\7bSv犷^0gVa>nօ[]sO\#4c{\HJ[{h JaQI}%[n\́ĠT R@sO9ERnx102tϙhǚTW^)5[WMl(g8VH]Hqݪ<]C<լ5!zRk-fDpksXS_ z^#5g%Sͯd^YT*Yk׼Zy?XTeZd's5tTQT|ҵVkύdvUh<|<&BQLI|"Őd$jQ HM:N%f:Ն#R=` TT!4 )c %~kBɺ>ރbsBn?ͱ`#iS#KtJuKܷ6ƠMO,IPjwV!ObV=8v6rkIWy:5Ofi`tk9 ĺKqڙ;̯saP p;ƇUc|%Ă[vZD#)ƍ /˔nUBSK_b(\_e\g~}}^G16'.ϓS..z M.=<7jĬ0 vW eENGg_DZq2͑V7Ɋqb?~&`)`j]-pgKvc(ÅG^%Ed,-ha=.z~q*#7֢1ϼP-FɗJ%$ӤoheD^ECTT{t's޴y#^XrQ}Kqr1?z=^t@N@%;*Zy-6Ez{";Km."!j[m7Ϝ/ۮ0BQT̎w\adYc24c!A NFxKKlͲ5]=e#dl ^;{bp[g b o,f $z#:b63TPпGZ퇳Ϋ[='DTwewN͟휙N"ZO}\"z:exDrus՝%74j*K752k;B3prBwZ '"хb'h9yVvW%@Ғ ʎIIHV("0 "47ES,n}]E-lЉ]G4sjw!t/jq|ȤquhY*4L-ADMI uN$T頵J:8s G)R@=[[+$P˱@H3>ʜtp@jDD@. hGwމ@*u1{@3cH~5} CRqsfIm@.t1qM/{xc/}7+gn-0l眖S?-?1|kUiV}L]!/hVt{g |gTAb&{ڼ[Gj^LRQ >qxm&38׮1M]z趫|NۛkU宾ԬT8Qɔ[#Y[JlsjPaK7|YN~pWO=uL_?U6ѽr'մHU9[CzSGpʳVPTv=p2sXS/)klD xssi$}r|']irJj}ǁm<L{#OznK@=¢yc?~64vQ16KU#}uI K_׊$~͙D+e/#ivnaTіim4tV Ѭ*_$,=^YXĊuI'~~]vWjǾzt8v {40>ּ O-9?2垐Tî'ո!\oYWXvn\-eM`:`ͥRɦ ty{l9R./fq4 l 6`KܖMÆM4GqOggS@ M=~ۂ{ZTYTV噳'H뻻.Yuf&M;rjwp~[@$ xi^:) );i./Ӥ-)0GwԠ^Z}RzqhTyddsB4-#D}IJ$BHMGB&pHyHUݩٵ2kI:KL1't@jhm4ALYOcn !J=e ̚L߿=c]D]g$NR2GSUҨ$ZkHN);ICӫrYMw\DiDQ%٥6*5* $2W^OuBR)6<=v~%DLMȌjHK\qxM{<%Z@fst>DV~ >-$IS١zN}eFqN6k{M[?-u ,[c1Ѹ賺|F;5=ph'*SzF )r FpqQqx jϷ/4u_ ]kL.#|˘IcKTw?`/eYOGmtoM>ir;6* 'kɮ:GԸLCrbBA/7nu\V0/nU|#9= ߓԔjZ݃Öfm1"¿Gu-F,ӿEOO:Tg)6CH. .7JTbds; (&b3̴+ݠ?V;ר|?>NK# {Rid4#ijFpư)q5Rw'F'=>\H垻 ڣZe9l+x51ja7V}<fKԝ;3c&O}C]~|tݓRٴ\vC&s_M|7|gx kQ/u<2-ʥa1?zHh= ],: /k.U=L>ۂm6 ̓@pGS;k?7ԊVWtOtw >3'G]59P.U딢Ze:Cp҉F䗈rSG-;U?kaUEe$d?=#uq9W쭙~DkHK5NGdɞAw2uIbt3&CU$\ JrCZ N 8|_;\E4]pV3'E..dš"1'@vf=}L+M^PjpQTNݪŢ_$%cȚ_C79'HGD)8t$$B3~sw")8Veb9Dq~\=_VH~l=:~">rf#;W~Qn3[$ S kNff|ejE+]`GISej}W|Mտ\. 6x_w HFiB89"]_mcs´J?Où~xTy q幥jhYMy2 B;T'2Nt}BuusHLwۉƕ?'DWT~0'3e`iJ ]D3Aج +L#^u(6.n}5--% *DwEmߓ +q?3"|#M۲x{R|~O7vjs;w{k"Jl$Be3a༔b،Pt:-P3 l_i3< E^?R^~ eyuU,2̎{%k՞/Bi0_:ֽO>ۭZxRziGagIOw0R< :v#Pص&?|n ׬oqN~ԒuV$.:h-%YǦr԰-ܛ/_^_R53ŨN ^h) [ވn^mٚJo[ίP]R3bSTF7,R"7NDz>/dW\+˶S@4yo\gp sLVKu=s;3G|l&^g=-?o}@L*(}u;96`Ч}_k۸mn9'|.I^ Kl  5פE?m[{ ɘBUn2W{۬)Յr?ryyNT/${=Z߳:y- EX=Q(sHJrDPڂ^߂ɺV_|etR򡵐#tUj!TG׆ZZrhKd%D q>qy䞝)r$w^`]$"F KؕC3{ ݑNG=2E>HRz&)"N#}U&٫MN녤C+Ls$YC]ZDHD=P}@D"8JE$gW^WgLuzёA=L󩠎%2v:`YgZLƎ`|>ZB} apT%NݵHe3öo"ڑU{@f4+s3 l ם`\"xo$ }gAncw0ŔKN-sòu9 Ggf،|ɘ;4*n{=#-MC%C+ @7*J恦MNKu#id;z_nw=JtEv+[&c0=-$|do:B7zz~{|%xʭ-5\ Azl@~c q '=)DVG{dIB|׽PFCݫgGtGk?*[;%[CgQ3eUBvCPBDN?3"Ef:tl4u#Su) *RʨF^[ԚEd{r@=4=E%HSlstd*|ĹeH:5u.>\HGUԪ}\o9uEsO;5: #)~hjr!2fѨ}RJUhVD/[sPL*dSU~[~B4MfR @b*ԞO%rh (>sx]RjmQ5QcBT {rݮڐPcA)$?lQivW1~=sb09M$+u(z ׿IL=r1O]z/3ńV_`IJ1VjsH_5WGNM|Q?JE<auDx#tz>}?ӿx% F&^5kJ /8wbĵyZ%.dr"x\QR~>G{]XӨ6ˬXabSh}hLLG|m@'?5FXj79k1fR44a'm-t󋕧TajK ^Xa*j.n]V;W.%vԏ//; -u^dM;0L3TS@FY9 :." r"Q֎5Z9볮 z~_M?b.s>;#{qBJ7T:T™~iFԆ$9# vUux87~ld͎E#RpTW)hxiDК ^}i:5s}twNе8,l8(澃=k;Ĕڝ甗4\b=dr U ₺w,R-^ё=k)N ёݭ͡S$\((Gt*-SdvO"dͨ{ j\#Kc!ɬKߵ, ) ERdUmo^u>v[KMu+Č6.̧oas;ID}@Vq@=pr@뚚+A<`J/‰%>U)4*7_zӟξ}svdM{մi0xy/]-BcˌǕ^'f`Upag~rQ8=g;a˫|qyd,6a$jZI!kJ=|Fnkgu\3AipWF_#[ONZVxq0i[{$4zw+{I&ˆ{}OO*-OüT[NKae\M?5|Y6lz]}}/MD4W"/VbW9A0|ƍ@u?J =\p-JIc߮xhwӔxy˰ D9Ë |dj:Eu4UݲH@<nL-v[w*d<;06g]6IޠpE܌R/*o&#uNvax$Q 9ϑd Ԫ2$A= ~7>CCDfmT!@w,E19$Euh]JLا l/^d_qf"ABS|>4=LNWI.HÐP&vC7M7%yvqw|Ʒ\nUB)f Au R8/E*>MD ;/ MU{~&[sV+oi̹( :OMjp#߫Mչ$3!zDJ-H_9-_;3 ,bnwڥ.tߍ*V3OQ{"n¨Ti;ZJ)yl.pW䑟f27ҨH0w5AAv_po˳IAaףj2_-GTI1Hk[*7dTn܏/5yl|T 1bϱMٿcG~{::m4vp/txA?U&{rǶ$ABw]R=i6.Uǎy6aϯG%>bWJXci@ ߥL'Kn 3׉"hN _zƗ3v6+J)Dq$sOfή˚e_R{?ϲﻯ\`Fuf[es`M{/r$ βh½1N鹷5v`{ZX_\0M^+I _M.a:aӟ-)D*I >ے,$ IٺNpt'/ۤ4zxV @r_zGzK*3" |O*5?bIJɂvZ~ VԽV޷3[ߩ'zU Y>9"߫G\\iq;DȮ c~tgs|Y.C2ս=㟀G8l-mFQIL ;'=2ZډF7W;N~.ǦG8O7ûͤX+.;# .Q"//Y▛eUFӃp}b^'EsX1K]nT$ꈰGP=թn[EGa%>Ur{Y▢ =:/jCnoN\LxkowESL7p&OggS@ M?\Iބg,t r VrU_qT8|RuH9EpW |نQќ1&ԚWSLZjTU`ܓ"9kE\m5?yKo~%;՘SѼ`NRu[=+xġxoqSo 6ny6LJq{iN+֋1^;C]m,A˔̝2qXv%[8eg<{ex^T󂊠1jEĞc|,ǨH>G:o**8JH61Kowɏèsgyrn9HTޅU-y5{.]:7C=Z/A+Z[DZ}$;UK/XNqmǪrsYmu2w?ȔwHpK .05;|Yj oAçTz\quQffxx~QVQPyl#m3bvp#Q8| b/(6-S- | ]5k1aL+Z%w.%!ՔwKݳ:Fwxeµ1wʊmw*05Rz"U~)=ӎq%ȏUvFl+]:$= IVvXMl=Vx;9j/Ġb['O f>=}y#k\RuR8S>ibZJWZ9ٞ,ηݚsTS)c]vrt7ZZjJs FK.n,n'>.]Y)곞'Ve\E ,7_wu6E&Zϫ匑:Nf$@M3O./cY#ްgQ:c }4brЃL?o]@(K67d~mYֲuHrb*_ٟIN.mSG\p]geͨ./|+]v5dW|S MVv@>ՍxKz~Ɋ"iDm8A$s ܻӘ=#NFw ބZgfKt rrkFaf]TUMr0CRj%:vR߾$B/]Zx-1Ǒ=CJSBwXa^&کDPw"'ުs+O4\z . S:F!nj{jT*{E,ԡK,*N: 1N H*JJߘ8 ]B925u]%mWE.L];BahREXUBQ"#df] ]ݨ̟W3t 3:kt2?CGelO3_}?QfvHG:3Dm ٛDѴjY;{(8H3 Ԙo|d&z]=:Pyy-rFaD۟ǢЎG! g8A KORvINمYt[ćM{ZDM="=Oe*.^)XR1BW\g[89C4q Q\6ix+uyz=3 Gk1}+rҍpzϏ// [xN. b|;/VB2u߳=u܄-&\)#o'H7kUMԀxz+Í%hSD'dK ֟ko98ׅ,iO iO^ȷYgzK k MmJ!t^{9״wANw Opb;wMe>5vC4=O0]9Dv|"bzޣ3ƣ$42|'amRMȫk&0~LUԎڽ9=zҝN ȥQ/,Ԓُ2IȿE~_]_~,:0Drm*4铆o'6t9!󏔅}*ȴqyMZQX~Um|?' I_X7?xKKۓIijz/iֿdԍb;}Ióz%x*b{[qc3K)|ZSu.eƖͼ-?{hk|"z*EG36~}Q'p=tggfG&M<~~"ǟHvvKUp6*i! YnYC8ѷ]DĮLh47͈Ȍ[, :Ox_?f-lYO~שSnI|p\AV3Ϝߔtݷ@YPn=-$ulkRm=6~J{%dDe4)S:M{(mܬ>bq3EC,_NkFo_ {PvYB׮>JeJ?t`;SɿDMna,fnzd6U]źX+ U\OggS@ M@ߙ}}ޔK$`*L015*ZE @>0Qju/]E3\3_@ D),K^RFDgNC;f=Xc(uZӣFğȴ4GSԮS ^VjLT;(.UnPog>ꠒ}{m7ϳ!Ȕ#)+a !**s;]GDQG(Jj/KuȉJJG专qjO5B_ɑ.ӢE {6Z=7g{R3x /ۣL&.Cgϔo2ĥI79NdMNHE_rsE U!E/]xPPtL5b UNSW'> C030U0L>F1ɥi`LysPt JO pNJk^7zWx؟sGsRjL[L;]9\: "VMbXlz OTmUb܅?v3'o a8}cVp4EeeLvh I4hN2q5ڻo`§9 s R-I-Vt9bC|]Sn d}w/u{SRP QӐz^\Z='m7zɅyE [WsI9ΗxY\O+ ؃?3w^4ᰣy-;E U^v,>7PiFz88)sUnwC^qq2ȫz,X Vn8TMqK ńM跐fU50WIelL(Ta𾮿dʞB2qo <6n4$Fsޱ V$=',[vڌZVõK0E@~++:6Q+89@]|~!ܞZq[g{{BRKToq[rS ;m*0DT RrvyTPqz}=/o?znl}D̘iѹ ;]cnp D{#DZ4 l*ZOrgi[k CqN"ڜ"DM^A׎"22|8745 T"츝-Fd(|40Ҋ {*!ߪBC%$K'4ݵ\׽F]Z] CtA]eDJVvI} +G grT88RyAQvEH;;s)Qs"l=a3u}shB%P4"g>dk<>8Jp@[S;g > ١u%(8J;ҏ^лLԱu=9eJ܄;NM`a(& 6~W\#FT@vpǎ׆G0-l)|зT8#w&Z#ư@ܢK[=n.1~'1haF GW 'ɹ2{`s ȕau =Y]XϺN86wՉ_~4pW,-.&)zwax~]QۛTH!γc-a&߼?\E=lŁG\jb ٷWccz-&_1dsrs8< X&{ˣ wZToV@ԘKN{^,wUb'>KWNjz|c3.#Kݸ2פ]t<6N\D XZn~.ͿP#X4?^1U [ "3~/\@ѧ 6؜Y B;]G{]qOx, m|Ee՘gzS |ij{ڛFꗏod?~Ul}K'|y`;ǃD&jMex?Y,.#bח'_GwQFT[?'*ǚ{@(?Tyg~Y~vIF:4eS> ;mzK@JUδY 5fԎ=8iu|/Vgv?êO(Ne>y<5QRxAӋZZP/Y+Y:ѵEPI_- YLC&L+:$+$r9BFP# {-2$U=֒F+M׹F"5zo$jD[@Bb"O2DL´oq 5;Uu.x{IurWZSМ9Ɯ,$)MW-2i~k8~ 9*qR8] Zġ-DhBt2RErd>SdnRD-E4_#*Q8 M<)t|ROw㦛l1dP#e9cЅ(A |X2O)65>]ɈkmT?θm"%_`lMʫ~n ׵Ls;}a$~YuT.P[S~w4L}%%i跲kqh ֢誀Nfyu<17~/QlH{[5q}a5 ;Lse*sX91KzFT_:%Yt?J&Qj fec \ Od8iRyBrn[0ijj~nTIxL%fC'# I)1=]ɿX]=``w/8Fj̽R BPA>v麯k,ǽNZ:R\M| f+Ĺ59؅p*S}nQW7muiݕKRKԫ'93gSp3; ;6hF[No2m4"{+l3N\z7]NO*:. +?W2!FK!ٟidr{7]R#||(MS{;lLo !*5x}gte qa9u3|w9s ot=i*{=n'S"׳uy"k?q _{`MMټml&bU _-nl;mN/p[KIP Zx.TхV妧{8qP`Hgz=#N~ k <,<Dn)!{9LHF-^Z2D"*Ns$СjfzKssWpj'df~hD!h u!s:啤FL.(8e:AA|Y3IDxU :Pu~"Y VZWT_U鮳sH/y8B:SCoӋhY;#_Z;Г]) э!z@8"sJ̵d"ALVEY5y9kӵf!E8''3B:9!HQa#}8Pfǁ/LB(ᩏ0^q||n=A!))Rz񫽎-kNNqӆmͧfNTYhM>O{w]_W?_Mn_2d1;&%3_d&U悐F~H@qkJA:#гRT3jMȹGg~_];2_Ê=Gu +EڧI`g Ț]JF1ˑ~$K\gRj=y.]3{8h>{'35 5TR3% FStHO̷TlFoZo{ :NTOjyN}%fByss4q e5ڽWR*M᎐nؖߩ-c&86 )mY6,m_=׋=.'<.7&.O1-5 FGc' ?sH U<+!Ļ\L a+8ުg<'}ʄ*5Qso~U{i*fFn^zvP%/qaGܿ`N{acAjwGqPnb|lړ/1!W_.wi2hC}9IQU2%r{'9#uaRlO}UgIʦ|cUPw]bqs< &&xx'S+Z[߆yߔl@$NgU.G<ڵVDOx_y?Pk )5[=+T &uW[+8h SéK֥Vu+~=r+Yww!u:ԐnA#3dҫ~TTډ;"f@*|Dפ^ynVS;kjsOtGu<4ܧ38T)B}E;4KwψWKGw1KTFdǢuxՐ J P9;0V)hTB =g8MbٓT_]vwM˾_,Q84ɗAQwPԙv"w*-HmPQGԢBiCiq8F=ܑ>~<ׁ9=)Iq'}@Jp HBI8oS㼔m?s.,&PUkxrɒl%KMt۵ξzS"bN0i2d/lԷXZ?,#yLRdu= 2ZrjB:A;wgҸ!Oըf~sy5H^dŅh7dȜO Tapț]]'l~F9Lbh);ǚX^r"xa}$!߉*<7Y#Š:hzxmϟKԘI{7Wuz7[&%΃MƙS]WMT:W湐>i[(iS_Lf<=ovzh|Q_9~<8ynMxWY\ ZTKW{LͷSm_Flߟ̵{)Q{ޠYOnjarr 0}'4=(o7Ĩn7dtH8B?6u G |j%_vD (YPkvzo'*bbė{;8_i8d9)q<.^VC8N~4'Z !ɶs4dk,oT7BlQh3^;#4t_`^ޮ?p&8RBGE> @/k)s5`9O]xp/DҥZꜵfRj޳:}=&hSd}4Sw<5w]* :YQdH 3>ޫϧ\_(9ss3*?B,d:QeWMpf}9^1tydG,qQ]{&#d`J6}Tfdn֥'G)&!4 uB롩r֚.HQuc%U͘c[~:5ً*C'|MCV'ՍƦ9W PNg2zТk 2jNM&O@ ՋGh&(p'OWL#xLkB8 EzT }f^il1XA>UGl&tƙ;0*H"Sj|lqz+tNS_I{OA8gCP((fp) aօd>]1O !}zY9cXDSa4?1S-XGSw!㥹Z#wbו?t3>Sq8f[pnrOX +{QT_oGzbmZrҬ{Q>'8-5"7a3PoU"w~#'vuT,Bzvj%(#{cfXX;y-Ѯ4uTK篪W"ē)}3RY:4l }lge~&{~0Pֹ.oxE1;>tmթ+\6bϲ6|v-s#/On }فq[/4]v_#S  nN |V}_5aw __:)Jg;= )wmbuK35:{e Wqt ߘNAK{+1hGQCP=Ћ2Ŵ3BIsRE0$N[VqTt{F΍Sk\CDwofA;TOr yxxN2UWGH@{+GX~HwyֺD7dwV];Uk3:Z?hQtWrϗJ&pȭ9X Rz/ل5SKe*sqd`5A|zi~)ktkgԈum&1VEk倄Dv'ؓ go43~f"5*79"ӡ@,K_ K~};HOU+hd >wsgFcP:)4iS9>CrxC8&4e-3] `HamNOjC~릖=V6}~D=lTr&(QZq\=x&COV#=L5Z_cUV}t{/ܹ`߳=Vn^:^B pO̓ 4" ?ԧ4!.9wkF#R$-4D&Scwgpg o^~<(g!=:ԎJ}׿o]ucwK '̎iԉԔϤcJ@F1Tp{OtrYu*pdz5V ׂ5jD%rsW-]nB{*}U ##E;v2svP.Ps+j$kKtP[N2ԉE+2]; #$9';*;G̓=hiz8] pfז偙Uq"w|M͘C@fȻ3@0tFwwH# \I͹;K jfE?I<Ş@mFb5N&=9ƟsrG)h!}Jw8FDY坞ğK1qcoU)ŘiSGP¥XeL%O>%íS8r]4{UM C;@i$5c\wѵZOcYΏ*x-˧>hS 8cMx}fG\p=dMԮ嵯{\a8J9F"D-0l^Fp<.~O;"-^SdUzHȿue{wMxS)¦M>RLjb%+' FhcByϋc%UIW-56.,>8y@o'hIg۟LfE\"z&\,d>0.3٪w>=ta1~)8< +Ձg Ѭ? v*xGY8ḱwĨ4v\ /-t.?L!o5֡AwSc?Y ۵&[VBl<裫{x>,\ѹ2|I~ ^6}\ȝ`BWQSdсr,pwRr}pˤp9L)>+a;2 mr_;}S2T3ʦIn{;OK~.ewOѰMZr 2q`MN`Xb,C>ԭ}Ism9e[6] #fo\*$Nǎ xZ e,&wUv#|o8J6q%o/TDKxHYh6q|?n_TT^ܓCgVSc'gpunz;d3'hSoWs#D\.jn'&*A|>kZ%pۢ# ~j)яڠ,EגծRxJa"8:_^Ӏ./EܡAC\=!^%rqSYgj%3k:JZ ZT Ԙw7-G?3f\F\cBu^HJ1et)1DìrrD]Zz_)q|cǾWaqd|!h@F TiV}5jrM|cMo]x«k=?jY{W4wMnP:duH%,pxB:wrSҭ$$J$t Rsqf ~KISq&q}M}=uW~HT,:IE0OqcN'N73c(P4nt?}e}e; (8RCσtNU $WvZ*v`{2icybZFF,9nC*%* p~\D|@^/"9h\Ztsv._5^9bIZc[@GeB%_+ ɺab 3T3d!ۨ#OM@2@L ;դő@$aT-ѨyQdvw!UwKvn-_7>ߟaGˤ 8VJ-i#I=W;PbwEc.Wij ڸ[׭#C>> ~R/8R0'"nʛ͛yrRJ;/XZXq֡Ro.3_^:W(DB&ܩo_Qfb.t]wgMZms^xJ{S^[hXpԐX>=MI%՜?^K=,]@_\x]mkyetǽ=F!c.K~pgEp-!PGohT>9{<@xhJKjZ:Պ+Ew |Vj%YsPe*C|JRvW}4MBCtDR5s0AgB/ A)DB\Ϫ)+EQc:e>y/[Q [һܳ>W,&]zFjKKԨ3ٕzD: GR4sCߏkИڭS6M;eYCiCQYNU様$$td\=zQ,$%w,GsW"LNvh7L_%Rkթ JSՉ8IԺz(Rr!5"EF>y\λ!*8[_?!8*{2CcnNVaV9OKĤQ;hu0 gG+_;^ CF>Aj 6*ɘai#z듉Y48o= >AzȕװK>6n1>Slmq#i]ŝ`[D8nޔ[ 2s3Py̴_Ktl;̹,įUԁx~LA<ϱQ6Zksgwobˎ_2d>׻_c_v]ZA]BJ-TOsܡ7}y&MYS;ּA5.T\"BYi/2X_;)߹qtI2.l+1窷z͆ڞ (HצOggS@) MCy\i^{pSB5;51 kh)^3TOX^>wwu` uSR ^ZVX9wqz yuutBE$čRף+UDq:{?'x/ L9Ej(;mCM2zτD]S5yN=LtHE̕s7"#R"vAykLp. GRtOe}.j95 SdL)-4NC1G2vPjd~-3Ԫ5{8MBy:A;4̮7!] ɤg!˒bv? sbR=ļBD-S'?FDAPuj )"dhQ W<3Cr<zx3~oEg)NSg}Qi#EMq0Es?gϺ{>g͎S`RrbP6z-#''pi S@܉Ol[989 i=_ߚf"2y:]g-;mWHlČ8K 1 9b=/5LzOio>Mdoh5]}Bzl^5PV3͖bu^ci/ݓL8LE4kl;W?օvq嫈=G/|e]Pbju64`!9L {),˽iW=?t0_yӌ׌"@|,ro8dӅ Z?`yMԙb`^\b /&c3u>zu=M!oHiT]Ϟ1oY3cIfw6mb¿],?]1k,cp#C-KUsGG0ק"=ˢw tl: rԫ\7Rǽɔ=tP{?<D%r+2.PP\ru\V|v& X1k>.29fw^,wi:)PVŹ+r]z[)yXz){zڏէyl P xS5('i>;#5,*K':mGf͚7A0Zz+"9d1#BsFܫ&t#u9{t 1׬);楣5jQN'H;ԘDE*,EyzεuϞЧttN{*fSfbukqglZ]ՎhJ 4jTqIy.Vjr-Dc Fk^*Yk{~*:GmZYB%fLKVq BYsh^h*}xGD1#!K>4Nu莔}'Jpu?849t0;Q3 HwRD:ǙHr'UIc;Gax7y4<ό0<-ǣyK2}M_~DNn+ Wۦ~;=$@@(UD'ݨ]~60A>G)sG.q2r Y׻oZlcCßj&}8] ѹ?~׀ѵCe͟X>FLDIp=ZWdE~K+NWjaAJsOI/$}BwbCWo"i]|2ÕH(eF|0-u{Yb-6ٰc/*̓<7 u8vE\|IHY=Y{^r}*sSUkWҬte\sUyw7 f10_v~-`'%w9,ҋa򸙲V Y=|FnHcf+5\6WT9zyqgZ_ ^h1&mS(inWO873L:,6jv|ޔ;oU8{Oz{gynWK+pֱp>O!ѫd~jYeEݾaei|Zb)Ů+FVN8'p(m'Gݚئuy@ 1nQqһ.wgG.WW>b?—}iuu Kje&||v-NXFA뺧=^~YP {POh^{%@׋gQ;1OǙ#rp-с1; pۉ\WWI1ArU ɼ*BNB;hTds8KHƚ153!&T$f=C"fhxADФʣ Mԩth$%Z_Ce+^4IcByr`ʜzD_z4V*ݺGkWd޿k֌N=tSpKsk{QdvjϨ_ N9b 21Nɮ^#C;C/{h0ѵ\rA%Z1ޫ=Jԗ&йJ{S1:k.ĩGԉNDT=f$;T/)" |>ݾ[㥕g5>ljE=c?z8P!J)z)B 0uTfj˨C'WJEQ{e>1^8xvO:4|Kf JJݦE0K㲾D[>*#AE#:-d~8ݲM''łNAˊJ'$;=5^J3dwהj~5_$3|j!U5#tw<j\|ϼAi%V3jG~%2vծjY4mJt%|0^$RItƤm0܋5{zzl9-uMapd36@mX58LźO?`x-عj~z!S  'ڷy 8B5!ަeS߿@Bk >{c !ws}=8 8s0p@ߵ"u5Auڎ@tZ W}v֪J?ASTM7·fBT.㨫PfErV! I>k8R>U!g+gd /^cp)2%hΝQg&41K:i "kxGZ5$S"IKDt}|ġW=Iy*]Q\$-YZ!MuRQSkR1> oZ6:_ $THhTw=BttL(*ISVZVG C$4t=0CEljC+ ؗe }l|| 6O-Myt>9?ѧqfG<03cV}aQqg&I@ta:A:+V D$ϠVWڜRnvm̎+cèG*/dĭ * =<KtY) r!K@׳YvҿϢ+#w>l&3T?y(TL{~t7+׽.fQBݗEf~=8}W%w%4d<~pضz~cWڴz^ce2|"zdjg`& zNU[o/h 1''=Т`ݽG18=[L;K8JT0@/ Zf Rm4(O6g'{oyejd73"M/1C.zq_',4A;[P0Λѝ` 6v9]\ǯj >"Gw:Gdyr8/Tk,7^Ol7C+b(&pQ\jאIuyEB#wkMzù|OggS@9 MD]9TCC+:r6ul ren>2n{Sء 8'T}u}~@ph2+TB})jDp]*-]<80k֜_䠅i2)BU !jvvjT#92q TyD+5*t<1{'> p<\55|,9r}N6]=茠G\St( ZUϤ؆[=!0O`]n*E|=_̊GiauO<Ң%r4\8m*Ms )ԹUG:bلWNgEq쎽qD^sWyM$ ӟWnQLow'x{;~|{U{naى7SJԞ [ǂ0eF0 c>Mp}F6eRSWM.~;rf"n.l.TŇLԏ}9pwGϓ˶<++g8UCl*QG.[!~GW=݂FN~Ãj;P6\8З\2DSᵷʷ6iP&2H*=-qZNr^ûaZ~L \}aR)x֟]θ1UUmT>/~:r[5p%( QSOɎGl#4eaRN Z^&xe4jN*Z&6pWMdL<_D!ahݩVwXjti:~cP,5-e9ƚn_hEYJl^{)@@ wIY+PS- 80Ly!w S 3^skKTXP釪[VZ^Im EIJq;AUr#R: r@5QGc}DZT=략t/WAV9Q%pJ%I)T2ș]ӕEt}d詝 ! q ub:zs~^* NTB'ɗi.%g*Uy,tJ#:iheV'ߩENfd'Tq8X9ZV9SE(HjQ jHȽi@OɁN9q<̌<#:wAG" q&2u^y| ݿyqoU5y\9H 2$8ǓZ!{O],jf0O&Eݸ'CjJ;^kaHv~V]NPI302,Ez8D{$ހSv"(N< 2z`}Dvyxn[Y9Y ͵|L}T)g"}O M,ڮr۬^|nMODiWw/=r`^{?#Gkl>{?=?QT ڪ8I> [;py_?jFڱm%M˂oG\BO*c )OOmV-f*ElJvCl|^馄8w~= θhЇ?dϏ# wNfy:*sxBy7?o9w=|# (wDL&VlYqUohl F(]3a$n@hg 3Q_NFC^H.sTq6fC$ُѼubݻqXYGgۃZ&CՄ o\M1&l&EgʇIIbe,_=$DO_)Ʀm}3 X/{ ,av9?^- dM*wf2XׂYQ|~{9R`w#@gB"r8 |>W HD턮#eLwuu.e!YP~KLxeqNЯ["E;:5*pP=gtFq }T1fF HtS =T5PcZoOΉYZAh֯yHD Yg|y+%gh8U~$Ykڏ3%+7tPVɃ%M4ÁeNo;Mmwc]o.ulM֐}@DI$D֍f=V^Q0ߚ_m&e_ ŃEETw27Q#Efڔk^m;T=֗*'G;>fShnKfGe?p=fәȠx=Rvgae7"B<9Bi.-{!d!gy& =8ͳ G$G?]L+ⶻG2wܮ\+*pim-*-nS1 +}GdO1xg8 QFڬw~Zjtײ>[%!l}_E<ບ>yq3n;Imd>K!+  16$}]@+1Wa/ř1|%C zǴool0%C<~{)P@@?B@SK~.:#@1 f}rK vмB2jk٩ZCfdIr+RM貥~앆(5wNQjO \ږ\_R04 eTXԑ0kd,U.&UqRn42)O5`FGT*Rp#)*c> Y>ۣ>DeNFiǟ5 DC~3gp*Q]LsOR|qӻ%3-xVYh{ -~hi`G/.̌E }6+7+YIgh_ ҂+c;E{uF,cBw;rt0 F픘L]57Onvi2JEhn՟3}74Bex N{q }ax\1VGO[O6KYi$!vio7ΝӞU}0rhv\[[Y fY$s]敺ӓO_1T=[>rsҢ1m>~[sRg/!;r9+]pa~^pFbqCZvN8]Azn֊6SlGc?8eF >?EXvY<;/1Ya'muYۺ2=Uqjs*kvxwg.҈-9tߗINqAW{qA6t*^b~}>ln' wqJCs 8׈9eM3i=uԵL>*;#n+$ߝlW6od̎۱ 3\]o?W" y/WY7%tHsK[pSg15?]o6a oSg*CC};z/HnR"FE3rnJP@&OggSF= MEJ_K^{)Ad@ $ٓI=&D㕾2Ce5Zq$GQS]:늉~\KGbȠpD{AJ]*8@HR79KlǼ$AAdTQ; y_J:pdf5Axbq?+Džʴ NQI@I Hڼ(U\EA5 jF[Y?=CQ(ЎD@2$PYS=& GHcJWI$C418Pcɠ+U& 565%]ݷm/w;@ Z GL3AFRIj׮ҵ+RǦc)=\*R EЋN7wg̓z 9=ں1Q)sڡ@]=Hy>bå¿v0Xk[Ka(lk﫞:2ߞ[sNOvyS-+팞X:q Rk "MKA9ՠlO֋d%U9C99Eng]~Oׯ( }զNJ%(>IG/.F9r2,+1`ijʁEPl*sK.$OjS{ﷲAq]k%%S~K2y'ԓj`sYWB=8!޹.8[ιy^5ߐ~En ٿώ0Az:5sDW?D^i%YiDZ`bo<Glֺ`358yCe ^ihJyNWȻ2"p2*DG4KF:RSՐީ.a+b5 z7b*%T{jgrWBiѳd?bs_1owԋbJ~b$]x&2A:<_=/uk1cy7? .밈D%޻KjMy`֗ltUkf&[3[z š2"d֙7/ӱk nS{ (|S ( ukui-media/ukui-volume-control-applet-qt/data/sounds/logout.ogg0000664000175000017500000034556115170054730023652 0ustar fengfengOggS R-vorbisDmOggS RRSvorbis,Xiph.Org libVorbis I 20150105 (⛄⛄⛄⛄)ENCODER=libsndfilevorbis+BCV1L ŀАU`$)fI)(yHI)0c1c1c 4d( Ij9g'r9iN8 Q9 &cnkn)% Y@H!RH!b!b!r!r * 2 L2餓N:騣:(B -JL1Vc]|s9s9s BCV BdB!R)r 2ȀАU GI˱$O,Q53ESTMUUUUu]Wvevuv}Y[}Y[؅]aaaa}}} 4d #9)"9d ")Ifjihm˲,˲ iiiiiiifYeYeYeYeYeYeYeYeYeYeYeYeY@h*@@qq$ER$r, Y@R,r4Gs4s@BDFHJLNPRT> "9@@OggS R&jzfئ26US~ asꗟ&"@(5tq2VHs{zr"O7v2U_o {i V޿k}U {/XnIv?7z>ץ#ux^sꚾzT}|%2ۭ Cٍ.?o %ZlS´g付rf e2iS~7B,LEϻ- >% Fv҂W&pn9K|Ga1jv#kɶ3714HݝJFwFK~\)~^}ަf73|ѶN~ : mroj'7?_4ܭ66Ϗһ%m̏ܝv^yYZ5R?D俹ԾGyc#rjڛ:ŽH0xnʣܡ2x|vH_^m˸hEM޺?Yt. BW0k?/zz"s 2w*+yl^zwZvs?>|iu|\Ϗ;_\_keW%*;V6z+":L$#[2􈾟,ƃu[ ~]2-Pox-ӔeCv4y䍎,6g jͿ7cuc3r5}OW rZq>>>n].=+K6YLgc.):UAkۊkYY%1m?i52|57}'5UWϺ|Lw-FnǭغltRC>==[߇b ׳Q/}|ӅXRD-AC~ ZQPo^r^U%Cff-o!|c 9z@F) $笫3__NUYO\UZ+ 8h:9I%]=^tA"ϻs9::CNn @>zR!tJ'D(4$a'W])4F6MBrM",SypkQkk)(jH@]BPJ8k*;NC//M_! 29;s7~it,8ضqU|fu׷X[;Msݘ^_Ƿ 6|']8fFN{eOơA#V>|O*C'a`G(9Of,x 2{Ǿn]ڒ.ڧ+tٚvN#C38LLKoW4rcw^!nއdwh\(#;AQlꜢ$/IGe2}e3!ןOkwh{y3d+f ^5 iXw|#!LeLorw{WB蘝/]>^^!MIÜ 2Z{E_hDȅq |4 RH|9)Zpm/z;HZaS6@*zJ @ QaL;ALO/>7r^>ws{H>>^ǫ˺AoqS_ߣ־#fGW/o URw/; Kpr퓸/ rjj x`8 \ԊrB1 34G42 ǽJ ۪Ɂ%ږ'*7c.^7GonϾ^0y"2Q.gݐp6t62{H:RSnl:=!/@]p@3׬7#52l30G9lE 3--P+VK?Lt,R2M+nE[>3W$ PHw+]~v<͞RzS_8?|x91Oۚ||EWRלDX^_j^ epnkarw Oe"Zu*пﴲsFAX`yfu8i/gj5 sz.¥0'wŸ=AY8{C6m$ֹB"rWq~ݻ^T/j{}߯cox.͞\b%OFx<-u_.D$}Y׻!+8[m|lmewiåI^ihَ9rgIHj ~h6o_WMޗcy1Ĺxdd ٩3O!G?m}6Է_q{|ogq[̧i?\J)"y{MI0ʓϳd!uHi lxM="4mqw;FD篻A}o#҇oUg?yxƕ Wo7(:;Q>G0b]w܈r%7~>zsKg\PVF;;Ytmُ_ MC 7k8>pp>3ep {yshP87UȌ`!Po@K#yH}" %rь̈ĉqV  \&Ds @= 2 CR1c跋XI$z& u>>g׵m/O1In>@ 퓍&T PT$a$(`_|&/?|F3?{N8ֶNy;V6}gEHg߾~E{Y; nPqjwNЍo5ex.vy' _a$l .#A9!7:e}~ern=Z%K5 LSBR,e}M:RaF52[ Ϯ]76,{1nA[KVIrP +;5fCj셦KF[Vu #|u3y :±rr-.6ioPel$9Ejy!ȝdjT\'vHZ$_Wr?$)=ޅY:?e[.j^X{Ot.^r h*;!0W$7ҐA2ܹE7l]We,zӗ՝)}#zE5粹G,S}tzf1{2'(OV)u9^-A<-gsʱɾ-m r}@Ϋ!Ͼ-N{=׌_r;9~U>.ۮ=]OM<=TqKrۯG^m>y7aV}-ϧ~{{\t*a7mSι>2ESݕ|ܭ\.;v?}CTy~l^~E|cǬ"WکVzߡm_Iz.F*NWP.ֳtOggS R5<R=`^YUZ%geZ>ҏ: HK筿ܓ }3@dS"?SMt+98শa5hw5$'(po ϣ`:S5N@8^H& YH4sR qt* {F H? zN%p@?[!|[L]pAب:#l]h:>s_yE>0 @RxQ@CBC"rw䡡ʠF7888܊HcG ד!ZF%4 *WhuPZ=榚FCJD>=Z $L ,:UTg~&W94^#v:g4築|z*$ \{&+PH3ixg^eqЏY.Οˬvح[oIޚ!BrznMzL3}إ<ܓ%)-?\ֈ\&E.pKO#oHc JRDiyp+ Nm;ĀH؀[`!,&Jj\9e.?ڏ N[[?=Vnf-?O֚./W{' p:P5vXU[k`;)zVcd" JAsQK@^*(TR I MNO8d:$jI+LN<5"p4bQzI4/A*iz2a~p8ndE$OD:}>ENBJC\ݭNJfΉ<#UR5ܩ;9LQFֳ_ pGDs]\fKY;RUjS S ^ҕe0oBvߺsѡǎst!_ \l׋U뫚¸^Kn*g8r{KL/tzJj/0h-D{!3B٭m Z^Ki_(c>U#Jb;.Co\*cpMns^NM?NHg?'ddgzq,-tN![.)%IGsj3u.-DRs+P+Ő#UX $9 /cw2xGͺ;= <>/Ed|{q(X$]W+*ד/鷀GRG3{q~p.dW))Y⺾e.\t8{Kcl]l~?z?U]{'[wSqOm4>m񶭞nܧ_o Ye놸owzws{~l~44eVmO+} 7q| ^.&n|'ȇ OL^tfM{=}]Ԉߟk]|백L/tWhq]ҍE<عɆqyV|r ?2qLfW=l'C5JDꤷ~v{2s䟴kԝOkE>f #o~6șed !|:{šyxUno4O{מҿwIb)eßG鎭%aKm%eP[i0ܣc1NP;gOęQ_méѣx^1opL"M\.Uf J}~ݟ=,oɗ?0~-̳{5>Iv:ߪS?u-?N)!o&e꿐5QIAWJ{Um>Q+ꮌPmcl g=ػUw,YO=$oY}Po IPiEzxO᭟{eJ,#5RN#7-dW|ta_8}st?׻~S8ooVI#|KEAkihdYEݼ9{ ӴvߟihUt"r>)lٽT@.Ȯ]}8ytP1=2]ǻ v>u4L3#Hht>EOw+1^gtrqKYHڗThQ9*-5#WQ#+Nq'k4] j|@ݫBKڕ rqύ.s;wdt yyCş[{]} {w%U+`b86BdNx_ǩN6mf9) 4kW>ߙ48֣WW_qoV^+<g)gf8;1\2;%XQTU48/_@dO=_/c˹+([8/n ^=xrӴC]wMRަ}:ෙMf8-\pKf-h㽔iS1b+9o/ [Q|c.8M/`MtN`=qG-M# ck"z9%hn4\:98k +^d_W1zvnl7UZYͥ 7F0k%K1$S- ~DsvڞkVAd24MnCK+_]^U֟_\dv5`O3ZQq]!E ymt#duқ9> q>-7P= f?Tp`ynyS?FP=\_ &Gd=W˯#u TX8RuT>x0.v(mؽ^8LqJkz}ŝ-~;U&2K)ݭ_jˆ}nOtA~:Tw^uUEvU8q3%ie@Lg[m(2 [ OggS, Ro5*>L׈(Y5!B$o6K( /EжߒŒ?ƥ7][L'^)88pd` T  ;4 S(<;F߮ L-U8Ɵ]k@Q(X3S P@}QYTb@8N՟ @eI@Iχ *STfQ|Ln膢+ ̅6sS*Nh7=l$Q>fʿM~nՂ sdKiUd>t!%GƫEr9NOI3yzc?<]:1CK\wܓ( pF4f7B/ּduHN)laM4Lh9/LՖv?![x{sпEn_|Ҙ'L>䷴bwǀϴp~}ܠ#)iunǿ.~If5Mj@j$x-fn)]g{ Zjg@!XfAK=Ę0BvX_ 4cF!nZBk hŐ E605 ;Y`49*JoO.~yN~\`$bZu"(z#gn0U-%j"k$]ҴYRTd;˻[66l2sZ.O4$?8H٥V#bdoaO@K9N<\e_cb0KT]Sk T&\}:e_bkS*Ϙ ; 8Hdj(p%P]lWIhq4ܚn@!/ ZGnv>`>!px0@ &yıp䓈i-_w:'G {G]tre0JjNp:TIWX:Ody}"K!y5$Ze>K{ǧ1-eɔNJNW?'#NG K:o P&̗0$`層&׽h4ءnzsr2Os(< ڷO|\M%Ij=֮g7W /׋Ϳyibg}P,%S^^0c&UU{k&A{9++"^u63JFO{4 4|S0hn:<2"/ K&7úsj9c^}33oGho>1[2@hKV `9htN-Ÿ.j12$:.8K7Jڸ3ZDe"BN=<5_R<7U[eo "b EMC?u9{hPĭ~I79%Z~ˆj"2fH]It|/Gո8k𓙔O7+/Ot0 cAڗ}VVNmԵs.C-n,CY\[XhM]M43mų~ EҪ}1is!r~Xqq쮗GfbT<. 1M}_FiWIvmT׵_6ouƚ*P"^#kX5RobPZG fb䈛đB Q a~.&*7ygk ;⤕@:$Pj[^?tARl{鳂 _~(#tFh`O~^8xDv DDX$<,b*{WQLFF~9|DV2ͪm>x@Br.%Z9_ M"2ȟ:Yg!Ns9Uܛw9cp@Ujt5Rw,HRTK8z_%I` |b}gjr aʩz^!ۃK+ b'e1 ;x뛘\i[@jS"9Aγޒ A~}U[G)vO+ߝ$IgYMi| c.%N!?35mqtJ? &|kgb5$G5EZ?*PҰk޽1k cs|q m/{tq'~y}6Y:!S~:jt&s+Ar0zfa13*:KJ4#@CoRM -J7zhaH6vєI?"]/smcLb`7~F>o~#I-/Sl5՞T{SEf>TKWr/WJF`S7.6OF/^΄H,MB){bL]SL5ۓ=ni$sG^wq0(-EYmEU=bz]1{ |‘IAi9Kg2M+cr)1"vib?CQ%3:JBGW*穑1U3>ǃ'踒o76-弎eWS+x?n6pZ„onWv]yعpV5ʽO'dqFG.Bp}?f[Y)ќ<>{enpuX쐪7ΰ^=A;U{xg}'lwr56] j30|#MUAF'A?`ݛ6S T#@+Ep$•zZv6}2P :{B|ۡy=v;Aqy !p `[8 `pU5u ه:3`DBe;աD ] mfmw+[Wz#(Zk.]k֎ J|Syt5뽪V LoE}}kCI3cCnJI$pPHv'I &bƀhMLr1( <4K5yIq6v4!8!,wzG @Wfc4ధm̶X7L{sF0& @3l A5~׌y&`gf%&' 唀mfԖ{S "CqfPD3K94@/7f5FV4=@C5 |?I猿E=e7a}e~74/(jdi0ja ҫwsBm3OcɸVŕA(>*Iw2@5gŦvap[bӷ#I&qK>9EQfq)8pz,w3/Bls/\7oOjp$׏dOggS< R`4M$0v~ëC_a FZ>5-_mT>1.IlMgJT 6Uܚ2쥭r%KO`qcua}{H:aT28 S-{4q!H4I!qUS ["4R' 04tkfU1H@bG|&RxTxTCzDy*@>&BTp"Ŋ]%thDFd84GNPq$J"hC>)PPZiP^D~Z@k* N*BrpJSQ IBs؁`!_B,G>cKy^G[NG!48" lyؿ>ߒ60ຸ=s k =gr%~ iӻ`H>Xˀ~yp'@/T :ލ^a[/8cޘév#/>O noXsv{ ~_O\^Z]OYDgYg;ӸZfՙ77kJqՔ|,BZ0pf&i"LK`(0|I_UK9  y, zU.| uAXƼ[{n"u3A!\9/Zuwnw\Ը|gWX~˷=*gTj{T퟈Sh`o~P|oIaqj[kb5_⢅R|'L7Zu.^u-bbV_t|;$~%s;ncvoeT MqOe|vYƢiX "q.[fH8~H GS %n:RW5HXG*9?^~&.n7z)*N,@z3oO_kS]nϱВ ṽ`]厸ؼ%!>)\GNu-QJa u#‹/z< M@GTtg/?k // O<8tU}P8<}h_ú-[_/۩t @oP;;SEZ}N2e><`~\.PRSerXA")*0.9>{9PaYEX> @CApԮt@ <F+Go\O{o|fSb&;[km(=,gz;3 V <2ߪ* ;]Cg<I\&hA=BN A:}ΞENCT|FFdaUTNIh,RT6pzW멠..P3_-'ф% @ߛy1ԼWUa)")n^LӇ΅p+>|>l9kwpmkf?jUw|=ΤlM8'N?w8ǟ+d~<̛@IW k|D:ޖ 貖]~OD&ոvbDv#xv7&U)=je5@߸zz 5ڒK9nDW.TaBYD6a1S HɯlrgoI,U Ɠ37s$c8_>%oZ܉$2|߹SV;;or I KyhJ*U]eueZk}c{^ x4k J\2Aϳ"kSqk4-lX槶@6r6~9\gxRG8ʷ.0i+Ÿwݣ`ނ)L x(cT-?o?}aCn=$4L.l7P$ *4:]{;K+G!$T gWocg 5H񉙊JUԌX^:ۚ8=*!TAL\S'(CN=#)Qk3DhJl#Bj=-<4SSCS牜fewKtpFkѢ8q<R$ACm2Kk88Tp weJj9jt$Ow#2P 'ݝhnĬNn_T)RŹ٫Tr[ME*>=:޵g48f3&*_r~씒Ӫ]Fє쳠C\9bUguhooTָ%Wn<ڇ- 2L>3g7|>X%I} 7?פϷGq/5pr6ԁo^m H17vH{r\ GZ X52K ?k8Y], 2. GQyV"5d؀R[|n7b+4츹֒nPLxS2(Ec4+}ӊٯroQN|kao@-}*b5PKaOXn{h{!{UᡯQTmRB;6q\-w@b^=N*3%y@؉~I>Ίz-}OVm\F {N~"_j¯lq?OK$r*V]yjy{gy׷D{# bol:wފxN.m߾ϧK}]_QF.I.XY%ib$4P7if|ۚ;Si@6(GNL?j ;w-z?e_Jߵ#0|D <_ZD p>QyhC7]g:UjNԽNppM[[_G4O<$$Y$8thࡹ?yTX/K1[wSf]1 .Yw>v> @ x$. G#hY! S]%E3@2:ڟ~JWgh<Zyի NKz^Vg] Efj7xǃK^r?BnIr]80f PHJ0`U.u+xv1Hl5yDa?N!+~. 1X WԦzR宖"w1I.fyIɒ9Զ]L|%kq`r}UK7|:GXxg-MnY.gz}5i-?|Y͢TN^{G^Hsb8ǸuK?=BT`c ,|բ=kA᫰^nbvuPVs#AW~Sc˳=.RŇݖ#e 輂 6[(Ntl~M ]{6:[*FUwl-E@+tgMteo5)84b=e] &wUśdbxrNf1E,÷YDgbk÷GrⅤwŏ9inP?i'r.ӬIpx )>;7nqB!1-Vgα$Oʉ'vΫ -=dMQf6G\{wt\$ nK%mSD奿Y_ѹ!]} Ad]d%qYb)IvɊ2>Bq|/7o^ϻ8C~s,[I\e ]q.>L?W=mӏ]6}Unɋrxn}Wת:=/ӯrǜy{l̓ː];R.ü{=^0WOggSL R;JowV,cJ8`9_n%׊|}_5mkE]SB/=2`OxFX_м?Ь<1K# =7c T_T5y3L!SJtuvlpXu^vQOpg+wyz lp/xO^׏|gx:Ց̈Ŏ-$Nq4VW%;cz HWh'E< cS޿l!}/&J\=IVr.` k%^$s4:+ { ~cӳR߸L-zCb`7[t_LK,iQ/-կ"oO~iҟ/Rԏk5<(/bqjDPx(pogm %\lw}]TŦ|ޓ-qx#o7[kJ]G+xzԗafȤj_T/ȾKd*hZZU3(zNUt-q0Wou _#XzmTFm # q_Y|?Ut'w$N)'>OXr/o}=c+/˸{]mb}nG4︴]2gL iՖ;GD?B, _3՗PH_~bVRRX؜MA*/r?!EOhqe,@snp\&|Gg%ha:hylSUnmz?>ząۿ[~~7|uZ׺W/yߏ,bX2ܼGk]@ v^on.򤊴\8umZ-|/3A4҇uD~XR7e9",Q={KH謹 d 6J|*v[r_xOwwwOM_w?zf]Yǹ*|zZ3iy~w/vxOX]f(D\B)qM#/ވp0<~,4Q.8=r֙w;~\LZęK݉?n ̭ו{k¥'?> pڡ][C` nPVU|жπmhӆ N~]0 o @(І"%x_) 0^EC_4d) }lrcbA8HNGҨV< LSN+ *L֎|^l]u8 4PۻRAf;D>[|>y|&lCJ#IOOC @#P*؃c@P’y$,S,N9+*s&az.Ɵֶ[YrKwAٰ~Ms1z"2|R,wdXwOp6|+NǤZ-1TqYE1± ERaH5z:9=o:rp-vv`D(VЍ.^3=/zȗYcYp5;7k*yxsb`ҍ ,/bK`Hu5hR5e}IUא,ƢD U/s^pߡCCT3E wrnaE`9c.QE*3!4!}jk=%k0Uu:xOO3fT{G3OΥd%}u>-:psSAA;[KXÍgƞLOLވ=癿_fy)SB95Qq$r[lP|]}T assf=꾧_nsۼNeCz?]\3φi\}($KK/iwqw6Bv]y8×оx3cۈ-9n >lG{s׈dcX??l7FL۱A7>O@f7:6_Dq%&V?W([G'On̾6u[mm  @4B功``&R' x>T?/pI|CH*<QxWpX5%gn9=7ttC"o+*N_`j.-NE @R+I\6eB g&47x{Oנbro4oq= ~&l-[;i硟j{enx_Ez3qY؃|1>E?OaȊoJ.2|p)'SXSMM1\O6ʸV61<4@saң1pk{5ߦJ o5W!R8O۔Gg~C7CឰĦ=X4#Ҭ\˚}/t`OṰ|yD\Ֆkx`^}EFbXy%}JuB*˗ Y׭ xiM<MIg1OMv(Q,o\q$-ڲfHxmΐ&<e}hs+iEl"f2٥G q$ue!Kk6QdU|d5EZP^v3w~K#}UZљY~Zn48T`6p(@O4xٽPv>/4YM"4л`o#Ƀ 8ECJ>$Ѻ?[Ǽ(|K# # ԦPG{i#J(p IR--m:|S*L=DrU\0KǞ}TyF3h]70CÛJ.N9 K;o6ghX_Y CLuhVDzL6-˝`S^²LSZO[2&T0<29œIަpmm 8+5O:`ua`:ysiTHG%+zωP_b 3UX4Q/"OEӞW,.:Saͪ8wӏZGeyX6o399Z2N.[ebjJ6 )ˆ) sO/_)R:(NHAR E/+E9`9rdb$)IIyJbiu꺧HsfH43r9Ja-{c9 G5_:㯤_3Y7ܙkߊϵ}<;U6 c[HF#.V~<0JGp9̨Bu\&Gw9o:JhOm<1K7~[H.ԩUfe׎83T։x&" sq/&vag1ވ$-Y0>Kp{MN [E?x/=^nɗ痕wW5% OggS` R[.J >KYE;$=. @u~ WY D]4QS `q٧nE_S1GG[33k 03=C(@wYyP{1T~@PzĈPU߭+c鯇5LŮD~gUP y5عBOb`{ $%hBkO@ӹUe! TG~ _5@~vy ~'%T$ ^ 9~3ZĆEss<>|ߓME#lɽ  ~4adk xVGlu0X7%/*ՍwbSOkOG>L,a.{ f-*LfրZ/8ƀK5PZ獬 6}V/?Y{,8zW[^VaT]m?3d-Bǿe{1:sQZX %ɪc-Å$ hENgtkBӭ9;Lf-|g!Ko$m'j@>54ҫ]A  ]#2J+Uիې(x,NCrzņO[NZf~̨W2Aq:y<=\DO mv.X75!BixY\ =YvU91$_ XRsS㯹,+~UY}{EVir7@,Vl#}SoeM D*M?O/)?Bnw8uSּss`nޘ$ſ'ѳ"H\#y$$Xd_\~=@h^ϦR~z@$] }V9k~A[v}Z>O> 3Ա- E/B6nnwwZˮlOѡ' ~2EA̪{?nAΞ6?VVw@<@8P.cП@듷\qƀWXHV sN <]XF>с& ߭8 :WN_QG2x'т\hكtڊ|0B\tIJ3 Mt? 13@x@}A}d5 2ARD;ߗqUh0sq裛סhKi]aˮ5 r`&vQf?{r;A U袭0VR4llpfk;Ǡ='}{:68 mjVqE@n&JXȉl׏uf󜜦.i >r- b`Y Szrd CѢdb4+ /_SI|L8~ mExԜ_:>9P`he-I)0f89J=B. 2|lJ8}I8U,Z~FXc6F>v VxaC~KKo. Za㽝crZ$.vxJq!Y[2Cj~+Nz<ϖ+'ϾWNVF*ۚ|q_ҐsRvW^eKMuLPL/Brn HLnASA31ݑ" *Y@=_]-sSm::H*|d4YКȼ)* jzxM%qQdxKK^MY3p{/me|4^\K3 cὙ@ɠ]|e/>nd@p.5*̃ g{~1toO rȾ]Ljc@64;v`鶾t*P` @Q1[@U>/&KQ@TPCfuwET<#sJ*偶6ӭykUNb[&'fu9I"$+H%TNkݓ4gplT.x U0^@S&'9)koSe{@Ca=i`,dtxioٌRXhh!Td>3<N=MmFnȿ]\k0yА)u5DEJV Qs=عn3İVuEz äTE D~}q(Vq~M*nMr^u-nMZ I Gr%j >?>.M}s%Yㅍ3S~ _?yő|#=:&Qbh۷xj"2 F27ƍY@'Q9[L{uQMd+YN;cXOwD>cǍ3tW}.m^0.Kx0Ƈ LG1~Pj[lVُA [)T,+i@EXXӤ4K1DE. qFYOj3A.֌ % p`}Pg( r.aP *`@6H<>_={pay6b0X mB+?լn/D]@uEyHz| j;V[>:a .5h&~D G/hr=?}p=aug~(:9B]Ҿ{" o1Dcn6=O*sl@;&}Rn J`'KTK({xi6 Qnӊe3q%#2$N2FV {^TΆ)Ι&,Z(Cȭ~~.f8XHm"E#5E>nMy-g|XCyd>)x^.Yw;c|܊cPƕpK_ofd'UuO4ld6g*K9can7_6zw{|GWo!C;h)zg+EPTƷ\(x-d0k9n}{W;D4j2R|Pf~lQiL]*}X~[ɞ3tHahJ!>&yF^Kuz}ĸ/"u蹖ɥh-g, "9[}d֚shawOи=!alR=fv7æ$WmF5ΕKGc;_JL _cz!h!t? $vLqZ?qH<[x@| 30*Q=caYl$pu8Z$tL0^̧;m@`gVOo7:cx.g~^-85)t"ڝl|;YVE\G*}i)_iuFţ_O65 ݎtHN=Ē:@ݧ/y]dLj0/ Z-y)>|oa꯳JyYrHK8ƼmcC.ZA>jYE]ho`vu |9ޞLrdiFƚOB>nYu-{?1.gl2r˟ߒ4~8^KmfOggSp RO=GKg x́؋ֽ]i!~MY iktk`+IT8'r$ nֻOZ&ogX&\[xM@57j+%_l:T[_K(9xW Vc*dv@'nf x44o_/JʨtW?|8 *p*/˅PG7Ү|Muɖ ?M&TiƳSwh37Qgƌ,(TW!] ,|Y'[~~>7u's@1f^~`aM) ȅ(r*}Ր3oy sTZJAjq;l8=8"WCi,ڨWֳGz1mRW] dJnU nEFv)PP.$]:egr^UE3`O?N$cyXREXY[>`_,ҁI⢐8,.QxA&.lILMDA-zf+]%6Dž1_Y@Ƨͧ%gu\^q:q3wnC$D^U,s`I2Bys(]zjN~yg7e PUqmeqHL^k3G?u ۅD2Oy8}Ý֯ Gl%M .wu]+\6W+u @Bh8WQj+G]Sͳ@fw*W*QP{ͣ|C:_C$4NѠG|е?A%R24so+58 TXx4HjvuDޙ㡇U0MN*BQ92b?8Yu]̩|%Bx X2g8@@& ӿ9{[>$jHs)!jHcH@YK&El c\`Q0U[e[sW5^TVT]Sﱍ/Ic)cی?~րS;򙻅`ȇF1`/l+F_.auz9݃\aM,ϊ&=|7US..FxT;m'|V83ڎǝY{EuJA-owx]yFWGcݓP9. @lHR7'2w\gGl&zn-Nz۷ݞE -FGU(Q|O[>G󒳬~/ԝraΐpП%=kR>~}RSZ/7 @-k`~+W\L, "H9suڋpů-meU~Hu+|wn \f/T4kBp܎W@B0t*U:qEyTOp`[ÝnP/ΏPMQϬk7iԤpǭNUjPK;j3w*(q@Q(W-'z?hjSt=j|8+4t燏 鐩'H}hH<~QAGUD{8 K&$|wp|S8Jhx1#D)>r~v QH778O~O{PTtONi8") @R`Nfbo yu꫎6#34Ǘ}DcE>|:ͦ8wfsV;1z5ؽTROFhfËg5yHN4]5<ԫ]FAVX"z9(mA~kY57ԟO96v9M0*rwuE7/ʄ 잏C1[q,_k~pیsC~*[!xix0wd۫:/oU2.O%t ſU{[#V-s9VydMInlI)WjKKo|[enM-O0cs]q,caOOyJ^K\}~mW~poΞ_.vkK(<3+uXa,?B˕ Ƴ#hbV!`rhT_~N?%jZnnݽ'׹ِخ׃l@GA:?1>c~D{J|e3؄^95%rHMћbx7eU;Zꭡ+S E4"L> PʳwS\7uGmlM>$}7Usؑ.yJ4C_$[+.7 tGu8Nеbno: ܜtxrxa|6׻㕵y( Nh@oIsw=w3ymĶN@Kڄ䊾K5]2UB~J:Ɇ7Fi9OJPgAYaS eERGS*[ o:Ϩ![šC8*P8= u;o;>/"h^P߭RoA&0(#Gb>OuW!D9v(BM*3H: pZ3*7 T*LT7~BU7!}HN.${DI9 Fdajbͧ_ SOgHz[&bbg8W-Bҳr6ϲ.v]0n\A8mƓ_wځ"ϡ<21: ?֧ൈ_5׿Eݫ0wn>U0afUv*Jxiȹ:Kλ2J{ ˒H٤3i.kg~Z{*2ũK9F;Q< x%,[yRdG҃?DfLjmXzVj52|[JޠTdMUcHCա J}b^7q7\'U=[vέq\keNK?ergw0dN}TSHI|G5T];ۍZwOuxYki}qhi/=5ZBD}wcXUOϻ^P4%ㄥocm% ˿.ʼn35]ُW((G &ؖuϚ#QdG!? UmX` L`c*?zZ}F_k[\|fФdY2-;(-(w !Q\*킱Xsauݏ,4⧩8j@lOggS R u<>,+mžaR>S}A?Cݨ![ޟʰ?"Iڍu{^6`/nW9Yפ1qgV{䧝E8p_1Eh ZF]1 pLX`K3o~S@ݤ_6pFTS+;qP` ua[rU hi͂-q/r$,ףDHY*6t]hh`zy4p74M&} #xuOvSw sqt~5ptu7J+!& w*N8d'$BY'fPGֽb΀T.wPܳUgB¡DcdP$O `( P./$nLgNچw@*n=Qӵ)&(P鋠6,x&ĠiAҏ"5Sq#+ᖾ?h$𺈛/ W'ᎎ2ftG3_99bE<|6vhKwC5nR Kxf>_b(ں\C~ʎ|BLW"mh"NCj5c4lM 4'X9pD4ZP~;! M}f@R׺ɛtHisJ2|hd;Gi0+{RvtJ.X/%ȗ L mNV˓Lcf  xRśk9o?g{fZ̊Q\tW`)0}niyo~hX_ I{wI}mwiV-nzQ\YXv]Q|lmbH.NuÞ30& pr!&8yi&:ΧJ7șbP=_X+uK7;d6)NdsNzy | Әh M5/H < eߗħ8#@ΒW~ \v\ƕ/g7~܌qT:!AG$pC㺬wݝiג4HoMnW׌vSgϦYŝE8񌧾20k7&w(,? &P&Q6FU8Q\0L2}ԂK* ڹyiI6ʠW䨉:>ɄfdfgݧT٣?}D^nl2<-μ-Kb/st#i',l>"Ei5pHVW"WjqcM3k2#wƫue"X k3S8^d^TOcXn5@. s'ߋi_˰ea/7';VW ]ҩ [i$/oL#?, p{5=`7gx퓭uA7v_=~>{!_YH?;G\:3(,t%:A˿zH,cl:㣴2[lbf/ϻkZӪUm,JI\n^ ӹy#{'c(: Qh4*UBa2_bz\۳B]y#^ᄁz<7 DYO7G#~)@jZ|S+7}p*TaH{AŁmVI Xm<5֗:_ppG uoRAW%^hVWj7<}A.+PA@пry 8J%km8TMHHZS4]PMIdn#y?^ M:0(H6oC>1gu|UK~Q] 9&],||Id,NSuD! S$g+7cy>$ނDFzg1X | l̄f ;W#ЂLeTFkhܪ:Urj%j7g{5V{Cqo ZmDX_u̻2%LD33/]]wqt08gݞވvGNg!wiPSK`Go 3n^ϝ9`yOfWDmgy\mܟ~s /)b,NV`QvM_gf =LqvI_: 41>ׅ.qwrarr)ў77+\G,Tٯ̾$¡_i폌 pa_~R3|K>,k󤽽X ~7Q=uVqYo}$p#>0Mmo܄ 5yutIY.k@B) oTn7s-vCFAwb+**$$80Jœ|@3%k0n/TJF^'@ED^5GkY^EȕhE}Th[%ghc)OO/6tȸ" TDC'/QYK}N}pvKwNҁ*oc[x8!8:SU^hֹݩ"8ڐ߯Kh&AQ!U=di|Hj'"|ԵxA*n8]m_Fw$=gNJ߷T3#hʱS XbJ;NzGi_AuWq&MC@uC;U+JY̴>ehnޤ?XF9bh_zIŠki#xb 4"z0,Ub{oU=tG{ߵtE5R~~s} Qc]@gI,ܯC+?Oh[$ T#|c;*3X/$W +P/`_Hbޚ^T.b:لuZg6A5Z6/xD URrc;Ϙ/Q>D>}:rU a-svcޏx:h rvsLbˮcTA,صNeךPۑp$ k *lwuK *ݻKVf׆׺\^ʓD߭.fSԪ.=ír.s:Pm\_ղ>O㺠З}]])xX)6>>[T(…g-Ύ><^$ HOggS R $?}1:ʹ;,Z%߅"I ᪟~㶨ꦭ87Yk5Nm_)ܬ&H 86^D\1u@SN4lK8 p[~h$pҰ;i<$ܖ@\b HR'Qh.@Qyۿ+t=H1m\Vk~WSDZVtur.x٥j_ESX.T'BKmz-xrsծӿKa5+,֤nVIƨ[U'ƭ g[x?m*Ҋ3UX?dnw1pؼW::t>ը'://f=qk{9ZK5wb:},|-޾WC~Ez\jiUZ0$2&!N.Y͊um+}k4"Y> 7u:۴q563n SԉV"ŧEVFӋ,Cóܻale̹nJ7$fSKy>Yژ+4v#ҊQՋO|fx1r& L0܌}LgingdZO 1zh?P(^SO28g,\D|fޚw'̙EV6Pm||l尚r`C+Gm&>2u*NxxX>;2i&r;q;Y,~Znq>+q/1fU o7*n}Ppv6aS:A <9Q >~y 7x%nG/ۯgϙ-949wgC4kR 75Zo54jFEd‹ӵHxX1 sZ1sWő:YVV \7_LKkո@88N_kU @u z~0#E/h{ҕ^Ell͞7y%}mfQ=CbW,*:_L?|wL UƇ ɡv:ǻκ>X\*gӐj`%ͯ Ү1)pOwssY).+ȕ ڍ&SP[w^>yOB唺) ^ZWlcNL+\o3]ʧiڮ瞔JTcWqGdsFi U;7.R6+hLu?)&O^Ԁ`hV<%^Ѡb1snfTMf=Ŝy7npҦvYw?&wfy5:Dt4 r;k6*#$y?Qo UUjy_9ps^ x/t'qVoy>.. VOkWhAg;2WOiߺ9XMkMsy}VA[k?>cMakuhvO:/庱fWPc.D鏢5xZF_P$EZVmvN?'cY6{o`TjZN t8kMyH=LdMv%k*D#J8WؐqLrk'qi@,62 pESTҗNj/"|??swwr~f)r{>K7C]tzU[sljk g$a<3 ٷ]j4{O*ߡU9nDf"Tr~.Yͪ{8}Ox1uGdS'Q57N49R B q6ׇ pޟ 7l|y>_{_dc񺹟wv~>Dx'9Tn9Q[yZm˽U@ O 8T"7jp2rM }LԃƷ;e~"-J}w''q/ [Rcg|('<18 4ٰNW#3]7P~;ӯw_f}*7¿=g]}z඼j̩9:z+dHR< :J4LRq;z宵DKx|+FJ~ICv4Lpr J+㦧f|wfz?%&MY Lg20 OggS@ R y/J@VYwL?`txV|~({ ihT%gd8yi#$; ӹW @LCdd\KG"H[p \GNou{{{!c_O>?cwc9а&7MtpO%^sp4-7($ң5XbI'?v'&ʕE3Y>k Xל5 4<4\}9uBp~Ӥ[O] D>=) Bѫp靣wiA3f.Q)U˸" %=j nq;؄.7u:֌zgK6fCm_VPǃ ev(`.9MHu{f!DCmgm@Q~k߭9I.7=#X&{ !v4ҡ*GKr:;YqK1.F?KSpkVa̜Hͷi|}\(=6f/Xͷ%a̛h7˦n=9arG,ў~%[>Jſ`GBFf{=X > 4{$!bTHW%!DBp?;SmSMUO3+}_@|z?o;>:u?ۤ%+WQl-7M{OKxMs_||m(꿾1StyCksNj:k}mP*4 7[!(J;@4@D/U"^`AJr)#||LKCDͅ&{O &@SucQJO{rfCА\Χ% pk,TKft*V {rZ?0];e;:aKG~5t" ۿ^WzM0}w"7`!F*Ɛ܏]Y݂CKL_4Y{''7-w .ڂo:4ɰoN-L= nߕJ!"O08%|#b) JSR6,J &r`W$VY%8s;u3+0V%͵fu"z~SǮ&WV{JeUw )g뽆U-Mbt$x)"Y,wF3Yߺ2^p@%v -yFa/0uCyk̊˷vdwS'f4C#Ŷ{$Q5X[Du5xw}ڐV1e&DQ武dY%"ymǽ_l(ɮd0𔱭 f;zɍɤڶg܉c<לE2^La hIr-̇?mv PlE=еh?3s|fJm;+䗡Pn;5}z m/c@/3Q!o_ @3om@ݕyj {WR@3)J/rr"d@{6diC 4j(#$_m3R%HiRJw4Ӊrμ|=^Sn[L=%~~;y1eNã|Tmj_l\Ժz9=}Nm_ڏH2z?BغF|wb~??RfpA"OKYmYfiPh&[Q5 ZQaڏ WU=u lqY e>n0A]]C ʒ =uE BrB83l`4.[[K"U噀)@$g(P6M @nRn 1*Bև;-sDm*,'7k,`\}j\<Hk{r&ٌkyq6 8Պ0 [IX?P Ol8nyF[?Cv#-ifѱRG_6IlGL盋}RewnI[%o_j7 [χqw_B+]72˜ΩsT$'$)$&q vq.+YWH@. sC5kHB,m 9R:rWڗQ1k3reҸ1I$j#Q8- UE%oȽ=wi-ls݄mU0;kֱ(=<Ѧ^:Z+Pl/L~,0,;>+L :^T2`C5f@$֪\lO5&QZ^j՟ T5t=9x잁 Y Ch4bEŔaJA{>x]!U?e3|wp|Cal{.-ͅ fhH+ {.wwŧϑ]/$&5Jk4,*5h?1J-3[ڝo~_c~-O-E/ yQb3oc~'v:ld[O}gw"ʘˊz|҆ PI*$ HZfyKc<~j.o̯ECCƀc:du 3!@[$D` @` 8N\bnAʑBh?2V߄7aFFP/^.g7uL_T|K9Y[~1ʉ̀[PVʏFD[_Z%˘*Ӳ9.˽Y'eT$U9wz͑c~"ʂnXV~~6q{ry8kkՔߜ_FuU KUSα~/_k\d| ]l]/e3sڅ *oNtۗ<.9oW:/ڿ.@z, 2S>s%}IAKiIZ} :e|k{@50 >U5-_뢿Ak[;|Sr֘Tݤ~Y (@Z!x;7p"@7< p:; &xOo `D hTņ9`T2F^xί>}D="qfro2viKV8A!F}?iDsykpS,h_`\߃ʹMxh9ѷ_&>m c/yi{~d׸o4g>2^`phBQ- :GHr  X #Z詽VU&eawvS‰{>d4 ۧe6Hdyq )Q!  Fk^bA s`4\aQ(`)-A4&=R^/WFEy04Vћ_~;B*n{2EvYjBf+qǙ{5Bs[,Td%W+s3|9O)orarPFxh?Juxq:vc/]FMonwJ~uQU0֊(rP .c(EҨ)KaH(uׇNK֞6e{UEVDYDY6k$ֹu˕fRkF)@)^2^`0CIf k]fO0+x67@ZOggS@ R %[^K?"i߳4GBWoBqEϾGT~ iJ^=)7 ~n8pki׬{kp#{}664vÔ^`HoA<kz q>Æ kWtunF.8800 4N@1$ 0V0'P_Lh]`h6TAS޾*_a1g-O:yI`VgOSߖrs[-' &"RQl~&|nW"TwJ_˛xl K=Xv4J9RZPs eƈF$ǓmG2pY;h>e֍1\}lU {&#AF*0DOM T|2h\ a 4iI|ihJi;#4 waojwy@NkMMj_MZ1tM=qx_%/;JRe,_Mł~u֢߇>MS?Tm=.pN}^bez&:u uuJ,hAiۛ}>-IL|\瑪`sʠ]-&'ȡ/š6yMވT#IbG';VJ"Uje7`lxc߬m.[l;cg{! ru1u62|k7嵪a8^~ثo~:IT1N޶|sY#A$WO`W%n/no>_wvTR W8 0 P,`]N³6]A۾*Pp a}=0m IP<(<дN_S9 s1?NkBh/yR@W'Rpq/p(`%5gGiR<ґP5>PI㨹ܑǮ*2`VL09xƻܹ?23Fl袙@)&H愦GR}B r%L#-lOcw|)A6nPq7I^e-ҘFٷ&t{{2/9f~\[36>cgfDW87iXp>Y@;:6PK>׾uku{xn:2uMP*2օ0{_.כma6zdũKz,mj4>:든 0q7G$l0 - =|OP\qE:W+(>FMt_!ƆGXȅ4,arD,WKAdNSWOWi>P}מi-Ǻ.)Hez&~$VI8Ʀ{s;MsD2tLqn`/4t(rX{Y:2DߍlDk ΉuN_aܯNnWAՖnOFT 1A2dhǓo|݆%GWC)KyLE947G_r_؞2vwWLj4 ȹɾM D8-3z맹w^<;&b@3ntȿoDjDz'd>fK54Z/\#@y~hݲصLw*uK~L,G[e[}cCl$\ o5gm r]c]O`OݗhI hF\{7 P@+C7z1"!JE?;ſ+Y5+}4XkaIK|[L;@n<:= t 81UghyI4I\-rҜbzum<"%O+KsL45>;)LGwNy([ 1tÄN4c pRY.6dmL* 2g]$! bu7}]fyr=@{ |x}1 = ]i7f8EOKkR'IsO.R"?5=O菡>~:ʿNhс\grHvcNǠ?diHVv^s괘18.&Aģb(dhW7|0ߨ*R9cE 5r,$ӸfrOwlj3E/4="d``@ e+l>i Md_giJ؟ m `(@-ޮӲ\UsbRPggg3 wm/ܦMpWoMР^YVkl5i]tYں\$!_P/ Q7tG*_/:̹y~oey7ќri__.vwj/nSftVO2|y*ĉU5F꟯# yZx '׬Z߂S_dûNKXGѷ|k/'bY2S֩]-Rq}cqL]XJy:G|R*~k|Fzw".߶j~'h-뽍8Pt}z2~IَpkV #YF5n?F;kOި'#*8YщԹ'x  %"`a&~J<])P'c BM(nѪďn~\ `xyß33Thzϭe5pSfoZ_;~!Q??P !RAP09r\W# 15&;u{;d~ȉ]"B3s@m @XGb4W T_jIA j=o<{.ܦurI-UmRpCn)<2iHN,Nq/[\j4m:qpf'bɗt3mskMRs{ T@!1V%47ZlY=4}b@I9B$3AVH$ ȐD:DiJC~d(Bath>\}-|%0 LزhѾv혘') =^>^?JмJ<N:3(D]AYOgF^ '(כo zNFnIh.cZg/Nvcoieꥒ/ls}Wy-s@+^ yA;\Bh&V盇+T2c6j;( zIqar-)F)p.ՀxjP iK1>%5a~B uԉ#灛=@p1?Ps+$f!Y*(E%q&hT#XЅɵiO"I+9j[gn֟KK'AɓZ=O)z /S _qI OQरi@q'CZ. YtW>r]{p\"9s5u֚`xxbgM*c/,1?5s՞n]X  OggS@ R ed(8kM^kkz@;?$pQ.X-f T8?:|L?zڠ7l|}ܨ{ <#U>չ!l1or:p^}kbu_MAHJ) <G?MQGug<+n`5M/ ~m`gaW`0es \ T HN߾DUP:1Ug0S|פO՘9 :sL~$`.k9hD Ov` d3eV%ǵ<[z̞?SF̀>DzvyW/[sv6} 7) |Yݬ 5s[+v'deި.'rz55g&3!AXSҴ `LTׄڒ4|[28&}9J#I;VسR %fE;ZK1 0\^pV$ylKL _{c+iU>93edmWO(-7SNZ>7Rv~tVSWt}|gX(^sƝ-[mu{cDd@"xݛ(_zAuvFZB<)BݻWΤ5eI4m oX6|nS1_..9m\y]H$(Sa JDe(/|Rkc޸\3%#8D;k~ZWڄ,V-ssp" ,0`r x)m0}tTtRll$l2{?DeIN,p.HZPa:GAi2]1uK_Fiٖ6'JHnMuR ]pj<#1R%_7JSA. O + TW|@7@   0H@{Q! 3WK1( &)Oq"#wO(2:/+twU'&@ ԩ ^[}ޣBn GR`Kj,[Qϫ-5}Ӈ 8>[Nr&/7)1"2 @puӎ]צ]>7tn=|I|8zhuk&YAZvY}Āg>FھvW}LA F\.<>ٚT<73"Xo:ճQYy9H0?R/r81ʃ_27NT#7G0Fwd[X{02vLԷj_8pA@I ' rj[s2yqmb%xw9,)BgIq:d3:6Ag/ 7240KM̈hF|Uj@4kwt0MXt3X.89WɻS:+`f2߲}}7:nb闳G܂gRA5nl.˸ +$9ѤGZ§SUY]hmLA/r|\5xyPZ~sl̳+&Nշ]>'A!^w-ep_YؔHak"KjsSl%UMp]#M4Wpqscǟ[}WտېF>i?iᐹT:RkI|sx8q1~1h̗G/r(y$ț_ž : /@*t{/wc٪TL,1XDR??+!EW 8w}3rT%. P+}CttTHIWoTdQfa;Td_/?VhpU* cq %.Kn6݅lcSV_?i?g~iZ}WP Cl+]RGG ka|DS &kZ6ܩ}Q[|,fz?&uJq<8A-fDBwuNXq*YVf򝦋 @* 4TΉZ)>l6XRg W}r;LiqԈjOQ\ sڿ*_Q9Y(%67GE{ıG./KPm^wyc۹YÙa; f~l ,cǧU)^?\`@ebr2_)w_.7kXpW(@/Z}7)²G@J]> ԮO8lq63@܇ԫ2='}]W>H$76 ᔽq㽷TU qYI7Yd̦U) c6`[wϐXa'g?Bot'p]k,ίId[݄M;-=T_fdA:Ǿ@WTB/@MWnB VI_ǭUpo/ ]-a}6p<+q@Vh_E#\T_8 53_h֐ Z3֩gWG#;[ 5@g?xJ{5ddJR..a|<0U8r_Zʎ*,uлd7%{Wv0~П'#顓sPvg~՝=đ_NUw7{ğ.HGFRL¿V/j;[| =FUվ:?HĐ7d=~уK^yiW 3KD. -4g?1,MDC,ļ84qa{4jz?=C C{~_5Tn]wކQ_jyX29ƛWثktUy!LA J,ڥ;[XS?ݮ3ݯW{QO^zˆ'S?+klŨxp=^p]v;"4LL_cM kQ/{,Xϐ\7Jlq`R\.E`)6'bR6$=ky7ԭCr\0'.qJ5{*ޫ#,b_ )pJ_>o(UޓY\fJ#ƋtS7hɪRm%1/+io ؚR٪RYDi'+/Nv0>ym<"KQ%3CO=eqɞFs'Wn3ܞAKh/ڥoo'fgվq*Wá1΅2E[T67b~Uݻܧ ϭ'sL:yȧk;Ȼ>NkZ/Խ ^m羁憎Ue}dMUy,dOggS@ R&Xqzc>kGXLo$pFHbc?q07c~?aTBě`?ʲvOiYh_kkgE@TkjCr +#&_<0ԁ(( 5{}v*W5.kX Zuxf8`@{>g%͗ QJ$O(@]${4 ,@֑qE|I!Ip$oFNG=[Az`QpIEh2[({K>U SV%2QSe_>*&TvՊCtӟdHAL} "=V10uj}dHТ١StWa"=/EK4ڑLEd4;5`wIB 8NmҢχfj-mxܻw45 lHA*Si쪂U`:7^|}]:.CƥS%@ TSv1ӱI~ ̸w >||Å)2Unﵤ3iT\s,Iw&FIS;[L2 S)i&B8-~K 6 =sՆDٻv~b/N^ :r4ͷT6Y)vW6I)p.}mVK58Q^^:vpPs1g:}cվΕN\{FZVߺ[fS%6KJcoU8W*ɵ|7*rC՞왭'__!N}คZg0-q3~Y?}ڀԵjXU|Nr{6R[m7N'd[v׳Hj CNp37yG:i_F"t8Ly>N}A,xh#P½EUC*]! *qcIaWჷ811%R$NgqFK1Ɋ Wߦ/t?oֿ]542,l<:r/8Ф}!v7}7%&VݩsP$߸BT{ދZ,qNmҌX5ï[.Y_Y91ySޙB\@ssMYLʿ16BﬡhM5m@upb#cR`rC]w^t(rwt| x -gҼ(F{Kb9T],jxT61R|_p$*?`/6Y%j76pRi.0xy7`5nVuj\=2bHji }uZRi[q?O*8@n+ʫo= *3H C斁XPz4ߴZBZzy@/3PwCn:WW g9 _DE;+S.~7qH&r@> EN@@{ddT \!8AUV2%W_~  NC&o>m> I[)8쟑̄,ه8֚AkI 2z ~!sL"4 >̍?gs,=K0blhB5Ё-l9'{\ׂ/|AH?FC){nM['&V잯w ӡq@[uveT |9h\ ֕/Иy8xrq6`7KCȂg{euo Ady[!V.$:yd˷!ٻq}Qm>`EEw:{sԲBUY~*;֘A/l53XBwi_Eh[SYV-nOIzv|-&ZZ]_g <6U~M~o?U-K_?,%hV7F*-fϗ|0.HƚfҐcdʢGGXX+.Q9؉2gMK +ooz~V14\vXyϯ^{]jgNeud4W\Qq'N|f"T%.\.[C2޳x?{9H& 39MO; g1m{q!<^l),-Ҟz \9ŔS6}CS~0U'Mt^[q\Tm8?gfm% ĉ j"_64]U/-z3Mڀ?H{RI [w:(6wd_X#Z[irS;2:qgBGO'-bi@<% q *\QZG8@7Pa}X5ˡ迵iSlf h-| tS@L?6jCßd8[y$ eiˣ&@AJD$GtѶݍ`vGuNC_j5\R h~uNn2*}Qֲ/O<G&T ~vǠK&[InbLd*Ms)3#x ,s%/ε42cQO|]O9k?.Ϫ"%09pȥ*(U܉h~o/.e/OggS@ RhYnv^^[wpu8~4=. Rk/Z~*p|xxp+G5579e A{`7[Ē؂un~D%$'q  u&ԩxMg;s8@~@qe`C#lf A [=D<4( n %귀bSF鯇ck{)=qLM"K#CJE?Gz6}; ZDG3]ljgIJ^{ Z4tc?F<~3`}?z&{{5sT!)*.~{Qs76 ɐ 0ziޯc..=~!Y̔\ɻ0hXHZΞlT _zU,[~-?\6@$4"_i[}$3IDCמq>gm'zZWD~Ϸ*NĈ3[ҎtKi< }VŚ\xLGJ!oF(5p$%-ĵXzhpriEmmhQ$yF?K1: Y:8FuTXs\N ԮҲ6(b`~H{9L,kjvSi17g o(/ \Ɍˡ0JHV*b>jbcņ{._)vAN\],fwy*uG7vP};\U2_XJ.~Gyt\2Iysi_oawYmrB9_nP&B>6<{rTrD &G;J'mVm)G"$*̈pK]lM+ѵUbE;0}$}=O6-acHivBkKoɘ}{q6wmӱYEzz;qNj|/ p6! T],BpE^Xh,X֓/e,j=M@^LfgX%qŁο* i̗c:j'yQ &/53fD˞Tӿ_^0^:Sf /^or  Olc/nFh^Py$8t (c-%P=7N-@͚Ǧb2Eԧ*< .@I%rDpsA$TxLmWF  zՏzv쮈Tf4:ڵ(;; F㵹"MWz%sW{hp&a!?ּ8j65ػJgJ* 19NJ%$ЂqTfH?FD]~iz^<+!P ,CPܬ ĭ6ȏ  M,9ZƮz p$r}tִ⣤ ,dZ'2J$9;0YZ$L5>Wg FO+rv6yܺA bTu}ƣ'=tşژfڿi-Ex(ݭ/Qw6xM29K SSrh| pZ5t?* N5w n](_b>P m S/{$(VlQAo8_ V+<мmǩ\@QUσ77՗@|9`BΉ ۺ?}/ _j$-}\,֒!_Tz[LN#(MxGo1R!j{w oPnOoL8c)rLYH2#w,*[ǿ$2oT7ΤavR 1iztu B}AMEeZNߙx*'9/ޠA* WHMatFTBO[1}$k.uUn)&gGpb‹ͩ"¯[ֻT7 WS}&ueO9#!lɽ&s\/^{+wa"cPϭ+uuum-7(hF2ji[5>}ޏdk_%[fםɗLprM8WcDQ&y}-q+SvDmi,g]VĊQfmypVqR-{޴16ɻXNpK4b͛8| ׬>zAyj1Y#1$*,yl\҆+~X !Y5.l׿*3YS\+a/*">/~|'+˟- E_nj[f-h;:Py' B@tBup nXpz}.tXǀm̀XS ,>qJs/|驆 tU|^|9mmA'(A4}sp/ P}@P`.3% &4T(gx$瀔,6CeJQP0/H8]9}WZ,ٱ'{(.J!Q0 { j:~CCʺwM S0aƕӘ(s$n&CD\Y2M'K=ʷ`&C-oY_nRkcFz̚$%؏ͻś1k3c|@LJz=5Z#zɿEjh6h6=W2⇒p/S pf_C!m3q_1^R븉ksS0eKɐsɩHYks2ZG?h:iNg}\&۲s9IyK+X4c؟>X76s.ߚ[m>s=I˧oecZ$!kmmMcOFUB ku- vˎ,OggS@ Rlmpp^[W8(o>3b+ux7M Dj}MT_t M٭[Q9VjtˉA1X =_b5\ <@~>c?z6f|4PA L!YJTvАp6 @W"lTn쵏'{8'>osK2أr:795`K|"BWV!]Qp2饔~tM5.S˳Ɖ=&Ay˙w bt>f Z} d<:{ӓjA[-JIc^LOisƯ82Hʿ=29Zy!-i;{MЙ"& uErJxYA<8؎Fem;s?$m17D`/)>w;`wh֫eTe i]iC0 F bw=OF6]/jw~JP8ӣPT9Z*B'ϡp{tJԼW*43BO.DN{Q8ԚJFJW؋4xTҵ~c<۝}tYbt~'eTD>hۑ PCR8K(’iL]8]ﬦ\0fs[ Z.]Lw2:&ħ.+Ax`V*#DƔua/Shߍ,ToY0 l)F#4$0F"nqLz |N=Lh_ cϔw\E![4j'h2m1eўgX @3gcchp&"3AI[z(뮲ʰ 7ݽTW67>3x,uV}Soȶl M-ϹZa4򼟳i5x; p4\A v Lwm4zT]9qoY#3Y\+xbG޻ z=ٺmxi{CVmq{墹7@V w{)pQylCq>c}erX!jj{]QoDxKm'F y>Ɓ.)zoo4gY9 HtsuעK,Up"%O 5BZpTr"exхRkg7IMR8΢j7F&|S` (V Yү{|6DkUV5,u?n=Rr(/V *;ѮkelU_%\I՛"8jサ@wTYe|Xҹ"3=BCnJ},Kj%iY{aTpDZGF&]*c+?8]+~E99Kref֜1Pv;.Wo&XUǘ6>kRl2;{ nk;H>+ϕnoۊUI`D3־0c\1%+OƜ9M_dmQ.wq"ƤnX;5.J Qbl0\6vuCɣvXł #4@Z:\ʈcEO5R{?|.[`iElw3a'pS>?@lISӉ%0<Z] zlpŚ׷_uzo;(?g@) |Fb-Sܓ 4@_omA츙~Tg"J@qx4??T|KtT{lsi(P+E/n]t7d!?%gAY;ωz2֣[{qxRa5ChvEt5qPp "'3Y;>Z% p=Rs u^ : z8c! 8??w#9qU!%+s̞Fxt?5 S;^nJ]H܉WϬ*p3tr$Yc];%I8/ęө=L.*,4^QDZ_ =}/CR񂕳Zak\aXC3,yVk_=i\&<*Clx JٶS_"G+i1=!f. KqQEn'֡rYVǷ^ {Rx2# =Zb-]ek01zT.t=nSX?k>N.3_4w#X[%֊fqqc5;ZK?uZwW ܢ5# ].pb[OggS@ R:zއ^ "EJY?)@I|M4˱Ka诀F6j\/k #Y@Yz3 D[n&'{ ;he9yc0L sMA*yx W>V* Q{lrqkf*sm:=aqjZq)'Њ&{5M^ ˁ$H_;ޓ2q|8V{?A7|H$FBUm=5Oz0*#6IӇIo rTн$6r\5\xH )ž!U6 \aͰ=K.>yflBWd? #f7ϤeTTRueUᶬ>㭟~.&+V#(û:z. k"unbxuH/sR[t6UՖ#OoH>6޳1–1ˏ5l{n#:le}pRVx!S-E^˝&F_d6޼d(,sjYvW|E텏kGQ ס-}INͼ>M}m;˫ -f;wFK\zN܊+³;9"k;T~WΧޜ*?k) *vwMۋ~xiGa7P&C"9b='.Oi6Kz}w`WײWg#~4vgkZU"pu?&|v _3ƆYk48|B9( L^vⰵ) Tx-q숰#Zkh"ØH|;IKbq_V[X8DJbhey9£L$-]((֝gF\dcmc卷Y;07V{S<)-hi)BԙmƱ&rst'7uZֲ~񭾏dd;&%B?FV.x8[b(O'묾M:{%?>˓S=3BVrLzdbO[=$J$;+{xas7אּÎ E{h!řk=˕^_n^ hlT93I+#s< j9dy`ri;rNU.3C#Xaxz: ג u9wφ3՚Tԧ_F:,* 1#y28r,i}w"Ŀ$kQRZcsC^c:MpզxV9* ˞p`BGh|@h֠R#@qC;T*؉Fh TJm bv{ a2r戸ٮ)Xi]󈪠Q@Aa>ovn?J"Wmŀ!7lx "l'fɢX[#FlFq)>'-a)óxohTwai=lہGDa7i! bp9;2R V`_386f~|vvh*rguiP;g0i^eøVd^sA3N974]MFjuqW ;}gW~ɍQi}#_']$:i?+$zBZXOMnw=ykלﶼYi>#k.И}+&o#CNj0&>D;<*3* WtGF=*Ttv׮Z]7Ȼ{]nS,ӹFJ[k1֕*ϯʪ1܀/R/)ʥo<)x$ŋ_~nv1\=_9&` izlc;v>s<Ǥs&ހIaBڰvKɗ r~"z[2頂[.s])  o` $}~p ?>@?vP~?X7Խ7raA ?0Ԧxޣݪ!C\įބ6Sw`ߡB P hMIZ 4;޴֧:h.TZKBz=K2'hVjHSPРj dJuDDA*rEB>){HfIqdSB%ZjLkNք^Y)xO)8ѪT!tU+RkTPT>E;vDEjO5r&C7#mwn,]J\=/o4U:7|n6ϭYujq3nw?+x9<}-w?P d~PsvNf}Hn=;V 1ʤ0 x漇zIcLF֙igE؇'97LV[>R ^{ u Dbʉ8_S'#'K jXODWU7pVa,oW[ɟo%%7zᑥL\넍%͊ cMNkh.49rE~I'H,x%ufĎl|Φ۔ EQ05F=hɌQWl]8Jw 9aۘU;aU֞Ư]!&8t)f4MRfW-.4jvNx=`ڄok&t\ߕ-Ug;ڜ$= 3)  }"mEyk^l O)OvYcnOA[vB/̓{F61LviXʨ /V*AE,w6>}Z_f, 'sc+#Ճw@4qIOggS@ RgvÃqxaއ[Fh eWџx[`,{KttzױoyRŹY:-/RdVԆfC"oxa{δ,]c@| ~^EG}>)zm ~ii _ E Z>.**3{I{J߿lj1+q&84Thgb0f0t]=S88@Ejstm#t|ST$t>Wgت&t9J}P38N'͂JU1J|lStkUJNE:KrטM_Yi*9pQ7G7pi >{Dm*/ *dQ#P4b;[pIS0#5v 0ʱљY+JW\MOS@ )𷔄dwk|0ll˃3bmb4"4Q٦/?Hv’*L,`j?v e)#ђ}']ii;Xc.mݝ@|elBY1r7.M#xsj|`2AOF[Lҁ'cwo#mY\Zt? ]m$|9jlWDB~Ôܾtm0q[7hĒ-fg}Wn[qɀY{71vI6a}ȌNBdhI>ڗE#v [* ߡ!+y]^+K߀hS>1 Ou;lo : #&mWjuu6Og-(ˏ{wڪNtgKkX#XHRlU淽lE?[w֞A|4lf7eNƭ9"06泻?ϏLMpvkY/g?/?ſH{Npdhh~-$5{1}o"'^J7 R[BCqϕpls]|[5n*ўDR͵Ɋ33 ?w|e.txK%s7Li{y\ց?߂qE{@cl., AELaއۃRp|h=a$(<_0I׏ o\_nfxX=aRƹ?}v+Ws` }+t,c3{ǻ6a1t }~⯥P'8VaL#Y qߣ`A>Ǒ7P$@,+5@Ѫ\TƳs r"uDEX>r#4qhTk9 P{ӌyttwG9ͼ~ŐQKJG-*g~RG*YCThQe!ACYOԤ N!(JJT =-hOR7+]@D#KsGfLRn\u?c"5Nǣ@x>U2yiı\_#8CaEC*dߘM75RPԯYwyNVG.qKD-!5\b :fpSũ{?/R~|$UW/ܣ>S=I57E>C?o5gp'R? ?sVE=9L_}4ొSUWp%I1(cdGҠ ɌK L[k 8ޯ veO[Ǜ*-)s=_{^g:i)-0sMvҰPWtd=eLZ|w_f{9;Ԫ ){s垸?!׊|HUx룽eװR׀xollz# v ‹?OT:GZ#=BI 1or.SFޡazśp zg{˟i2?lvF5uTX3u{ st;~-՞,?|2.INWi.Dt|L2FU`zg>۞8+qqOeLM^kh~}8 81Qb4$T jiO0W-_@gSL/+wqE7vX\T~3Lx hĜ?iZ crޞ:}Dv<^uouGM~:S<%G2sml*ql-A=m!o7"`RC}3Kԟ pB,Ѭ{?18|tt`ZI~'Oi-@X%* 5r!95^LvlH#@Ϛ ;)̾%ԡ|#~t:T!a944SKT:ZGMez 79½?2C-䠐L8Tu'r5g_o+K,#cdJʟtg_bC]'D988 ^1MΡ`w~OFjzHzeKn?VZzvUd(%O} C&u5RoD@6~%(Fzu8h~ܠLB1>r^2 <1|[`Xqdv5Ͽ/u'\`jǮHc3BPot;և{:źe{+5-bkꗭ 7I@u[KE=@ز-|3Zu51Ri YݵK ^#BZ#Gc֌A~i[ TVn.¨LHVz>*3NkFTY]ϓQkR5$}jzfMkbi}yd#ֈ"(l\ICʿw_[!#//OXU]`|(P49Q^?CI~@kÉ'(*[gxLs;Nlvp^_6&"c0 8pXo?;%=&[J /x #,bJ6q;+GC ~HHl"H)_OcKc$A欵ռIk,^:F:^r`o{f{MP,tMODE7:T;/ֹA弬2< v.o}N/y 'GSd W$|WLT)䒼&ȵNުz~˿pvq ~X枲7) U/;Woj<VyjIZY qh! 8x/.*&__f~Zo$&z-O>sQ;)2vo竕[ᗥ߻K߼wݝ|[%nM( ۽ğϕ{Oy`U*b)95exf"V.YWlEPbfr4$sעhB .uQdjo-7X>ʰPl>+>OggS@' R2c`gv_~ÿAhqhExH \7~ވ( Ly-ߠp3?SOϰY CökEP!x {7}nKJU_WC[<ެPdlh_ch ?Աjkp) ث*'|EB| R@OΨiM%juFyK`)WƲd"q\T.St 0qTdH`}hNgMm9}_yMNù&, f1{FuE 5ϥzNDt8@FEρTH'#BޏEqkj9W)d?KCߓ*f2eI}T(@&*$r"ZꈝhLNI@ޡ));H4> x~H߾|=-4^Tu\(>7q4Pi\ ]scQ.y몧o*(L!֟]r {yT=Ou׻,v6zwE˦U]-).b n#i`ϺA&J2*8p⪒$BjNh3pC5dzCD#M#vK- ee?EÜt 3ms2 5Tlt;~R/=LuhwwLuyӛ?sJ kZ d *%'zV{$o5B-W.x?iVtjd#q_?{}=#HOlelZ-[&A6%aI,Eruk<.\F]?S,|[37ogC*ƾ"wnǚ@b^O=Ci=&qWwOGX9e7s&8^-oYߺ{yc:$ӏɦppN+ 8rJYw\{ռW#i̊0vՓ߼ =Jf9MuNͅ?K^"Frb& IB,z̊X3AFP0wz7s7@ @HqAp("Y3WDF1 -8Y &2ТbSK{ mK:}t!w=蔄EgyɠB{NhJ9MG~`SK!WN߹βy;I7۳ʑYAWJA A *5G"[[:{N 9v7ݙP8<wSWSS]!q8펍VHo4)Gt$qx$D`UrmR~Zg`\Tn癋%SYY|c|6.H/ Ե~E]x7tMYtDX|BP3HCt&uy.4jjg$˵PEދ ݁Ud۪e~\ θ)7q>!Θbo7Ѳ\Gmͯ5j };yNv6s[5-ۯcnb`b&^zO]뙵ﲻ]{X^?{S6Qj 0{Qo;sŚN;^"IR\ڼ/OͯF%y; 8Bnzt|xQZhl:H34Er˗=pe?{ xI;@' o ƀ6\' 1CQ@nR0_GsM5'ǫ BqWA?@v9fU c5@l2&(*{g'$r=h8S5rp 5NkLZ%YCOTVт mPH*졧Ψ"1̟o/ >c4/ZFţgڳ@&Ǟ:[un&t`7gIV3IdA@f3`=W~l;ʲmj5M@-NWR**v[" ܻm lz,3d~h~y6pO#!rW/.iP_}&D3P־?MbHPWg>ynԗsC)8Y[/{Xcpڕg"w71'ǚ[V}~~pwWa(m?{Χ2O:M"Ty]yjaoﯯu? 85Q^_^IB6^[̓VH·LBSip m߳`z@$@OnO5WauD*dpv'qA? e^vZ  7LmЯS0@*`SW(gIQPpِ@GÁnǦSK|+VPmK,NeBf>.8LϯƇq"0UhZ!CDԉ T=cFgz=w./<=,R ̒UZ?ӼznI 9E󵜑zǡ!e#mHo]4ݼU4=!h*^^Cs c Gpt /}n}b #\Ilmӷz쉝ע8/?(zdm$Smynzc#ӫ$Z&ёқuYN' e=#x'~ֲ$6.[a^Ocْ;Ȕ}y<?;m^UGgq5xV :J Qw\ju3>缆yrmM~y\6sՐt Ea;ٜ؞-:~j]~Kf.b۱!zςw*6bn ˱Nl/Pxji9~{_ hT|) 6qY6Uw^viϿ0>ܱs*#ykܘC}V.ȜƯrvn ,soh CKfp V_"ly@aBCRM73l'Uw/Pp\X?eBW_~Z-fw;TT;U8M4b2~<޴~Q)Aw^(aK<&S6ucSdyHRUJTpzn/|F.ٷzƷDƝDSlqpf\HHٝux S1֍jlwD eߥGjrNw$fT* ]Qىl;pdOgV]UH dH&1uyG7 v=5axWYNe$NنVKdY:Š|",gkh"˷kflVU0?V;*FbUg,}7-涠ڦ}&[]%eC{^ͻD>yF>ĠSuZx`U{ |iK)eN0_H s#X*J9|uq\#k$P @^gVj^Zf\FWtߋW砎VWko{?n2'_MM\߻cA_h -w(Vō鮿]6LVvu4}ּyY.߮m^v[~(GF>^5Y12\Jq>k f]T-W(YZv7*X㷦LW&Gk˦NX}iqx 7N{N깳׈tWVwqmg I^fz{z"1>.|5Nk;N:lwTrf䡞}ý{6%lyS m&I5^qT婚(2khj+9vF깏Ͱѽg|hEAS+{垞/ (HP&bp&/'Y$f~}<6TJg@,yHOrml iiH?^ &y 8!@~ܿ԰o!bWGJoƏ?8<*MŚ}+Und. @ٝ@UP}4,( #NH^d$_Sq.~_~:FUZ0ks?()ݍfsS+PQ43T'f[Q>֞Of *{9oA^Tz<h2 IH7Z`;<"uP.(1Qe iqxzE,\qqq_7\А ߹ݡh{m*r֪#nb;tE +KwGMGReȬ}୻ ۯٔ^'ͯ̊-:{0u |&#X$h zƕ*s+.%Jt[mB6`2ds^Yr-yݷ2'8t 1R[̢K[30N!m0V~Q7[)ZA[Am)qܕy bMY;mrCxRۿ|[vmFg~(ƾ*G[sUEϿVӺ7yC{q"Ο/rv+Q? pI~(7[u?U#|/oypߠ#0m4/v&UjwJH~bO&%/Z)9дa x[̹56K5*3SP̲S{NB߲9=ޭhmtnޔHeB~9(c:>;k2TC aue_v۩1,G`*/-=ϑsL_l!%X'*l5@r ?0(Q*G _v6܏ jk<Mޭqi e(/濗֟JHauȰ\!*^0lD7ZfYr@vá^O:\q95tGۆUh{d }W8u: _9m<Fv<=0#M)wÇE +W1lGWu{?qx˪Uv|YA)W4*Z3m,#'_ x xPy7ewfT]ey\FrFd󠼆0yݥA['i(uNJxy69TDgk˻RxUt.ZE 5UGVy9n}(zFw6\l)-ew + xs دq/~QƒE=[/n RocM/I*\_ѥ+{OsDc Ɲʑe 1gu8cKH|$FmO ѶOi֚y29kycܲ_DiXfms |%mkҠIVn<ݴj{xi@IY{?65L+yVf\j|>* ??'~q [4@>D6|Y! .@lo E Y2lD'DΜyOwάC%&kO @T^@cQ NZb.$F&Ʀ~>G -BBG ]td]-8K)M1Ӂ<$ y=z̴Č$OC:<%%33籥uZlS 9hn%o5S/Etln|!#ĵ$L4 $2w61%+ߵT[3sCFr]]Z'L]D;_yPWXj$s=:u0.~!wed?0 ]%p& !=Jݧ yַ*Qe Im˃wjZ1W""Ucv?($Van~AqKwy +Ϲl?m.)G׻VC=5~F/8|9X}v|.m_S \ԜH1[U)jRTs'|3A4 g?|忮uͲ][>gnhӱ.3?1i>ckrNd-HV ]+4[$~{UKS6L/W ^瓑,JUaGdjf'secC^(ա۱-yc<K:)@O&Zpć#.Nԍ]UqR?ıP?y]mFT03!6n@hILjWsD'epRCSmo4(O9ƕf_Ǐ 7idta}/zjKz4 PI/{1+WܟuOD\Is@CN%|SḳUr"zN#yTayw|haȟt>j5>Z 3_\m9+Wv;@(/h'3T5@2b/q󒝽7ơ`a§ۀ:< .\%e#O$*t-D?*ut̐D~ Hvsqlk_x˱#V3hc+^ BB3MA tqm*aF `и6p~l^\aS6_|w]3y@1S ;w<K,Mɀ}[BT2Si J.zv|gfv1b~sR?Vw\9W Y&ĭL}\4G67H蒧[;r ?$;m~ H}{~/#)dǴZ^c=*37' &PzX>AJ"ø%rܿ#2\Rajwr^vL7wm`Il򤘷v^Xl&dnG2[Uidwr Ϯ\q[s ?W]#sNOMOe"&R}O}ZEnW Wp}Uι8;[-_qFqhf8d04ݥ(y唶.˭ F5U3o֍xc]SsvT|ކտ BvѺ$Vn.yZh[Χ'($SM\HS4]4. <{WB)tE_޴&QON?i['_NƮ3@JTb]lfكI.?TZm҇Ltĝ;K|j0XP_!X!:L TgӇ%Ks4?r;_!5Dl{)YJ*Z\K:pC/zc)G/LŶufr<_?i6KO*?-8Z]3lғp?*Q(Z9lT&M)sjoj0/|SA=I8P6\sf75Y˼[kSr IVŹIns_婌 n)<ʎu gטڬU.tslҭ *n*(g܉Ҷؒů#% o%gsˀ^jrdRS V|cgst22/qXK^/6O#i(]d ޗ[ekv d Ⲍf'{ʎ]K2]ŭIW:+ơH8NUG!0XD+/Q>vnUI$vou?NnwJ8Vm~׏ΗO |Pzuj'O\M5SrP/*>Kߤ J9g6WC%M2=\O`#'Nj ov?4<+hdZAB¾}|&`=6a @@?p sT~IAАXJBx$DϽ nO3*2y<2~ٲ/ՙLP)hiUbJ"@!  \$3sNX]-&աPiTY_}g<鮅}*hހq!nj<"[qNtA9*"Ћh=: =PCNH|(ӣ0יtQpS5PJɆa,|`qץT ƽpʓgOzNŘW4oǠ5{@ns9~͟UkPK{j1rc,`Xϖcw}ܘK|e'l] <!l&*z]RVb `Z񽧤}n|%)Z*Sl2ulmE1 3\jyh"rzJ~WW*GGy_̔JAyT.sCrsO+T x{0ζqԺ|A v Ml_I\gj4גr=qOb-tR~~d9BXƎAu 7\oU 9k3fX1;2ڏ-XV9JBd='teMc3}44NVYU)͔ZY})wriAz?M"TNJ<:q4eVw)?j.4' $FԖ{0HC %7Z_$?:K&rN.A94Z`/6u-Vak03t~ 71nfyw߹r)bp7ֈ[>>BH؄ !jA|ׇnTFCl~\^WU_&.N$v6xkT1x Vw'2m* hʳXSvZkuXqh*P*0x] &pzz 6fCS/i@  oY/:̲'<T?} \8WGKw&lW?M}zɆ@uu*~&\*!3яg&yn%?ԪqT:WD>eZrXʧVMܡHf-۝:cix| h}#@wX. >$C@ D@^)VO'|r)s/M!&;>`%I9Th,d9kMH~}N'[kmrT`ݜeD}>\[Qe#}Z5#w&r`[ˠ4[bVV9;5.tjuGxĩK ޚKj2^g,M.L}i)sݚ Ƨ7pぇO|&&X᪐%b`Р;+^zw8ų3jo呋OKz1 oo.~I:+:VR%2?ur q󖘾sփk`S7. ͪUVW5UtbsXKy ې&m(]`Uc$y!pW }f[VK#: 1O6CpsAیG*/ם??9)9fhoWmo߯ߩ8.g<\g2rYk(ӢVoI½F8*嫟"_|8cq< *oƺĤ/Q[[noxdyT^$lPuxsNYZsgD! i[|M@|& P|ux&EeI&rљ?f[ !$Ɔ{x dQ+:OzrBX'A~ q.^U%G7 8ms.Q|9{H9fL$yp' ߿(Hfk!gZ twT)K};+>x9͓eR~<3%Rx`#$p?YO1zI-]ͧrT$ r<6"lf4`OGߞIjOo-U p7=U#ɾg(I5˳ںukyd6 C~R q-U;f]m b>HMɄ<*F6uvMԪeAC$29Pu C[m#v}ռz"~{N&ra CT׿uM#'o(HieF`'@vqK(I[0^ b_3yQy1NH+6!?tAbwugvIc=6SK\ t|sCL;Ш/0\J?|Bv6S\2'5w(UQd둒s,B/xW_v W1v74"w[Y?㵦_Pwd;bßMM!}ҼO6*qW^ս}ܽ7V|UL>X~ [4ߏk'FL8ul_t4l/]toۂ9C3r⌂u*{"W1{]}aξ7GsrJ*:3.ZFՔYVy[F}.Ft~[(TԀ(,ۿe-Pn+Xn7y"_nY8OUNbh| v5[DmN\f꟔;qq㏋LFK;?17Oێ-lE$nwШ'j$R6+o뿧xWYT?'*R"c0xHϗwMyQYu߈w@uލ5#CvoZ8~@R8NjL\y yƄ&<<ԢDC! e Wt<ҙF9&E TqiT8](y8o{z̪pxg>wL2--t GN<)58miĿp3oi~KL U?V41i3XmR._Sr%ޜ-)ݙSno^{{:/0&j4972ىÔ`;U^x)_ϻ7+g]y1]lAu~+h<w3gӿ֛ƺغ2NKv޻V@wdZ)[~ Z/?wӥ֦kidik+L@ϱ8{eoH÷2K&Wx}Y{ܖpcj鶨{m~XҸsp7*q&ρ[5#\YB|t*ԈQhv2z8QO^%?n֛;J?p6U#~pcbޓgHeߴpTY$W*kр7];Q ~S}mzlo^qK+&NO\Ql8,u]@amd1AT:;/=0 /9ꡙM螼YD2t}D= "D۷VOr'͌4>gD[?:N Djl2];sѩr4o_ȭ@\܏{b˵ɧLz0ݷ {l>ՍGT&lb촠t:cOtx _S^V-:Atv.SVu0kA1o"?ޛѰ;z蠥'=6~p y90rqWyjl(ü>7d?S dP/B,3w܁`FYZMP]0j͇dv-J;-u[S}Šٚ~`$kY{oĐЋđ5͜isHg'$v*SMXuQH _6M!"I|xV$~vw  E?p9Nrg-ۊz Ͼ%D"~!4ɤS>~ `\ `0}BNpH#Q?$+gJ(T9t*Gp'B+[##)>GŧW(eZ4ti>pB g0URw4UOɈ$yHx2M'tDuq:lلЙ^FAէ<ʭ[2܌LpuTy#1fWIF3L$M3S=["weZ2&moHV ?fE4<瑩=@z3HJ ltW~- 9rE2{rf^ܾAW7k"BS~dU}a:}SlJ=jW+zwP۾H:Fq7s-F.~\9rǪE~ct<\@ae-9?s>ʚ|1oU9TD~eEx6W:&B_+/7yn\2|uǸx|O@?GmXzAJYdkהMW+r:~ϒDPWlr:e۔)_NܳͿW69 vqk9Cp|Z^y)燍ngnp`m.isM>>dE7#`,{_1XK#=ENƥ6ݯpIqQ+7F_vn "c/} v˩CF SL8Ԁ}>>~B }3hRoqWNj7NTR`{j .?lFW_>h7m7'_wP* ][*c{Qj'ҀajK|=DAS^ FDDk:t0x/ Ug6G>wCc@4 W錺?S`T;%1)##W2_K #kNv<:i!9ïAP.%z2 ds3sd51}v=ԍ! O&+BLN)8. B\|g|̑f4߭GO<|QOlO4F&0R[GWI^&DNa%o,v1t-kpt]k+4yEM׀̲^VZM?˄js 5~m, Yģ&bMkwTU\ʯ;Tx-R8}g/z;2MBWӶ]R ).Zg6m4Q/~KugrUoB.?ɫ[2mI&sTV>!{l{hOݰYɝ.1N3_Bdyun/ב[N)].T|"A7|1yTw< P㲼~*G[\5ڝ5 #6ҚиpyֶƘݭAVS&jqwHV?>g#_&,\VO}mU87쾯'ɾ6<_]{[X3\/84yES=Ikxtշk"uCuM7S.QcE&y; ZOP*vKܟiH])8oٴ9ڳhe{׮|{t0n+w]Tp(}66:\Tɐ}UI_%qmټLs 4 h;``=> }L7@'rע| ;@3GtĈ'rpKP(>叛pJ<"hD\!s:Py9gf|_{?;wNuYxQ yݲKl13\[-֢8VnkPRgy2rV>'/#ɗ̺J߬G"G?猀{S]đb9qH( }7^||&] }l2KRǻϵnnEEWR֜Mξ̮_U6^,jP^vK`p >EE|eAxV)R? l p.%)87͹KE[~ʘp;2!un8}A=uTL5?7`~X088hs}Pdz5w#=dd252DfU"21}+.TIze4 %2R`z_Ir d{\Z \UNGKep~(C/d+=U>Mn3^Vcz> GE'C0^ݲ(.@MPُ0b{s;r@}읰{S 1]# WC ¯ŧ`R-eܦwVl}bilLNIZh\-ԃilk.*t !)o=q#CZ Bf֦6{?Jr[6JD=R;BlՀwE8#ȕIpo{Svbc㜇Ju _/JATknLc ]JJ-Jp5lS7C_f0;qJI{][ +vxaY(s#z͙x|2Yp n1BХH%ݫD`TNV!!?fOm7;|\Jki?Wk0EAb&l|R4I1(/P@O>'kc2 l#X-OV<el]/O]UŎ΂j7Q„5+5Ջ~9L=@uQ5fAg6-cއi?FG{>' ?#pﳡ/j~i5[)WPXA b-Pʷx1Uk>!X[E"no8ĝSgϢFʿO;0{x;ͤ]9>S~e;#8@'1a=O0?耿%+❥T8+I$KpNۏ.H #Rp@?ɘfP+5FE)IͭtFz%y 0B];E3U _iyN&&= yL-Hz&>2F:Lu]uy5p 'HI`@c]E-X3B7A%]=#{1fz T=8փ$=2B`㎍fvV$n:7˾~ݬF l R_2JI?WIyuK3Jt_aٮX`uFd@r.f"#J]Fſy_譅QLeTgHB5W^*nVs}*l0 ϝTe,o"}a>B &@\(*Œ߷mMvr"M}ًݶRƵ+su`Li~]C3֗AA)+6cki?M3ju(~I()|ᘔs+$Y\_#%v%P9zU* ѹV1V]kk= rWd@,47}'Ov޸IWd<ݲud:Xu֘x[kh->a7 2k(Y[Rs߿qg8^~e8\{+ut}'5~;Ln' ~<,/H9rPg{j/v?лPQ\OggS@ Rl>Rg^e3c)M* gAvDC%}} =c];t:8sׁ!LW=3 n%@_ﳁc(ujrκߦ}^@<5 K`tNdQE|h-@ΆEml 푭*ՃjPZ-I =g#fKF[2>tJ=kb;}G$Н 08Ϝr#XjDfbWKIje"֡/IaS=\+ҹS9gA<¸\s$K2X߹:ҼWu3$ *IRsSY\+P8k+2p7&ui5A4?a RI޻ Ou O{_ _3!UEaw?!<5(+1So.©W|oU_]X6,ey}[WbGμAd-!>94*`~bX{fw?fv}Ws;Ԣ'tIV}v"se`-Zi;qG<,g|V{YoKĜ9]u]*_ '2N]xVBݰ ǼjߢPzqJ]K>s&ﵜS:$3`_Si&a!Q[k=hr-_c^~#A"Ή50vޢ\ '#/xK9׃e֧5qӦ8j?xy}rچԪUG5UoRß_^fn?4C^pVe3n(]l,[Yݪ ǩsQ9nVt}0ZoAyÐ8 N?.ZTf7Tn\lAK!i' @؉o~;;@ PV^lJΙ: *T#(.`+|H$ B" 016Sa1|3*\D̳Dd$~ 0wj};fSBD"-6fi,_gɪIY')fz|uƻ5Hd(%=6KWl a$ iǛc1zVg$X [BP\<0lm~MR04- UGYUK)Dpҹwz EyBgOV!y`Hhk^ٛ!FF6ݧ`XMޓQe+̎$͡*8->tSgU40klc{MfAdG"zn1JMVR ,㶌Q2Bs̯.H.ȅ㭧Le2-x_|RaW>\E1,0M4%of{/snJkj[Zz溜=\JA+U!ǫMcޒKNI׌$wת:1^{4;y|~zvyVH~Iѱ/= `CBq.n/l8ݑPzf֞ͫug Xoh.=/ejj앓q;M~4vn#aD-QÿNE:|VU(,m3orY#rS_;u 9cvYC"CQP~ucʿМgZv2Оpp^vy#{_#(Aɦ/&| `#)/N״R}k,@/?0~,5a-T#٢űV7P<; Ѿ?RSI%*:D0@J@+DcR!~O&eLeHIT k#$;J9;V*5<gtx@@/ԟMQahcPPpaQõq7#^[ީ{}h;fJG+TWH\ =$:Ȥ^侥z<]ϸS{MЋJ}w,WV{ɤd"˸5"?=j׽h]gN۞:qhiQ.Z"ؗB*94u^:\ճ~f, X(i+b335K E;TeҲzH$,Va+yU=D_6wX5ac/K}oSB,Ͼ$=0_⚶*q@<0iwl`<:[9Ok#K\F$!/z̝Yg{BVeb%-~G_+Kc>һ)}okw3^nz3?"urIX7~8ڸg;{Q/j@el9lR~R);ks0@/:|Ԗ9']}Ek ִz6hW KMX=|BV_6%lW+Kd4b3G.Tgvs^g6fiCN͔ױ87kH뾬t}0^U??O)!2Y3 tWͪuwyӝeKO4Nsg~w.h6h $0d1%(z}쩻P_B 7ϼ_婨 zM,jS_{ % ; <`_ 6N@$y1NC>I$|(ތ~ J9;j>*SE ?*| Ը@ h^ 2$`lᔬi.2)Py2`xPؽ IJY.sƻJjh \FPPc]$̽N쨪yჼ]ʫ6OPFֵGI᲎CaOcʞi(h;{Px |7oedơ?"/ m<OЯT:\.iyR'3ɥ 9eI)zh'gB ~sMZS+vLV)7rh֥Onvmy8YYlɛ\H2yo>WVxO^7˭CWzi rHByc=jCM(Yq@%k=&e[OMEAޭ%K99ڷ(m+/vK벴7r[tjܒcž_{U Bl?t6l 'Uìb_rU wg^b{e4PN7žя` zﭳO; rA! oAih_y[95{ V8\@oyd(R٬J|آ<"LR˭2#?ߜE)#?MK[RWXJiwr 3OggS@ RIC`R?(ecH.Rl=ڽKn>҉ Oop@55`+7䦃x]@6zr]elct"~h6j:RJ g._-K`لHKك;4#h{6 4/ ( ..H#1wn.FlS"NeIl7~VO.-UCa3z 7%,xL/,1l~Yuٽ~#:Ƥasc&˵G:.%?)y^gմ&(RХff&p7vO6de)d_鯁6QT8_w6"Q#?L^~vg(iXCbwVn@x;Lü yHO&:URn|tgvUH6N}he P* @NxVe"<@(W)S4pisO{-@ R @~VRw+OƵ $@[zQINZ}☢'W&o:ƨΡ}є L؍)} ńU^Z=gEЛP cNnGP;+$5 0r(MVOU;~ڡ43LBLuSazD]7OpF%G-DÞ b )D?k! xZ%džB/ay jQiT^yc_Wt\Ȍ \N++ a_-oI0-U"}Wnc[ c?_al_QQ] tVm$ݷ[vKHRYc.O'q)A8oxQ'O;wb{WCE1SوRNl8_Wq[~ IIQ8uϤZ&:U_c2;4j^Ѷ5inC_yWSm8V(=ǯ}m lӒSsng!]T j豛߮?].pekWsͅ?\B?t_-!0ۥT +?'OK1観uKND ion0RZRWx_xMK ΜlXqj{R@"TP.  (uI. @RÃK@}l~'h4t d|) (E3_ovWg,I[{f{:)^={>[4 И/d=MgW57\ZJq/"_Ek^ ՜eNX_ҔԖFնC833.-Tt|q_ n$b^qCɸ MIN mׁ㼙VfuykD{z/FY\BVҌ IQ f2fc 2/;>hM#poQfqKfHz~luEn#ϼ+[px9P!wқ*׶{ a6\GWcrvی|ґ9*#Ucxو/jf?G{S-^pv:Q_Rg;eWJX({jcŹt#Z=p'hh赅 ?X{1 M-?" e5VIקsmx oꋿrjAʭu^r9>?Kr[~6ʏ0ݗWla'-I7xw3?٠)c-dA"dsw]Qv:M6s7Pv! oE|t} ll,y:e9m{BG\OL#|$ p1gnK<@6槴eu z0Q2By@xPBAA `A HGv@EM ]ut2$x%Pkf~ԁ%;kcq+w*b4[B5I7R<Pd4h87z]/ >[ 2B.9^vٴu,3;NСp9a^>XyjBe866`Z PrjN. KS4c?%oчIH*zeb!)(tzY%P4#AeI^j'~\Ĝc ] T@* -t- zqvAX}zY[oo-QDʮ_t).z﫮SlC ՎQ%_aMÊ/^ x>_k=d{J$X~x6=ϩډvzaS϶=}{?m|>&isjZM3RZ@Tu|eKOv se$~UhJο?7 0wxp1@sj:Y|Ll(mwqhj_yDG* -P )p*Q<@"\5 * PC?@G/ ( cnݶ0B' Jgіbi'_^gnU E<. _O_<&/Oé^,IH3&і8~F^%?@jƇkJ15mͺ v͍ƿx0\p(2Dk;=`Hk C{ꗜL$Kð0 BsL~#f:v9 H/@(Ro epkD@nhj9^oܮVF` -r\>y>͸r\KoEn94N>ވۡ~eǣOq5)mV Q%w>׼WLXSs!R#0y|'GɖoI0>زN)y`˧|pk5oZw7BtmWuqi8l u1^*(><(nr]V'b_q*Ƨpon꣬~ظiY՟ɑ~6-zJe82iJ{h'cN+kj._.jzWI+ѾӮ࡮Z^[u+OggSM Rmvq8PFew Yl4"5Gkvc/jCB2|"f>ՆӀt~S1X_/72T٢SjQ =J'.Jҡ| ,21%@((p ~6;B0d!7m{&l$A@ Y-eOF.ݑx۷63aVFKkK Ԓ?:^WGޟΘn)V]B2i2|%% 6vI7JNلs>'tNb) e~j$ ~tE;,Ö Î%wBX+7Bp 1o8Yx.[V}XܾJ]q-oHx~ȂA/==X& a -`*\VZ?G^r4DSUEִ~ 8&  XGjv3&@ j¸tdYqُ۪s2T k~/&{V5%~ t)M~{\^n'ߜ-YxGs T1~2Ybfùʏ[2NioKzlROC5<T?yS,K{TC?5pպ`a?t\ydWr+NB)@d3w50Z1&u.8@:8uN|o?xuaz ogzFp튴vZ^#Py9 ,L  ]~SH8P|D| |N!*H.@9I8EDzLJu *Aen(fˆS-~VJ ;lzw2T6j8'x8R5vQaYW>cXiy7+}eÒVwtض_o]c/L,oZ}fUD9Ө yEq~}ʯ%2ȉ@|ȫ[V8pALEq)aw`Wڇ}6D"SX$Z+V# \;(]zvᏚ6 lѷIs FƝ9¥== 7v=ls՘csjjJ3R0we.~p{/~7²5]峴 2X&$}XlohM6V>FgիS£yysYǖ@C^}a0 M-:/?0%T1R^[ |)%~q}l7G|ǟ5o|#<۽ hZ3]e.fІb%j GȘ Y]U C}*6?>>ewg(\dwn,j\o6 C^"΄5ݾ`*}UW.:jLwj:2%U{x y|8'Qn n9H*@8כ=[4Z oф54["ߨ|k]6xZ 4~yN3tPTvR?ݒ+^.8qb a/Fj HnpMK gLu˭mEQ$۶ϴqJ笄tn,,5/syMˢ$%t؜f-?ju Em[Y1>oD@ y˜& _oum6&.Mi8m#UG 7`cJoìڝa67L5z4w=Qe{wlT춓{;2{'Ó=&c., E]_ЋeVo/̐ikoޥ)2 b#z[o_{Ko.|㲶qm'!*V| [ yHJUEImw%~2e.w9jߎj*]==\$ ?E͗$ "3`Χ;E蠟P_.s˄G1i-c\FbI&/R僚q˃ͪk|6q]cs]hە^皹R@mD[ s\Ԟ[NpkǒORZ ~ڶJ/~yu_c *_1s&4c֊įk &ɇu' kUmݬרNfv]eXsM;q9'vEk3O&,-mi ˓w^4Hd"Ա s e~) oC*u~DTkx-@F%4H'м?hv^+ ^7|]&HBiϮ 2XsW2Cuɣ2H if\P&reHϫ@9c) 1RrSHM6zUn8Ux{˿J~WY;'0lW˩fl#an8_ O^QөzUwBP'uq-"D"*a=;_z0Ehlސ唎#~j{?>9Aܟ=c5N*#U/Xt J\TOQR{^1\5p S{ (S{ (LB bdۓwαKIQ9eukui-media/ukui-volume-control-applet-qt/data/sounds/ukui-sound.xml0000664000175000017500000000570715170052044024457 0ustar fengfeng flop 电波回音 /usr/share/ukui-media/sounds/flop.ogg clank 叮铃 /usr/share/ukui-media/sounds/clank.ogg gudou 咕咚 /usr/share/ukui-media/sounds/gudou.ogg thud 回音 /usr/share/ukui-media/sounds/thud.ogg plump 机器人 /usr/share/ukui-media/sounds/plump.ogg quack 机械 /usr/share/ukui-media/sounds/quack.ogg ding 金属 /usr/share/ukui-media/sounds/ding.ogg clang 水波 /usr/share/ukui-media/sounds/clang.ogg gurgle 水声 /usr/share/ukui-media/sounds/gurgle.ogg fizz 吐泡泡 /usr/share/ukui-media/sounds/fizz.ogg ttlou 完成 /usr/share/ukui-media/sounds/ttlou.ogg rat-rat 煮茶 /usr/share/ukui-media/sounds/rat-rat.ogg splash 电磁 /usr/share/ukui-media/sounds/splash.ogg bark 犬吠 /usr/share/ukui-media/sounds/bark.ogg drip 雨滴 /usr/share/ukui-media/sounds/drip.ogg glass 玻璃 /usr/share/ukui-media/sounds/glass.ogg sonar 声纳 /usr/share/ukui-media/sounds/sonar.ogg None ukui-media/ukui-volume-control-applet-qt/data/sounds/weakup.ogg0000664000175000017500000052167115170054730023633 0ustar fengfengOggST UכvorbisDmOggST RSvorbis,Xiph.Org libVorbis I 20150105 (⛄⛄⛄⛄)ENCODER=libsndfilevorbis+BCV1L ŀАU`$)fI)(yHI)0c1c1c 4d( Ij9g'r9iN8 Q9 &cnkn)% Y@H!RH!b!b!r!r * 2 L2餓N:騣:(B -JL1Vc]|s9s9s BCV BdB!R)r 2ȀАU GI˱$O,Q53ESTMUUUUu]Wvevuv}Y[}Y[؅]aaaa}}} 4d #9)"9d ")Ifjihm˲,˲ iiiiiiifYeYeYeYeYeYeYeYeYeYeYeYeY@h*@@qq$ER$r, Y@R,r4Gs4s@BDFHJLNPRT> "9@@OggS T ÿ4B6^a<-W\R~/g?w8>FF_έǏxm˖cuϤP yJub5K׍Elԑ 4htfoiQJu%ڗ/~r^3|>:(1"(\ٚ"8Ku]t`EWf>IR2euӪL<+$^JY5Qlpj-G9TDJJ8;;Q.IΡ1<ŗMv۝wu޺sW=7Dk$QYGmC^[EI`| Jd(UW*_D?b=٫7x"і=DXa(I阅XS/ד8vwyf/CbёRp ku̞]Ol?fU>WOa+*F"zfL ݡG箎wl6W?ܐhK~G4IwjsG%Y _Q-t p2ۇ_?Fw&=i__;z_RG7:Q;gr Iݛ; VyD@,o>Ù*l~3pꡤSQM_&`yG&&ovQcjUu*.ZM$-md?M$VޛGl#$5;иwՂ_HĴL"969M2>[nZQS?9R@wOڴcs&b<$|WRr@;Js&Y'~nDti ne&`u'"Qʋ~鉾ڃ#k*~8*tYyWv^a=-6 /l{?S ry'),4y3GYUUGy=ЪYq-x?\K995o2Yx^YF)]n u2 [CJ7vT/̽.;C-O(.{]N?T ~UQo-|O\t'xVʝN雞d$8:߳5xvdt#c{y}͢fpfكNm}ot͌3:93|LTAr #=*<˲R )F)u6}XFzqQ`߇6)_stCn-xB4\0`[ ڿtɆe c+}j=XvxVzy/zȺq/J~krFjUdJ&eX8#t9[b5ؕ/Ds;NH2/2&Oxބhgݏ]V؂Ѡ.T;z5yO \SBo~( aݕ>Mw#w'ε^OuN=*H;*CvR8bW41V=Y_3Ṻ+ P^Rf74WmиW#p!y}?jFuDMzx%|(כ_G&uo=?_0xwՃu s]-]*tʜcftM(CNXk[_pR|hJMH{3AxEfMl.]J8d*XT3Bg2p'E|yfyz=MCmi9=$^:/k.)FZxj& ` 6/{ޥFRW|]`8ߊhgEpjgJ_/&-YoblV>KZ 2_u**O-MMx&92)/!ϓ^bGhn⪯ٛ9ݭ7l,>;y } E'ħ+Os<); ztazCZpO7m<,jQ.:sIyҬ>ѡE#&9lRj_8g oluoRCྜߪ9ҙg;=ᗜQ i|o+ÎH9=y3f` d/duy ͒s8zo~x7}_?#0Gץ.,Z׺Gߕc0G ѩQ'G&f @jY_𯼔 @=siM){+ٸYLE 1QObpLZ0Xp[ L4&<~7gMC~qP+~7˗/G^߰K}ݧ{u.jVx;yѭo)ۗF͓Lpcw *NRuS{g{/Vk' $cKtF[ts?e85>W컗?v^e{g]>u_0 l7yc"?[_UgN ᓤ$\!{5g\ΔEgkqSzU_xvaL;9/):L eFn" >]<k`6?a{\Oy89w❞O&G7-wDN!n~cZOd+3/ceG^b8Ӱ\@qeHqs{甆żǟB|;ecG82QR8_B_᝿|\h@c-y:l<,N-aԑ/Rه15}gaʉuPRE'pIʺN,Lb5`1ODmq*mAB+D Y6"?`l:c+ !K-Zy_9gv] BQcf׳LsV};"0*ZPEI13}鄜2S\j&[nј:V4cp%4h3JN/vXEYW=~ȏZ?V*ԙmG}wyyPO^źW{T!8 Bevޤ6X$6W#G~n/@JpA|-=nD!S'CIP'Zqkkkao{, Mhu6 ʦR`h18;Nzv/ lw {~Ʈ/T`oyٱDh U֮m|<{zD=ni{v U9*m⌋ͩ[9ԭz{y"uM-+|Eo+R }coޝw)Ҧ;M]OggS@T EzO?ƴ * #`[d=% ;lo?vxv/oݬ'={4{vl+O;ϫqj-؉J6k;p[sd8 Q@u>T)0EQ*Pݡ%u_Y^LO{uW((#rJ$\ 8/代@Q7hޣ]HN.&鳙|{ҞVϺL4G~R_<[`9Z_=i_VT㇃=#ЙcgiZWGՙ_ l\B{ՐI$Ѯr"`as90:'ƅy"'CMW6*8A%Bg X#0NM,SJB[m3m4i4ש25>ܦueߛ?tJ?o9qǧ(~X׶<ݿ&?>׭O^_yttzw5pTLfr~qﲮF2k6?_1t=Tqc/*T3Oc5鿻S8kܐSs}P2 ,X9Y }4K}0(׳ߒC\0~q6{Me'@, a%Sٕ*=41/gZW"'T zંI[5PQȣ\SWp0ףQ` hM'̀@v8WWt߻hѢEg',wNYT"DV!jyJmG7&XPniM8~'ߣ!&j $o?Rz $$oyJ7tBoħY( *p_}6=Ʒx)>QxE<ۢVPLJZs9 uO8k.@8kc%i/me_]8l BqdϻeǤfz]YWMomkzƍ):h\WWuƵrU^ԒA @^sdP3po}xz(^wzRtv:Ë˱kȽ[A RqNWXl[c%2)eaE]#bX? `I 0ݏ|n} ZoLеˎ UY$dqZ*Mki; :7)Y ,0t{{Tn?V,mGnxXM wsVQ~敟oef$P7A*rL qcH`-Zf?=>UM /e:q uYy+uFlb~䭀-a!}MFʻUR }2w|ΧΘֻa R/)֟ү+xzM&,^͹|LʼtnD .`xS "rEfT"Rnl)1mTz{q3)S 33Ky >#aqwU@Ƞݖ&i`6$%0+$Z4wC8!,V=X]OL71+qyլ-D2"e\/^ 1EgDk0v32Zdtl+5!gZ2nd94U&xs?ybroԇ|USM[&NIz^>dOư66.q?Ca.ح瞋%*-P%ed9]{TPi8`~ a]?1ej\џ$2^X]<:Rf醭ܱ.Bg2Q~x ^x ?juɪw lp2;;Uq+5ϩkEs52?S"qɓ$YLEʏI."=! Pсw=Dߣyw-?Y ^ N¦Rr pkFP4p  ?pJWDت?X;]K7.dLj[`VSmtrFjo,y.*[d$/tQOY;QEGA/L}-[_}$/»Y{B1'|qӧ `2"(zdۧb4=ЦAbiܺ2gnt֞&p*lkGB&D Ah̽p./s qߛIU%츢[^ ~A pKxl?_ 0m<SHe&>\l!"LF( fM܃d}Իh-weh9f.mEaC&<--K L.#mv I-LIjRS:Q(I">U>:ַ6-R. AUam5 B" o/Js~_˗cr|).fXT7[Y߅e]VQ|VW9شCV7=0mGEez̚B9wpċv/4c-:]AwNhCGkq#_1}M `~D!jvã/7 \G|f_C@UE<;@#=>PIO|Xp*( @T_@~< P*dBfR)bQЯ G6*@R!҇dW7L$FyռC3TmAI8&bc׺tkJwa8%q;G?{!YK{JߘFw>r#~3=ZwFnk M̮CoqK'Vib Sۺ AȐSW̒.pcCyr.ӵ?<[-Fq`NZO>k{~e _tf=,$Sӳͅi*wKnk! N}&od$<;6UG.H3{=.:ږq7!&m5k3X20G]Xd~&M6O|U{0X̒,׸Se*OggS@2T `mp^ps-` H̍ 6vω>ƒ= |څgBH+ O擵 4()g x袍Ka_!VEfW>x|۳OCDT~e!j(} 4_E W<$aԼQ CQ[M Gd4]5<Қhi;NXR ZծJydބ)daI!=k&-ljY<畫q=R[BD]x\7RS}GVUXTD);;`;Pj?t=ߑ[xHt|/oAk ,VeX;onqƽ t[3_s3g?:cb#_Y}{jawJy=2;I*kƨIi* Xz񱾣{EL_7sT\dI}jb4g.KVā(@[rYHo~PXihp,cq^%{u^|Gp[4{om340feǼnz,oT}l]X!V++]h Y;j •U+ݿjyZWPȪy>f h:{9x<%[(x)tjUQ>E*:l_8|,mWK7.v|\dUrwߛm[^bK,* _m}M,309Ѐ[[s Lb{-#%Ý#\bM?dVP"z#-0n0DdUh})uDv ȭn[Wɗ_ߗjBn6 mt8E#0˳ם: z4vurt#wlP@_eF%ñ;詘iË x>k8Owxx@hXg@,htk${݉ n䀚꼊;\rUhZJ0\5=~(-k7Cy+x>Xء#ͥl8(qp7+o7pxr++Bo8 |)Ffi՝f:aӌwp^;W?7H:)23/Ps8]~'Kv4 mFzL[cj9Q~׹ݝ{ߺDˈ5wR5rP_ү*kT G`ŠD|QJWcL_<[֢1-oŹJg}*昶>*ַrDRR1r2d5?ٟ)Q:=b$ {? jdT 1JR> WV| ^~gҨO`5r {#E +_@% ҺNã>ǔ[s)V ~c>egDGNەr(uR(vȕxRm9rnsckLdLM vq8IQnfe7d89EmxV_K (@Ux:ULɌ>@vTt~OS5@ Mal%QME-f_zVvZoӡ;DX{ kGȝM W+[Z/UMTSQ7.=_m_k- WZ:SbF{RwoﴄkP~!"L6|3g%PTxU=WIj~UzMnJ?T h'kԛP`t?Ff`rv[p%R4 @44NJT(x/}5PkRO_ 1) /?#ёBZyXVyԫRyYEa.YjuCM`Y. h #Hq}o?)0czu6}x%b1Է!Mcդ.^cμ#$J~cstʎxu8mXKt~sb:(O;J CIؤ{j?UBkn[h$:O7O_>%ff s>IU]V)}#@q!u޸غڝ*ݠQ[u HK W  ݜd!Ш\UhԷOx*wx+p0[dB1/|UL|)?A%q*(B~ergi]&h&#_iU"ǘ&j׀dqȦ)q>F(T稵'g6+;dHQ&o> lWg}lN"{_qXypa@\b2 iw59ۯ|XzyS+e{ns[eM@"+m0؃GH~`mZ :iu].×ܘc;mnj "hG.-l ^Y'ߒ*J9(K/K,۪AV@> [*%xf TɠE"Df]x[k8= Qf}63UpK*ߗg/Xp(CE L5E"/:=f9}RZM+%V<(@-ӴZTU됨&2@$h(Wn&*A5QqJI2Xy9=蒿t0ޠo].G%I̛&<ԝ@GH7*:=z÷ӎ XVr`_vy/Ob*vkmI{Ll@>/1P_\/8ksLi_V`H;vi'^GHiVr/Nt—6s'',!Ɛ ++krRn:㵄h[|ݏS|KEE4K   L:{Z觡,hv9(քJbY8@?t/;҉pڍ,$ ^wFrQ$"A TOm]S'-XYzIW!ذ[>WIZC]iR0 ge.Zܝ};;5C3DaQ7QDweNobngR;`2l%fRw2:JfϽU֔eD4E'gm06$u393N톛پ1^3:o m~nպ~5 ua*>/8ntP>r#M\'^c &qĨ̼/5^0Gx5(@_u596{zvP P_5*Z QxK*,0 ,uDၭZhx\ԨTs3XмjxZ[ *Var.aip>*JA!)^1|"8+[ߜ֪ʪxQUPwb#:S7ux@D K\~yXE}񥖐*J3Z"JLAH…wsjg78d-ȟ3B= Íkw INK liҺC~.dHO8- ?ASSq@)@ ?I/LC uǕCE-8d?9}|)B$֟&Һe5> G̪RKe ՟K]_Gl)lE'I=̅K Ż/rE%=n8˩` PbB:L %4SVl)z*Ip:SEa{W7KV"xǛwQd4cq"$v 2a <HhJw/otԲC Rw-YPUE7E$`u[XIxEdgk^Hi[ !^snTnPDeƛu⽏p=wwu3O΃}QyY2Ҟ^KI5'z]֍إZP*OggS@BT !))^k3 >sVJj>\(q\Ctsφ@ >[\P[= wnPe,&^s"*AW Qo HMnPbԞ Xf\{?cUTXs}z( @ #(/hy0Ok@K+"(eA2ރ Źz< @:G@3 iʚ_7ԔCy^S{梓 o˶-5^hU$Kүʿ5̌b\5fj0Hգ "gZ|W1/W 6bRi9GZt ׾ZP)_k[!v#p(D*XUC3SS0_TqG p(A絸_=$EPŠ!rޱ V97Jx6ܱT5>tOd-o jZډ(;㏑vVL4 [29Q)ʏBs$nBTc|jԝ2W5jh9[I(sͫsi,9]Dm%F7tWEU2-OW{OiM:d@77ǧ3< oy_םr> ƞ9r %{b ͖+) ^B}RIgO~9B,66F/fLנ= b"S^CYR= hg'pwx?!9Y\[UnUT߰XDrJkxۨP3_'ΒL8%@ưՒ !8VNGyOe~R)ʴBo+Ә)~rjOh0[܆Z~eñĝBj)۹gAHuf!Uٷ1Gtz rDȈs}s@qwJ\\>Q\pX5nп~cӯ\ɭR+; q۲֧u^XcjIt tP]cQM8l P׆~'l^=V$ ,OTWU(֬Tv*v HuV40ӏg3w*"rZ=Luhaj˪<HGtKZ4qDDxzED@5 Dx t=D5`qgtO-o3Ccb~~,fzD4bk4$蓾իh:ea{ 5'c'K2#7cvSwGzgwHjQgIBOخFlٖ=XgGiKnN9dQv/m<~ }R8g@8Nm|&"?<4{Ly}p7h?<7cPɲl?oM/%'΢Ez$'Ʉ+> 4\k2hft?a|E >Hëj OnLi=d{g/ m#1IiZ/- 1s|0 }X Ga=B])"Cٻ86΀窥B&v>Qu8AULt;qL= O]9c}_Pk`/N5I`;cDMxJG;Z(U:/X0MN뵐Bi´ܼ. txf;B#Ͱ,O:~봮#GnttX#"̟Ql7}s&,Dq>-OeBtW.! 6l hhh6[;ֿR=8C5ل3FŭH *^蛃K0h_'_A*"Os*##pnɞ?@D׭ߎZ_}1( a*[ qWC2z8_z+{M5|kJͿ<+h0Yi>Pgbz-5n8s$լUX]) @1yB;(tyV-y*vn; as@3@\a=z3&=GE3~y>CDJ\8پ>@6 `&d\>H@UQQd/(sorɄQzK=Q@1[.F)ܫ2W߮~:;[RLQM 0PJYŘ8LRT693ά.(ӽmAbM!o=PfueT3*(pH&HN;lɗूcHhs/-Vp g:M&R{~j5}~݇[JvJ8.i}J\wd5Ai'0Dzg|S[Y]Ƈ@2pDtD:fF ynpB PrK|9y)W9ՆhD퉕 >iHer^!= #dŒގf2iP 1(&<8;D2q 87 J='$pi޿"5x$&$TTTJ͗ {؀Ͽ4 ;QC)V,o,gP!UTɼ3P{}Ty )*BjZi_Q`=P.`!"霪0grcvq u3m1 +$c$Cm˝SІnSi4*zR=5'ixlno>_3ic>NeP_J(wJ ]< DeͶ֥8/m6]k7 I-fOux};Z5|Kκj)Ů_r;wIA_U&C {ˇ"󘋋%k!FQSE:wl{N̫qW5v F59rPS"~ A=J̦n'Ju~%-BJT`mL}D%؊7(mqkÓ*ojχ3n 1/bTweּ_hQխq"BFxA}5p%CIhN]0rk) ~z;>RWo O*JqSn "i[bL(rc=u2f[SH?Aݛ[b%9y1bu^> &lݔ r_0772#&`(m;X*>u]F.x.ݽF:籈dž/QfLS!__}=3K*GaY,џOggSST ." ث3L"LC-ef B`Tci|#,֏}}Vyhjm5#nl_hSNoSlr[Pkk-"/JO<^BަlEud1d׵.՛1)@ 9*kgЎn^r[1U1656? Pa!ĎZE;򮷾R$z*S2ԊIK0T*  zgv5Uxjt΁Gչ(YU3cV!Hm6CP10 q} QT*"-JZvWk5P- t1, hb[X%VL˻sCjjHn?JjFU: s^-[i뷿Cawq),ڥMy۹sjϤ'[8:}O-ѿ~J[q1D1@)Ï~َMD+ Q6 osO4 %ղ M9\~bTy5!3\McEG:@W);(3A8M]A(PT؞ 0eu ~( Ȋpb(\dP}P=QSVIlj>}Ak5y2oV:']w}\# ѽUVgJV~G̒zn)0 irp>w-YmRsEZFe #qo!7W++sJbZR칝o `s.IҨvmK=qJ/u'B{,8Z1{AN2_ 5m^f wJi5ɩ A}V*xcWDcw]5NSę^dzib [핾}N Uv@eR8SU0Xh0L)RC6~eKSK}YTD w{@%pF  (ε}Bki2A)?1])*̟%kWؘ߆P@J;\rzU$28l8)d`SOpȌ*j洉PY*Գߧ_;'C,^T;Zj°?TUUFʹsT4WUX(j4PUGhi vagXv%X)@iZFC .( "JוIb)SqCE%r42XN TbA JߣL=yY ,C*(V*d>V!Z0f38 Ȉ ˣUWLI/$jny6:⷗WZ)l,}c}]L C##OL4Ţ.{BNN:y+mge0ytZGN!gL7~#S!avr{:Xa/[;|y_fL},^1|-PKt`t::ox=.KyFuz@FAP7(>Q,YdA@ffWȪ v?]s9osz>}lN]P5v}29$ERpӫ;sF=0$֛ ahT{ukQ| ,sپ jIo<5l qp7(Z}ҡ#bA 6# c2QKȤʭT887Eüng>#-CNz޻.fXIi@fS!=VU g{8nխn^~7<ܒunnϜvl+=PҁZ/NkO7Rey.K/STxgWD˸A{>3n8<@AĭpC@AD' s*c*Kb?ܟ9iͪ(r]3,|fQSDK9닢T bv؋82y%Z j,\=@CP~nU{YEZejmbV ]7z}i:ꬼƻhV!fh_6h{d""}UF̚`HzTϻyGY0K,sM.@9vmo[f<=>FU)!vAK.j}<$ԢĮՕjl jEVTXfyLVޅV ef7Y]s Wwnv YFi04Nlۃb|y~uo;vvLڜ>_stMyTv]{xتS6zs;-OjWP8vX'-νdV~<3VϠ.dRpX/ˬ\r{׹/Q>g^Y"'?o{;݈_/#΍KLs#CHspnxK*?T%in^9֚~ ݭ>Zܷ;~既oZϏ ȵ==ĪyԊ`3]CjZn:ڭbqN8UMiq;9oܾ/klF% k֔ztC# Is7b  *ڻ]UOggS_T  otFB.3`Fry.J7NQř/jc+vg^>i0QuF}6-ج88b[éPq-vL]ނoz=@7kAq0-T˕i\".Wc)0@8s\A%]f/^N|~lC\cFm?t?o%?Ҵ-89e#un$װߜ1el-CnuO)<篌\-Sӎ[ءM0d& z֏}eSo龺|?"x͔=>Ȱ鉵a_>UY&II99k;NM_}R[';;]{N*5Wykߞ=^De 7)* B.s4M3OxfQlh;qzY%d Z$QmeM2R~ga֬ԨҢ+aueb65tp }Xeͻ-EQcYUcE-*8KMϗFkmGVsmj,LWSL'!I@xZ,U>n+c9siύiѯ.Ζ\hT8~ܫ$M[ڮ|4߅/8ܴ˙^+7oI+=.`jޜKƩ.sVo!67:ܷh-,20j1ٜXBM.}Ld{ݷUɳЅ.qʺ}Zq( CXoS&+ B0M{y\xtD+ uv*JUS^,rK7$xsK7:>?`(fUM{hBn#:=v[h=DDmVQbRiAL,%F}%C﹌ ʁ`ؓ۟ywrxT:ma\d9ѸM-2Y+GQ/4C ͟^nQDm:'K]ZcxUSO1 A{nb{c;'TK/l/\T\rz?=RaH:-̺c\1(αS\*^N $YK΃ d srY.U8]Qvy)|"3uJ'f%V/rD~jmk4Wx춻Ztٰyܾ1G:<ݔ b/gX eT/3xo@|ᧄ8 Iq5[)4 uz飒G8TG||ԑ:T)""뛾찾Xt ڜ2y ć-b^Y럫BJjRXHToO3cZF̚Eu/v^z7d/WWi1S>LvYl?G=St3*m֍Kԟ9Woқ*z;)eA. J S::X&U־;+8Mdx/5yL/21>dXAxK%Nȍr_e M˜/VjTB/]˫?~/7䆊\*yw?2Ӳ LJW-B= UN'ágـ@A~;wciU|[n.L?sWҞ;ªyJ{ўY{>&xADe_T\\ 3SږDa7{ W }k,F>]TolZon4_̾]>Wftj>䠼O2-ӭi4 T8z46d1/ϾY^м%To׸,稦Bkz|xbƞ|ǁe3ܺHfԶ 3a2(1:Na"LI?t8ހFpuU-}_&݋lj׿{ezrQ{U٫:gN9 v?sϰ ($eؿQٿiàkKw/}\ΘQy ]܁œ}1"_qE^Dʛi?m]Yc jYVozmmvW?|]?Ƹ^^e<2N_k?:zm {+SD(Z'[Z<̔W|Ɉc」凷&v܄ '(xA11/ql/vɠ:6\&ƿ5Ij& /D1}5&l=i},ǧϽN7ٿ_~4L?^O[>%#eCTNKBZ!#{ZXe';Wy Thy<9pxt_[я_8S]`5RFLBvO1nou<-o3 lMO6yyu3ɠUgs?nKvu!4(#fg?RXdzE_PQ͊r0m!=mɳQ^EwܾlWaci I4{#H KŚgptS7\tY(ч) qnz v*xzVD; p-ϝߏ_]ן>O--rz//^Jnv6k6}G,g'0zm믗PlZ*8ի6KYtXև+ׇ1{s^:Qlq# l\-W]7/w]N.w-_@3;ߚ:o]4CdօXX]CC) ~ /x|jņܾU>@n?ES^qZ3ЮI&n>uo|KKat'5^t~ ZIS?6TqR5$ꂛSMwqzEh47;M Ы=i/@Cī,w.nc+  Ϝ:h(Q{>~ܫx{-ӧPϬ\[Yͣ. W||W 5OF#{'%ҤͷrrZHdC.?}f5R~PPrЬ^\.y{l"L#L6Ӭ4Oi{ҭB8WH7zjOurcn{6>~}h/?hз#wYޛ"#9vY ],yv_օu\zczN_5ÁGVy-..Gw\6;|՛ A'8ᅧb' *HoXj.H{Vf]7AEϹފ9/8of{߰ OggS@lT nFɹʴ4kXt=L np>cⷻSsӛ[h񋦘rT7nǴfLX e]@ϥ|&kM.Zj;] ؙ+LQ17浑@Ua:Q&vY2T-Lqڽw>_0u|V?sGzW#7KHiqu O_ G8t"=|Wqd*M< 㪡'+q(;*XNrӷ/lǐO ;U(_W;NuQ\a}v( sik&v J|s{kPO&ad :N$?_e<_Y5xpL))+-cô=ͽ;~Aou(ěOB*Op~Sv=d]Pn~)S_lW(t%ބFל񥠥/NYzxfS.sw]uf;d8S;: Xr[O84 ɬS[t$a#O lj="$0|#&rzb]ۯFk&^ʭӲno`lʪiqε Cq<ٚncZ[Ms^8}4{{ ;Q6~.\,r!}fRQTVC8(poqg9-rjNPo:4o ̀8߰yU?ʶıɡbZ#[=F/#}7oFP3 ro Ȕ H4X{1Gl}ZG)ҩW*^v6TwZ;ǻVu~5bq3gx\jfS$WJкCy2նIh~ nR~d ~9nj;}YgyBom0 qTC(z: @yxyNr}sɳ~pTwK;/4,И^ORutnaG_~vCe.s|Ǝ y֑,39$ϠnuW O j̤njuSp L^s{IS'ۖ Eo) ڷglF7 fҽ:mTo"3djPpi1Uoev-5G9"wۇs{c]S@s?jyw+)؛BgV疏45pM~oA/ lhƛ˜?uXK z+DBzڂ;|lev\owޓ qAsh*ucA/ƺ@F>!i_r3_ExB,N(Nz>X\Z1pH f[0=7 7}SXoБ=>)_~?3EuuXT2rKۯcܖ;[To_Akֿ&R僯+.'R4(AD/ެ/`:8pP -m.&?E2H:i- ?k֫'O|v D(R%f) Dۣ7>|>'"H+) @cr, >_8bd*';E8x>={Ve:lgǷz|ձޠ'C8=U8WyS!_&SA "([{7b|m&rL#āi&m~(oAf7pV ae\c ch9ÿƧP$[4i LMP:0\x& (@͟z6P?>׶7#9Jn/\I?Ưkƣ>~ӏm } ۴>]*N[ioھϲxm.ZEWSzݲ[hu`j/p{lr8YU)*WyyvN .dF G S{x9ZjG1=scѵf^@:L\WT~7;1\sVYW$f3<ֲgmg5_ЈK;/8Uz&@Vf=Vu0ߣ;EE*m 욥_i+R3=$VU*d`誗YڻMPPp Pn<<y>NE~>TZLa 1kU1 Sa-?hmq!Qm& 6=G J{g0h`v'1mqm͓~u{_rW|ƌl9X9n޿H2Gs$^X䂓"rWU|;]#̆.^)ۛ#!Kܲus9KF,ZN6.1-qgBH$tXZ8 hsO~pߔwiOtẂ^EloJ*OggS@T d`&4[=#n燩7Ƙ8˾t+).xZy;JpQC<6zU+ l#Z6md&c*:}熝[BޒΔ+1ULuzYS[>޵H~?!KY\ :5g7W@E!*뎦"7?+a𹻂x Cx0r$:ĸUEݓM6+b1$B~|>iU8v;)s!)CVg~jo|8ԏ^D@0R,OZ粥0Z΂{4tD`SM SH7J}a\vՃxW L{z %?iI3IL ]ZG]Qh{c4: |u?=W{?o:L[zݧ*w><=,jxŎȝ,ӫsgJl_y/0ܯ$ 5fVku_fŖi K$ d4dCL:{'3I W]ɯ0~;|"`&+&ɭ"n eS,@_>[hBCrSC=s{Ѵ /xc+4ߠ̏$]RIX~ji\>1;Kʝ|EͣTݮe?Ɇ:%Z7-NjϵYYuvܝE^'Yw6T6+4wyݯj{H(Y (TYT2wRF.+ן hb I3*O}Rַy1 ~xqikNjʳ_[mԍ7{15op?">UOiA< ٤|JU7Pe88=|&6yw}D v};dF}֑m2QY.q,h< (@FJ.z)ȽAo`7  *АE:}˜l)M#w;Ǖ `<=DTLgc[D%]kHg?5UD((R)־gBdz6YdteC {]@IS(8t^I8O؏trdV0Q p"ػӿg}~R͝o4~fغx6V< ¿%K?rF?tvjOM[N9"g.vHm*|A!j%(w7ȬdS~7v:5%߇7/ҵ,Ǥ t|տwMXU2f>wXo+b+FuE*<@uCJܯ` :|eY7m &MP<=F߮$HlN@TVs?ÎPd@P'! #WY'^i5(-}(?*^ $dTp 35lvevs5H}d%BF%XW}轞[+>3Wq"U á)\v>Aχc7'~HO~ըylr{Z_MqVVht0zG5K* rEuKhݿjا< =m_`bw'4̤Jw$${=ۍlqɏ>4^m_6d#8e pHgGQ)[3ʇ˜dJDW'ʨډk`T~!,92ozTDUջ?7 Fo4 Kh^|"URo#ͼ~Ο$ VKGot!o~xzX0cF PB;o wP JU!R籑|,iU'wɩ/FrTPBZ4b[fU=sKq*#Hj=Õ5ҟsGP+mC D =!UQ"]d%H`$'"9Dchc,\ȀZRh_S2uRm>NN *N8ꂃv۰|C3j|Iޓ(?Y֕:zބ{d}g\3>Qcdܐ]"4MG w +W벭ĺϭ;T6=NA =̊Ҷ[' [MM|j$6 TrHŖN6VQ~:tɹ;Y^BLm<>S[ưq N @a;/ pK'ѻRD%B݃}δKѝ9X! *:m3tP) 3LbeQpi9xz >`:}"N/_CbߍAPYi+؏FmP|m+啷"1Ѳ~ݫDy88i=ddc~.Žo`+ݜ$q ]UJbv5a{njٯq7[ΌRsX m̺EjKAJxЪws]Ll@!? ϛfD::fpQhg#gR cĭ7mN&Nr5M/]qcdR@#ܯ;̹˻b`~K5ÿ[m0@ 1kKlQФ,Kmᢶw(D2}[uPA+@6˞jDPk>629q%Vj>WJSJ\Sz3z=haqR ֆy L_5ShhAntNdM)!lt3JF.|X"\ T%1zWU=իlIm%dBN0rwD$2/~|=h .5:Cb̎M<_gRE1%| .!1fYBҸN}HHavM2{FAj>V iwwhYڻ~)lCH$ey"ƍgzyзX,'?'տ>8e/Ա7).z=o- hFD>#3qlDByӌ[$_8OPu(}GԸI#ǘkvBjN2vw !9V&η] E{?8YSk{!GT'{Gߙ:p2􈟍Є9nliIR@[XTA]VfcJ(cL~yn|#50`YĀ$fsH%ur55ҥMkmXSrNIIPuDLz?o-UvʟeSݜ߄dB6ĬS].60Uf[^L̀]'i,3ALC篳"o֬8/sq5_Q/,[Fnml^\]c?֏?#qJ.I\Zuoxt -q펿jVM*3XʧU7a(\5{Jȕmy t."Qo(VT0Eڳ dV_. ^;{` "5~y05jRH!qRU=ħ]3[j +-2?RZ[U7q8d练O5Yiu浕?斯9O[>Ԧ&H Dt ZOnfV.!-T={@Hڬ.Z[Z5QH#}t=""(̛pRJ8/**R3,ڢdzuB DQ4( OL J'u]ڞ1'PfCm #iƎ@6 + l\:eTM͝,*]U(m^YrWR#ٲŞ#;: _ k'kZ_ֿlWY+;O[5Ѓ^?TsNCv!e}1?j9n*}٭.xR#{;$p%іzd}i%قMcvn ſUy=~ 'kfJ~ngu_(Y~}tTlow|h_(>v0YP+뾗*mw_D37&8fBpC@\OggS@T p#&,)>G$Lx >˦"59 s.Lw Ɖ$pG(j';98pGK~:pڶ^Ɖfl *`',Wi/5St<7:k^;Te-E;թ![yyh &4-*7՟֓=tv2'㤊h*GeH:{ Uwff2 XѥoO"~ {IwG:4 U'A0^^1/CzUQӲb6/55r7RjJQPfBLboOnYg%8;4jfFPLwD:t#ߐ&EUD@*_ՀEP ܉m*W!9 0^6ȏDŽek{,..`Hҟ5jM ~Y Eh\psYd~@?5UjwJ@ݫ+oNthW;*;VcTIW}b5ywl*F]y\g4)-*꿠)zpjc{V 61VTim+XPA_rWMRylLwU|سN(&I.KJ:rm^pFz)%CeV/h2!VYT2é~k`DHB|jwN"ûC>87[0P0[W~d؎X^aŅWT/ywGh#) ڮ޳rPZ+؜Q5;HkBQ~gRoꀂZBƴoѮ\[e=G~cV[C@O*8i闆鬴8?AUTF"J@Tv'̭ Y3<q?~>$AT@ RIGStH?NW l26vs=f8 f]Kk5a(>lߟNPwfϜ}͕ulϱVه`-D{`u^-|u%*qy\?{{t9dx{kL '8^Vmt!I*k4t\#?v'FلNLߜ?L.&.~[+&3xQ$D)5ߖM_4r"ɪNUS}#՛Q QRAױJu ђ_R<s/(myckŎ-ЊDReCXaj:{v6^UStMqoQH:gcf&*}cbvn*vp_t1%ԵLHB:I1xĩ$bނ+A[Vʿ0}`agԒ(S:Rz/xR&9%GW7l|\]);EE%wouoOכ/0vԵz~2߹e}4p])Aۜq⎐hOM?pikD \ua#IًWI^|wc(SJF{Jcs{D:ݡ>^hݲ[#+r~r.{rT2/䎏=$ymAVYL˰Xm~;HroHq̤o#SF1c*UXh͑ &EPڅ}Wa5K9fP5x8R(HuѨS<J OZGg<8A8Toj1xTa3֞+Y 9>v*+i/{Va%Www$((]K*TU-{Ƞm&j&-SG`b)tH+$~$ѭf ^Gw ]_Lw[ec*O94PZ#mq 悻Q,_^'VvG,6&1]dCЦklw8)e6!-OJfNկ ϥ";~cc7;wuZVְ4v1d;b1+k ngНMǴFPgɵP.4wijv<P*_^\nuF}bXweO%eքTl}ƳCKw"uAZdZ4hdiҌJhxI_u Uzxy.*&Dcڠp.ŅH\[L1;aK 9:nB]QJhy)U~XX|ۺ?bp+.!X[᩿l=-yξӇ J* 2k~ j?)eΕHpSY'j+CK|X\}[7S6G-i_ kW_r]WҜ&l+OggS@T ?oO$5.1;w3wW XQ ~þzIgD8*M/i84Ym۔T!LӉ(H:;yn0O~J6P]/'y  UkpAPZAk%0qF2'rKBܿ{Yjq"˥"+'>?B&Z4x|}_?I Apz `s#@r$we3UWiðUu9M/¿Ԧ+}S [L@#|PˤC|={~2^)g}\fBT3.纹ǭ]ep2x?@~x[oTJؗϸ:1U=-Go={u%鄎.9mm!rq7#&3H\O3δua"zE* K4X*OVOnYvRuC)&.?kfe'cJQ^jN-P]-qu2Wz/ /٬ 8XeͺGr43*o4CJTOwi#>TذN>7ouT&3Щb),P(ǚDmgkSUwF  fKoMϛ>Ŏ4o$*^)+G|LsumUf'"R7}L Bab_E9uv\4' ]o?O&pJgչvt+Jf9m=@ %n ,wvqw~Hx!=VeWdGs\3׾:fn?.ָr73F3ylU@bۂW&s-|})Y=[,KjȰ Ӎvnς5Cu~We O`wCpRw 5E7,s| =$! S(*VԲDw!*5m]r*S 2Q셈3PTC8k;]8U n8Nh|zEXRI茢|A_ru 1 yzK:e,|d(ƫ%+zdC>Y!ծ,TkE BӔ@Dr8RQ=U,EJe:3F)h{"dBjlH]RjltE:)Fh%-; ȩ;?8*3L e XʗN@ 4}iieGl g#KLg&*|ZRZgA̞ 9S'5\7z80 NxWطy4'wq]\YYo{![7l3x|:٦A~j T~jhqbO5J\W&ϭ\_@V^ܘ\:e9 >ݯqaZ^|{_57a3/ Vȩ+*K h`-'DɾIdϛP{]CD~!ǃtkr\9J9յ',up6jW+ .6V/J[f=1 v5P5]6>όCX3x 3A]WG",>=6.ܼadIP,"! ;LWnO {..e[hIN9HݬzOS7;J Q& Fi:Ee.Eŀ$ '$7Ż2}PP:]}yX`uyvc#K4L=f@}4ŕ͟.Y 4w! sZ,ȕM 97W}"" ӧ>WWkS/ {:]yGxATom^@O./ٗmFKA/jSK~8׽"1輚jL? 8EQ;_09H@M4c-e0abN]m_um+l1]nmʈ1 W+1UYf_5 _3 hݰ;]. wcc6S*}JM|zv{I~OeCS4n|xݯ}Cv\rs>~յ80d8S4p)E馟ٖے<9wgĒlgD5(ޫ~P!Cǩ|phQ ,o-CH-Lӛv lqgѓf,/==2C]?; Fp(1h0F7oh#^ }-2d482-nq:)E(J +&V<;* rd}wwЂr UY@Uq(Cyr 9s5\TPQaTA &CǫSPĔSqrYm?|6؏?V3BN_=}=P<^; 1QP2l(_X",Q'@)}UQ~k嚚[@|]nޒR堠QP.@ L"n4wJO;./d5=zlbS vC>,9}jjE']h%XvUarr?MefEˎo!n0F6G{I< 1Qר:ʢ5Y؆4Z)q]au='ltd͋.pf1x^ ^,h *i}dU 2uVy cVF5DGH4"B;pdVm4NB X唳f9侜̹L^ouK5 r[49;̾o>HKDn8ҭ0221.Ůj&5_lł&x+F}.]B{: E:لr_]nd[>=#q2e=JhNʘ Dn@}1';={i6GyESݬ<#=Hcء dϩ+VW&K'ݤ!"V5w^j{gGʼn^-"I&{g/16%Z2`;hxYuy7ٟ3T:lO|{ L!KŤnA%zZpK>YOo8 V U8{(6)tZqty֒/` qƫfsu:U:?.eXu . uXoR-x|/BC&sZ!ɣ?{i K(_#Y}mEy[ͩl| c9~gw}v huHI5 ~3B6H=hݯ˿S,lAd\"CȕyՏץ,Tl f'e}S{Kg <hJƴ?RvK1|t@)e B u#ױ1̏wpݧ upaQo*{!4-3 x4DDEzqM  yX"w:1 M%Z;=钻'^7=>uiNҘz;x:cjX1)dP* #"m8.b`b9@\N?aWP4@Mُ␗яR"z >7eg\-L"=co:Ϡ) Y%XPk-&6z /ڬ;hI!4$Υwn+aYJ3;Wby-٧5`ig8j >w1~sTH*mL3JEΫe{cuCgdTp&ob겪ElzE]{;#'ܺ*?]sv&A}[+tג"72=rus]/-lҿ~;:i_a^YTyVZjSPe6??4Ȇ?eaԞ~~.ZI6N|]hnX~zbHΉfpou$ޝ݄[1W5m[{sUwzYjjbivKcncmxQ}@I괯[{un$1ŪV2,,T7tڒHȊ> ffRF_<71_{ȗioS(cy~i2G̭u}st9g3ք]8j].Ϫp_]ʏ}n"ݪN8D)`RCiFcW$|G 2al^p`F=2xx=ⵌƏ_2k?HxފZ! Bd+ !8\A'9gbaHSĻ;Uf+'px]~=X*XS͖|]wuNף :1 .R=Ihi<>/⼻`ܨW3=)Q^[{Kt8->>LclljA6cMIoթ d[Hi4IiqnPg.F`=/=Dmۜ +҄m{N*X%^GZ5p,ЮcK@:> L4e嵧APX~ ws,:?9r>3_\̵nn#wWyV*a fhT*έFhuB _ }!a3|mM7{lY}vvT;*XɮS|{Z*;5%f0BYqYNb'|)a+Tݯ|KXms~:P$rhLTe!Foq}>=A/OgknϞ%k})2U{2S.4ӞeO6a J!.V"OGE⛖kœ6;>A-7ǻxg9Ȳ+|Yx5n},ޮ{*^;{?4srK|yfMK_*WApzE8_RơNc0R#Tzݒpv?ƶPuO+82̳lN*ֳ@SzL'VwnN/ǓF\(RiE㜂r猠z*$nȘI?zL8)EaWeWBPQ&1 +<c-SaƇNq&J_DUGM=ݵ:Q#X,vAej׿׽'jU;PǾ!b7>* FfF\ ʑ@l?}!Ti J`kWrO8*[qcY$Tl^u[Tj tnM{Z "W7QT1"zV$! T@@=/t5[ǒa͍oUE9k(zy-=hَ&RyUǩۺ9 (jTv_}$"/ɧϕ nODRd9.Sf*S5usҝ/nTXL$\xEb%xXw.V &G4T >ӛ!7#)o:7b9|@JT7p];\ez|{S |J0{9٧oπ2k[@}kk9AaZe5`)yǀw[G| $Ks ~rrB:~E/QȩH`+@-C\ﱆ긢V@|CnU[9Yބٖba.λ¯gKNpqz[7Si8K{vՁ&{Irg!#s Do!^c4'O_⩴USY&ˉ{T$t*prq@̽t\v×aL剘 ZvKK!d$햩Sa6qoCFAx='6@}jt+)#!OggS@T " (!>g nAG`L MۣpB@+Sɾ~@z̓+z(~wA1#tM6KgEWag{N/2dQy>Q˯:gtu(]RYt*>} YEErxm/Py.J@GPCɧ$ p!8FEPMVP}P&I@FuRӝ*-PCP76HQc@凧CRCY},H-*(k[+SQ}1 9;5qWR A$ۂҮV[ߤI,=5s,uEQ;pe q5b9d^_Ucf:9EtP.\WB}7K*%ۆzFZz)g .b@\! ;#LV#08v X ʋ]|E[uUޕDXw GHE(zut[lSdſ5rSKjЕ}mˏ?o}~LvnJmzMAF͕iÍn~*]h5J͕skNRDUDPvQdvPAz4j!EG "*C7]Sabw: DG3Y_" 5\2C2 UR`Hw5hJ_( ((*rs60W^D&DN ˢՌؙN{] NLS:X<3S*1(02GDD$r*ۚE%i6p}X)*۵wΤp.gľ8a+Epaם^zƚe{°r9Z *_vŧ b?cpIwɹ٩zݢ* IfO]ֈ߻un͚ER?XZ.=n۝L~'Km+ jZ\,ԣ;T㴇0Y_YVH]ʌ {+VbI3q۪⥳IKܫz΅D$L!EOrS<^aE9=?S ]c(0{p=1^:ƲjG_49.?e/GJ>ا=xrUqsEbz"POUƢ@P:,2qTXYBUZq/+ wS*ٱ>ҞH]9i8ٯd6^EfA^ PHnY j*C(EF{xbuaE EWBU:*Gvų\7c" PLTJR+1eGsw>T8S= Rzy툠5uzP+}SS {Ka"MxiE'Y?3!PQ Z#c|{6vsWI }TvmgzrlM&^ҝwM}~%,PppYn]]7~L 2-%`\װC%bS[ r^"ڶS\}bI^N]IUtj:N-OT5{ωcsq%X٠lmL]ꇽyU\?+| 辰J]x;e+/B)@Qj#+|Pge00X%V(ѲBC܄jyyA4ص$sNKJFL8WHsV!bRSi8V2 %wlY ĨjK7#^BSP_U'h5K0KvC,".5]Zgե2zv13{Tt0rݕQ:I '>M[ݳ,>,) %>hڠV!hjW|d#ofy*Τ*Deyl`Lf0'I'L0zfLWX@1mwRgv3(m`npMQhlWw=Jƿ@OLCvEt"h[@f g)1umxEFKJ:{vy*- ՁJ<@[k| Q̧n:RSC6!sU|)}XlCxDШ]ܚf: ÝPQ{jf#B2%F|@WMPU=!H/'9uAZ2k׌8$B xDP@ Q]w\*c H))^Pb%*hsbCgz*<;0*#܃JH,3s+41Wgy}^~b23 !LL !͐%EI8ΒL#e3wh&=T~=OP14xب73Oݡ`w+p%')h& '1ӯ+ o򆾚W=b7F9pM GeFw۽Hf=^ Y|kx{훉Q¯bWҁAH(!}|#ҹa|4O~=~N?N7h~OpOWx=x֔1bL% i1#l]kL`~Z-G赖)jxT dIF))skF 9aCc*"'ξވcFx,rd[4^{`9)5{=_X_ <"tKHu@/;cI `$ ~.p*=z"xs3qIN OggS@T 4j/C&8\Տ{jpOv\IIr*n znbS5?|embB6 9g RVP|EfU$Y Tjf=Eֲt6"5×y]kSQ4EEYnT(Pٞ3 ql0Hq$ Z>##OIS!C!>iS>>No G((Zyj1i9e* w5sԺՆhw(Pxn|״ԢEXDDP[QUvRI1(=qgkUg9<|UR-3sFP-N-ըnΙ">=ymڟ)$$:F|3ڇß=Z$@@k9fLTڇc4z{A{U]w;YG}ɂy'F54wlL 1PCsmv5Ыpr!>/}\*B0>q*g02ߊ.EVfʬh[eMKտ65'~HDn̍ ;eE4G9.Est,ƴٸK_Q>ŧs61JHҟB9>Rܪ*1Ȳ] sp\ֵH G)gwq?^| uqڑT}<^*Wv7ns*#Rw?񾎛} &/hU_ȡQyYL!%Y.?dyKi@$,e?b:j ޔ'[:':<:qWMV߭[.[R5 Ԥ+Rd>eus $Z^}_VωU&7V#g3g.2:M t=NqaPoBokQu|M#+lZwWĕ2#HHwܕ{G6pjnkY|*O_ ̐3 ԭ|C$HPy_OmGE35ST$4()CEB:(}𪭈0.U!H:M5^@A7 &йf6r}j=RFxdMU)pPSStaӻqvdu"֩:U҄U?f6`.@ۛpR#3+Q`y"Z,Wy*4mOB2s(̲~ו5H ]!. t^{+3 ?jHW͌MT.Q<$p$SR1)F4 5'Vﴻ RS=rkQ//Sp}qI{tMA?/o4<42X#u#+5.kɝ[?*dQC;Vn7R噢U/1 g[[*Ό>qkaٸLUGdqc~S|ϥKK |)Tzِ紡6-dl6*ҷ3#n-6LP86\_ݭi&nե"k̖&D\F3NN_~M )~4m5إ:ic^? .dvCI_īCp?G`⹿VJdf}ݎXurlLXpO4ATCgq9pS?8[W>T4/rS"z}uk2z9T婪\wU*:|{)X-~|A 4dV{8)'!՗lbV82tm|oKvQ%\|)BT& 3R-EuȓZw9^G.Y_̿yٖwZ+ <1Dg9#wm295Bܒ~e;C+uUq[X:0v]o6}嫋݌jʴam%F7Nw1S?o#\| 2b5kUny\`=DBmu\f7r9t4iv񔣰=Gt!>pzuF{ūE^s2|,#8MVp7zYߧzÚȱVb9^^{IJ>cBᷬҭ./+MЧa^iSzt q9{Jyl?9A*`ICkvDyU{z]L65>_a N={z6%E[sT95ۦ-}l~͗?Ѿb{AR`mEڦ ׸@\zVX7$]GK#>+,vz8!1LL u=}>Ogd>q c)Уhe}Fͪ&ԾZԪ˫=#-)- SWBF6KJmNjHV\e( _u$:vD*=^/zNMt%q9ލHf{y򹨉)j "XftCꈳ)4F(6Yb{ ,PaR>d@,K)wsb"!o!u6r֏M 4#~LYb61 Js PpJG@L(N{Ù8vU]ݩt3UT s}hj"tq!9IN ^Ƹ59B=yijJ ٹsrG:/kg'4<֘=b~ hϪ>~Lbgf@h8_Q#/O;55?qLm)4Yt ">ߴYeiA;㦋PnZt$fFm/:1S_0uխ0:7tܙ-UaCƜT/Y|ϓgIX:= _ybw1GmΦxٌsxes>MԮjWZHԎK,KԩV. 8}Ie)M*vXd8s#űAmqIi|lމם:tW[Fh|1|Oԓ|Ǩ$MOggST ACQ%ϸ `f40Oj@|lSu#~pxEηZ"ז 9~~(Z6 J%+JJm+ JF_~,3͏3:"*17"e]E*u,\ doEAD*LN8Nyg.U*Af } S۲ ȸ9'/R]tigQs-$*)*Jm=TJ̐A22Dld;@ʹ2 Oً(cµ1B6:P f])h \Q>fE/auWK 7vu~A+'1u %#tNOu)h߭d&xY_/5mvUWgn6ߟF=bſPx$`槟E".ss `5_Qw.J˺wܜɈc} %N=n.cN)wSnTw4̞ 9X8e"G>m,X)?d?3kIS6/]}z>|Y={clr/qvg]^ӋV=x B(3j[BL >fyܦ gbh6`w-{ߵ=MU~pg:_Lo{o7x0Z8>1/:7x3?wZ}Z{Rks}Qgl)]\c4|[]_{OfWԅZoO׭FJjmyxV?2{:{b1UX Ҭ$k5kwMh 9h}Ƨtn}/7OxwfD5U*@ FWT5?iq\Ҟ88i͘zr 7 98rODunSϏ+٧1ךexRߪ9 T۩~9x\ua[uָYJ9[[ X۟)ZQ1PuYk"*z:mY οnq#Ư=,3:u( s#Kpndk]TRl`:?!dNU]qU-wl&noޝ^bT4}+ iP8QS8Wcq|-L׿[BTUk.j'(}ҫC_k{GPנo^›-1>|P_nBزѐ3go#:{W)ERu|+QP|B 7Tޱ z$?IueQӊ6yIKUL;?3:0ѕ2Tb2Cwv>KZohsZLx̬ZqUnsʨNBe?ă|G??[7|O2cՠ< ) $P$?\8H2\Kz wd~ev.ڗ-κOki8u9ƥyUZ>RC; 7byot5$17 yqnk`uy* r[|/{K8v%yZ%, coǛ=Sڭlϻ Xg*,^z·5+59jO)tܳhVd_kˤN3!X T$ԧwU='P/4sK\rXpK3G:,/K{:.J9fu?h)ܛe +1XEtׇ{'M'p{=*l]绺y/1WI _9(jYX{f^H͓U=RZ=B.f wU?cY3s9g{NDFO놨q"Muq?ȂB-Ji3 mgmhjrD/*lw,]|UAoЮ)K}kO?H-=uLkjHx?;Yxk% _d[ Ș_[)l5X7|c޽m;L/pQ ι7g]YYۅwr=]m?uykn~,J>ލOU^f﷍E懛SNo(m#!PҪyHʦ W7 kQ+F~Z8Iȿywe':Eؐ[L~vZ0Hׯ{k~>g$6dv3+vw֔2V[8G{C9U#U sx}ƞQh*)5UYi(<[geWJV$AҠwk5ZksQwE2{`]Dtǩ}i1aC 5C:"uM? gˏ켯ϿȓX\[U3F.H-&(˳迡SL/o?r_,u Oz<׭VuKޣN{o>AaByy{.{U^ {ϳ&l:#A m̡r>?{Uqtgq21r]Y̥<+{-0zF=c[.1>i 'G0WGVU驮 #Xu7GHiH BR~UIyr1'f8x}-5v_ɭrW}/?_>_U|F~fcZt?U捸^ u=tm,?X[z\KlSp!'eO-E8[[tu迯?)%TE#*yR[qqb%QhR8bqNAP*)n0-faEEEUW* kY'Z% 6 5]ىjS tP~8>WS^VurVCxi}5Ġ폋OkC4)NqTH 8%aҫw.yF@Eش֖~0/x^hȳp] Q@VǬ8撋]/G]|(uٸ~:x;~DGiw=O4ik(*3 ӟ˼4M#qO 5kƮ0^iޝ/<\cJ\Y9V~{VȻI~ޱiXyKd³јxDKYB^5*3| Wlи=Ubg(=W%ɝj"{l$aZ Mp`oOP1xd\@,H|[:zzZZ[Y*>X[-/"ke'GJ'HbKG_Atmeڍ OOѺIVcRr!&?XLC36޵zq 7:"/GMOF.+6N6JzCv-v+ ކ=CR)ތI<ĺH}O5wu|vh ~$JvzG.SDrI~}j,Mw|yZ/ҭ6^ vc-O?,RH-dfOggST cP%6+9ܪ,dEl372.(wmiH{B޵D^ Dw_N,HNέ*mB!+V3P xƳ$zE \ zhK|qdMa|.P -g:| N<Uue_ 편2+?jWm:$*G;9|b{)ޞ+OlrO9>u:!s  5hiԇSUUSFqi9FmHz=2F {+Q<1uZ{bˤh$E$\GM@k^?J}Ƴ]{l+*7i=?ʺNT4`$\EBxskDmdWvi]yoXF3&BB%{w_3ܕ:NL>ϰJNn3 |ݳ6:N$m{_Z_n7w} yRY63L={1^o{ٮ|// lm/$VW߈$^~î?w͡OBHc^?៛8v.s_>SAs8r)P?ʹy7akY#O]6e6tE7D04 hp>e(ݕڪϿܣۤ髖"Q4+_#ZOCms |gYp۬?yniS>FډEn>Ppx ;xs>ZhsbIxQ}֨SA-<$jMРTO Zl]͂}|M6|1Z%Uɸ/lZ67Wmyj"~Da/!H-TZ||HRMڗv`!ks3N2kkƛ<)Ϩ7#0bxwK /3 8$85ʱ86PmOHnr!')OAP cP1E"13;()|"G7*z2Lʤz}_wT)'7Da ] Ps{}l \*|mBLyVO#g")WdwKgWTRE8n:#Wd !]QC${8-9Dݎt57Ρ 9Ǡ;*x2aɢ*Ƅ,mcC }"X=r$ 1G L8Jv7L',hOչ~;AU5 *ghG%^*pRg =$L~8<,q{Yoex#rmU> j+/Ύξ_I9!*$NL5s#'}XS=,uθ#`IUn&}NH^a'g 7X8/OOguu׬jr]T f:i]ǎޣPzm9?*Gp f~]ۆwOɊKˁ*y~f2řV29GJ.I\8[3]gs8<_TU[T FVR=~zD<#]%l}r5TSckNk,n/#$iN$Nq?x9=DA6COuynsmDbk2s("?G屫Y|S]uUb[S:M|}c [2?W=DjE'P*<}ךƾόT)y_׳˪1*oz>՘NiqkcCWt>ytl1gySǗ?q{mx Ύ|.KU=iܭgٽ}[Wn@;1Sv0k;ӯ(lM}՘; ~KMGnJPog1\({nRkkiNkW0 3cNP.ikѱNF>S.52孆 v6AYFm(语ԨPgnokSUJZ5ryDoY/qTQD%c^6,\:q]=Gו.$spTk%yFĴ%:2&1g\KM󓏐e8$p8N$領[2paе$th!'tUqz@DZ.{(${!cT Ct1(NAwCW^BlWCGD|Rnr.1<)f*ghatX=d\ 0{'71%؅Of1DP*;<PJXl:=4fuSuwN7M+\421;1H[7YUQerv<~M)Z|MUrr͛agBfM8X.3QM\%Sd,K!ZoESP)';lM=$78GwTO_TI.wA Dt>kS%vxl9 >-ƞG/7k]#T}fb'KG.Y{w" Nk\ ^Z:V3b] վU_1R/G3pz-Qm!)罾ֺhum\7#q5mwS7`Ït ܋~M@H\?[*o^cہ5 I C]dBud@ "6jR˕ɠTTq#cuHOSNȍ]:5|,9d'Pf!"ޒԒǟGUx͉Ej04*-|_]J/S4E,(CGƕ2'S͘%EArTPPY΀ "l :h.qT>^pV\-ErFa/C2TT'+}Z7_%j񙦹Orb̷&8ҟꚟ?kCQZD%М 0?F6,VyFJ&*`rqZ %2?XLBEPu;i_F lZ+?NPKIF4kछ 1qwofo-gV$vPU~mܣ 'G]jߓzc߳?ŤԘ;<dHM sTrnr/ͦ|Bv8?39 ߵNJ'F<+1 rȬ8$e{8odlj2% y(:~j*z ^wW( [+:klЭw51~W)2IҜ*-̇a'&GOڊq ㈒4ĴZM3V/uēm23/A}g:Fku4ZyBp'svf{MQmm/Ycwͭv߅h69n [KP^ԶHҳz) PX⯱rZkYaA~.g o"~͈b4lrJ_ǂYƪXWa^-Tm%.uLXCV"gHuu uVb &mV}JW=٬v _fwsm;\P.k)_EQ)OggST GR" 9]`[xP%uz[/<ݮqpzKptL{әKzZC> -{B*ܫ  #k5UIM*56'.Z,kDNYZ m)A#cXb0\wz٫#LRDRufS8t{,}h3(!n8smQ[Vz9m+_eeA@he8Jm*e@t/F<Q!S( ߫ ~Zĉ#vHt̝"FF>i{8v@D/7 b/_Ǖя盜LkAE NKq|nlE(z>L]$ڳ,TqJv(=݋"ى40,\kCiUy@$& o~OdHgA3JȔ @y OUɱ69H0۟2m ,Sumg-Lp^8ӃW-x)g AE+좥 մ=6Hi}X7Ң7dOND:_v&?М|˅O\\|ʸblC;)0nKw6 WkuG9r 'G± 4-G_2'IFn܃s'Yi9X}k+G1AaW}űƾA>o)B ݃\6>@W+I! ][zZ(lx^`w 5 [=U9UYPxg e'Wm{OO m?]o8;pk-|!Su WvNI֠^V޻%5'4V|&rOz8]Ji{ípSbEh.ȲwP%j/z]|h|k _Iɭߛ񶥶`]qtNm{ r 2,Zkt4lc/sG1A)?I-{L (t [gVA ,s/n Q!Οsê,Ǟ.x[-’p١:WTRagD>Aw5f(GS-I:Cպ]+Q/yHT1n(U/P['ǻx(ȇwvVEg#~-U-5U@U7ُ(:DS$]E@=4 @%uhQGQ|UPU*U?Q-7OA֫fvQ/A*g'GFyjK;Š1Hhω@uf`XU)GvaFK&-1L^}DiA_޶-8D:Ectե™RQ֞Nҍ*= U¯;#׎ :R1g`nM?By]n߮ܓojO\Zl g+|KvܻȆb8ۘ;^Xҽ55IYo|m?Ҩ`>K; (sn' W VT긫|V1k椵TS d Tuc;ָx?7 ) nWI />{S#n ^.FQf2QfkV+ R8 C6lsC II{PkM"\ t<2z5& .K}k;0ux-E>_YBœ%CZy)k2ܒ@vS7+ؽ8 %:;lzU =*+XH.qlpE5׷kK y1eqr.%ʕzQ#:.=xku>c\?O;[0VD#YvaSyը#cKa5b- HF 8Vzj`V{@dBope0ð 9l(\b~6O{ b 7t~3gګ_)FHu&eu!SvQsn:M x:^"Ԋ0kK*U2DMX(`}ĤO*zwI51Hɝ#urc}w9Yߤjt@N'"јtT3 Õón䗗s% P)>JqcFjCDWA89yt5İǡn8ޗ驈"jjJBAd] QU;%S}u Skne20bFtخfxUը]Ֆ#3z"5Nu)JٱӖJ(`*dsF"ӬQ!ɒI]e۟\M/+}'ē&Np*A>3akL@?3~13ijnik`/yyZ&oz|JK{yǩ};i;+}lE;Υ͇='/\m#e>ǂM}{aAM_DԍιZu gt #ԷcQqm&݇ZWt_5vTBBHZn]o/u[3^ ;CYz=3!RjV#tɏ,:]zC_@q3RԋaF+;%:)@_Fvj1Ung6YEI2GGtO8-ExSͿ4X=c#c=৥eá/quF<\gWO$;s]GKI2_eK5SUj*{:xUtLj&T)&Hugh,Iv}ib(:E><~ƚ{ !1x`6l|w۟Eµ T&&(܃{Ot-u~G#Pp-}Ռ۠ |6g ,&tKm&MKB"$I/Ȧ[+ )r~us D=.Gu8MEZ'$ &,'%@{(O3йh||SOU5HDY (45"2 V3\s?r<ʢ{GK 8NP~ Q PC"`Zr~*SKDԹ'z$ AD5QT?C(*VSd J6@t^:HIU ce9!J GyI|YۮªEo )̒ypfT *\^"@dT3H!%T _;(B)4t=r Zz'Wd3iވueU&p2r^9yDGl6x^V@h-2TMsj{bߖT.''ɷnYߤM-vbP9Ʀ+ ˙ f>~ AUtq2ރ.(PC>ԑћvח_'O04p@q_F]s5Njp&ƨJH{UN5?ؐx !7azu=ᦻ')W̏{B:4AK,W.yUU5ܮ(z^}8C@wTd.(x`4@({ve<_|]ȅfX(rUl䌗\?UtKŧMY~ܨ.OwcV+R;FfM(\&7$;w\R&0_)|}w)_Zܝ|s1-?jl|Q` %[g+vm6ӣ#1bܘ"5fmn2OggST y!$ (Wz-`o_Y!w{ׅ;(;v]ex:^Tp}A3MG˄ϠWZz2$<k-d{T'U&=h]R -_=!YEA;Z}lew+(@#`$8N 82PCmqڐ~=r՚qay^b|U"ǧaXVՐ! Ux1s؝yZV3ǟ"bBKDjSTНC;YK8T'Eß3*QAFEjRޔ_%Ub~֫hʣx &+[+eq뿧O@{֞~y3NQ!\k:CmSЅ8ݦ! "G>'{)SQ1c>*'6!x2ݍ*`(D#]/gtB/ ff?Qof-bkR:Sek\뭧Ӈ6v'FYib,UiVS $O?å/'6s{dG<ٶ4Rbs0)G?ehče"- Y"C?}{|n)\Ky6q至wzJ΢x_BjoH9hqGqXYU?{/P/N@B!^N!Rg~j*ypC5[@+?جZc|%RiI~E}Fjv DYv}zBu #TՓ TYS"U]֘ [2ϲMBt+٩;RSߪ5,ՓEU˩-FZ<τ]W7T'oQ 1Dgi*\*g KNb"2ãvWқ-:Nb"aY)L7P?:RW5R֨Ƕk |iR/p@>VB?>^l7E֯s76!$H ~7f-8CWdzb$YfA:{{3eݹswNM|3x/ٺ>/vV]Ewms}?cχ1 k@ю8o_ w|0ysW4]nTٲ|FۍK>AQʉ}OspҘ$eX:Z6wHn'w.T9+Q2>-ՖO6>>Ko6R fj"_ޡ>3P۩58Q7捿QuT_t w-|5IfCXcgN.,5QpV%;tG !+sIm۔s322i@>N'A!Si\.[F= *p*P PXߧ $efqrQIR8L%Eٓ<oe@n+*5C:}N~ QjWpZddŷuA-)@@0rه E+P($[\:zH͔ *t5*}g bD @XkӬRPS SJힵʒizUǵt;+5T<.Pm5 cPͿHJNJ"@3P}f?W,5UlC8 ąk#4S,PrEnGA;J9ǵ0poKݛ;W_gXbբh6n…K=vaV+7:A|[fY2'#{!Gɍg>4Vڝz)q^${$Xlm8zN<6!>^Q;tyـDq4*H7s0mLx":I잎*2ЏaWP:Sr} nwGl@8LS Dg.lR|N_yFϛKyt/Q8{_=]gŃoۂh-1i0Qn0gyw&<}ad6XotN,A?.pZƍ+]xhV>)U*0l4B=:?W^w?'Utv.Xg,H~$ o_Ϳnas5TV sK;PDȏ ܒoᛝInz:FKW=G-{B\=cTfq~O1kTϫכw>m3kw Ц83]y5\4ݼ[mA6`'Y_svfgrW\.ye_ł~3t]%?ڀ W,f6^W)A>^-ѥ{j_n ~1N x7²ޢ JOΪ!&FC@ze;[ ]uçB߁“}*TbtQd~8-ePUS_uFpf9# ?-;t-NmDTLj @_** *t"* bdd[yu'NJTóCL_{2_<*׺( y`Kmtt-ł`xXkX/}X%\}QbIRac"o/yh'SOn2.sТoKy($#MIC7 +VU4&'N: -1|R7tHW4H}j/W0N ID9|PV}t׍!N I"N kSâŧi<֣d:x4ZI_9-m'vEn5ݒ]A&_?2Ϗu ᱰv| ;)} [(ȗŨag:AtGZq(9ty\v  $*7"G0)U#CݺXՊ3~49?^JYy~>AD2l9%٢M(ݚf.ޮ}2l_%[ YlQjH dh蘀H-NdmkoU:&OQ\⅝~Yø]N.d1 6ҫmjT6OggS$T  ^3D`oո&o}@P7%*OH/ PQR0xcD$uSk像ˤ+09եH~phLj7rí+h0t=2jC7v/pktch7jԾgw":iDI*!|Sԩb{6ж$a!| TEb%V @3e'(xÕO;PPCќ }lV9CVq͕i FI$Ј:F ?SB!6e[QTEaEJ(J+M$U|K-m- dְ8:5EOL2V2+r`/RVPΣ(i 'l{e~ҧ<7XفFQzzz#{uhu4]"WKh%5 'ݯЗI1z"3=ohG*"7e.S_8WǮi7uߦ[)>\AG[[h5|nŁ{-xM ,sHEwHؙxkp k,D88 t#Ba/ 0z@rkv&9%I t}*RĘu~keJ-G2~CRtjJfdnbFt9&뚙_ zޠ_qK|Yձr` ??ɐ9l뻓]_ze}˦r0ZoWH>匏6⣷_sNWƂŷ`r6@QwUTv~Y;du8yArTa`XhPQi?Ƙb JßPȯ ڪ\ C%B'_lúa{ h]0KO@Wʷ:QAfjcmHl"^׃Ft֤<]k׏SX $4߽UYe $8YJwNum򂍏)h8`c &~(gDkLU/33(Wϭِ@ip(1N79`Y> Q'^5}8xx8e' >yY=(j&ڞdkd^MFi-u$jBkmxx-KhH[aŋُ8Z={?q( HK(>md /EPU<骐Gro"W!Pt"ۭY?* d4*<'}tk(+^ȯX洧iSJ0$ YjN_IXVGzC9ΘEct~q/aRJ"GR*h MuJ" ̤;L9)-~p] c{9Y)ir g(,%rECUaeDTV\+{'MN.*Ft[M|ҳIuWD e_Y! _뀀t.jOwZ+%BA LƔ \Vu"5bc&9ϻ[S8 ǹ}M_H3h}]9 |7^TXo 42z=Bofy%z4eh!r%!S J?_6)]@=^~v=Y旍w[hwwxPpAX 53l+%J?0 su>k>wJfEu~FW">}R7ɃJUeH8<řYQuVoS[PʲTM@3adANW"+2~|sxqVK!TcW (YȽjAhB_|*C 2@z&wEss{)}`G:bhz1 ۸@JR֏U칭5'hզb\[Msk$ǭ%\h1c;Yu ׏r,U{_lqrǑd6qŸVuߴ"w4m'+t_ߔUFu.$N&{7gI!ф(_Q32pؿzm8RuOL)ܕwh ];A^[V+ڞgh~1 zoOy`yrwL4]9`ьxT?E !" UK)D|w?e9&FjT|Pi}Uy.rq7N=<'SY6M¸Y_*JNﯱ~=tc5`>~l(raM^[&uF2˪_f "YKjPY OggS4T ' ++^9v$G;/V*1솰BQ-==\*(ND$,`SL4[urqX)ф r^n)EHy>r.hh8).K^d{Z$ Sa35}Þӣ|ߕ10C^'ʦ(+ۙ 5]hW;R %kհXimV+ #&p^'0N]M}\)P!w4x= NgY^W-QKGmf'݂dզpo xCIdl/`e( N~n~-Y*{yIFOxm9J>K|IۖzoxGo3^9ГK OYi"F}u QQұ hl\=\TI[VN^b'ox] ѽ @?eP$kJ6Q1 \ϴ*oo|F[ca{'{eNK}WܴܿBQ˦ vpJH a?GԀWN6CGce%?uVEk>R]@98.drp$>.ty.9߷HMPTA!%ԃ"Rv: OVx>N"kHdv'ϵTP™nq̚o^woydȳr]*u!b >sGsu^ Ӿ,F6((9*p<j*xyr Q:ӯP*x@4EE0b}} Ic%y duCܹ'ϛ֋Dg&tl'Nσs7C*ڱ bh;[rU|-@XY 6\<Ķ/s~cT)Ct]3 ~};٩ĨSh7v!JB63:¤Auk^*^K4} _9\J.6Z>_P-c¯W +dwX&y(ZG P/v}S+)J+ ;T4`7w-0'o`>"jH(5qY~N-G$@Aa]r~V tA4FbO8K-xjҩ.*]k{zPyPP^6;ѕ`A."U{d"ڵp*h=OLњZ"hC]bbl(n]Q8D׮ Bz2Ji8|GT9TQ@>h.}45͝Oqu~` eD.p"YbxhԘUXwнlv#ԌOOkbEG Tرg͏x"KyeMF8 /HRxDfDjƳË*]E@kٓ]Zk&B(M9)4$>Q;NOoOŻYE tQpԵ@;۾e 88g&@tp%r3 p:hVDQR-5 ԾA#)} 6qL"@u@\CE]Q=9 ]%8"YбHJ*;tKMBv~US< F"1)#Ul$~P=4DB hEXԅzǕ]\ Ը 4KOػ[dq>e͛ǜ|τ"0I8zPuH: k!b=UJ,ܭ-ƻJU<ps]=L pg򏆁$&SAseUW|68nB]~0+xT'j7(:nmEOx]KmiVJ{&jf}R򣑄mr&; Բ_`m?8 usF!f,qrJR[ꞩu|ү4%s,]%s}8ھw y){M/؟KLRw"i-V;<,^41D)K}\$U S˰WitݳM0yoʧCOO=K6h#3 d8w*FɮXNƮFGi"g?^v]jGiklof^.e91W3s=gJ1ω,p++#P_Rw]M9ES3LV8<[Uߚd1}.ctwú+ OggSDT +dDb_cە&Ȇ5ޅlt/ s}u>M<ڋS-=/P Kz^>]YȫqdEU,I>ìPTT"sN3zZFAChd;.S9]b.z km=Gh !x'Y™PAB N>Ҋ))1=ۓhgIá@Cv3v[IjҠVAL@>$ŠE +:saQE#e]v>!0D9V}y96OߝO*Ut# r^_|R㝼FŁ׊qe cl8$Q$P+yjf\0T:@웷vh9%pWP #( %ʯb, KT%S2x˨kzT.1ggvS3쳥}5_>;~UFI=hc@ɺ8˷`KH8bp )\!T#Qk4ݏÊGVD2*ܛb]׊ɺ w%.<7)z6&YD9#.">"]ooL%1-}!{5Nl|t.\Xՠ ^+73w|& gfd.>|=lr$׬w̭R˒عץT½AGsrb1AŠ/9^`"P^, y?qVvn1YjICɛi3ؔ(IMO'1̾/Fؗ[k~K~ fex_yBㅮj UUΩ ިگ4? MW/ Lo1dJ8YA}/`t}7oqw<. wKIEbRVbZzb6ޯ:G1uF]>u P*V D"o+{ߖ'qEMxUDvBwXjTjY2GBE.^Ȩ29P79uG8u\QnzhNN{bB/ܠvʸFqv6ӽ{|K'BXoˉqzdFFNxJHeFLߝ9pjC\2m+b:w n_[7Nms;}VЈwGӾ'-MںfY ߠ9UsVѰ3_4(i9$kZ`. xSq^]J8ܧ#Lf<B*O߮,X`&n.L*x|cjYdY_yxU=zfx-&͇r\Ks&9V'=!|HPS$MJs=k#kЏdwXsGO<>ٮƪ< /`S҇nu+\W#+?pY_{$ԇoB6ԷjL_ ~m&ٲܨ-d(aÛ:9dhõqg=nv͞;\O"z.h[ni~05wUA0wnuyNc7HtCVmb۶s\ k.P:Nvydes>"vֻAkT+[9Y']7͂8LShW%(4K흌BM*S--4~@s_D.PҁsPkD~f"R{LPQISbRFɗxy%S2tS~VY G$/SKߑpB/8ԓ*/Lj&y^yـY>OidUI]Vs}~#N;Y^hEs-NF}k&V+}N9ncE+֚Z،߯A|^Z2fjjyaOV;)wȴZ񔞗N9}7l ۼȵ⓫mu:̏-w}\5o׃?4tzbC{R7#sf Oz"Ԧh}YQmr]r͙ו^8Qx=/")1zHO-L54"A N* 7(iJ" 1(b%?w6ύD^L"X۬^\M۩QYbװ=ѓ!n'~_e6G/}ϫ wqg\Ϣ$Qs򾪋lǣ:Po3Hp!̦́}{.´m*m [_qc+m_7egk?hT+>\ğ{%`WK[Ck`No,r^.}K 7gMl 5fԻ3;:itA~s)/:v8MT6Iֺ]a<ȽN0Y/yk>Gב{HrhA՜'C%n,GLT }+WEUDǯ.ʇ_+(l:"uD+Bˡ@~%'"ji>T.*gy|Y?C_<~ړD^ԀZKC#" Yk&U\$VkD1;2EGA!UԑQ;y֢#JpCAՑZk1K@lh8B >[ ԃ6v'{YI }t^i>NqfA*$8D">0'/pDCN_>o:ھ |̭ӅvG<@@.4ڤ{LjL䡂tW'T+Dž7O%l =Ƿ]l]U,x9Nʡ:Ok]}؞{o3k)Rq؞H("M]-bFdܟAk-cG⑎SFs fq=,oh%y#eB7t崼"?kd:}K+ixU'=7ΕV285Ɏ _݉а*J v[Z,n_6#5ֳ c=ywg2p:h;J|_d3%-G=Jc] m~sņ$"i|kGT4J-'ԈmczZg / f}!]W *Wۭ+[HzwMO~-JY^> э=NLI݄neѲ~g>_k JB*^ \hm'%bGSTUg\㓮:$wciވzG[wVi 0hQ']//4+GwLHP1>o>T izS./I-@*z8(! aڷlKKr[.04 8%OggSTT llnmޘ;ϬvAp;WzWy:ЫSznim]|; ӬR:#Fp>k%l;>qu]۝zEBW{]J eL {xySShLM$ZZhK pRlA$jB2顥 1B@' T؞9uq?])y\n g)ľ3 U' )_3yR jZ+{;Z>euB c>j(NZ4}g<~:I [l}>Gq\"$7X_N4U9?eQPa8|\slG!g">D:^= $هm"wT"h}u!Rغ(P6m-CүM0]aźg&wa+7Ujv8G|NbN)'1sE(jg'^Heząv|F}SBE 7}mɈ|B7d4<:JSaR~q*9}=aWYY ʷ>Z7w_jM?u?~7 MۙVE|kgYw7~ĨsN_+5m@nUpP"i/|_jC&+/_/aXE?,jQjḃب5_jK_~=> wAb(l 'AbyI|\,'"jk:YfGԄ 9;^3#;Ul48JSO^QKCMuqhLy}n  rb}..ܜZ0}nߞX̒"ßJȁm7\bbyflԻW]/V :a~jg@*%boqGN,?Eu__]zO=ܗhtӜ[yC%ɉחD%c,k\ Z_q:5Ht&/ވpϴv<RWk G#R<Ɠ6 }T9G~Mu.@ {W= 2^'Hu;oԸ]5Dרߪ>vHV ǬRq FvTU/"h.fG~ΌbPr<;>hP+*Ԑ{MӊC=knTwZD3,TD*9{c TJͪ8kmZaumEvHGYg{)Z^bO>XUOYx*Uc;SwMwSp&|S8 gT۬xm Žvi8Yo{}nm֛Π*CaRMny{Un-]DqYniWKr`c ƨ:aݩg핮.V9Rg“{ث]Yk~l={/y}s\z|bּZ ǡ _ERQ +͉0;勒IiŝQu/OUfΈ,Al{B;Vo(bMk4AlHܬ49x<} ~9 ϸEyeun; g;%--\h;FD_线;HC&FEۥ1˟ QJ|~vVb/F2%QJ%u0]-A]EM߇;Lѧ>_5_]bp{,^Q˜|;rmM ys& wIZhq)}f\qRk[XZqKiudn#B޼*dE+ 5R.+=2%<^ [ukhQ^UE-䢘7xiܼ묉DOꁶX,XCkw``o0|!Bou(*sGrY<7.wz<Z][kar^n)k=@i\%qC"#LJ+.@{\1)],&Kʿ:(]ڜ(rCնGN,.Gmwy0r~|%=Ws2lݪs]aP,uY򖋼i,k. 4|V*>0΋/}6xG? ,y+OPިMDŐkn?jYu 59hr[3qv:8TҘר\xgZP(r)HzP׭M`Ł}9я8a 7Nt"ݹTQ'#!]TR3"#'zpowKL\w"byUThѵJ Nu/ dȉ>5jR֘vhG_hkvw-. ";y\=_Ug~Os>v][5p( :М(%r3%GpwDQkN7G#J@RvtF9DH>є19N3rotˤ&; i7x<:qF—#^9}2&kO'hGBqݛWCKgodCZ,jI|s&OggSdT _ tphr~q& &Տ, i+/o}+N6Gޟo۟s>*EL.σT"aF;<𖲵L-m_,|^#ggLpɌ& IC Fͬ| Z`Ł*?Ժ_V̄JDw^tyL)NS8rT L=DSmŹ ?(,2Z+85kk< HEu>UB,CFN{QIPh܏5`ёBJ=݁+2Rcnd/Y:O@ X/AB:2"f!AQTu督/ژ|\KB(oѤS9IX #WgGkNEk')}B@"z6C3tc`*9Oﳕ*J7?Mf ;v;7XUr9nšovpIINOJ%kJ%F+LXuT6_o|$^X8oJ؟7ͯЪt̷y}l^׊m]7+_vyp8xVJ74Wwxd"L:S3 Gqf+wg)?/%7jl_e! +֭EU[ ㇤P}\+l1Ŧ0ap'1]z/_i/d{9YI,]f5!f2 Fh!Fkګ YXT׿SGc|h^g/i?C6rty} ;yf[n*)do򇜻Mdeͭm_L&~Èmw❚|1 ^ 3C y*YkʧThT@Nu` u4MG{Cv:g}fJm=A_A`SJO;BT䢌hp17Ѡ -TTEuofɅyk"j}Zϒpm=>R:sS"ZA>v>džgojtAP?e!O:I Wo[,`%(ZBPهyrE?Crɀ~&tƣtT!8#hE$~Q_8R~;p[Qx$JðGXv=ly:y1V^L~ƉM0v]tLMHeѐpx!/KJ1:ޕFz:;*,ǩE//g|Llzn6O3w%{j3QyjAJ&ϗ|9Ԏt64ϨΝ+euOyI}Pk.Yx W[ۼ5T+(t4{3m%O^;Y~+յi-ż}jMX6v"踃Ѽ2|̓]8j{υjF-g}PjW2 GRnm-b!L›=߹BѭHvXS2fͭ-ɣ/>˽^iSt-vn)n[KLsh+l?,?euydx Oy˫Ƿِ1/;2Ǟ̴e8%,4t:Ŀ/nVWVhk;}nW㝻ڷ-Vx'\ҔWNC^ab*S:k4}`spvθ^gǶU U?sVfy H[Zkoʌ xSjVrg'ןS?1@%SY?fZJLT{ZLo!ۙ.5C[ }Y!F]5>=#RP jԐ2"7yX{wDdـL49g}[jf͏No[xa$faσw(IONsuȳDwv_/d[ 2$X$/k+iP-&GC&~N+KGT3]ϯ==Sb0T/hzנ |r"5]j~`zq16T?MEz{#W2McԃWt%/I0{OY]s R[C6sNk8=Ri g73=S׸ǰx_T2Jzjc7Nt _säDCPms`tY6ԮYĝ'l ѐ2! p-ζt$7MFŢ@ߩ&s +*\n헏9&o. S)JޥCC^ٯIkq|ngYv$|seTωQQbzg߼ݗS99Jnf%,K> Bz% |94OUC }^Hm]rv19XItf"tZA߬aZ,')-}aygĺy' ٶw' y`\iVo]?SZZ<"xt烒lU5a8rs8p~oq{6eӚ1(ԦDIk窿,8ޗ[,& Dp[ϢX@q} ִZo;|~PJ~vs oYX^˿jY 5)*8R[pFV*( J;]E!Z JtsH'] 4'C \|f'f Ą@u@Qu\x/}GQ#ʡR9D ԥfnN_S n}IH4ӿ/FWLTvmp$URYZ4Z hwDkԚ;̪U)tsJs$~t6Nԁd^TͅwԹ&HTz# 4G Z/S!tpdnq zv?~3ҝ+?vvQDu<( P`TL`iɳ+XO0dT\ywNF4b Cw˸vgR uH9Lv jF%̤v/G`X?oZ]n˵32r.rsCP'SB7R5Ou1ܥ];k' AXÕb=,.]%<1i`V!zD194hۀ)$k7ç8))vX V{!6wî>0)yئ5ٱs7F%߸oYqG7ZcxQy*?V[S8D֘Uz吻%5]Es݄{WO_ʙ@v i2{)\ FB5>[ :lfyYg(|Ҹ(ƺys;f.u_a6g:߯o5V+6 漳^iz(ǟk5=v}=g3ʌzw܎j ^ŠQ`gGdCګS(#a{:Žeafw m'^]rBfkYǚu[˴2r-N P>OggStT ޜz~vwޗ[_ŪPߙȔ)Ɂ+/ںouJ4kܚ-L=Z!xo y|hu4gA~3ig&g:w9kdxns$%& =9 -^1LxOdLdm0"2)p:`[mm*ݚ-6چ Fa='M5ދhKTC_qQ}5ޯLT8{ы~wx(VVN7&[w߸xrL ^bF@SI_?o+&+C5?b2/&m{~%pD3MG$mogŅ>'\Β2iU`aVOH9Đl淹aCoݕETRgᳮnk`Ӱc-9>c&ej)$0=w*2\gwt`}ݱeXVQcUpcRu\}(ZE(϶5Tހ0.~{*XiW<<%Q-U!!v$L3((iZI txUtVIye37[Z7txV?wڶnމNN,4?5xN8@'0e:?n[NxGwe)x5T%\Jw6a5U<2AG6&;^|kld x]ypؠi]˞;b<&TvovM8EX֋-3ō)n>?d41 HV,}>8[rdq>Ptz+M {R7nbHx6ț\p?gfyYi ڹOIOoP)^,1t,jgd{fzfJʺD]O|TzzTp!z2?8֧F'h; $ToZR%$uU"YTj{nB AL> 9%8ZL(IQVUKMq* YyQH}w8EU*g١Nhf1ٳ_pDP*JspBk-}$9C yw-5kpUHr=WpK`Qj_ ͔ZC{-{ֻ> 4k4;hAgE>+7aF'6s90v{*&T `aH7H PLIkdvVLejXrݳ"x2xMs,^=&h}_%G$BAۢu|D,k1s7M6Z_POܰ2M_&*ݜxQu!fGբWīh}{nn;N"'߾^v4eڝDĕ:Q8'uGōߜr.#h0y>o3[L_gs׋3)ĕݭ+"^SOOO4Zlg:Li}~Mr fj]nM#S(Et# [])1S|.ݣD* }HnCx(c6鉼^qZ4 UZ+XޗG*+c^Cm0yV3+c[WkQ*)0Dw7{4䌣IDn,\m=~p!;7'>B_,cbh/<eYa>MlxEǗ?{ ~-Y1ê{cjYʽC+w A*.2{J8\ ~eA+0|~;JQFPn=ܺ-`f3.yP~8ʄթ(zg!]˩{i:iS.!N}Uz)je ?[#|GISkԛK@73" aW-%*8GSPHV>BaItCuvz}PW Zɨu%+ Χ> sQCh:r•5Ht8 RӔWVfT9Aq㨕ZIjFDF\%SV$_ZX-9YNV)RGToJfcyA8h%p mEdGy< IEٟ]C\3nqu^5t11{ _ ?kr!켦GB'b;AS{ yU4$xyG;uC$cnBaOMB/rV(E ĶynUY78XB؆9q-I7Mq$7$Pѫ(j\ߋ+-eDJ  y*y6)v~#J撫J'ZCa KYrVu7HwU4Cew$|Fom-꾩>sŕ9+ΰ a߭.+?i٢o_+g[?5Ael2fҟڥ#^vY*}}D|<= qްnF=O\3ڤ{Ϛ}Z*Q PUyoWtZkwjFo+Ppgrc9sO~TR<TYĮX}$6#Ve#(. r|Ǚ +ا{'޷C}fģwEi,\j'ͧ,]9ʙ\bw &; "F;tmt\Tyz혐CS }%R82&/j /h(%W)֠әhʟ Vj3!{觼3e~jIKJv~J8D ͐}vl{opFxUH(X9GGCYM8B*|SJuNGm#֐5\N'BHkU SӕpP8DD`pӲekdw8{yC֠Ea ⳢZkĕFhIn jF6ި3W c/${uծEg@H}tSwEߵC &eqy8Bm5l$!xs{<{/o  ]_l"rs|?/UӘ5/~|PuƐ*ߨf/6Sgy:Q>4HZZhwim>*գMq!@Rё4ɯOz W$btT;ُX~_o 9pw+/+Ƙ\%5ZJp°lwQgkd3f𮇈9>l=E9dEqwfjQ]܈tr8iيF}^U$kݮM_CϗaYZU 1Vd &k6ժ my _Yws-;㏺(\53lj׮RafhN~|a]&Dk O˺؁FwF׸(nX1!yGr}?q |ՠjg} _ۡ8. ~\u[)7y\ǵBrOggST Vdwbw|>[L[!-ON5*${]ۛyKUw"A63x*O30yTz>^-N~_ /qZNPn{]4ܻq&I*=AsЬDwoU[k Wfǜ酮|n1 NY)X'HM~85EGd){s u9F;рMȮⵟ<&Qz88MPkOHF9{%bf 8RY;|G,U 5SG~"Т*RDBt )'wǪ)Vqw7]+;Qg8Z@k]R43 F\tB R#+{:E֣O!-´gd] Oju˝<+>_`F:z@2&Aft̳MCofCen0]GGsYґ1Y {"In I{ E)w<򉹐͛oэaɉ5n{MRf+ ͚?{?;ljo5W HXNO9]ҍ)81?B\=9Bƣ+ju U7 9\խҽ'4J. Gi¬/v{3mxi~S>"2RP=?w2x{~ g$ S1/V ݭQNO-czCɧFSw`B0=ص:7pV][O_y&,`ػE2Cͽ Ʌk~3Ti4EN0syDjy׼v鞜Hk6|gfҭs0VM=Ț0p Hb K}bVDㅿ4E4~tNY%S?w&$^/suGyFKTiC|M *ֿ6y.Ro>k6TVw:,E-ne͹/a{]o>#wTK|Zٺ'<¼T;K\u:щ灻T[,,wiVWWqlzKpMIn$A' Ė8"j& ODd䟏1ܵzQsM(1v͋[G3.se"CB:,8!"z?{Kѿ5j:eaBa5oW8D5uJ'͂'[QGx54weAf8fI$]?f=LoڳM2_UxG/7mk[KǾS/'p FP{t6cc'3#/i{*)G ǿVѝ)*s&}\1x3?-?Ft>x%]ĨOٞNNюAb5,L3Ik1\(ϻF0]X7x94ظﭷ!vbMM1^WJp޷—q}@oa`F;~,π[τ6cX 0{;5 >p&/Tn5XCXpxx@:{opܫ3:oJ5@ȎGB7͓xh\EF$PlP3%C'KF }C݉nGɨN{:EBSB6 eG֤k'NL)I{Y8}.K>I|K[Uۡ>\@eDsgǫDCIT#giꜸO%%hjTZa9B z!\ZBuJEhmR.cqͪd^i6#xؗ|2L Izrg gl[60+ϴh`Ls!@<a}`0֐v /iRgpT"D^PC4k1+S H]9e/#Y5Cı U28a*f9 7ps<@gg 4]AY*.QQJ m*jo%)8nFCݯ$Pn"C玓vОe~ԲITV$%]%r ?ݵJέTg'saAMjRW*~ԏQcNTg+Y]Ε&(`WixKB'DžC%$󪱢"G6!hQF2w_~&uԒΈUBũg(n:=9K>A59CRw 4b>{S8轒Z oy"/M(͘6# Umc$ JZ8z]?j9K-&lj\ԠZ%k^ jHW y8Ji3urD Z:]M^cEmYƉ;Ximʷ-@R+~q^//R}cU785}KͲq#ЇpT̨l@jeZfXw(eVW쮗1+rM57M=bStiBFrBz'5 e]窐|> A/b<]}׾+fk`X;)A q$NL5&mpOhsbWOt `7fW'kЋ6+i~;n#yWTYLڜn%2 ydH48RjBter8 7-ǽ;WV _ܻfcO7ܷv5gxm>۹_уpSl&x$joyWnEYϿՄ/$jqr /'>7 r B&8λZFAvJBjɷ*J40UjYk_;P٩zhϊ]RBh _f\#2Z9ԉZQ`ZX.a"Y:j]/1Or|czMTT|FtZGVZ:zN!Y2T~G!PkQk=q9Օ 8uɁ_%Ckmj@G"}mt@!^HbǗ\ckks8_Ԉ"fPȮZdy8*P;R0 |c)pP8Zhd4J͹$Pvg?nT#咽um`B 8"X {$s?]yBoPj8iۧ"H|,nOJ%Y3VRG {3D 3{E~eƲ/a&"t/H8a{bT==KEU0'@5ny1[C+u۠Mh1ӕƅԯ u`yY76l*a oh<^44U'#SVfx8ĉmt]x R/Xsj_>`C}#~zĎbq].b\ H]4%)sj{q)8a;N/~*QΟtXc6X{ f3ϣgR?-}[,V.,rd8sMc䇿dnؖ )1o$u:4f; SN^\ =E"U ͤӾx[S:W.8\1pLcwU[*_VgKV_k52iKJ*Btϥ[R[|i ‡KV=qͯU{&˺pe>;_Q c6ܒɌ͈o1pާQQwHYҗLsrι MSk] {vPԙXQtvӃ.:!dKٙ?J-Q/Eh.Yw`( u598 #*t8"-tUYw]DIYS#2=pWK=оi֣{雔MOV:uu \TR{r8SgHݽ>P<srA`J?;|cߏFAyw(O= D8`8DH:2ǟܵr3{ EPi~G:N DVHcmoOj-Ttxsh [wfR[Lf\U up :~&w[lXvoof/áAhswpsEU T+a1ܔґR*vgwy̐V&禯HCH+.H'g |]"qrIƠrT1Bt"0(@mXle~6s1Rq]60~:}Qiۄ2!Fuп-K^$mqT9ͫڎBA){-L=F:H4u:l HZ<WvRUE6~~)=e\L]|mM~wFr̝3M_唹ćTy[lS*U"ri[-dt k ~{ o>6🅹n?쿵\y"‰LsooG{8f;Ҧ9>)ua{1yu?iA pY >Ϧ=g2y}3 UgAiN1W \x f~i:DjQON?yJ/I]4T4RyZ B9.3eIҿ%:pe?&"BWAhĽvyӁ9z:hyV$  kWO5V避k}[5Okt<'+ QBfЂH(`q1>0:s&U;ZL#+i)*)y H~W=y^ArŠuߗV׾3WbfPcfCgnz #W.bU0`=™Xy ˡqYc\ev]IS1rE]%-_8Q㳽~-dr~B= K/(NosM&!cqO4ue)yvVfWkgz9hWdBפ{5D/0Y$?~JH92m?TCxZ WC4ƻ(cSv.WP侏u7NY_F6оc0;MXڬHOr]dX6oU1\bJbxy[ij^\ssѴgh?_U D3ۉE F[/rT:QkVw,gH;JY5ODuJY/chEp娀:v_/Z212c/E ԯJǯ_K7!{#zh@01&tq-%19W`OggST  ȫhra^9¤FW55`߽NS 9#" K__N ]E]'.4")j2+SdfT$CS5kQ;3џR9~r"5$ۧ 'T[6ys+Ht`KƤ$mwpj4YkG걜. .w4z;x'W5Xqf4/Gm` }g&^3;DBOeh2/y^JS0>P[M WJHIH[ˋSJ$~))Aہ& )s_5]ŋH9u8y26MK4w`?nWGԿi! ZbBUOq63 kJ{Y GgƿS<%w+7L!asˬ+\ˇl{_nR6h82tjw#?̉W e*ӡ<%.}nwh?yzHT\!ݞYsy#{QeM@lӳ'r>jqo'/SVv}%Ix.꬇+[TuE}[d B:[3V*Zu`ܗҏ X{bX8”*s6? .}n*[?K>R,4acؿQC@p廴j9o e:s|4[PVP#qqSIVEq {h\_׃8~[KG澰|a)آX*Pކ[tm`rk+= @(? v>K bX4Bz/R*E"ZMYE?!Eȣ63No_qbj8fǎ3){RDվ*sLS?Q.ऒa dQ|F٧)4`q(9593T?矒AgZprg;PBI4hg'cgA?&S%5J]UçCs:Nj=cИ80<ż쇛 YOwFC"""vMLsQ"vBI4'tuvTy(r$~hV 3ǐ%gLIz?h1ǃpCrΰ* WtIkMԲ) *_hT _9 lp{"7: Ɇ oJ\ _^rs$ 5#'Idߣtwbӂ;QS:̎%jj6!3C $~ϧ{RFkC+3Rv 4_j}(Z< *sLs7_=\96u&P)ءf^e_{8lWyNÄd RkD?ա7{}KbѦ$UW7u~ad=:^?uG{MynϦzI%s"֎>Hщp8 -'%&= HF_&둋c+]kAqZV{^bb+y%On< 'O=خ7oܛuwX|Գxpm55(?4Zҫ kwn^UA}z.*ї7&f஡ 1{afNdMw쪘;US(VnB K<{}G+ )=)O, ]gk/`w}{.qq-ٔkjmN']+i/k z.,SKZw?W#+9$O ǦgaFZ>/oSeZߛa)*\ʴ5%5[9bܛMD%c4vm( 0)Η뾟_~k+eA~.=' rWț2/fJmٜݮ0;†7T7bn4\wˁ FB-%7 `u%,j^:w1?5U]œK_;nr}WY tK:'H\/܁}P_EkZ֩]Ȭ5?Pז&?:3\Eꋦ-Z)zKx*zB N?>/Tj?pz~ EҧG35 IVߣ33F7US$P:HgndBv {;Q#kJWn.tȚ=sߏzhU=t"uD{HL1U"27nh Y)suq/GLGJ {|f<*DCRJ*OD9=@8J>[;Ì̡#p?O|Y9l RO ]+uvBR@j<έ5Sht*M><'{2ECMkD:;ؖ.%(C@G쟛yhUNO%:i'{۠LZdZwoxSĉaQBJba訮 D.~MM*c1[h2@"e7jUaxe~?*yx 5] ={uz?*^H=-OoQb#3R/*j6"/JuPm]Yl]y0TLNӜ0!i:%DrB᳆d+KD2kP;v"ԉ^Oda$6|/ΰHueI3yG;8OC=٩ۻ7[٦ӢtCt`cK`N'Mj HKzzKv4_jV t>I~+qk5gx.~}^d||-ylX[#[5dAi*lA>7YH_*Vy:[~xreS}4զ@ɮ?.ݯ";EGk#vǷ.Iա2ڡ*ۯzkEVL `mV:|෌A  }7SK|@OH O3[oLv;p0jCGu~jtT8TjPUTUȦECMt:PYJ{B@K XDױGgӹ JRoKKYNXNr\M8_uѰS|zE5J?NP#UM߫ ش4t=s8Z4N>j.W{Ǥl6x8w}vhѿ (4W?cI"vB|Ag7<,J*Ռ{ղUsGXfaT<пk duuFcz6c2ZЂg~wϵ>tC9w?I:-{ݽ,l5}/;ᑖ z i4$uE.'Ϸ Ǭܕ~p J=% v\6mu!sT߷7ԹT_壡wqp~SfK)6,n+eI Ue=*Z?b9Al(g=8o_ձ/VL7=\πqL\l9+oqv{T2髩k"yGB:>w4PAR痯gB);c~TuJט N_fomߓu~72ǼA_pCwޖ;jdadp: fRz a\h[P@{hCU[u”Ps+Ah%8"r<8w!4*{OT/B"K NNy*hUsYAڙv'cQ%$fϑ.Y(ڍ̟Zv Y[ TRJ%)u>+'Ʉ;d㕲)e_U3?7sxGC!S)ro(cC*S, Y÷kqu$Of}S{MwԻQj}u 14Z*?//oHzd9 A􊺣ATn:ł5xo<%7u\ϭȌ'-y55kW8EZN_"3cn)NϞo:9omg܇hzApq} 2H[}muʶ _z9 OZ஝U,){obwnebY05'Ѻ4|+>BtD qa]еCOݷz:+#BT~gz!Muä_s"|*.T淘bOvǭb^뎿)h|ZV]Mp%)\>hӅdKk8 "KL* NG%} تJ;guH'b ׵ٜ$<^R8ܹQ!n;7&we}z^j݊*,+8DBِPۭ(`0n szW0]z 8hݰHdTI{LW&-OxtWe\Йi5y*no܉:KG%3i]q)[sPɪ*YPQ;©h!InNQ+넼[~u??܂/ERDO:cEd"F$*ӽ_zטoCU w>zB%_}r\)JJ}$J("HkV\"E"&qPkj3NTu*YX28g#B 3EMOxfrzp(2醹t^6{kv6DTe!T"tNN+E ''s?O?4ث=BdoxVk<| T"- C֛TW-g6͹>#.&ncUnma*NC͌^gnoz|@Iިh̒ӚwI#gq8n1]o7½KםKPHtq"dxys.9~)$ΤvObk5b v^>ďa,붅F\( qdl(Sw)g),ѭ&_Hn' Mhh[WGĮ"sU`Y3:dM#:wOX`@GNIwcRG&ҹ,^v6M+|e.Uc0χ/MI Sgbu]uTo*IQ{jQ?5ut to2Yog&e3|M%xckR:gſpΥXKHesY7s[Mt٣R|O4bp}m^qs:Y$`21-yH!ᾕ~3wvcuéb'<߹r?e0?e7?NWVz}Ol}T`M;\oKU=TtcLfT~ؘu7|8wal"f]g H6A̾r+.f`{1r'AF!|Qy7V}|_`3_^[tMRϤ0j Z-\(ͳkzGD6c[Ձ|i9~xY4^+Nk.N,|rj3Tjݪgm1Hg]Ļͮ^xL)8~@&:RS҉jԟLN fD"u:TP5jUy|f4"yTEr!P;!"R /1M9Kr%q<<PG<*Iu(t8>k385*H}eU5"+㔳< Ou}-( E㴮D;A4g<ňX.Cz8 ]Sk&,yf%Yus|Q_p|ֽn>y%wmf-&zܪܦpEu"꿮!㬱+x6m̚ӬȨбRU+}4%SH,*ɻqnLZ8=rzk_K7P7ˣ=ىg\g99N nk0-<|F>r Po) ""IǒfJ8CYCr&oդk{u{+Эz=P8h=jJμ Nmz5@|dgڪ3N\{wDKj9܄/}EUL_j݃(p}#;MȢB]QwT~n( X(%shR~<_3g5N/)ja ) ^[rzrXW*fO"_Gocs#PhO%OZ*βˑ%_GԊ|Zx$ Ls:@VoecF yL{Q%aw9"`u6K9_C3rꪽV{LLmw2I_~uk?sC9+ZcGgjݑ ZF'Pk5_Z}ѕymչ]=\~׫sɃq(gTє!#km#>""D: 􅹸 HQ)J;hVT)#+BE¡sjkT#Q p(Hu?G4n2#3_q,to3-hDD zHx*'9< %_DȦ<xzIE( JWAybٲ fpCÐ%|MP-kgk`CXvs Ґ\NV4ż!J0 Spw5ڳuTX.#^]=cjmF"܉ƒ:[q\}0sMKĮ?r;j#trT|:(1wR9)o^c|]ex^JSbʤ`V{puoSrGW;Y'Mq-z%Ob]9q/d^ߣ8 h-?Q'.r4x&:i Lv+emys]֝PqRu(B;$ﺹJ`RBݍj2ԱӀz)?lR $!7OEP'|'' 3;3i&滮wN{Nn k~*g_u@o> iyy8Jrb1ZLI>G^OFTNYT!mЅ>Jǜs̺q;,%F6KʔiT-F჉>/`W<Іׯ1 pqjob ޅs_iln #Q$fu"k=QTi<% rخ VW~ G<JGҨL 0\N ">G(# ջOOD˧C"4T'A'B O\ɽ˫6vI~/yY$kޫ"UEzU)h ,oj|miDjQ8֎ ]htM!hVɹ'>Ĺsd%.j{V^ 2jC@;Uk +BR9Ѫ+=[fM $#CV.߾Ȍ$%(ZYwGHZT*s$p4EWId :qg,))Nϣy@?!XcZXfǞ/3xO?3!q?M0^0c]?>樫Eᔇ3>OA}^PW۶Ql8ewv3Qa6>΍m3_,(/Gz8Cܺ-zڏgm3 ,ɦ1q.|aV5ƫUٝ-u#}4^[b$[kaG r$vֆMM^E á+nC1&r{5xFGttʭ~ƍcci!깑ZQo cz޸MaZ䕍zd1:Vm `g_M4yR2ԧf 9@yG՛q34[V gU#ݗqH,9`$*Ur~F#D.uET[d=)lEvd(y"Uf , ":ZTBz:թ4H||V'z\ <'7\YŮNF1Thu-{D0=gt뒃HWiNUby5MrFGr qHw$h=f?rҹܣP(`(X6t=KN׏?rKqt%}N a V{ʩ#>5ԝw@:r xO o*q>|T#L{E ~,3 sN0*Agn=#s\a$ślk^o0۴FЋT)ٶSN[KFfմȬH}ZP' 1\Z]}ixEw?1qd{{WhPNzy66qoԠIrun +̱.訲,!/ÚAC2TUn/ns|?U֜{4g܊ڙ? q׾[ |}Kc)" D'SGLjgF~s]ӿ6%Lɮ pȒ8sz[U .IQX: QT w) s;37򐹟FpCx,oҍPS׽Tsx?ןMw]n)S4~_i>O?bxd}lr4؋7cD*&.!,qNbvP]qtF;㫕GVSr#xJ2n L6#dJiv8k R}OggST woamr^`dV#0Iq8z=_I~,7W@ݡ|W@?W>@wZ<>U}=U`fAkh5݆zVFߜZoSvM|@*rp dz-þgt5 Dq'\IbA\"LjS~1璁WVdaB;50>A{݌\q(2:qxT=]PZw+)^jmdk*sy9Ql8C?)_ˠsYrQkPi'NBOЈɸW_jUb:Q"{bNs'۳TZ9u{r.Yx.9y.fbuMr|̥;+) Dm@Qvm8Ln:C{h܃/Gzk*=K|JPl\_Gg)blpR T E7_jF;GC{Uc/JW? ]NYyS\}Vx䞴|vM5lS)Sukq׽]㪍.Ag}Ld5X#BN~dRטAfcfۻ?}OSHiV@=y \7JtY2x4d+ '.i h !j-#qi7:kPr) -דS%}Y#7hmvÆ 3^mvsi _P.5eӇJ&ںQ&4)yRj1ğ'?ݿpJ[A!ߐTY_a7Wf/xC]<CH\j(/^SȮ!n?A+tѻi{v~!{隟B9Q:_*yis;_ ugn2B^$9m+1aY,x;o׹ ^Q}K[^ݾٺLJĄϮ(Xwp˯R1myk-gK[^X-QklqʯKWu bu7;^lީxo4 ^;J@ 7ߙ-A @ǿ/D>DUù~lq];^j^TnmUq!_Qt7q&LvgT܏9o`hn:'fD=Dۇtd;*ɞ8$)D|x^]5RRuqRH$'r&JgU:)YṠg//q'dQ{}6YR%$, ֎%V"/bG@v_aTaϮ_59cn Ez{uܦyq ѫ%Q 63Hl^A+Q.w q5 ףabrGw}n`םv:JƵCqե.|sx\fIDU{YBmX4Vb1%m~gw'_#P|hW]{0#Ė;zpp5&4+mA3?. O5h66s:,~u*ߐ'WEv* H?qnj4{^#?"*OO)gn8;*Deíg$@?~4ݐMss05=@ zx|pzޒ5 xVwIBtq?&e ܞ3f _Sj֨:8I߃j% nw1lB5t-zDD]Yiq>K9*NEEĢхS ]gΩ~BZFՙd.SW׫&1%ӵNq𐔈fgWΌhLE21u!S(${!TgITtN莦L!tdW%jsTS%" |:wBBV&S{p2W<ۇLE|~+ړQ>WQ;HF>wC.?N%U؉[(&T)sБGMj-pPdYde#_=IWj{nJHcV5=j M7&M{bKEkM&~gR.ӷ8ݵptox}Ƅ0&G:w}U0M8@za]]I5=3ŹkIJ|"V:|i9QN*[ v'w܌+:~Z1sf\M4=s&ގa-"~g3Fw8<vwZ=HWs*,^߳xM؁=@b4 =mUΰ,WDկ=Z9 3zp4stZ-Wo=] OD=MjSPb:뜰1yt=!6@kt ׳F{V`$ޖn;ѻ,x< bJ̵x ߲V@^ğH4<ƘaeÐMmE);yf\[w</n}$zؘ oR.{_ E? nަD\.h*![pL[Yq>sWw&*5Zs@_ !;">-[fg̨ʤ E:dUО58hFIN r5'G.S<ޡd\I=Xg蚩R̵>$WĐCYIvͮܨk?dhѮkA U W+d]ZxOf Gv;YpfTӹ*- S /:IB"̐Ԙa<{(TGvQt$3#W(2+}3f#-[PA"!36ѕD3|2h=($3NY maQp8v펰EoׅSv]}jD*dTt}8 W;K`S5ahJ=g03y ̤kɊVRWf7OD},ЉL_ sy-}ϙpQ^{;8#z9p+dֹƀOt5=r[A<з @6lZ.at6S9XJQP{LIMT'3/v*ŇڢL9Sܮۭއw.yԈt@]=џt wfTK>?wZӑ^E}p;ZzJ뼖}?Yk /ƭS2䮹irO61-8ó+ǎܨ__Ͽ_j?4D˩&MFH?wh,~M2O鼉-jr3<]TCkVWڒ=QKӑK5?׎B3`lU?W˷x%7( /M!_mC{DvF];8Jݔ]| Y^[Wzj>O`Q}rih´#&OggST `ltf;wrMJ Sp>3Sn =Sx#8 C2~K(I#L࡮\_Ӄ{eG)yNWms{5SD aISW+W@8}|h/gLSgu#'BfR$oRVD崽+H,&^>,= jQ Ϳ;{/1.Ǭn˼3]̫Js rOUQ-ޒw,8o]IۉX]Vv^ 5w7׷GpFf$^s1Ѭ_aͽLڽow!jz-M\&#{9{.=JN=KV\O jupmlnYy,{5׻IzD%㫫-!4_5pI,F3G<ї .rH`Tr]2=TO1/]H<8>Jxo(Φ%2'Ni>6|Z5%ޕ;w '\~e+V_R\eC8 HyfxSefMǯe Y'IFTBu=4-&R!UZ\^)SA81Gկ׾'=43\VYv;84 Py8LJZ .VqלV!$$d~R9UBתG啹XDлtW?tJL5+aViޡf:jdҢuH ݝZHBh&N$"QA*Z]%t{g8G@s\%O-:;ȞIt0) +R|A9;2߻JIR8[q]lܳ|lW|LNh ۰%bs&Azy_=%#H?l_oz<@rƧ€,If%p !s]+)0S'Spg?ڎǘʧ Z6pb>AM{Q s3,jx_ɪK#-m:NH.Wj~PXgs__S-*t=Ut.wYSJ(Oí()/%,k,D`-K/^ (m1>IYxDl[ԏ phڎkir9rqHc::f9M'J8wj2 r$)ZSO7!yL_W@#R!pg \*{ ]1SGxpJ$ʾU"7JWvj 4=''cvMt&SGvg/.ΫTAW'ZiiȨ*ՐZGw'kQNdDNҹ*3Qکn8фFu׉~_iJ>>ߓRۭЕ2)lE~*wRQH!Yd GZU4yy|>?zێgv OGuw9N)hVR8)և)+TsN*P6šև<<>t=HD|N si_d ٚ~%  ڒ_dJ#% UkGvƊM73.(Rһy +QlI41OH{|Bn%TƑClv=y4\0i;`85̮k]ؽn_qko3R#՝'R(p&l^\1TZƳx÷7v\v4 @|a͕(X  ;gBMf _]yBe2M}zg@)|/W =VN*/j&jc"\B8k] CddB}i=FT 8tMETSuz9B~sN1OQϓ j3kfYsd>p;M]=IX"O3 1f"x^"ɑA:G$,{H9\3]/AQq?g*Uw "vpuHZ1Hs j6dԞE|t#7fsSؘl@wk8 wT27 YwloF;quuS.B7ivIs uwqpq;Νotw[_7tKf߸c+ү|4ϒh 6* t9B3;tOFsr'ҫB[=[qU>-&tZE)Fr}o qz޹Q~gDL꿄rK]Uubďuʚ*8){;?(i}<꿍gs)vaQ .> wS>mSzm2gT.r"Hn BxM g7/C=yєZںe_xqջ}eN% /mUW:b|_yJ|z8[?_y aލOh[Ƭ,>r~0PvY龃 ($mY`7 $+n* o{ߜau[/|kQc ѡh5cm˖ w[mʊblI*Y }]צ{dc\ko"o >OggST ee bahd[wZ%Hr' z;tV[('w}c3())ZAG8c CW>ZcوP xt͹85)=qbѽsNUjKʁt: oЫ5ΦDK~+@dU4uogDn&'B5`II=tYkߴ+҇S:S_OwE+nYC.v) ];뤇{ٜJ>p,Q)>8qLRHrHg t10~*D {c=] H:g͢iDgq镥|JVkv|t\ۋ( d9r*d4"{<)9krhז[A "GOCڐ<aܮ&-{T=ZmS!x@&f;0>Ƿv~WGJL:REpAh%}X@J2JXZ^̣H ¡k-dZ?5dTHl"WVvy^Nf_>lsSwu,fmu'G&8&J ShyݞnKg6YZp67U_`${p|ZnF'y=}6oMhE3da-Ě"ak19?^_Uȩq徨5J=PxrLݵgFPR NiW#&Al׻J/ b1e\k}# '*ەG2]w3Xbd-S<~=lhg1#x_9B<+7a~W4(gݜ`TGdܚ~[f -jѺVix\:s4GʐW\2wO׼G? =vpոCR\2Xo)KWF&@!aԫu!kMV.[8LDϷB7۞m26n̂˧[wp#4ntmGWֵ}8 ޙaA* .7 g\cBFwʝ!Kx2Rr|XTyjOkA)rў MV}7&.xYhGjN_dߕ hu ( \$TLl>_ T@VP$c Gq|ԝ=&ɩoŝ=jj-+ ۦB:jٳ ɁlIƙO]1'!w\# Kj ~[{/{Z\e{1\B1 {yCvֱP6g $O0.lˡfhb}gS{Åv=8ޫ:5lwjN/wohgL: ꊁ"]&#jb>{t?p X5_?X leM֨c裸^)A4cB3YCHUM:8v{+6жCxnA 6EXt?N?\Ȫ ܉+Z^˿k@4~>WD]OHb1l1ֲq0Q͖B7}Y[}_~OO=iX2͐?rބ_(`13Xq rU^[WjPY 5As̵nW k xYR^C'4ڡ+d]Y`@)w3:7 ?@ؕr zjRKxMZnk5^BR{fJh8j:) Ǵ{L4GK29+1IvœJܜY@Pc|[K 089~jD)wokh}jJ[ A:h"ڰ%w7Nѻf.JR.&5F.s{rMMyfHA9oJ2Q)Ĵ:6ɑ!_ 17"AT@؁EB{\|_#T4c&'rkʮ5?z09;*dN*}_߿P2rOfQ >+?lOϵ7dU*P~.6M0pEO\=Ֆ{\$*]1E&wJiuʺI]YZ.NO{ٱc mY;r_Zcm|dr"9\rE\zg(AMkޖGVOQn؄ ,/~69O(luOj8{8D"L݉+ W LsɥmNog}E}埙~[4t+=^!~NU&AㆸyK5cG]kRl0Qjp?f/*;tUZ2ɻ|0e!5bc|"~Ӹ+uvd=oKHYȺ;v@RhdhWK= =PQרr*տ;6seXhNsU3{;M{Io/6 9?C "/g6'\ u't.-8`3w "PZ,{4X4' N>T^Xk\+S_*+m˒Nj3QX/$_&*ipckP!7O?rT}UQG)t0/[wJd (fcȁJF{~x8(HyE":R5'}8M:R8_)h/ RAa.vtlj:ܤFs_l='q:=owDLTH2%E 1TCD,p}_>߼ (  S%Ta?J#3OjԊJ>}*U$pP+ġ%f+1сFk'tCr Nڇ<;Y~.W??kPs:5`RI;uk:xf]paN@DGh?;w5ON ISJCpםG&Zó2tx>&8쓝>>{Zc$=&m&Jeo؛vn;蘧xZC"vXf[-zUi0վHJyBՊwB|&=a~;?8r"rLa eU+2*J4fJuI7ޓ ]{)-I&7l?71r\FL0'.%;'W)cg[~\a0Bc i򈹟&%|\QԻ{W~}9UJWk~!Q* hjg3WYB#bޙu(_7 qVX}Y#SF_qvت{B,s9z_>j-Bow-!^6W5.b<*[Ɋ;uO\$ZKr߾X;gM0ع+$s٠sBTscTz&l{9S**ȶwhb½ZwaڗZu< Wފl-?aV&|ig|w>\2t[kHbYԄ oMTLf:玻[Dk~N`,(+A ֋E&mo/i[Wr5Bn;*+*(OggST !9z[cX`~[w)nV_i3𸾽ev@f|9L puvBq~4w2L)^]4FG*@5!Lޡ@ͥ"gɮ:e44!rSC1JNDYU(e¥f3>jsWS/'g'ݛ6-%ѹ+8hmHǜ>"뽿/q[8 e9Hl)>]LC!5Zq9<9'N끄?CϏ 5B/eHKQDNƆ|D6S')RX;KcvQΓ9y3R̷hcn.G_ ((|,@j>$|2jNp7c^6Y.S-fm߮:?W2]džYSĻ}Ρelc{Xp">BTU;cEvaL>OC*`wɗ䩽cہ[iz 9~6>*^rn)7u2!z5S&5`u.d3w ̝n!Dy LLxuϗ {tz#c>H9#k V[翟~.BWU,:b)`]{¿'??f*^La<0HY陊u RlizCպqIkq.^:[u|"5ݾakB$Zt.iDcv+XǷ:aNHIVN׋~jt#xe7kRl{ʲ,brf/7dz?㹏SAyRt.#ltQOnT/i˗}9GCDX`(r/]# 1,=];7U?'b͠.^W@6ק.8{m]_bMߡj˯Qް_hD&W֓_֛C%?,j [_rLr߭.ScZVoyClpKXY_7tlԔl뒿{޻G E}zflmNRbȓ[2C践_rM_⻱C(ޅDOdr 1Bu[4Hwt%"ɑ訩YG%EAŖ/HAt3S/ϖ2[:gB/nB=gC3W IZ3C {GW'C28eO;ALLRo:Kz@&tEdmif :-UuVuIt7~~B7N܆RoL͹AsR3iwkWY[xlRx?#m}n|7+oM CMR,W.ۺ< |bPo28T˟ggטXhY+/}ihOٌ9Z&3_̆ oxO\͛?S @̩F_a槝zDoFϽW~놖dB7囓4_[s!/eK%jUfq~岾[܀:ڶW]KUK^ymܺ_Ύiuji #D/(̉%zҢ rbI]lS48ȵoSn˿ySȩnwN{ۡeQߜQ;V}-#ԫwײH?%:%{qΘ{;;8 mƚ1g 2mݕ'{<>-?ZsTjڃds>״?kqܰY|^w4_1] ,֏(^;GL2)ܡ>=A+ kD%x{R5Lu2TJ8yAљu@B7@ӑN48/"mrOf5촾hPuюu9 5$ʔdg}oOIW!N $5§ T(pT$k|+MP:sH4Emt{{NJM'?GQ)sOԨ!n,߿y|RmQˁMSͣ&.I(!u'_BE\|j$D !lZ?kJD:LGQ*4Y4dUc%NT&tB_FH@g~(Df/P>WX:釶cA%t9*1fD# "G<\fybr=rzϣG>h"#Dsdd&@  p#o[E>le %l\SkRSimhm8V; Ĉ\zqSWM;li/y(f3~{I'甕s )mUSCs\18V,kᭌ:yMx;%l+c0p| n(N )N]@wpi!^~`I`³&)BT2OggST "eFFiVc]^;G8 =D ws?:ĒO K3L@4r: }$/&,;UMU '?uH?ߟ-D8*kLkp塧UI%4]>* Zn8 8-(RkN]hnwИuM{J8GT%HpEZ?GX}&AFmtk7.Gv @^%u1<> 뿎Gkh=8}]~Ő|FH 47ER u-nfEenRÑ t8]C,B Zv¤)GѢUk U#mK'eyye*P pЙJMD!P :dߘwR:ݟ_T$K5嚏 Yw/q.J0GPrޯ81>w7UG STFr րDjA$͹E2ǫ=rqSz| 4i"bɮ* ^5?Bl4ToY흮Eͣ?gqE߯cKnqfњ6 xֲ \>"hk̽H ~RkUF7ryx ] c<(?G`W5Klv5U+)UFF1 >Dc?Y8LѠC}^̰uvK; Ѥ`2f7b[ZX[lt*?'¹b|ssYtG$@ގj҄IeJV8tTt抛 i4jgbm_C(ўlrc{ y_RJV7W>gZ9<ן7,;JQL[?Jʒ1k&s:*߬w 2+ܚ'ozhbf g= @ۖU;sC;~@xc@ۢGX &i{}>Lk~{B&>K dӭNQ#ېb `"!U%/@ ;:wa'Nz5AzenW'Pk1-M:UfԐ]ShI ,b鷯:i.qI>!=H)2ף5<Ӽ' >HZDkCiZAHRznENfDOZzԩ窒q MvεDPrM(>ߟJ+AX>g?tۢJUq/fokB)1?s+:Ɗ{=b>39UC\9>SbÄ۰ fk"z+}K !i g]]lk%ǝ̗f7HŬNOi AƄTnV(="!+Y3fpr.!QE*H?Ѣjor'Jmz۬tI/wꁱCg7 RsۼyrӋqvS} +zt鿎7]}\zZ~rRRngc|Fc̕B0uN.񡗯X ܓzCy돌ZAf}i^Ÿ;i^IXXXƷ%ycdI>rI)|́RD.:<@qdW H݁&y'yBnh8k$?ryG)WgW+zPqFi5S#NBhT=+6)I59(Z_+J]hs6YX^s;/;${s98C92QJe *GI訍P WŤYْ5p4F͞puNup0)bϜeyTO(JEkT}tjIB!CYpjPYVRqjU5O}C ј*UƜ2"}nog']8b} j-?MxzUnu،xbEpϴ.oGY[Ԧ+\/it]V#:X*^oӖf쯿G#X;Tnc[]k*Dl8u e-~s9mNۜňba=JM$5 (dD{4C/[\*aeBN)Z9hkF'\gjFe?iD\nBRL\*\au[# `$h?R{7ĚP2Rig=Av ^ ] 9QwAV!gUTeJ`ԃ4}MtBu{xt|ZT*]dR1 !+;89!6z=cSM2ՀZ)̊L+ՏM_J:]锈֪*$RTaGqM5v'3!BG׻J΃l'CSd+k7c=.Yg}|9$hN0eȤ;rC@T Bz(ԪA(dm3݀;5%;zCjj R*]l H?k\ HJk/g 2q [3>R&IeHs;)cXKHE5>Efz9]Ls꯴}~J12o"WwaXz\= JsDε࿒G?b{oD3kuNO꽚 jlL ǎ;G}ֿ"[ŬyqUqf|UYI&]W2+$fxvK7땵`oq}e~){/!hh';ĕT!b%՗(UqE FYⶑ4w`ਧ'&y[rNl&?S}i\Wvy¤sjys,[ go&gW@;UAn'3o].r ~-}bxP_UT= #WdK85wdݪ^s2L"FRU+Q[U Bt.~Gu3|qՂ{ھ ms0[s%95OjbqW 8Eӗ攭ғ4}Te炮'ۓ|JȌ5-kiلhqxNoS{5%~|h)yْ7f}(VW>9$?o׼U3'- hW431 OggS$T #{:_aae[g)P@n'_Ud,Sh:+RǶ@vW/ZkgEЦe.T7C%`%zIU^pdgwܑJ\}p壪1sM YUA& 2dv*5wzsy>83 Das:P ܎/3!?_:KlMX2*U=p",OPgmᘚVN!k"2Jkz:Ԍ:DD;FUfTɠvYoЇ9+_n9|1W+N{Ǒݡ1?aεns~clQ -||}ڌ1)ytȁ|x|)dd1ӡw䳥?3;6#d/i㖮˶ٳmRs+v2w*  8ŠJ}g1܎$ -~͂K-V;<'a4r?wt܃᫡`_mya^rIDr>65e /Kn^)6grmɑ*83-Ď.H ۞Lj؝D]{Oˋ/%*%iϸ?1mjyԍC^{-x߷6<{j#݊3Eu_yJ.6=vE߇IPHDoFgwo)NN=!X<MMLN*=Ѕ`v'CeJ2(]HJe1z3Mξw;6/2]b>?4$i|gZIXԃ\sh1.xt *k J _n}iĺuRbEˮ$f)95foͧx.g;^MjR * $9&-[6c5v`e0r݅Jv@'{<_; uVW =!~mN׹^1OyTdi={w;:.-m~vi%y~~1P7kr$ψo ^ک{5\ߔ[GIP0nTR Xӷ؅wLBҍXAޑzmTl,ibmuKDw?C;f|v,bԎ9~|"HPfɡU܇AlyB`ňJ]w9Yqaxb_j?YHwí?/*Qhݴ ԮL,_e/|+(ddQEu7Ԫ0] P\ELu.J׎ݹ^7Q:KJЍ<83 tsFlD@ г6LDvD頩ǥgS=j\[,RѦ;95P8E}'73.暿;RB ,r2RTx3"#z%y(vBΉ ɆrǡhT<]䎂 .TGBRpIپ.m5]]̍mBvL UN"d p͞?;{\i6 *NͨML"&{O{ >gl ԷI 7{o9 S_qSnYkQfzinb\O|G~F?&r↸;[(A?'3-ϱM "fc6L[O<8gҋ *>27$m"~c9YyxnXXzԗ \IBTn}l to?ge)4;1LH 6t!O\XM#2IR0Ս:Q]w3ߋut xmO%gD$˲YW)"U޼6%2%m؇, z59# .Y3l&]BYA<^\Ew\̮ God095}"A! Ρ~vvR?{mJMkZ䊓ʉ;zO\5/w.bRr>w-;f$xw +m{ I*Y_TɾUiv,Xߞ;{%Hh'Þ bpM`ŋj]Q"ϳ u8􄜣 4ПF*fucKC@(u塓K1)ubqI%EkCP P:w^o$䐰.N~DԜ$UXc'cבe?dG3Dd@$`SԽ$h8N /+կ +W s\!zt` *EЧg^-Y9ԿY}Q1O.;"ZMȚ"ԕ*)$O9mc<:2R9Z2 ;QNC2:Nj@綻f{"y?l=~Os;Gu  >Α8Uf޻?!v= tz%biho µM䡀2$Z/$X_{IR}8+ ~^Xϰ"fBr5hH8^-r .sJw>WNOړyu|Kn!I%65ډTv :.7nPMo [xhV` q|Z"Y1wA -E 0H7!"M+~<[RYJd}6.ٞTaqiTU۰,+j!.ŊSrKab4]z`\{LN" )IS S,AHGd}k^ bE*@u1GeEl{`{}njeuw\37ITYĎ%ء voz7g(v?_i -6N!9cKf]ym?d.>gEr!OMG1Xؚ!vY4bTДU Ny|+jƖo\_[6kP[^:7~iDٝ:Z,ni`MvgOkIaG?tN':,G;^]J">, ̧,p #,.H$sMg3 @r&s(:6G(P8ֹ: C2~4xWDbo* (63`ae$\.-s G~Mv!n_]&Ru>ͶI]4{ /CC"m Ԯc枨-ur煽cBBәz׮U 4PKhI=E*Or$L1ubi8⬫)B>,Iv-x^LAȕi+>%,;SM/>A͛HØWwo;Җm|oLݧh<􉊿W֫IƿUaO! /{).zNr![OCurz}5yaM'ԯ7xw}FfAWZە۬1Yz 7IL6?TUy=Zlub|raC 9 >.-z'*T%"\N‘B aO NYS/:y wϮеy.^*fTgϩ@*@!i"$,7_:GIg$!99QF0A>N MD"h%dgKѱJ->`rdQjDtݝl=x@ʎqXaFpϟEGIP%Ai> x]&Pv4R КݒsDM][S G: H/oQ:]85+TuQmt5Gja%@ ot Et4z.2#|\SuSluksOYEdK9} *GJ y5$Խya]Ǖ/5).{)w/;r7 WM wa[^=:t(4~pjDթvYxmi=vW! ζ>DR#'vR {:Y |fjva/Y`*NHbsٙn]}@;Oe+&lsa)Jfo&ۺ+7m~E8*R?j{ /y+!PjvN.od ^]"29>0_?(RC|I*+vPԍ+{n>'|㡟*^KQìew;Z$0_*>~3^1I:/*X-k'us9o@6~y / \pLz&eΕ\.}@:Tk64'b^ ({nw<QڽԇQrn]̕/%{v߄QC8Eav*S)ߔt8K-ǒW$<_B;[(xX^Р}-+w׌r[֢_ۥ3BFf KX4vy ,p`]w7/]~<VJu$u"g.RoC=yd/ )]{Ni/瞧ԳTzo8JYi9=O2d|R;5$EJft9Kޑ5v$'UzˤNOIWLh2* SU43SRCtR#"*Agr鴓ή:3TsseF̾W^Zi֢2͂Rn ulZřG"ٻPNȑ4EWR4 %.:'Sj4wT9fģɚ>\DZp<wT}-).?]'cE%|ACDAgp;n\ؤv]dn:G~W|`) vJ {ܹ-g~O`V4faT;7XG<(j^ tݒ%yOo^ p~`e+tgLjz8r߇WԽ:=XgqՐ\c<2X1'6ߕ[+8d[;{B.Y[6*$X_+Cn[[y:?_o=ux˟zAZ<|ahOjÖm 0Y<\t"*"ţٱ7vΝY}HK:4Qh7RDSϧ5BQ=*Y qD'^5*Qcn7mO8Sx/kS\Na%Jg桂E*±c]]?h֮0_p Tr0D~Q~r/sw,O5;wӏ~f{==!ßL~LjfEa#H-qwy"Ȑb+<0(F'o$d @Z0`1Fo_N[xktgOәĪ4_Ρufׄn U^rSGxt[WW~\d6'V׿xϚ`/\*]T_JpQmݎ`}T(:cN}#:Uf]O7Q~0ؿx2AWvnj#1?9xnԌ5_x aƎOox<S/R[e|EGjzQgt]3D3 ݵ4V'9-vI/BٵUzƸ9`{SON,:zZUpiKYs-{r v`b{r¨TF<ęyWC]JdͮJOKkr]*vTy~}g'5̴17k48g>ޅ|RM"׹814^^>WjǙ.eEkxy:9_}w>=~mfwu' fs{N**W*o\V^ԔgJ%6C#6R&Q_JK:{SjUj:)nݢw5Sm/yPyd֮_.]ū 7J'_թM fd#52H `o&9P urDhWT r'oh]>}"[#0}|#=|#ODɦ?SDPqRb#3 ī4VL% rLplZIoMw?-G|h#yALR&<-{rF52kcg|%!TtTa w!x{Br64۠eRk`UNU'<>1r#.,W,Wk=]dԬ̥lx Mg fvN~U<R[p(|Ӄe9X8\|F:cć{`GR`;Vb6J{ QWl[3]+N^1 E1Yj=\#A+6}a+q熰Fe 흁~=ysB: {oV{uⵢu35"*qS-ᶝMV+Ϛ]L4 T?} niCWc4hP4W2n3_^a|v<ݤ)u56cWAxk=Õv[u=gX\r/w=F !4άvQ5wbiǹ.+[/\SY}zq29sN{ɏ/ٰ^ȸ̅v͙ly>aw2y3^n1MROggSDT %mbfkfe^[[bV d_S'AP(XO4S}N&=Y85#xjRhPfvnR8>=AxڂNHOcoC*G%e$]g3mfX{e f| }UݹHѣ١d8إ|3P%;wZu2iFguU!)U:?罵.zt3͟#q=OA;dS|̞tf3vQh9#Q Bs4#NWHE:Up걎I Nj]ȮAչy>r(>p L"[1%ǩ1Uhp޿|b UH#q ;  hj'{0'3.E%e?D7cl:2޽w$[4~ Z[wꐚ  ڢ~ܔWحYWSEK]oYL0-8EmM3nV^>?ylM^܇߮W;oTb$Mޔ;5 4` [לU``kOځ>adž"~ԮYi"Fy2lmU"U#vN蜊l]ϳC_ܕu.\i8:ɑ{mnU=9ـ|%B<:dO=j‐=ZСk0Q@KJG0MG2: -NAm8=_빷HRU9L੕vIBI}Y 8Ե1a tgk4SAT)3$}^jRtEVia"S48T4ȚKY?UP"; + ZCPY#Ql89P$Ԍ@m)}z2"[lH纶gE䳞9A5Ǒ< 莓I1/{G_ w vKtk.jљa?o$[׏5}T㮱ziֶ;$ROo: wL&#+nOՕwLpؚ j_OEzaihla^X<e>s:<%n&uAXE=LwL|?nƙ6jL} ։slsy\\iggg]>i}f{VGx|%^~UF gWY_yלwT}caZ;>>_quxE&"pFQJ*ouShߗ=w<>͇ !bͻZwui\cR+sv?}lث!(\UDD;q^(PФzk(<.b,*^dfMSf x ʿB]XA}n:KWe{>j 0U:haʍl7&CTӌPS'yS y0HT=ԁl+ZunnP),eҌlє)U꜑k:_U*##RxOt^ ե5w2š+%F{H%S#QB35jU#˜/rF%GE=E퐹}}O<"A1@'?=A8nR.9F˯t zT*zdbڡ. DDu> Ԛ$G8 ҿ.]FyڈNўҤ-[#"ۥfݿ}>o7,Az섙 I.\@QUrgL3⡣]7)dS/RWLB)^A@}K{N{3#&54p,s\sUJތoZJOEdPݝ׫G ~ďK~7eڋo!y*EQ: ܨBJ{_۰Vhl=#54p.1FB i|| ʬm'f)r,!Gk3~.9('{Q5/^8hԽ1{qztx..Ky$}r[F:[K&>W*?outQ7(R=k]OMG]"[}1pM۴@"ܮlLg/k8J-{R_Yx݋uh5PvP>&6&C+BlhCݻT'kQᡤ#<,"W@::5muBG}8JרJTu|[hGbb'Jtq]OlFIMIɮLQⱝg,vvəhZАIGVre/r_B s޵= 5cVY>T e`LPnC H-0(tԈ[֞IRdmF!{^G=fnbLz0N d$P~zQ/ÞϏtpu^f|nYCwIDVtulgd'HTiaKÕ K2gmm}w4d?"R."^βKZݕOFC)& )9#FȦЊ$\<^éԹ{Kq&10Ӥ7s3{<nQΣ90(TUrZ,ts4jJj2ܪz#ǧ^O6hba #ՀuaDn'g|f!|ofppWT*ʑ6t?yޝzD^"FNz)@7o$2}j=a2ǑQZҦ9-{urp>mpjcSdj; 0_ B:72[Ebng eɢ쳘_r)*'W 6 A|]X7$}o|ug 9h[l,$4Ȭ׫BmVvc^?QD}dGQOggSTT &c9[mgi;KI0~pf V:i%PgM/]Rl8NIuAZ p!ݿZIwx"5̛9 %J;BLCJ@=Ue5%i͙SI(#};d םpd&\T 8Χ"狄T4i,~a -Se^^/QP$qZYq&[s:)rJR*]''HG> U|FJjo)>O_ Ȗyf6ߤ[1=~uld~̎dyqV;/.2Qۨ-B8"Aw|Pe+i{>׆.-lTc?+EĕK\F,s=F;CA >n$sD?%3#v$1SUKblekq>k't?R|닇dm:u3sL8!&LI'YujZL.JETڳ3eEjdԒ85{O)fQGT)cOGntW6ҹס :h8U;B7֌ke*!ҝUO=$Ƚpn!Ou:CX9W͙- v%]A!MFB!u}<>7(Rrdn*kQ%Fy{zEpb$U{sM~*PwG۳t0u5ُdj8JW0E};[bb"hnTHeHxF(1kM?Y/tl/4[i=RwT]f}B>j:_R'3ow:6{ʞi1i ͘Uͻ7r-l~"=~Z2Ww-1dwO~?W)FxZ8q~W\Ch "+qT>&av2L.‘SAan k_\c6?Oa仛Ǥ{Z{"5y؜긯Аh=[=9'to_~F?K 4uo=EV22/m#q@ncǿEᗍ|XX3W6`r&0s׆) cGwUi4~AnYu SD>ES\^.a\~_iz۞pOj_n`MEŽokwC12YH nѴW}u A!c2E2/c5@=ZTK;4zI_Td^p5D"SsI8y}ܗUY=p28b$#1ZL&^f-4ܝHڗsXN.}F;MsYt l;?ή-9w*k4XL/mB /Ɂr})>{V"ܮlg.2,GՒ)O"DR VH)ow+q%<"g}~StCd~}^E@s ApZB?h-@ 9*Gh 4O\0݂׬D8J@TejpZ5 =I֌Pn=^dT*$D8s5mLhSs}1 GqT^TMz[SIHЏe{9hG7y0e?|>#Et' Y?=荖-IpʜYC2$rn>O.HfX/\7w7L?SF_c?`)R]-m\.BΌ3DpL0 -4+*ÌjRݔajû(h|톼[|SڜrKक़Z G_n%X.4tJ`bNzp)㻳8՝I9L> +ҭd3.UN.q U4uЕ}iUʮQ۾V.AZOŌ`ğDXg Ep]4n`|Tk$La,˗Se粸H^3 bѴA+}Ӓ擼H(&YkߍWu =wUo]|kL=m`@$rr6, Q{nc|zpkڣ zbl׮j32ϋ9*g, R`m f<텶 4EϓzVH@ՊshAЗR*KP}(RrFӥ&]JAtw梵s>iPOxJ"UkǙK?=xu$JlP4תgW(9*qCU*U/$4[+\]#F4sM2iJ\YU{'\Pi(FimêNqY:%4upZߦ4€XL/. zc0eUBl( ?j'?ק?Vi*{wd~\IuQ5T|NIVߩUtɄs^"m/1Uvk) ei-H~J#$W,-k`kHknB_~-TL$bZ\hñR?E/c2źRaS[ Y < mǓNNUCt'Jupf6] u?a/AK/1s_Im#9ۻ4 떶=ՠ^ztIfPjG[*oxSFޔ]G =!uTeBDH.1ٟ2w>S~YcMUF{ͱR+M]ʢT"Pœ")=m?0%70vi]rϋEӁtuOggSZT ' n^gZoD)0|}>'m z9(r(fx,Ϻ(,'Χ;=c>[y-:Cj"u !C"6jyU$ByCk)t^!6dC獧SW߃P4kT1qjD̩] i,ϤWvM*)jKkv>*ffUd k{rtJ%GkdӮ㯪BgEqd׎}o}y*+].wlRhQDY*Yx1SofD<9CV88ncE;¡5 ź%Oкw'JS. C&{ϑx 'D{AJEK{;!_!ph"|εd :Q)PVړ(f]Hpu)IPypGn Mu~o:5qo&WyZjgQ;/M"pآ]}?F**?G3ǥ8$;ϕzs9q4"ذZ~Ki=rXyo?XOaz'*tǛ_6:o~j汝eHø%x]GٸEBT Fު#N> N4ϟtOmFS"F:. /^W?Zk"Ĺre5w٣bATR2ۯ{Hl^Oݠ&ը>5SW;*deJ^hhb\bXѡ?ZMhƛ`9VRW`si(_;~IbMJOkJV5M8hGNSA{%_`1{H#3i9k:xow yވj\J-]W!i!EU㫶I׊9csyzg]dhxyoʣ.n͟s/ W$}Ƕ}_k-|vItٲm8 ?X$3-n#ܹ'icrc{8flocp YAEE>a>rnn'%/95]UڵT,k==sh@G=Zmpzt[vu Cy< 7v ukui-media/ukui-volume-control-applet-qt/data/img/application.svg0000664000175000017500000000150715170052044024111 0ustar fengfeng 画板 1 ukui-media/ukui-volume-control-applet-qt/data/img/mini-module-s.svg0000664000175000017500000000147215170052044024266 0ustar fengfeng ukui-media/ukui-volume-control-applet-qt/data/img/audio-volume-low-s.svg0000664000175000017500000000177615170052044025263 0ustar fengfeng ukui-media/ukui-volume-control-applet-qt/data/img/microphone-medium.svg0000664000175000017500000000174315170052044025231 0ustar fengfeng ukui-media/ukui-volume-control-applet-qt/data/img/audiocard.svg0000664000175000017500000000144515170052044023542 0ustar fengfengaudiocard32ukui-media/ukui-volume-control-applet-qt/data/img/audio-volume-muted.svg0000664000175000017500000000176215170052044025333 0ustar fengfeng ukui-media/ukui-volume-control-applet-qt/data/img/audio-volume-low.svg0000664000175000017500000000220115170052044025003 0ustar fengfeng ukui-media/ukui-volume-control-applet-qt/data/img/tick.png0000664000175000017500000000024115170052044022517 0ustar fengfengPNG  IHDRatEXtSoftwareAdobe ImageReadyqe<CIDATxb`@@@<i m3Hlgc3d?#Bv!B^h#2 ⎞QZ IENDB`ukui-media/ukui-volume-control-applet-qt/data/img/audio-headphones.svg0000664000175000017500000000144115170052044025020 0ustar fengfeng ukui-media/ukui-volume-control-applet-qt/data/img/microphone-mute.svg0000664000175000017500000000203415170052044024715 0ustar fengfeng ukui-media/ukui-volume-control-applet-qt/data/img/audio-volume-medium-s.svg0000664000175000017500000000220315170052044025724 0ustar fengfeng ukui-media/ukui-volume-control-applet-qt/data/img/device.svg0000664000175000017500000000162515170052044023046 0ustar fengfeng ukui-media/ukui-volume-control-applet-qt/data/img/audio-volume-muted-s.svg0000664000175000017500000000171115170052044025565 0ustar fengfeng ukui-media/ukui-volume-control-applet-qt/data/img/audio-input-microphone.svg0000664000175000017500000000115615170052044026205 0ustar fengfengaudio-input-microphone32ukui-media/ukui-volume-control-applet-qt/data/img/setting.svg0000664000175000017500000000177215170052044023267 0ustar fengfeng ukui-media/ukui-volume-control-applet-qt/data/img/microphone-high.svg0000664000175000017500000000170115170052044024662 0ustar fengfeng ukui-media/ukui-volume-control-applet-qt/data/img/audio-volume-medium.svg0000664000175000017500000000216515170052044025473 0ustar fengfeng ukui-media/ukui-volume-control-applet-qt/data/img/mini-module.svg0000664000175000017500000000146715170052044024032 0ustar fengfeng ukui-media/ukui-volume-control-applet-qt/data/img/tick.svg0000664000175000017500000000102615170052044022534 0ustar fengfeng ukui-media/ukui-volume-control-applet-qt/data/img/audio-volume-high-s.svg0000664000175000017500000000162715170052044025374 0ustar fengfeng ukui-media/ukui-volume-control-applet-qt/data/img/complete-module-s.svg0000664000175000017500000000150115170052044025133 0ustar fengfeng ukui-media/ukui-volume-control-applet-qt/data/img/audio-volume-high.svg0000664000175000017500000000215115170052044025125 0ustar fengfeng ukui-media/ukui-volume-control-applet-qt/data/img/microphone-low.svg0000664000175000017500000000174315170052044024552 0ustar fengfeng ukui-media/ukui-volume-control-applet-qt/data/org.ukui.sound.gschema.xml0000664000175000017500000000476615170054730025351 0ustar fengfeng true

Sounds for events Whether to play sounds on user events. 'Light-Seeking' Sound theme name The XDG sound theme to use for event sounds. false Customize the sound theme Customize the sound theme false Mono Audio merge left and right channels true Noise Reduction Mic noise reduction option false LoopBack Monitor your own voice false Volume Increase Output volume Increase 125 Volume Increase's value Output volume Increase's Value true startup music Whether to play startup music '/usr/share/sounds/Light-Seeking/stereo/notification-general.ogg' Customize notification sound effects Customize notification sound effects '/usr/share/sounds/Light-Seeking/stereo/audio-volume-change.ogg' Custom volume control sound effects Custom volume control sound effects 100 Alert sound's value Alert sound's value ukui-media/common/0000775000175000017500000000000015170054730013024 5ustar fengfengukui-media/common/ukmedia_common.h0000664000175000017500000002375415170054730016177 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifndef UKMEDIACOMMON_H #define UKMEDIACOMMON_H #include #include #include #include #define UKMedia_Equal(a,b) (strcmp((a),(b)) == 0) #define SETTINGSWIDGET_WIDTH 760 #define SETTINGSWIDGET_HEIGHT 520 #define PA_VOLUME_NORMAL 65536.0 #define PA_VOLUME_MAX 81920.0 #define PA_VOLUME_MIN 0.0 #define UKMEDIA_VOLUME_NORMAL 100.0 #define UKMEDIA_VOLUME_MAX 125.0 // Init Port Volume #define MIC_VOLUME 100 #define HDMI_VOLUME 100 #define OUTPUT_VOLUME 67 #define HEADPHONE_VOLUME 17 #define HDMI "hdmi" #define BLUEZ "bluez" #define SPEAKER "speaker" #define HEADPHONE "headphone" #define HUAWEI_BTO_SINK "bt_sco_sink" #define HUAWEI_BTO_SOURCE "bt_sco_source" #define HEADSET_HEAD_UNIT "headset_head_unit" #define PA_ID_INVALID ((uint32_t)0xffffffff) #define PANGUW_SINK "alsa_output.platform-raoliang-sndcard.analog-stereo" #define HISTEN_SINK "histen_sink" #define NOISE_REDUCE_SOURCE "noiseReduceSource" // UKUI Audio #define KEY_SOUNDS_SCHEMA "org.ukui.sound" #define ALERT_VOLUME "alert-volume" #define AUDIO_VOLUME_CHANGE "audio-volume-change" #define SOUND_CUSTOM_THEME_KEY "custom-theme" #define DNS_NOISE_REDUCTION "dns-noise-reduction" #define EVENT_SOUNDS_KEY "event-sounds" #define LOOP_BACK "loopback" #define MONO_AUDIO "mono-audio" #define NOTIFICATION_GENGERAL "notification-general" #define SOUND_THEME_KEY "theme-name" #define VOLUME_INCREASE "volume-increase" #define VOLUME_INCREASE_VALUE "volume-increase-value" #define SOUND_FILE_PATH "/usr/share/sounds/xxxTheme/stereo/xxxFile.ogg" // UKUI Session #define UKUI_SWITCH_SETTING "org.ukui.session" #define UKUI_STARTUP_MUSIC_KEY "startup-music" #define UKUI_POWEROFF_MUSIC_KEY "poweroff-music" #define UKUI_LOGOUT_MUSIC_KEY "logout-music" #define UKUI_WAKEUP_MUSIC_KEY "weakup-music" // UKUI Theme #define UKUI_THEME_SETTING "org.ukui.style" #define UKUI_THEME_NAME "style-name" #define UKUI_THEME_LIGHT "ukui-light" #define UKUI_THEME_DARK "ukui-dark" #define UKUI_THEME_DEFAULT "ukui-default" // UKUI Global Theme #define UKUI_GLOBALTHEME_SETTINGS "org.ukui.globaltheme.settings" #define GLOBAL_THEME_NAME "global-theme-name" #define SYSTEM_VOLUME_CTRL "kylin-settings-system" #define UKUI_MEDIA_PATH "/org/ukui/media" #define UKUI_MEDIA_CONTROL_PATH "/org/ukui/media/control" #define UKUI_MEDIA_SERVICE "org.ukui.media" #define UKUI_MEDIA_INTERFACE "org.ukui.media" #define PULSEAUDIO_DEVICECONTROL_PATH "/org/pulseaudio/device_control" #define PULSEAUDIO_DEVICECONTROL_SERVICE "org.PulseAudio.DeviceControl" #define PULSEAUDIO_DEVICECONTROL_INTERFACE "org.PulseAudio.DeviceControl" //设备插入状态 typedef enum pa_device_plugged_stauts { PORT_STAUTS_UNKNOWN = 0, PORT_STAUTS_PLUGGED = 1, PORT_STAUTS_UNPLUGGED = 2, } pa_device_plugged_stauts; enum SoundType { SINK, SOURCE, }; struct portInfo { QString name; /**< Name of this port */ QString 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 */ }; struct sinkInfo { QString name; /**< Name of the sink */ uint32_t index; /**< Index of the sink */ QString description; /**< Description of this sink */ pa_cvolume volume; /**< Volume of the sink */ int mute; /**< Mute switch of the sink */ uint32_t card; /**< Card index, or PA_INVALID_INDEX. \since 0.9.15 */ QString active_port_name = ""; QString active_port_description = ""; QString master_device = ""; QList sink_port_list; }; struct sourceInfo { QString name; /**< Name of the source */ uint32_t index; /**< Index of the source */ QString description; /**< Description of this source */ pa_cvolume volume; /**< Volume of the source */ int mute; /**< Mute switch of the source */ uint32_t card; /**< Card index, or PA_INVALID_INDEX. \since 0.9.15 */ QString active_port_name = ""; QString active_port_description = ""; QString master_device = ""; QList source_port_list; }; struct pa_device_port_info { /*card index*/ int card; /* port direction. Input or Output */ int direction; /* port available. */ int available; /* port plugged stauts. */ int plugged_stauts; /* port name. Example: analog-output-speak */ QString name; /* port description */ QString description; /* device description */ QString device_description; /* device product name */ QString device_product_name; friend const QDBusArgument& operator>>(const QDBusArgument &argument, pa_device_port_info &info) { argument.beginStructure(); argument >> info.card; argument >> info.direction; argument >> info.available; argument >> info.plugged_stauts; argument >> info.name; argument >> info.description; argument >> info.device_description; argument >> info.device_product_name; argument.endStructure(); return argument; } friend const QDBusArgument &operator<<(QDBusArgument &argument, const pa_device_port_info &info) { argument.beginStructure(); argument << info.card; argument << info.direction; argument << info.available; argument << info.plugged_stauts; argument << info.name; argument << info.description; argument << info.device_description; argument << info.device_product_name; argument.endStructure(); return argument; } }; typedef pa_device_port_info pa_device_port_info; Q_DECLARE_METATYPE(pa_device_port_info) /* some information of sink-input or source-output */ struct appInfo { int index; int volume; int channel; bool mute; int direction; #ifdef PA_PROP_APPLICATION_MOVE QString move; #endif /* name eg: kylin-music*/ QString name; /* sink-input's sink index*/ int masterIndex; /* sink-input's sink name*/ QString masterDevice; friend QDBusArgument &operator<<(QDBusArgument &argument, const appInfo &mystruct) { argument.beginStructure(); argument << mystruct.index << mystruct.volume << mystruct.channel << mystruct.mute << mystruct.direction #ifdef PA_PROP_APPLICATION_MOVE << mystruct.move #endif << mystruct.name << mystruct.masterIndex << mystruct.masterDevice; argument.endStructure(); return argument; } friend const QDBusArgument &operator>>(const QDBusArgument &argument, appInfo &mystruct) { argument.beginStructure(); argument >> mystruct.index >> mystruct.volume >> mystruct.channel >> mystruct.mute >> mystruct.direction #ifdef PA_PROP_APPLICATION_MOVE >> mystruct.move #endif >> mystruct.name >> mystruct.masterIndex >> mystruct.masterDevice; argument.endStructure(); return argument; } }; typedef appInfo appInfo; Q_DECLARE_METATYPE(appInfo) enum class SystemVersion { SYSTEM_VERSION_KYLIN, // 主线版本 SYSTEM_VERSION_OKYANGTZE, // openkylin yangtze版本 SYSTEM_VERSION_OKNILE, // openkylin nile版本 SYSTEM_VERSION_KYLIN_2501_V11, // 主线版本 V11/2501 版本 SYSTEM_VERSION_UNKNOWN }; class UkmediaCommon { public: static UkmediaCommon& getInstance(); /** * @brief 获取系统版本信息 * * @return 返回当前系统版本信息 */ SystemVersion getSystemVersion(); /** * @brief 判断该机器是不是华为平台 * * @return 是返回true,否则返回false */ bool isHuaweiPlatform(); /** * @brief 判断当前系统语言环境变量是不是哈维柯 * * @param str 语言环境变量 维吾尔语:ug_CN 哈萨克语:kk_KZ 柯尔克孜语:ky_KG * * @return 是返回true,否则返回false */ bool isHWKLanguageEnv(const QString& str); /** * @brief 音量换算 * * @param volume 音量值 * * @return 转换后的音量值 */ int paVolumeToMediaVolume(int volume); int mediaVolumeToPaVolume(int volume); private: UkmediaCommon(); UkmediaCommon(const UkmediaCommon&) = delete; UkmediaCommon(UkmediaCommon&&) = delete; UkmediaCommon operator=(const UkmediaCommon&) = delete; UkmediaCommon operator=(UkmediaCommon&&) = delete; ~UkmediaCommon() = default; }; #endif // UKMEDIACOMMON_H ukui-media/common/sound_effect_json.h0000664000175000017500000000342615170054730016677 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifndef SOUNDEFFECTJSON_H #define SOUNDEFFECTJSON_H #include "json.h" #define SOUND_EFFECT_JSON "/tmp/PlayStartupWav.json" /* 判断是否为首次登录系统 */ #define FIRST_TIME_LOGIN_KEY "first-time-login" /* 首次登录系统 */ #define DEFAULT_FIRST_TIME_LOGIN_VALUE true /* 默认为首次登录系统 */ class SoundEffectJson : public IJson { public: SoundEffectJson(const QString& name, const JsonType& type = JsonType::JSON_TYPE_SOUNDEFFECT); virtual ~SoundEffectJson(); public: virtual void init() override; virtual void remove(const QString& key) override; virtual void insert(const QString& key, const QJsonValue& value) override; virtual QJsonValue getValue(QString key) const override; private: QMap m_keys { {FIRST_TIME_LOGIN_KEY, DEFAULT_FIRST_TIME_LOGIN_VALUE}, }; }; #endif // SOUNDEFFECTJSON_H ukui-media/common/user_info_json.h0000664000175000017500000000564515170054730016231 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifndef USERINFOJSON_H #define USERINFOJSON_H #include "json.h" #define INVAILD_PATH "invaild path" #define DEFAULT_PATH "/home/" #define AUDIO_JSON "/.config/audio.json" /* 保存用户默认的输出设备、音量、静音状态及开机音效开关的配置 */ #define AUDIO_CARD_KEY "card" /* 声卡字符串 */ #define DEFAULT_AUDIO_CARD_VALUE "default" /* 系统默认的音频设备 */ #define AUDIO_CARDID_KEY "cardid" /* 声卡ID */ #define DEFAULT_AUDIO_CARDID_VALUE 0 /* 默认声卡ID为0 */ #define AUDIO_MUTE_KEY "mute" /* 静音状态 */ #define DEFAULT_AUDIO_MUTE_VALUE false /* 默认静音状态为false */ #define AUDIO_VOLUME_KEY "volume" /* 当前用户的音量值 */ #define DEFAULT_AUDIO_VOLUME_VALUE 67 /* 默认音量为67 */ #define AUDIO_STRATUP_SOUNDEFFECT_KEY "startup-soundeffect" /* 当前用户开机音效的开启状态 */ #define DEFAULT_STARTUP_SOUNDEFFECI_VALUE true /* 默认开启状态为true */ class UserInfoJson : public IJson { public: UserInfoJson(const QString& name, const JsonType& type = JsonType::JSON_TYPE_USERINFO); virtual ~UserInfoJson(); public: virtual void init() override; virtual void remove(const QString& key) override; virtual void insert(const QString& key, const QJsonValue& value) override; virtual QJsonValue getValue(QString key) const override; private: QMap m_keys { {AUDIO_CARD_KEY, DEFAULT_AUDIO_CARD_VALUE}, {AUDIO_CARDID_KEY, DEFAULT_AUDIO_CARDID_VALUE}, {AUDIO_MUTE_KEY, DEFAULT_AUDIO_MUTE_VALUE}, {AUDIO_VOLUME_KEY, DEFAULT_AUDIO_VOLUME_VALUE}, {AUDIO_STRATUP_SOUNDEFFECT_KEY, DEFAULT_STARTUP_SOUNDEFFECI_VALUE}, }; }; #endif // USERINFOJSON_H ukui-media/common/user_config.h0000664000175000017500000000354015170054730015502 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifndef USERCONFIG_H #define USERCONFIG_H #include #include #include "json.h" #define LIGHTDM_CONF_FILE "/etc/lightdm/lightdm.conf" #define LIGHTDM_CONF_KEY "SeatDefaults/autologin-user" #define UKUI_GREETER_CONF_FILE "/var/lib/lightdm/.cache/ukui-greeter.conf" #define UKUI_GREETER_CONF_KEY "Greeter/lastLoginUser" class UserConfig { public: UserConfig(); ~UserConfig(); public: virtual void initJsonMap() = 0; virtual void insert(std::shared_ptr json, const QString& key, const QJsonValue& vaule); QJsonValue getValueFromJson(std::shared_ptr json, const QString& key) const; std::shared_ptr getJsonByType(std::unordered_map> map, const JsonType& type); std::function getJsonFileNameByType(std::unordered_map> keys, const JsonType& type); protected: std::unordered_map> m_jsonMap; }; #endif // USERCONFIG_H ukui-media/common/user_info_json.cpp0000664000175000017500000000347615170054730016564 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "user_info_json.h" #include UserInfoJson::UserInfoJson(const QString& name, const JsonType& type) : IJson(name, type) { } UserInfoJson::~UserInfoJson() { } void UserInfoJson::init() { for (const auto& it : m_keys.toStdMap()) { insert(it.first, it.second); } } void UserInfoJson::remove(const QString& key) { if (m_rootObj.contains(key)) { m_rootObj.remove(key); } update(); } void UserInfoJson::insert(const QString& key, const QJsonValue& value) { // 如果json文件中存在相同的键值对则不进行任何操作 if (m_rootObj.contains(key) && m_rootObj.value(key) == value) { return; } m_rootObj.insert(key, value); update(); } QJsonValue UserInfoJson::getValue(QString key) const { if (!m_rootObj.contains(key)) { syslog(LOG_INFO, "the json file does not contain the %s key", key.toLatin1().data()); return QJsonValue(); } return m_rootObj.value(key); } ukui-media/common/sound_effect_json.cpp0000664000175000017500000000354715170054730017236 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "sound_effect_json.h" #include SoundEffectJson::SoundEffectJson(const QString& name, const JsonType& type) : IJson(name, type) { } SoundEffectJson::~SoundEffectJson() { } void SoundEffectJson::init() { for (const auto& it : m_keys.toStdMap()) { insert(it.first, it.second); } update(); } void SoundEffectJson::remove(const QString& key) { if (m_rootObj.contains(key)) { m_rootObj.remove(key); } update(); } void SoundEffectJson::insert(const QString& key, const QJsonValue& value) { // 如果json文件中存在相同的键值对则不进行任何操作 if (m_rootObj.contains(key) && m_rootObj.value(key) == value) { return; } m_rootObj.insert(key, value); update(); } QJsonValue SoundEffectJson::getValue(QString key) const { if (!m_rootObj.contains(key)) { syslog(LOG_INFO, "the json file does not contain the %s key", key.toLatin1().data()); return QJsonValue(); } return m_rootObj.value(key); } ukui-media/common/user_config.cpp0000664000175000017500000000365015170054730016037 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "user_config.h" UserConfig::UserConfig() { } UserConfig::~UserConfig() { } void UserConfig::insert(std::shared_ptr json, const QString &key, const QJsonValue& vaule) { json->insert(key, vaule); } QJsonValue UserConfig::getValueFromJson(std::shared_ptr json, const QString &key) const { return json->getValue(key); } std::shared_ptr UserConfig::getJsonByType(std::unordered_map> map, const JsonType& type) { if (const auto& itr = map.find(type); itr != map.end() && itr->second != nullptr) { return itr->second; } syslog(LOG_ERR, "JsonType error, jsonMap dosnot exit %d json type.", int(type)); return nullptr; } std::function UserConfig::getJsonFileNameByType(std::unordered_map> keys, const JsonType& type) { if (const auto& itr = keys.find(type); itr != keys.end() && itr->second != nullptr) { return itr->second; } syslog(LOG_ERR, "JsonType error, keys dosnot exit %d json type.", int(type)); return nullptr; } ukui-media/common/json.h0000664000175000017500000000621315170054730014150 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifndef JSON_H #define JSON_H #include #include #include #include #include #include #include /** * @brief json文件的类型 */ enum class JsonType { JSON_TYPE_SOUNDEFFECT = 0, // 开机音效类型的json文件 JSON_TYPE_USERINFO, // 用户信息类型的json文件 JSON_TYPE_UNKNOWN // 未知类型的json文件 }; class IJson { public: IJson(const QString& name, const JsonType& type) : m_filename(name), m_type(type) { load(); } virtual ~IJson() { save(); } /** * @brief 初始化json文件 */ virtual void init() = 0; /** * @brief 删除json文件中键为key的键值对 * * @param key 需要删除的键 */ virtual void remove(const QString& key) = 0; /** * @brief 在json文件插入键值对 * * @param key 插入的键 * value 插入的值 */ virtual void insert(const QString& key, const QJsonValue& value) = 0; /** * @brief 获取json文件指定键值 * * @param key 获取的键 * @return */ virtual QJsonValue getValue(QString key) const = 0; void update() { save(); } private: void load() { QFile file(m_filename); if (!file.open(QFile::ReadWrite)) { return; } QTextStream m_textStream(&file); QString str = m_textStream.readAll(); file.close(); QJsonParseError jsonError; QJsonDocument doc = QJsonDocument::fromJson(str.toUtf8(), &jsonError); if (jsonError.error != QJsonParseError::NoError) { return; } m_rootObj = doc.object(); } void save() { QJsonDocument doc(m_rootObj); QFile file(m_filename); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { syslog(LOG_ERR, "open %s failed, %s", m_filename.toLatin1().data(), strerror(errno)); return; } QTextStream m_textStream(&file); m_textStream.setEncoding(QStringConverter::Utf8); m_textStream << doc.toJson(); file.close(); } protected: QString m_filename; QJsonObject m_rootObj; JsonType m_type; }; #endif // IJSON_H ukui-media/common/ukmedia_common.cpp0000664000175000017500000000634715170054730016531 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "ukmedia_common.h" #include #include UkmediaCommon::UkmediaCommon() { } UkmediaCommon& UkmediaCommon::getInstance() { static UkmediaCommon instance; return instance; } SystemVersion UkmediaCommon::getSystemVersion() { auto systemName = QString(QLatin1String(kdk_system_get_systemName())); auto version = QString(QLatin1String(kdk_system_get_version(0))); auto majorVersion = QString(QLatin1String(kdk_system_get_major_version())); if(majorVersion.compare("V11", Qt::CaseInsensitive) == 0) { return SystemVersion::SYSTEM_VERSION_KYLIN_2501_V11; } if (systemName.compare("openkylin", Qt::CaseInsensitive) == 0 && version.contains("nile", Qt::CaseInsensitive)) { return SystemVersion::SYSTEM_VERSION_OKNILE; } else if (systemName.compare("openkylin", Qt::CaseInsensitive) == 0) { return SystemVersion::SYSTEM_VERSION_OKYANGTZE; } else if (systemName.compare("kylin", Qt::CaseInsensitive) == 0) { return SystemVersion::SYSTEM_VERSION_KYLIN; } return SystemVersion::SYSTEM_VERSION_UNKNOWN; } bool UkmediaCommon::isHuaweiPlatform() { bool isHuaweiPlatform = false; const char *cpuInfo = kdk_cpu_get_model(); QString cpuStr = QString::fromLocal8Bit(cpuInfo); if (cpuStr.contains("HUAWEI", Qt::CaseInsensitive) || cpuStr.contains("Kirin", Qt::CaseInsensitive)) { if (cpuStr.contains("990") || cpuStr.contains("9006C") || cpuStr.contains("9000C")) isHuaweiPlatform = true; } else if (cpuStr.contains("PANGU", Qt::CaseInsensitive) && cpuStr.contains("M900", Qt::CaseInsensitive)) { isHuaweiPlatform = true; } qInfo() << __func__ << isHuaweiPlatform << cpuStr; return isHuaweiPlatform; } bool UkmediaCommon::isHWKLanguageEnv(const QString &str) { return ("ug_CN" == str || "kk_KZ" == str || "ky_KG" == str); } int UkmediaCommon::paVolumeToMediaVolume(int volume) { if (volume < PA_VOLUME_MIN || volume > PA_VOLUME_MAX) volume = (volume < PA_VOLUME_MIN)? PA_VOLUME_MIN : PA_VOLUME_MAX; return (volume / PA_VOLUME_NORMAL * UKMEDIA_VOLUME_NORMAL) + 0.5; } int UkmediaCommon::mediaVolumeToPaVolume(int volume) { if (volume < PA_VOLUME_MIN || volume > UKMEDIA_VOLUME_MAX) volume = (volume < PA_VOLUME_MIN)? PA_VOLUME_MIN : UKMEDIA_VOLUME_MAX; return volume / UKMEDIA_VOLUME_NORMAL * PA_VOLUME_NORMAL; } ukui-media/README0000664000175000017500000000045415170052044012413 0ustar fengfengThese are the media tools for UKUI: ukui-volume-control - UKUI volume control application and applet Refer to the following files for license information for each sub-component of ukui-media: ukui-volume-control - COPYING documentation - COPYING-DOCS UKUI-Media is a fork of MATE-Media. ukui-media/build-aux/0000775000175000017500000000000015170052044013422 5ustar fengfengukui-media/build-aux/git-version-gen0000775000175000017500000001272615170052044016375 0ustar fengfeng#!/bin/sh # Print a version string. scriptversion=2009-05-04.22 # Copyright (C) 2007-2008 Free Software Foundation # # 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, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. # This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/. # It may be run two ways: # - from a git repository in which the "git describe" command below # produces useful output (thus requiring at least one signed tag) # - from a non-git-repo directory containing a .tarball-version file, which # presumes this script is invoked like "./git-version-gen .tarball-version". # In order to use intra-version strings in your project, you will need two # separate generated version string files: # # .tarball-version - present only in a distribution tarball, and not in # a checked-out repository. Created with contents that were learned at # the last time autoconf was run, and used by git-version-gen. Must not # be present in either $(srcdir) or $(builddir) for git-version-gen to # give accurate answers during normal development with a checked out tree, # but must be present in a tarball when there is no version control system. # Therefore, it cannot be used in any dependencies. GNUmakefile has # hooks to force a reconfigure at distribution time to get the value # correct, without penalizing normal development with extra reconfigures. # # .version - present in a checked-out repository and in a distribution # tarball. Usable in dependencies, particularly for files that don't # want to depend on config.h but do want to track version changes. # Delete this file prior to any autoconf run where you want to rebuild # files to pick up a version string change; and leave it stale to # minimize rebuild time after unrelated changes to configure sources. # # It is probably wise to add these two files to .gitignore, so that you # don't accidentally commit either generated file. # # Use the following line in your configure.ac, so that $(VERSION) will # automatically be up-to-date each time configure is run (and note that # since configure.ac no longer includes a version string, Makefile rules # should not depend on configure.ac for version updates). # # AC_INIT([GNU project], # m4_esyscmd([build-aux/git-version-gen .tarball-version]), # [bug-project@example]) # # Then use the following lines in your Makefile.am, so that .version # will be present for dependencies, and so that .tarball-version will # exist in distribution tarballs. # # BUILT_SOURCES = $(top_srcdir)/.version # $(top_srcdir)/.version: # echo $(VERSION) > $@-t && mv $@-t $@ # dist-hook: # echo $(VERSION) > $(distdir)/.tarball-version case $# in 1) ;; *) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version"; exit 1;; esac tarball_version_file=$1 nl=' ' # First see if there is a tarball-only version file. # then try "git describe", then default. if test -f $tarball_version_file then v=`cat $tarball_version_file` || exit 1 case $v in *$nl*) v= ;; # reject multi-line output [0-9]*) ;; *) v= ;; esac test -z "$v" \ && echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2 fi if test -n "$v" then : # use $v elif test -d .git \ && v=`git describe --abbrev=4 --match='${UKUI_TAG_PATTERN}_*' HEAD 2>/dev/null \ || git describe --abbrev=4 HEAD 2>/dev/null` \ && case $v in ${UKUI_TAG_PATTERN}_[0-9]*) ;; *) (exit 1) ;; esac then # Is this a new git that lists number of commits since the last # tag or the previous older version that did not? # Newer: v6.10-77-g0f8faeb # Older: v6.10-g0f8faeb case $v in *-*-*) : git describe is okay three part flavor ;; *-*) : git describe is older two part flavor # Recreate the number of commits and rewrite such that the # result is the same as if we were using the newer version # of git describe. vtag=`echo "$v" | sed 's/-.*//'` numcommits=`git rev-list "$vtag"..HEAD | wc -l` v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`; ;; esac # Change the first '-' to a '.', so version-comparing tools work properly. # Remove the "g" in git describe's output string, to save a byte. v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`; else v=UNKNOWN fi #v=`echo "$v" |sed 's/^v//'` v=`echo "$v" | sed "s/${UKUI_TAG_PATTERN}_//" | tr _ .` # Don't declare a version "dirty" merely because a time stamp has changed. git status > /dev/null 2>&1 dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty= case "$dirty" in '') ;; *) # Append the suffix only if there isn't one already. case $v in *-dirty) ;; *) v="$v-dirty" ;; esac ;; esac # Omit the trailing newline, so that m4_esyscmd can use the result directly. echo "$v" | tr -d '\012' # Local variables: # eval: (add-hook 'write-file-hooks 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" # time-stamp-end: "$" # End: ukui-media/ChangeLog0000664000175000017500000000016615170052044013305 0ustar fengfengThe ChangeLog is auto-generated when releasing. If you are seeing this, use 'git log' for a detailed list of changes. ukui-media/scripts/0000775000175000017500000000000015170054730013223 5ustar fengfengukui-media/scripts/detection_output_mode.sh0000775000175000017500000000155015170052044020161 0ustar fengfeng#!/bin/bash for USER in $(who | grep tty | awk '{print $1}' | sort | uniq);do USER_NAME=$USER # USER_NAME=$USER USER_ID=$(id -u "$USER_NAME") PULSE_SERVER="unix:/run/user/"$USER_ID"/pulse/native" OUTPUT_MODE="not available" FIND_STR=" analog-output" sudo -H -u ${USER_NAME} pactl --server $PULSE_SERVER list cards |grep analog-output > /tmp/kylin_headphone.tmp while read line;do index="$line" echo $line if [ -n "$index" ];then OUTPUT_MODE=${index#*usec, } OUTPUT_MODE=${OUTPUT_MODE%%)} echo "输出模式为" $OUTPUT_MODE fi if [[ $line =~ "analog-output-speaker" ]] && [[ $OUTPUT_MODE == "available" ]];then exit 0 elif [[ $line =~ "analog-output-headphone" ]] && [[ $OUTPUT_MODE == "available" ]];then exit 1 fi done < /tmp/kylin_headphone.tmp done rm -rf /tmp/kylin_headphone.tmp exit -1 ukui-media/scripts/99-soundCardAdaptation0000775000175000017500000003204615170054730017344 0ustar fengfeng#!/bin/bash #-------------------------- 全局配置 --------------------------# set -o errexit # 遇到错误立即退出 set -o nounset # 遇到未定义变量报错 set -o pipefail # 管道命令错误传递 #-------------------------- 常量定义 --------------------------# readonly LOG_FILE="/var/log/audio_config.log" readonly ALSA_MIXER_PATH="/usr/share/pulseaudio/alsa-mixer" #--------------------- Pulseaudio 路径定义 --------------------# ANALOG_INPUT_MIC_CONF="/usr/share/pulseaudio/alsa-mixer/paths/analog-input-mic.conf" ANALOG_INPUT_LINEIN_CONF="/usr/share/pulseaudio/alsa-mixer/paths/analog-input-linein.conf" ANALOG_INPUT_REAR_MIC_CONF="/usr/share/pulseaudio/alsa-mixer/paths/analog-input-rear-mic.conf" ANALOG_INPUT_FRONT_MIC_CONF="/usr/share/pulseaudio/alsa-mixer/paths/analog-input-front-mic.conf" ANALOG_INPUT_INTERNAL_MIC_CONF="/usr/share/pulseaudio/alsa-mixer/paths/analog-input-internal-mic.conf" MIC_BOOST_1="/usr/share/pulseaudio/alsa-mixer/paths/kylin-paths/analog-input-mic-1.conf" MIC_BOOST_2="/usr/share/pulseaudio/alsa-mixer/paths/kylin-paths/analog-input-mic-2.conf" MIC_BOOST_3="/usr/share/pulseaudio/alsa-mixer/paths/kylin-paths/analog-input-mic-3.conf" LINEIN_BOOST_1="/usr/share/pulseaudio/alsa-mixer/paths/kylin-paths/analog-input-linein-1.conf" LINEIN_BOOST_2="/usr/share/pulseaudio/alsa-mixer/paths/kylin-paths/analog-input-linein-2.conf" LINEIN_BOOST_3="/usr/share/pulseaudio/alsa-mixer/paths/kylin-paths/analog-input-linein-3.conf" REAR_MIC_BOOST_0="/usr/share/pulseaudio/alsa-mixer/paths/kylin-paths/analog-input-rear-mic-0.conf" REAR_MIC_BOOST_1="/usr/share/pulseaudio/alsa-mixer/paths/kylin-paths/analog-input-rear-mic-1.conf" REAR_MIC_BOOST_2="/usr/share/pulseaudio/alsa-mixer/paths/kylin-paths/analog-input-rear-mic-2.conf" REAR_MIC_BOOST_3="/usr/share/pulseaudio/alsa-mixer/paths/kylin-paths/analog-input-rear-mic-3.conf" FRONT_MIC_BOOST_0="/usr/share/pulseaudio/alsa-mixer/paths/kylin-paths/analog-input-front-mic-0.conf" FRONT_MIC_BOOST_1="/usr/share/pulseaudio/alsa-mixer/paths/kylin-paths/analog-input-front-mic-1.conf" FRONT_MIC_BOOST_2="/usr/share/pulseaudio/alsa-mixer/paths/kylin-paths/analog-input-front-mic-2.conf" FRONT_MIC_BOOST_3="/usr/share/pulseaudio/alsa-mixer/paths/kylin-paths/analog-input-front-mic-3.conf" INTERNAL_MIC_BOOST_1="/usr/share/pulseaudio/alsa-mixer/paths/kylin-paths/analog-input-internal-mic-1.conf" INTERNAL_MIC_BOOST_2="/usr/share/pulseaudio/alsa-mixer/paths/kylin-paths/analog-input-internal-mic-2.conf" INTERNAL_MIC_BOOST_3="/usr/share/pulseaudio/alsa-mixer/paths/kylin-paths/analog-input-internal-mic-3.conf" #-------------------------- 日志函数 --------------------------# log() { local level=$1 local message=$2 local timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo "[${timestamp}] [${level}] ${message}" | tee -a "${LOG_FILE}" } #-------------------------- 链接函数 --------------------------# ukmedia_symlink() { local target_file=$1 local link_name=$2 # 验证目标文件 if [[ ! -e "$target_file" ]]; then log "ERROR" "目标文件不存在: $target_file" return 0 fi # 清理旧链接 if [[ -e "$link_name" ]]; then log "WARN" "移除旧链接: $link_name" rm -f "$link_name" || { log "ERROR" "删除失败: $link_name" return 0 } fi # 创建新链接 if ln -sf "$target_file" "$link_name"; then log "INFO" "创建链接: $link_name -> $target_file" else log "ERROR" "链接创建失败: $target_file -> $link_name" return 0 fi } #-------------------------- 硬件信息获取 --------------------------# get_hw_info() { # 缓存硬件信息避免重复查询 local -g _vendor_id local -g _subsystem_id local -g _system_product_name local -g _baseboard_product_name if [[ -z "${_vendor_id:-}" ]]; then _vendor_id=$(grep 'Vendor Id' /proc/asound/card*/codec#0 2>/dev/null || true) fi if [[ -z "${_subsystem_id:-}" ]]; then _subsystem_id=$(grep 'Subsystem Id' /proc/asound/card*/codec#0 2>/dev/null || true) fi if [[ -z "${_system_product_name:-}" ]]; then _system_product_name=$(dmidecode -s system-product-name 2>/dev/null || true) fi if [[ -z "${_baseboard_product_name:-}" ]]; then _baseboard_product_name=$(dmidecode -s baseboard-product-name 2>/dev/null || true) fi } handle_realtek_alc222() { log "INFO" "检测到Realtek ALC222声卡" ukmedia_symlink "$FRONT_MIC_BOOST_0" "$ANALOG_INPUT_FRONT_MIC_CONF" ukmedia_symlink "$REAR_MIC_BOOST_0" "$ANALOG_INPUT_REAR_MIC_CONF" } handle_realtek_alc897() { log "INFO" "检测到Realtek ALC897声卡" if [[ "${_baseboard_product_name}" == "CE720F" ]] || [[ "${_baseboard_product_name}" == "GW-001M1A-FTF" ]] || [[ "${_baseboard_product_name}" == "THTF-6780A-1W-JZXETH01" ]]; then log "INFO" "识别到基板型号 ${_baseboard_product_name},使用配置: Front Mic Boost = 1, Rear Mic Boot = 1" ukmedia_symlink "$FRONT_MIC_BOOST_1" "$ANALOG_INPUT_FRONT_MIC_CONF" ukmedia_symlink "$REAR_MIC_BOOST_1" "$ANALOG_INPUT_REAR_MIC_CONF" elif [[ "${_baseboard_product_name}" == "FD2000ZX200MB1" ]]; then log "INFO" "识别到基板型号 ${_baseboard_product_name},使用配置: Front Mic Boost = 0, Rear Mic Boot = 0" ukmedia_symlink "$FRONT_MIC_BOOST_0" "$ANALOG_INPUT_FRONT_MIC_CONF" ukmedia_symlink "$REAR_MIC_BOOST_0" "$ANALOG_INPUT_REAR_MIC_CONF" else log "INFO" "未识别到基板型号 ${_baseboard_product_name},使用通用配置: Front Mic Boost = 1, Rear Mic Boot = 1" ukmedia_symlink "$FRONT_MIC_BOOST_1" "$ANALOG_INPUT_FRONT_MIC_CONF" ukmedia_symlink "$REAR_MIC_BOOST_1" "$ANALOG_INPUT_REAR_MIC_CONF" fi } handle_realtek_alc269() { log "INFO" "检测到Realtek ALC269声卡" if [[ "${_baseboard_product_name}" == "CE720F" ]] || [[ "${_baseboard_product_name}" == "MAXHUB-NZ61-6780A" ]]; then log "INFO" "识别到基板型号 ${_baseboard_product_name},使用配置: Internal Mic Boost = 1" ukmedia_symlink "$INTERNAL_MIC_BOOST_1" "$ANALOG_INPUT_INTERNAL_MIC_CONF" elif [[ "${_baseboard_product_name}" == "BM6J80" ]]; then log "INFO" "识别到基板型号 ${_baseboard_product_name},使用配置: Internal Mic Boost = 2" ukmedia_symlink "$INTERNAL_MIC_BOOST_2" "$ANALOG_INPUT_INTERNAL_MIC_CONF" elif [[ "${_baseboard_product_name}" == "Phytium D3000 Desktop Board" ]]; then log "INFO" "识别到基板型号 ${_baseboard_product_name},使用配置: Front Mic Boost = 1, Rear Mic Boot = 1" ukmedia_symlink "$FRONT_MIC_BOOST_1" "$ANALOG_INPUT_FRONT_MIC_CONF" ukmedia_symlink "$REAR_MIC_BOOST_1" "$ANALOG_INPUT_REAR_MIC_CONF" else log "INFO" "未识别到基板型号 ${_baseboard_product_name},使用通用配置: Internal Mic Boost = 1" ukmedia_symlink "$INTERNAL_MIC_BOOST_1" "$ANALOG_INPUT_INTERNAL_MIC_CONF" fi } handle_realtek_alc623() { log "INFO" "检测到Realtek ALC623声卡" if [[ "${_baseboard_product_name}" == "LXKT-ZX-KX7000" ]]; then log "INFO" "识别到基板型号 ${_baseboard_product_name},使用配置: Front Mic Boost = 1, Rear Mic Boot = 1" ukmedia_symlink "$FRONT_MIC_BOOST_1" "$ANALOG_INPUT_FRONT_MIC_CONF" ukmedia_symlink "$REAR_MIC_BOOST_1" "$ANALOG_INPUT_REAR_MIC_CONF" else log "INFO" "未识别到基板型号 ${_baseboard_product_name},使用通用配置" fi } handle_realtek_alc662() { log "INFO" "检测到Realtek ALC662声卡" if [[ "${_baseboard_product_name}" == "THTF-FTD2000-1W-MBX-F20ER100" ]] || [[ "${_baseboard_product_name}" == "THTF-ZXE-KX-U66780A-ZX200-JZXEMA03" ]]; then log "INFO" "识别到基板型号 ${_baseboard_product_name},使用配置: Front Mic Boost = 1, Rear Mic Boot = 1" ukmedia_symlink "$FRONT_MIC_BOOST_1" "$ANALOG_INPUT_FRONT_MIC_CONF" ukmedia_symlink "$REAR_MIC_BOOST_1" "$ANALOG_INPUT_REAR_MIC_CONF" elif [[ "${_baseboard_product_name}" == "LM-LS3A4000-7A1000-1w-V01-pc-A2005" ]]; then log "INFO" "识别到基板型号 ${_baseboard_product_name},使用配置: Front Mic Boost = 0, Rear Mic Boot = 2" ukmedia_symlink "$FRONT_MIC_BOOST_0" "$ANALOG_INPUT_FRONT_MIC_CONF" ukmedia_symlink "$REAR_MIC_BOOST_2" "$ANALOG_INPUT_REAR_MIC_CONF" else log "INFO" "未识别到基板型号 ${_baseboard_product_name},使用通用配置" fi } handle_conexant_cx20632() { log "INFO" "检测到Conexant CX20632声卡" if [[ "${_baseboard_product_name}" == "EAECIS-FT_D2000_X100-1W-V0.1-NF14" ]]; then log "INFO" "识别到基板型号 ${_baseboard_product_name},使用配置: Internal Mic Boost = merge, Mic Boot = merge" ukmedia_symlink "$MIC_BOOST_3" "$ANALOG_INPUT_MIC_CONF" ukmedia_symlink "$INTERNAL_MIC_BOOST_3" "$ANALOG_INPUT_INTERNAL_MIC_CONF" else log "INFO" "未识别到基板型号 ${_baseboard_product_name},使用通用配置: Internal Mic Boost = 2, Mic Boot = merge" ukmedia_symlink "$MIC_BOOST_3" "$ANALOG_INPUT_MIC_CONF" ukmedia_symlink "$INTERNAL_MIC_BOOST_2" "$ANALOG_INPUT_INTERNAL_MIC_CONF" fi } handle_conexant_cx11880() { log "INFO" "检测到Conexant CX11880声卡" if [[ "${_baseboard_product_name}" == "LXKT-ZXE-N70" ]]; then log "INFO" "识别到基板型号 ${_baseboard_product_name},使用配置: Internal Mic Boost = 2, Mic Boot = merge" ukmedia_symlink "$MIC_BOOST_3" "$ANALOG_INPUT_MIC_CONF" ukmedia_symlink "$INTERNAL_MIC_BOOST_2" "$ANALOG_INPUT_INTERNAL_MIC_CONF" else log "INFO" "未识别到基板型号 ${_baseboard_product_name},使用通用配置" fi } handle_conexant_sn6140() { log "INFO" "检测到Conexant SN6140声卡" log "INFO" "根据适配记录,此声卡目前统一使用:Internal Mic Boost = 2, Mic Boost = 2" ukmedia_symlink "$MIC_BOOST_2" "$ANALOG_INPUT_MIC_CONF" ukmedia_symlink "$INTERNAL_MIC_BOOST_2" "$ANALOG_INPUT_INTERNAL_MIC_CONF" } handle_conexant_sn6180() { log "INFO" "检测到Conexant SN6180声卡" if [[ "${_baseboard_product_name}" == "LXKT-ZXE-N70" ]]; then log "INFO" "识别到基板型号 ${_baseboard_product_name},使用配置: Internal Mic Boost = 2, Mic Boot = 2" ukmedia_symlink "$MIC_BOOST_2" "$ANALOG_INPUT_MIC_CONF" ukmedia_symlink "$INTERNAL_MIC_BOOST_2" "$ANALOG_INPUT_INTERNAL_MIC_CONF" else log "INFO" "未识别到基板型号 ${_baseboard_product_name},使用通用配置" fi } handle_realtek_alc3328() { log "INFO" "检测到Realtek ALC3328声卡" if [[ "${_baseboard_product_name}" == "FD2000ZX200MB1" ]]; then log "INFO" "识别到基板型号 ${_baseboard_product_name},使用配置: Internal Mic Boost = 1, Mic Boot = 1" ukmedia_symlink "$MIC_BOOST_1" "$ANALOG_INPUT_MIC_CONF" ukmedia_symlink "$INTERNAL_MIC_BOOST_1" "$ANALOG_INPUT_INTERNAL_MIC_CONF" fi } #-------------------------- 特殊硬件处理 --------------------------# handle_special_hardware() { # 华为特殊处理 if [[ -f "/usr/share/pulseaudio/alsa-mixer/profile-sets/pulse-huawei.conf" ]]; then [[ -f "/etc/pulse/daemon-i2s-huawei.conf" ]] && \ mv -f "/etc/pulse/daemon-i2s-huawei.conf" "/etc/pulse/daemon.conf" fi if [[ $(dmidecode | grep -c "UNIS D3830 G3") -ne 0 ]]; then log "INFO" "UNIS D3830 G3 udev 自定义配置文件" ukmedia_symlink "/usr/share/pulseaudio/alsa-mixer/profile-sets/UNIS-D3830-G3-audio.conf" \ "/usr/share/pulseaudio/alsa-mixer/profile-sets/alc4080-mh243a-usb-audio.conf" fi if [[ `dmidecode |grep -c "706-LS3A5000-4-V1.0-B40L-41A1"` != "0" ]]; then log "INFO" "特殊硬件处理 AutoMute处理" ukmedia_symlink "/usr/share/pulseaudio/alsa-mixer/paths/analog-output-headphones-enabled-AutoMute.conf" \ "/usr/share/pulseaudio/alsa-mixer/paths/analog-output-headphones.conf" fi } #-------------------------- 主逻辑 --------------------------# main() { log "INFO" "====== 开始音频配置 ======" # 获取硬件信息 get_hw_info log "DEBUG" "硬件信息: Vendor ID: ${_vendor_id} Subsystem ID: ${_subsystem_id} System Product: ${_system_product_name} Baseboard Product: ${_baseboard_product_name}" # 声卡类型判断 case "${_vendor_id}" in *"0x10ec0222"*) handle_realtek_alc222 ;; *"0x10ec0257"*) handle_realtek_alc3328 ;; *"0x10ec0269"*) handle_realtek_alc269 ;; *"0x10ec0623"*) handle_realtek_alc623 ;; *"0x10ec0662"*) handle_realtek_alc662 ;; *"0x10ec0897"*) handle_realtek_alc897 ;; *"0x14f15098"*) handle_conexant_cx20632 ;; *"0x14f11f86"*) handle_conexant_cx11880 ;; *"0x14f120d1"*) handle_conexant_sn6180 ;; *"0x14f11f87"*) handle_conexant_sn6140 ;; *) log "INFO" "暂未适配的声卡型号: ${_vendor_id}" ;; esac # 特殊硬件处理 handle_special_hardware log "INFO" "====== 音频配置完成 ======" return 0 } #-------------------------- 执行入口 --------------------------# main "$@" ukui-media/ukui-media.pro0000664000175000017500000000033315170054730014307 0ustar fengfengTEMPLATE = subdirs CONFIG += ordered SUBDIRS = \ audio \ ukui-volume-control-applet-qt \ ukui-media-control-led \ ukui-login-sound \ sound-theme-player \ tests ukui-media/tests/0000775000175000017500000000000015170054730012676 5ustar fengfengukui-media/tests/unit_test_ukcc_audio_dev_ctrl/0000775000175000017500000000000015170054730020764 5ustar fengfengukui-media/tests/unit_test_ukcc_audio_dev_ctrl/main.cpp0000664000175000017500000000350715170054730022421 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #include #include "../../audio/audio-device-control/ukmedia_device_control_widget.h" #include class ukccAudioCtrlTest : public testing::Test { protected: static void SetUpTestCase() { int argc = 1; char *argv[] = {"ukccAudioCtrlTest"}; //需加参数,否则因SDK Kwidget运行构造函数报错 app = new QApplication(argc, argv); ukccAudioCtrl = new UkmediaDevControlWidget(); } static void TearDownTestCase() { delete ukccAudioCtrl; delete app; } static UkmediaDevControlWidget* ukccAudioCtrl; static QApplication* app; }; // 静态成员变量初始化测试 UkmediaDevControlWidget* ukccAudioCtrlTest::ukccAudioCtrl = nullptr; QApplication* ukccAudioCtrlTest::app = nullptr; TEST_F(ukccAudioCtrlTest, getPaOutputDevInfoTest) { ukccAudioCtrl->outputDevWidget->getPaDevInfo(); } TEST_F(ukccAudioCtrlTest, getPaInputDevInfoTest) { ukccAudioCtrl->inputDevWidget->getPaDevInfo(); } int main(int argc, char **argv) { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } ukui-media/tests/unit_test_ukcc_audio_dev_ctrl/unit_test_ukcc_audio_dev_ctrl.pro0000664000175000017500000000166215170054730027601 0ustar fengfengQT += dbus xml testlib widgets TEMPLATE = app CONFIG += c++14 link_pkgconfig no_keywords PKGCONFIG += gio-2.0 \ libxml-2.0 \ dconf \ gsettings-qt6 \ kysdk-qtwidgets \ kysdk-sysinfo \ kysdk-hardware TARGET = unit_test_ukcc_audio_dev_ctrl #代码覆盖率工具gcov QMAKE_LFLAGS +=-fprofile-arcs -ftest-coverage QMAKE_CXXFLAGS += --coverage -fno-inline -fno-access-control INCLUDEPATH += /usr/include/gtest LIBS += -lgtest_main -lpthread LIBS += -L$$[QT_INSTALL_LIBS] -lgsettings-qt6 -lgtest -lgcov LIBS += -lukcc HEADERS += \ # ../../common/ukmedia_common.h \ # ../../audio/ukui_custom_style.h \ ../../audio/audio-device-control/ukmedia_device_control_widget.h SOURCES += \ # ../../common/ukmedia_common.cpp \ # ../../audio/ukui_custom_style.cpp \ ../../audio/audio-device-control/ukmedia_device_control_widget.cpp \ main.cpp ukui-media/tests/unit_test_sound_theme_player/0000775000175000017500000000000015170054730020662 5ustar fengfengukui-media/tests/unit_test_sound_theme_player/unit_test_sound_theme_player.pro0000664000175000017500000000123715170054730027373 0ustar fengfengQT += core gui dbus xml testlib TEMPLATE = app CONFIG += c++14 link_pkgconfig no_keywords PKGCONFIG += \ glib-2.0 \ gio-2.0 \ dconf \ gsettings-qt6 \ libxml-2.0 \ libcanberra \ libpulse TARGET = unit_test_sound_theme_player #代码覆盖率工具gcov QMAKE_LFLAGS +=-fprofile-arcs -ftest-coverage QMAKE_CXXFLAGS += --coverage -fno-inline -fno-access-control INCLUDEPATH += /usr/include/gtest LIBS += -lgtest_main -lpthread LIBS += -L$$[QT_INSTALL_LIBS] -lgsettings-qt6 -lgtest -lgcov SOURCES += ../../sound-theme-player/sound_theme_player.cpp \ main.cpp HEADERS += ../../sound-theme-player/sound_theme_player.h ukui-media/tests/unit_test_sound_theme_player/main.cpp0000664000175000017500000000317415170054730022317 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #include #include "../../sound-theme-player/sound_theme_player.h" #include class soundThemePlayerTest:public testing::Test { protected: void SetUp() override { player = new SoundThemePlayer(); sleep(1); } void TearDown() override { delete player; } SoundThemePlayer* player; }; // 播放音效测试 TEST_F(soundThemePlayerTest, Test_playAlertSound) { player->playAlertSound("audio-volume-change"); } // 播放自定义音效测试 TEST_F(soundThemePlayerTest, Test_playCustomAlertSound) { player->playCustomAlertSound("/usr/share/sounds/Light-Seeking/stereo/audio-volume-change.ogg"); } // 音量转化测试 TEST_F(soundThemePlayerTest, Test_alertVolumeTodB) { char *dB_value = player->alertVolumeTodB(100); } int main(int argc, char **argv) { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } ukui-media/tests/unit_test_ukcc_audio_plugin/0000775000175000017500000000000015170054730020460 5ustar fengfengukui-media/tests/unit_test_ukcc_audio_plugin/unit_test_ukcc_audio_plugin.pro0000664000175000017500000000412315170054730026764 0ustar fengfengQT += dbus xml testlib widgets TEMPLATE = app CONFIG += c++14 link_pkgconfig no_keywords PKGCONFIG += gio-2.0 \ libxml-2.0 \ gsettings-qt6 \ libcanberra \ dconf \ libpulse \ libpulse-mainloop-glib \ kysdk-qtwidgets \ kysdk-sysinfo \ kysdk-hardware TARGET = unit_test_ukcc_audio_plugin #代码覆盖率工具gcov QMAKE_LFLAGS +=-fprofile-arcs -ftest-coverage QMAKE_CXXFLAGS += --coverage -fno-inline -fno-access-control INCLUDEPATH += /usr/include/gtest LIBS += -lgtest_main -lpthread LIBS += -L$$[QT_INSTALL_LIBS] -lgsettings-qt6 -lgtest -lgcov LIBS += -lukcc HEADERS += \ ../../common/ukmedia_common.h \ # ../../audio/audio.h \ ../../audio/customstyle.h \ ../../audio/ukui_custom_style.h \ ../../audio/ukui_list_widget_item.h \ ../../audio/ukmedia_main_widget.h \ ../../audio/ukmedia_input_widget.h \ ../../audio/ukmedia_output_widget.h \ ../../audio/ukmedia_volume_control.h \ ../../audio/ukmedia_settings_widget.h \ ../../audio/ukmedia_sound_effects_widget.h \ ../../audio/ukmedia_slider_tip_label_helper.h \ ../../audio/app-device-control/ukmedia_app_device_ctrl.h \ ../../audio/app-device-control/ukmedia_app_item_widget.h \ ../../audio/audio-device-control/ukmedia_device_control_widget.h SOURCES += \ ../../common/ukmedia_common.cpp \ # ../../audio/audio.cpp \ ../../audio/customstyle.cpp \ ../../audio/ukui_custom_style.cpp \ ../../audio/ukui_list_widget_item.cpp \ ../../audio/ukmedia_main_widget.cpp \ ../../audio/ukmedia_input_widget.cpp \ ../../audio/ukmedia_output_widget.cpp \ ../../audio/ukmedia_volume_control.cpp \ ../../audio/ukmedia_settings_widget.cpp \ ../../audio/ukmedia_sound_effects_widget.cpp \ ../../audio/ukmedia_slider_tip_label_helper.cpp \ ../../audio/app-device-control/ukmedia_app_device_ctrl.cpp \ ../../audio/app-device-control/ukmedia_app_item_widget.cpp \ ../../audio/audio-device-control/ukmedia_device_control_widget.cpp \ main.cpp ukui-media/tests/unit_test_ukcc_audio_plugin/main.cpp0000664000175000017500000004144015170054730022113 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #include #include #include "../../audio/ukmedia_main_widget.h" #include class ukccAudioTest:public testing::Test { protected: static void SetUpTestCase() { int argc = 0; char *argv[] = {}; app = new QApplication(argc, argv); ukccAudioPlugin = new UkmediaMainWidget(); // 因ukui-media与pulseaudio建立连接需要3s,因此需要延迟3.5s执行测试用例 QTest::qWait(3500); } static void TearDownTestCase() { delete ukccAudioPlugin; delete app; } static UkmediaMainWidget* ukccAudioPlugin; static QApplication* app; }; // 静态成员变量初始化测试 UkmediaMainWidget* ukccAudioTest::ukccAudioPlugin = nullptr; QApplication* ukccAudioTest::app = nullptr; /* -------------------- UkmediaVolumeControl ----------------------- */ // 获取默认输出设备静音状态测试 TEST_F(ukccAudioTest, Test_volumeControl_getSinkMute) { bool muteState = ukccAudioPlugin->m_pVolumeControl->getSinkMute(); qDebug() << "Test: getSinkMute" << muteState; } // 获取默认输出设备音量测试 TEST_F(ukccAudioTest, Test_volumeControl_getSinkVolume) { int paVolume = ukccAudioPlugin->m_pVolumeControl->getSinkVolume(); qDebug() << "Test: getSinkVolume" << paVolume; } // 获取默认输出设备音量左右声道平衡值测试 TEST_F(ukccAudioTest, Test_volumeControl_getBalanceVolume) { float balanceValue = ukccAudioPlugin->m_pVolumeControl->getBalanceVolume(); qDebug() << "Test: getBalanceVolume" << balanceValue; } // 获取默认输出设备索引测试 TEST_F(ukccAudioTest, Test_volumeControl_getDefaultSinkIndex) { int index = ukccAudioPlugin->m_pVolumeControl->getDefaultSinkIndex(); qDebug() << "Test: getDefaultSinkIndex" << index; } // 设置默认输出设备静音状态测试 TEST_F(ukccAudioTest, Test_volumeControl_setSinkMute) { ukccAudioPlugin->m_pVolumeControl->setSinkMute(true); qDebug() << "Test: setSinkMute" << ukccAudioPlugin->m_pVolumeControl->getSinkMute(); } // 设置默认输出设备音量测试 TEST_F(ukccAudioTest, Test_volumeControl_setSinkVolume) { int index = ukccAudioPlugin->m_pVolumeControl->getDefaultSinkIndex(); ukccAudioPlugin->m_pVolumeControl->setSinkVolume(index, 18350); qDebug() << "Test: setSinkVolume" << ukccAudioPlugin->m_pVolumeControl->getSinkVolume(); } // 设置默认输出设备音量左右声道平衡值测试 TEST_F(ukccAudioTest, Test_volumeControl_setBalanceVolume) { int index = ukccAudioPlugin->m_pVolumeControl->getDefaultSinkIndex(); ukccAudioPlugin->m_pVolumeControl->setBalanceVolume(index, 65536, 1.0); qDebug() << "Test: setBalanceVolume" << ukccAudioPlugin->m_pVolumeControl->getBalanceVolume(); } // 获取默认输入设备静音状态测试 TEST_F(ukccAudioTest, Test_volumeControl_getSourceMute) { bool muteState = ukccAudioPlugin->m_pVolumeControl->getSourceMute(); qDebug() << "Test: getSourceMute" << muteState; } // 获取默认输入设备音量测试 TEST_F(ukccAudioTest, Test_volumeControl_getSourceVolume) { int paVolume = ukccAudioPlugin->m_pVolumeControl->getSourceVolume(); qDebug() << "test getSourceVolume" << paVolume; } // 获取默认输入设备索引测试 TEST_F(ukccAudioTest, Test_volumeControl_getDefaultSourceIndex) { int index = ukccAudioPlugin->m_pVolumeControl->getDefaultSourceIndex(); qDebug() << "Test: getDefaultSourceIndex" << index; } // 设置默认输入设备静音状态测试 TEST_F(ukccAudioTest, Test_volumeControl_setSourceMute) { ukccAudioPlugin->m_pVolumeControl->setSourceMute(true); qDebug() << "Test: setSourceMute" << ukccAudioPlugin->m_pVolumeControl->getSourceMute(); } // 设置默认输入设备音量测试 TEST_F(ukccAudioTest, Test_volumeControl_setSourceVolume) { int index = ukccAudioPlugin->m_pVolumeControl->getDefaultSourceIndex(); ukccAudioPlugin->m_pVolumeControl->setSourceVolume(index, 65536); qDebug() << "Test: setSourceVolume" << index << ukccAudioPlugin->m_pVolumeControl->getSourceVolume(); } // 设置sink-input音量测试 TEST_F(ukccAudioTest, Test_volumeControl_setSinkInputVolume) { ukccAudioPlugin->m_pVolumeControl->setSinkInputVolume(1, 100); } // 设置sink-input静音状态测试 TEST_F(ukccAudioTest, Test_volumeControl_setSinkInputMuted) { ukccAudioPlugin->m_pVolumeControl->setSinkInputMuted(1, true); } // 设置source-output音量测试 TEST_F(ukccAudioTest, Test_volumeControl_setSourceOutputVolume) { ukccAudioPlugin->m_pVolumeControl->setSourceOutputVolume(1, 100); } // 设置source-output静音状态测试 TEST_F(ukccAudioTest, Test_volumeControl_setSourceOutputMuted) { ukccAudioPlugin->m_pVolumeControl->setSourceOutputMuted(1, true); } // 设置默认输出设备测试 TEST_F(ukccAudioTest, Test_volumeControl_setDefaultSink) { ukccAudioPlugin->m_pVolumeControl->setDefaultSink("TEST"); } // 设置默认输入设备测试 TEST_F(ukccAudioTest, Test_volumeControl_setDefaultSource) { ukccAudioPlugin->m_pVolumeControl->setDefaultSource("TEST"); } // 设置默认输出设备端口测试 TEST_F(ukccAudioTest, Test_volumeControl_setSinkPort) { ukccAudioPlugin->m_pVolumeControl->setSinkPort("TEST", "TEST"); } // 设置默认输入设备端口测试 TEST_F(ukccAudioTest, Test_volumeControl_setSourcePort) { ukccAudioPlugin->m_pVolumeControl->setSourcePort("TEST", "TEST"); } // 寻找sink活跃端口测试 TEST_F(ukccAudioTest, Test_volumeControl_findSinkActivePortName) { ukccAudioPlugin->m_pVolumeControl->findSinkActivePortName("TEST"); } // 移除输出声卡idx测试 TEST_F(ukccAudioTest, Test_volumeControl_removeCardSink) { ukccAudioPlugin->m_pVolumeControl->removeCardSink(99, "TEST"); } // 移除声卡idx测试 TEST_F(ukccAudioTest, Test_volumeControl_removeCard) { ukccAudioPlugin->m_pVolumeControl->removeCard(99); } // 移除输出idx测试 TEST_F(ukccAudioTest, Test_volumeControl_removeSink) { ukccAudioPlugin->m_pVolumeControl->removeSink(99); } // 移除输入声卡idx测试 TEST_F(ukccAudioTest, Test_volumeControl_removeCardSource) { ukccAudioPlugin->m_pVolumeControl->removeCardSource(99, "TEST"); } // 移除输入idx测试 TEST_F(ukccAudioTest, Test_volumeControl_removeSource) { ukccAudioPlugin->m_pVolumeControl->removeSource(99); } // 移除sink-input idx测试 TEST_F(ukccAudioTest, Test_volumeControl_removeSinkInput) { ukccAudioPlugin->m_pVolumeControl->removeSinkInput(99); } // 移除source-output idx测试 TEST_F(ukccAudioTest, Test_volumeControl_removeSourceOutput) { ukccAudioPlugin->m_pVolumeControl->removeSourceOutput(99); } // 移除客户端idx测试 TEST_F(ukccAudioTest, Test_volumeControl_removeClient) { ukccAudioPlugin->m_pVolumeControl->removeClient(99); } // 设置连接信息提示测试 TEST_F(ukccAudioTest, Test_volumeControl_setConnectingMessage) { ukccAudioPlugin->m_pVolumeControl->setConnectingMessage("TEST"); } // 获取sink-input音量测试 TEST_F(ukccAudioTest, Test_volumeControl_getSinkInputVolume) { ukccAudioPlugin->m_pVolumeControl->getSinkInputVolume("kylin-music"); } // 获取source-output音量测试 TEST_F(ukccAudioTest, Test_volumeControl_getSourceOutputVolume) { ukccAudioPlugin->m_pVolumeControl->getSourceOutputVolume("kylin-recorder"); } // 移除输入端口信息测试 TEST_F(ukccAudioTest, Test_volumeControl_removeInputPortMap) { ukccAudioPlugin->m_pVolumeControl->removeInputPortMap(99); } // 移除声卡信息测试 TEST_F(ukccAudioTest, Test_volumeControl_removeCardMap) { ukccAudioPlugin->m_pVolumeControl->removeCardMap(99); } // 移除声卡配置文件测试 TEST_F(ukccAudioTest, Test_volumeControl_removeCardProfileMap) { ukccAudioPlugin->m_pVolumeControl->removeCardProfileMap(99); } // 移除输出端口测试 TEST_F(ukccAudioTest, Test_volumeControl_removeSinkPortMap) { ukccAudioPlugin->m_pVolumeControl->removeSinkPortMap(99); } // 移除输入端口测试 TEST_F(ukccAudioTest, Test_volumeControl_removeSourcePortMap) { ukccAudioPlugin->m_pVolumeControl->removeSourcePortMap(99); } // 移除声卡配置文件测试 TEST_F(ukccAudioTest, Test_volumeControl_removeProfileMap) { ukccAudioPlugin->m_pVolumeControl->removeProfileMap(99); } // 移除声卡输入配置文件测试 TEST_F(ukccAudioTest, Test_volumeControl_removeInputProfile) { ukccAudioPlugin->m_pVolumeControl->removeInputProfile(99); } // 通过输出设备idx获取输出结构体信息测试 TEST_F(ukccAudioTest, Test_volumeControl_getSinkInfoByIndex) { ukccAudioPlugin->m_pVolumeControl->getSinkInfoByIndex(0); } // 通过输出设备名获取输出结构体信息测试 TEST_F(ukccAudioTest, Test_volumeControl_getSinkInfoByName) { ukccAudioPlugin->m_pVolumeControl->getSinkInfoByName("TEST"); } // 通过输入设备idx获取输入结构体信息测试 TEST_F(ukccAudioTest, Test_volumeControl_getSourceInfoByIndex) { ukccAudioPlugin->m_pVolumeControl->getSourceInfoByIndex(0); } // 通过输入设备名获取输入结构体信息测试 TEST_F(ukccAudioTest, Test_volumeControl_getSourceInfoByName) { ukccAudioPlugin->m_pVolumeControl->getSourceInfoByName("TEST"); } // -------------------- UkmediaMainWidget----------------------- // gsettings值变化测试 TEST_F(ukccAudioTest, Test_mainWidget_onKeyChanged) { ukccAudioPlugin->onKeyChanged("TEST"); } // 开机音乐开关slot测试 TEST_F(ukccAudioTest, Test_mainWidget_startupButtonSwitchChangedSlot) { ukccAudioPlugin->startupButtonSwitchChangedSlot(true); } // 关机音乐开关slot测试 TEST_F(ukccAudioTest, Test_mainWidget_poweroffButtonSwitchChangedSlot) { ukccAudioPlugin->poweroffButtonSwitchChangedSlot(true); } // 注销音乐开关slot测试 TEST_F(ukccAudioTest, Test_mainWidget_logoutMusicButtonSwitchChangedSlot) { ukccAudioPlugin->logoutMusicButtonSwitchChangedSlot(true); } // 唤醒音乐开关slot测试 TEST_F(ukccAudioTest, Test_mainWidget_wakeButtonSwitchChangedSlot) { ukccAudioPlugin->wakeButtonSwitchChangedSlot(true); } // 提示音开关slot测试 TEST_F(ukccAudioTest, Test_mainWidget_alertSoundButtonSwitchChangedSlot) { ukccAudioPlugin->alertSoundButtonSwitchChangedSlot(true); } // 降噪开关slot测试 TEST_F(ukccAudioTest, Test_mainWidget_noiseReductionButtonSwitchChangedSlot) { ukccAudioPlugin->noiseReductionButtonSwitchChangedSlot(false); } // 侦听开关slot测试 TEST_F(ukccAudioTest, Test_mainWidget_loopbackButtonSwitchChangedSlot) { ukccAudioPlugin->loopbackButtonSwitchChangedSlot(false); } // 音量增强开关slot测试 TEST_F(ukccAudioTest, Test_mainWidget_volumeIncreaseBtuuonSwitchChangedSlot) { ukccAudioPlugin->volumeIncreaseBtuuonSwitchChangedSlot(false); } // 开机音乐开关slot测试 TEST_F(ukccAudioTest, Test_mainWidget_bootMusicSettingsChanged) { ukccAudioPlugin->bootMusicSettingsChanged("TEST"); } // 系统主题gsettings变化slot测试 TEST_F(ukccAudioTest, Test_mainWidget_ukuiThemeChangedSlot) { ukccAudioPlugin->ukuiThemeChangedSlot("ukui-light"); } // 获取系统默认输入音量测试 TEST_F(ukccAudioTest, Test_mainWidget_getInputVolume) { ukccAudioPlugin->getInputVolume(); } // 获取系统默认输出音量测试 TEST_F(ukccAudioTest, Test_mainWidget_getOutputVolume) { ukccAudioPlugin->getOutputVolume(); } // 系统全局主题变化slot测试 TEST_F(ukccAudioTest, Test_mainWidget_globalThemeChangedSlot) { ukccAudioPlugin->globalThemeChangedSlot("HeYin"); } // 更新主题测试 TEST_F(ukccAudioTest, Test_mainWidget_updateTheme) { ukccAudioPlugin->updateTheme(ukccAudioPlugin); } // 查找输出端口测试 TEST_F(ukccAudioTest, Test_mainWidget_findPortSink) { ukccAudioPlugin->findPortSink(99, "TEST"); } // 查找输入端口测试 TEST_F(ukccAudioTest, Test_mainWidget_findPortSource) { ukccAudioPlugin->findPortSource(99, "TEST"); } // 增加‘无’端口测试 TEST_F(ukccAudioTest, Test_mainWidget_addNoneItem) { ukccAudioPlugin->addNoneItem(SoundType::SINK); } // 增加‘无’端口测试 TEST_F(ukccAudioTest, Test_mainWidget_addNoneItem2) { ukccAudioPlugin->addNoneItem(SoundType::SOURCE); } // 输入端口索引查询测试 TEST_F(ukccAudioTest, Test_mainWidget_indexOfInputPortInInputCombobox) { ukccAudioPlugin->indexOfInputPortInInputCombobox("TEST"); } // 输入静音按钮slot测试 TEST_F(ukccAudioTest, Test_mainWidget_inputMuteButtonSlot) { ukccAudioPlugin->inputMuteButtonSlot(); } // 输出静音按钮slot测试 TEST_F(ukccAudioTest, Test_mainWidget_outputMuteButtonSlot) { ukccAudioPlugin->outputMuteButtonSlot(); } // 音效主题下拉框slot测试 TEST_F(ukccAudioTest, Test_mainWidget_themeComboxIndexChangedSlot) { ukccAudioPlugin->themeComboxIndexChangedSlot(1); } // 输出音量滑动条slot测试 TEST_F(ukccAudioTest, Test_mainWidget_outputWidgetSliderChangedSlot) { ukccAudioPlugin->outputWidgetSliderChangedSlot(100); } // 输出音量滑动条slot测试 TEST_F(ukccAudioTest, Test_mainWidget_outputWidgetSliderChangedSlotInBlue) { ukccAudioPlugin->outputWidgetSliderChangedSlotInBlue(100); } // 定时器slot测试 TEST_F(ukccAudioTest, Test_mainWidget_timeSliderSlot) { ukccAudioPlugin->timeSliderSlot(); } // 输入音量滑动条slot测试 TEST_F(ukccAudioTest, Test_mainWidget_inputWidgetSliderChangedSlot) { ukccAudioPlugin->inputWidgetSliderChangedSlot(100); } // 声道平衡滑动条slot测试 TEST_F(ukccAudioTest, Test_mainWidget_balanceSliderChangedMono) { ukccAudioPlugin->balanceSliderChangedMono(0.0, 65536, 0); } // 声道平衡滑动条slot测试 TEST_F(ukccAudioTest, Test_mainWidget_balanceSliderChangedSlot) { ukccAudioPlugin->balanceSliderChangedSlot(0.0); } // 寻找输出端口测试 TEST_F(ukccAudioTest, Test_mainWidget_findOutputPortName) { ukccAudioPlugin->findOutputPortName(0, "TEST"); } // 寻找输入端口测试 TEST_F(ukccAudioTest, Test_mainWidget_findInputPortName) { ukccAudioPlugin->findInputPortName(0, "TEST"); } // 寻找高优先级配置文件测试 TEST_F(ukccAudioTest, Test_mainWidget_findHighPriorityProfile) { ukccAudioPlugin->findHighPriorityProfile(0, "TEST"); } // 可用输入设备是否包含蓝牙设备测试 TEST_F(ukccAudioTest, Test_mainWidget_inputComboboxDeviceContainBluetooth) { ukccAudioPlugin->inputComboboxDeviceContainBluetooth(); } // 蓝牙声卡名获取测试 TEST_F(ukccAudioTest, Test_mainWidget_blueCardNameInCombobox) { ukccAudioPlugin->blueCardNameInCombobox(); } // 设置声卡配置文件测试 TEST_F(ukccAudioTest, Test_mainWidget_setCardProfile) { ukccAudioPlugin->setCardProfile("TEST", "TEST"); } // 更新设备端口测试 TEST_F(ukccAudioTest, Test_mainWidget_updateCboxDevicePort) { ukccAudioPlugin->updateCboxDevicePort(); } // 输出设备下拉框slot测试 TEST_F(ukccAudioTest, Test_mainWidget_cboxoutputListWidgetCurrentRowChangedSlot) { ukccAudioPlugin->cboxoutputListWidgetCurrentRowChangedSlot(); } // 输入设备下拉框slot测试 TEST_F(ukccAudioTest, Test_mainWidget_cboxinputListWidgetCurrentRowChangedSlot) { ukccAudioPlugin->cboxinputListWidgetCurrentRowChangedSlot(); } // 自定义音效下拉框slot测试 TEST_F(ukccAudioTest, Test_mainWidget_customSoundEffectsSlot) { ukccAudioPlugin->customSoundEffectsSlot(1); } // 重置自定义音效测试 TEST_F(ukccAudioTest, Test_mainWidget_resetCustomSoundEffects) { ukccAudioPlugin->resetCustomSoundEffects("HeYin", "audio-volume-change"); } TEST_F(ukccAudioTest, Test_mainWidget_addValue) { ukccAudioPlugin->addValue("TEST", "TEST"); } // These methods no longer exist in the current version // TEST_F(ukccAudioTest, Test_mainWidget_inputStreamMapCardName) { // ukccAudioPlugin->inputStreamMapCardName("TEST", "TEST"); // } // TEST_F(ukccAudioTest, Test_mainWidget_outputStreamMapCardName) { // ukccAudioPlugin->outputStreamMapCardName("TEST", "TEST"); // } // TEST_F(ukccAudioTest, Test_mainWidget_findInputStreamCardName) { // ukccAudioPlugin->findInputStreamCardName("TEST"); // } // TEST_F(ukccAudioTest, Test_mainWidget_findOutputStreamCardName) { // ukccAudioPlugin->findOutputStreamCardName("TEST"); // } int main(int argc, char **argv) { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } ukui-media/tests/unit_test_ukui_media_tray/0000775000175000017500000000000015170054730020147 5ustar fengfengukui-media/tests/unit_test_ukui_media_tray/unit_test_ukui_media_tray.pro0000664000175000017500000000604515170054730026147 0ustar fengfengQT += core gui widgets dbus xml network testlib TEMPLATE = app CONFIG += c++14 link_pkgconfig no_keywords PKGCONFIG += \ gsettings-qt6 \ Qt6Svg \ gio-2.0 \ dconf \ x11 \ libcanberra \ libpulse \ libpulse-mainloop-glib \ alsa \ kysdk-sysinfo \ kysdk-hardware \ kysdk-waylandhelper \ KF6WindowSystem \ kysdk-qtwidgets TARGET = unit_test_ukui_media_tray #代码覆盖率工具gcov QMAKE_LFLAGS +=-fprofile-arcs -ftest-coverage QMAKE_CXXFLAGS += --coverage -fno-inline -fno-access-control INCLUDEPATH += /usr/include/gtest LIBS += -lgtest_main -lpthread LIBS += -L$$[QT_INSTALL_LIBS] -lgsettings-qt6 -lgtest -lgcov HEADERS += \ ../../common/json.h \ ../../common/user_config.h \ ../../common/user_info_json.h \ ../../common/ukmedia_common.h \ ../../common/sound_effect_json.h \ ../../ukui-volume-control-applet-qt/config.h \ ../../ukui-volume-control-applet-qt/custom_sound.h \ ../../ukui-volume-control-applet-qt/ukmedia_main_widget.h \ ../../ukui-volume-control-applet-qt/ukmedia_custom_class.h \ ../../ukui-volume-control-applet-qt/ukmedia_volume_control.h \ ../../ukui-volume-control-applet-qt/ukmedia_application_volume_widget.h \ ../../ukui-volume-control-applet-qt/ukmedia_osd_display_widget.h \ ../../ukui-volume-control-applet-qt/ukmedia_system_volume_widget.h \ ../../ukui-volume-control-applet-qt/ukui_volume_control_user_config.h \ ../../ukui-volume-control-applet-qt/ukui_list_widget_item.h \ ../../ukui-volume-control-applet-qt/ukui_media_set_headset_widget.h \ ../../ukui-volume-control-applet-qt/dbus-adaptor/dbus-adaptor.h \ ../../ukui-volume-control-applet-qt/dbus-adaptor/bluez-adaptor.h \ ../../ukui-volume-control-applet-qt/QtSingleApplication/qtlocalpeer.h \ ../../ukui-volume-control-applet-qt/QtSingleApplication/qtsingleapplication.h SOURCES += \ ../../common/user_config.cpp \ ../../common/user_info_json.cpp \ ../../common/ukmedia_common.cpp \ ../../common/sound_effect_json.cpp \ ../../ukui-volume-control-applet-qt/custom_sound.cpp \ ../../ukui-volume-control-applet-qt/ukmedia_main_widget.cpp \ ../../ukui-volume-control-applet-qt/ukmedia_custom_class.cpp \ ../../ukui-volume-control-applet-qt/ukmedia_volume_control.cpp \ ../../ukui-volume-control-applet-qt/ukmedia_osd_display_widget.cpp \ ../../ukui-volume-control-applet-qt/ukmedia_system_volume_widget.cpp \ ../../ukui-volume-control-applet-qt/ukmedia_application_volume_widget.cpp \ ../../ukui-volume-control-applet-qt/ukui_list_widget_item.cpp \ ../../ukui-volume-control-applet-qt/ukui_media_set_headset_widget.cpp \ ../../ukui-volume-control-applet-qt/ukui_volume_control_user_config.cpp \ ../../ukui-volume-control-applet-qt/dbus-adaptor/dbus-adaptor.cpp \ ../../ukui-volume-control-applet-qt/dbus-adaptor/bluez-adaptor.cpp \ ../../ukui-volume-control-applet-qt/QtSingleApplication/qtlocalpeer.cpp \ ../../ukui-volume-control-applet-qt/QtSingleApplication/qtsingleapplication.cpp \ main.cpp ukui-media/tests/unit_test_ukui_media_tray/main.cpp0000664000175000017500000005441015170054730021603 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #include #include "../../ukui-volume-control-applet-qt/ukmedia_main_widget.h" #include #include "../../ukui-volume-control-applet-qt/QtSingleApplication/QtSingleApplication" #include class ukuiMediaTrayTest:public testing::Test { protected: static void SetUpTestCase() { int argc = 0; char *argv[] = {}; app = new QtSingleApplication(argc, argv); mediaTray = new UkmediaMainWidget(); // 因ukui-media与pulseaudio建立连接需要3s,因此需要延迟3.5s执行测试用例 QTest::qWait(3500); } static void TearDownTestCase() { delete mediaTray; delete app; } static UkmediaMainWidget* mediaTray; static QtSingleApplication *app; }; // 静态成员变量初始化 UkmediaMainWidget* ukuiMediaTrayTest::mediaTray = nullptr; QtSingleApplication* ukuiMediaTrayTest::app = nullptr; /* -------------------- UkmediaVolumeControl----------------------- */ // 获取默认输出设备idx测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_getDefaultSinkIndex) { int index = mediaTray->m_pVolumeControl->getDefaultSinkIndex(); } // 获取sink-input静音状态测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_getSinkInputMuted) { bool muteState = mediaTray->m_pVolumeControl->getSinkInputMuted("kylin-music"); } // 设置sink-input音量测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_setSinkInputVolume) { mediaTray->m_pVolumeControl->setSinkInputVolume(9, 30000, 2); } // 获取sink-input音量测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_getSinkInputVolume) { mediaTray->m_pVolumeControl->getSinkInputVolume("kylin-music"); } // 设置sink-input静音状态测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_setSinkInputMuted) { mediaTray->m_pVolumeControl->setSinkInputMuted("kylin-music", true); } // 更改sink-input默认设备测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_moveSinkInput) { mediaTray->m_pVolumeControl->moveSinkInput("kylin-music", "alsa_output.pci-0000_00_1f.3-platform-skl_hda_dsp_generic.HiFi__hw_sofhdadsp__sink"); } // 更改source-output默认设备测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_moveSoureOutput) { mediaTray->m_pVolumeControl->moveSoureOutput("kylin-recorder", "alsa_input.pci-0000_00_1f.3-platform-skl_hda_dsp_generic.HiFi__hw_sofhdadsp_6__source"); } // 获取source-output音量测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_getSourceOutputVolume) { mediaTray->m_pVolumeControl->getSourceOutputVolume("kylin-recorder"); } // 设置source-output音量测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_setSourceOutputVolume) { mediaTray->m_pVolumeControl->setSourceOutputVolume(9, 30000, 2); } // 设置source-output静音状态测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_setSourceOutputMuted) { mediaTray->m_pVolumeControl->setSourceOutputMuted("kylin-recorder", true); } // 设置声卡配置文件测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_setCardProfile) { mediaTray->m_pVolumeControl->setCardProfile(0, "test"); } // 设置默认输出设备测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_setDefaultSink) { mediaTray->m_pVolumeControl->setDefaultSink("test"); } // 设置默认输入设备测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_setDefaultSource) { mediaTray->m_pVolumeControl->setDefaultSource("test"); } // 设置默认输出端口测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_setSinkPort) { mediaTray->m_pVolumeControl->setSinkPort("test", "test"); } // 设置默认输入端口测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_setSourcePort) { mediaTray->m_pVolumeControl->setSourcePort("test", "test"); } // kill sink-input 测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_killSinkInput) { mediaTray->m_pVolumeControl->killSinkInput(1); } // 移除输出设备信息测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_removeSink) { mediaTray->m_pVolumeControl->removeSink(99); } // 移除输入设备信息测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_removeSource) { mediaTray->m_pVolumeControl->removeSource(99); } // 移除输出类应用信息测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_removeSinkInput) { mediaTray->m_pVolumeControl->removeSinkInput(99); } // 获取蓝牙电量测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_getBatteryLevel) { mediaTray->m_pVolumeControl->getBatteryLevel("test"); } // 移除输出端口信息测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_removeOutputPortMap) { mediaTray->m_pVolumeControl->removeOutputPortMap(99); } // 移除输入端口信息测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_removeInputPortMap) { mediaTray->m_pVolumeControl->removeInputPortMap(99); } // 移除声卡信息测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_removeCardMap) { mediaTray->m_pVolumeControl->removeCardMap(99); } // 移除声卡配置文件测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_removeCardProfileMap) { mediaTray->m_pVolumeControl->removeCardProfileMap(99); } // 移除输出端口信息测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_removeSinkPortMap) { mediaTray->m_pVolumeControl->removeSinkPortMap(99); } // 移除输入端口信息测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_removeSourcePortMap) { mediaTray->m_pVolumeControl->removeSourcePortMap(99); } // 移除声卡配置文件测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_removeProfileMap) { mediaTray->m_pVolumeControl->removeProfileMap(99); } // 移除输入配置文件测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_removeInputProfile) { mediaTray->m_pVolumeControl->removeInputProfile(99); } // 查找输出端口测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_findOutputPort) { mediaTray->m_pVolumeControl->findOutputPort("Speaker"); } // 查找输出端口idx测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_findPortSinkIndex) { mediaTray->m_pVolumeControl->findPortSinkIndex("Speaker"); } // 查找输出端口名测试 TEST_F(ukuiMediaTrayTest, Test_volumeControl_findSinkPortName) { mediaTray->m_pVolumeControl->findSinkPortName(0); } /* -------------------- UkmediaMainWidget----------------------- */ // 获取默认输入设备测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_getDefaultInputDevice) { QString defaultInputDev = mediaTray->getDefaultInputDevice(); qDebug() << "test getDefaultInputDevice" << defaultInputDev; } // 获取默认输出设备测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_getDefaultOutputDevice) { QString defaultOutputDev = mediaTray->getDefaultOutputDevice(); qDebug() << "test getDefaultOutputDevice" << defaultOutputDev; } // 获取所有输出设备测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_getAllOutputDevices) { QStringList devicesList = mediaTray->getAllOutputDevices(); qDebug() << "test getAllOutputDevices" << devicesList; } // 获取所有输入设备测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_getAllInputDevices) { QStringList devicesList = mediaTray->getAllInputDevices(); qDebug() << "test getAllInputDevices" << devicesList; } // 获取默认输出设备音量测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_getDefaultOutputVolume) { int mediaVolume = mediaTray->getDefaultOutputVolume(); qDebug() << "Test: getDefaultOutputVolume" << mediaVolume; } // 获取默认输入设备音量测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_getDefaultInputVolume) { int mediaVolume = mediaTray->getDefaultInputVolume(); qDebug() << "Test: getDefaultInputVolume" << mediaVolume; } // 获取默认输出设备静音状态测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_getDefaultOutputMuteState) { bool muteState = mediaTray->getDefaultOutputMuteState(); qDebug() << "Test: getDefaultOutputMuteState" << muteState; } // 获取默认输入设备静音状态测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_getDefaultInputMuteState) { bool muteState = mediaTray->getDefaultInputMuteState(); qDebug() << "Test: getDefaultInputMuteState" << muteState; } // 设置默认输出设备音量测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_setDefaultOutputVolume) { bool isSucceed = mediaTray->setDefaultOutputVolume(100); qDebug() << "Test: setDefaultOutputVolume" << isSucceed; } // 设置默认输入设备音量测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_setDefaultInputVolume) { bool isSucceed = mediaTray->setDefaultInputVolume(100); qDebug() << "Test: setDefaultInputVolume" << isSucceed; } // 设置默认输出设备静音状态测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_setDefaultOutputMuteState) { bool isSucceed = mediaTray->setDefaultOutputMuteState(false); qDebug() << "Test: setDefaultOutputMuteState" << isSucceed; } // 设置默认输入设备静音状态测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_setDefaultInputMuteState) { bool isSucceed = mediaTray->setDefaultInputMuteState(false); qDebug() << "Test: setDefaultInputMuteState" << isSucceed; } // 获取系统默认输入设备端口名 TEST_F(ukuiMediaTrayTest, Test_mainWidget_getSystemInputDevice) { QString portName = mediaTray->getSystemInputDevice(); qDebug() << "Test: getSystemInputDevice" << portName; } // 获取系统默认输出设备端口名 TEST_F(ukuiMediaTrayTest, Test_mainWidget_getSystemOutputDevice) { QString portName = mediaTray->getSystemOutputDevice(); qDebug() << "Test: getSystemOutputDevice" << portName; } // 获取系统当前正在使用音频设备的应用名单列表测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_getAppList) { QStringList appList = mediaTray->getAppList(); qDebug() << "Test: getAppList" << appList; } // 初始化静音灯测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_initVolumeLed) { mediaTray->initVolumeLed(); } // 设置耳机端口测试测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_setHeadsetPort) { mediaTray->setHeadsetPort("test"); } // 提示音测试测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_handleTimeout) { mediaTray->handleTimeout(); } // 字体gsettings变化slot测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_fontSizeChangedSlot) { mediaTray->fontSizeChangedSlot("systemFont"); } // 主题gsettings变化slot测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_ukuiThemeChangedSlot) { mediaTray->ukuiThemeChangedSlot("ukui-light"); } // 音效主题gsettings变化slot测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_soundThemeChangedSlot) { mediaTray->soundThemeChangedSlot("HeYin"); } // 系统音量滑动条slot测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_systemVolumeSliderChangedSlot) { mediaTray->systemVolumeSliderChangedSlot(100); } // 系统音量滑动条slot测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_advancedSystemSliderChangedSlot) { mediaTray->advancedSystemSliderChangedSlot(100); } // 系统静音按钮slot测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_systemVolumeButtonClickedSlot) { mediaTray->systemVolumeButtonClickedSlot(); } // 系统静音按钮slot测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_appWidgetMuteButtonCLickedSlot) { mediaTray->appWidgetMuteButtonCLickedSlot(); } // 声音托盘鼠标事件slot测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_activatedSystemTrayIconSlot) { mediaTray->activatedSystemTrayIconSlot(QSystemTrayIcon::ActivationReason::Trigger); } // 声音托盘鼠标事件slot测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_activatedSystemTrayIconSlot1) { mediaTray->activatedSystemTrayIconSlot(QSystemTrayIcon::ActivationReason::MiddleClick); } // 声音托盘鼠标事件slot测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_activatedSystemTrayIconSlot2) { mediaTray->activatedSystemTrayIconSlot(QSystemTrayIcon::ActivationReason::DoubleClick); } // 声音设置跳转slot测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_jumpControlPanelSlot) { mediaTray->jumpControlPanelSlot(); } TEST_F(ukuiMediaTrayTest, Test_mainWidget_verifyAlsaCard) { gboolean hsmic = TRUE; gboolean hpmic = TRUE; mediaTray->verifyAlsaCard(0, &hsmic, &hpmic); } // 鼠标滚轮设置静音状态slot测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_mouseMeddleClickedTraySlot) { mediaTray->mouseMeddleClickedTraySlot(); } // 蓝牙耳机电量变化slot测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_batteryLevelChanged) { mediaTray->batteryLevelChanged("TEST", 100); } // 初始化蓝牙设备音量测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_initBlueDeviceVolume) { mediaTray->initBlueDeviceVolume(99, "TEST"); } // 绘制图标测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_drawLightColoredPixmap) { QSize iconSize(24,24); mediaTray->drawLightColoredPixmap(QIcon::fromTheme("audio-volume-medium-symbolic").pixmap(iconSize)); } // 绘制图标测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_drawDarkColoredPixmap) { QSize iconSize(24,24); mediaTray->drawDarkColoredPixmap(QIcon::fromTheme("audio-volume-medium-symbolic").pixmap(iconSize)); } // 计算托盘弹出位置测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_caculatePosition) { mediaTray->caculatePosition(); } // 开机音乐是否开启测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_switchStartupPlayMusic) { mediaTray->switchStartupPlayMusic(); } // 系统声音相关配置gsettings变化测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_soundSettingChanged) { mediaTray->soundSettingChanged("TEST"); } // s3/s4音频设备挂起测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_onPrepareForSleep) { mediaTray->onPrepareForSleep(0); } // 选择输出设备slot测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_outputListWidgetCurrentRowChangedSlot) { mediaTray->outputListWidgetCurrentRowChangedSlot(1); } // 选择输入设备slot测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_inputListWidgetCurrentRowChangedSlot) { mediaTray->inputListWidgetCurrentRowChangedSlot(1); } // 更新应用音量测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_updateAppVolume) { mediaTray->updateAppVolume("kylin-music", 100, false); } // 字符串整理函数测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_stringRemoveUnrecignizedChar) { mediaTray->stringRemoveUnrecignizedChar("test"); } // 获取蓝牙声卡名测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_blueCardName) { mediaTray->blueCardName(); } // 查找文件函数测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_findFile) { mediaTray->findFile("/usr/share/sounds/", "test"); } TEST_F(ukuiMediaTrayTest, Test_mainWidget_getSessionActive) { mediaTray->getSessionActive(); } // 播放提示音测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_playAlertSound) { mediaTray->playAlertSound("audio-volume-change"); } // 获取输出类应用信息测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_getPlaybackAppInfo) { mediaTray->getPlaybackAppInfo(); } // 获取输入类应用信息测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_getRecordAppInfo) { mediaTray->getRecordAppInfo(); } // 获取应用音量测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_getAppVolume) { mediaTray->getAppVolume("kylin-music"); } // 获取应用静音状态测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_getAppMuteState) { mediaTray->getAppMuteState("kylin-music"); } // 获取应用当前默认输出设备测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_getAppOutputDevice) { mediaTray->getAppOutputDevice("kylin-music"); } // 获取应用当前默认输入设备测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_getAppInputDevice) { mediaTray->getAppInputDevice("kylin-recorder"); } // 设置应用静音状态测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_setAppMuteState) { mediaTray->setAppMuteState("kylin-music", false); } // 设置应用音量测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_setAppVolume) { mediaTray->setAppVolume("kylin-music", 100); } // 设置系统输出设备测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_setSystemOutputDevice) { mediaTray->setSystemOutputDevice("test"); } // 设置系统输入设备测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_setSystemInputDevice) { mediaTray->setSystemInputDevice("test"); } // 设置应用输出设备测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_setAppOutputDevice) { mediaTray->setAppOutputDevice("kylin-music", 99, "test"); } // 设置应用输入设备测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_setAppInputDevice) { mediaTray->setAppInputDevice("kylin-recorder", 99, "test"); } // 设置端口是否需要隐藏测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_isPortHidingNeeded) { mediaTray->isPortHidingNeeded(0, 0, "HDMI"); } // 设置端口是否需要隐藏测试 TEST_F(ukuiMediaTrayTest, Test_mainWidget_isPortHidingNeeded2) { mediaTray->isPortHidingNeeded(1, 0, "HDMI"); } /* -------------------- Bluetooth_Dbus ----------------------- */ // 发送蓝牙耳机电量发生变化信号测试 TEST_F(ukuiMediaTrayTest, Test_sendBatteryChangedSignal) { mediaTray->m_pBluetoothDbus->sendBatteryChangedSignal("test", 100); } // 获取蓝牙耳机电量测试 TEST_F(ukuiMediaTrayTest, Test_getBatteryLevel) { mediaTray->m_pBluetoothDbus->getBatteryLevel("test"); } /* -------------------- MediaAdaptor ----------------------- */ // 获取所有输入设备测试 TEST_F(ukuiMediaTrayTest, dbus_GetAllInputDevicesTest) { QStringList list = mediaTray->m_pMediaDbus->getAllInputDevices(); } // 获取所有输出设备测试 TEST_F(ukuiMediaTrayTest, dbus_GetAllOutputDevicesTest) { QStringList list = mediaTray->m_pMediaDbus->getAllOutputDevices(); } // 获取默认输出设备测试 TEST_F(ukuiMediaTrayTest, dbus_GetDefaultOutputDeviceTest) { QString device = mediaTray->m_pMediaDbus->getDefaultOutputDevice(); } // 获取默认输入设备测试 TEST_F(ukuiMediaTrayTest, dbus_GetDefaultInputDeviceTest) { QString device = mediaTray->m_pMediaDbus->getDefaultInputDevice(); } // 获取默认输入设备静音状态测试 TEST_F(ukuiMediaTrayTest, dbus_GetDefaultInputMuteStateTest) { bool state = mediaTray->m_pMediaDbus->getDefaultInputMuteState(); } // 获取默认输出设备静音状态测试 TEST_F(ukuiMediaTrayTest, dbus_GetDefaultOutputMuteStateTest) { bool state = mediaTray->m_pMediaDbus->getDefaultOutputMuteState(); } // 获取默认输入设备音量测试 TEST_F(ukuiMediaTrayTest, dbus_GetDefaultInputVolumeTest) { int value = mediaTray->m_pMediaDbus->getDefaultInputVolume(); } // 获取默认输出设备音量测试 TEST_F(ukuiMediaTrayTest, dbus_GetDefaultOutputVolumeTest) { int value = mediaTray->m_pMediaDbus->getDefaultOutputVolume(); } // 设置默认输入设备测试 TEST_F(ukuiMediaTrayTest, dbus_SetDefaultInputDeviceTest) { bool state = mediaTray->m_pMediaDbus->setDefaultInputDevice("test"); } // 设置默认输出设备测试 TEST_F(ukuiMediaTrayTest, dbus_SetDefaultOutputDeviceTest) { bool state = mediaTray->m_pMediaDbus->setDefaultOutputDevice("test"); } // 设置默认输入设备音量测试 TEST_F(ukuiMediaTrayTest, dbus_SetDefaultInputVolumeTest) { bool state = mediaTray->m_pMediaDbus->setDefaultInputVolume(100); } // 设置默认输出设备音量测试 TEST_F(ukuiMediaTrayTest, dbus_SetDefaultOutputVolumeTest) { bool state = mediaTray->m_pMediaDbus->setDefaultOutputVolume(100); } // 设置默认输入设备静音状态测试 TEST_F(ukuiMediaTrayTest, dbus_SetDefaultInputMuteStateTest) { bool state = mediaTray->m_pMediaDbus->setDefaultInputMuteState(true); } // 设置默认输出设备静音状态测试 TEST_F(ukuiMediaTrayTest, dbus_SetDefaultOutputMuteStateTest) { bool state = mediaTray->m_pMediaDbus->setDefaultOutputMuteState(true); } // 获取蓝牙耳机电量测试 TEST_F(ukuiMediaTrayTest, dbus_GetBatteryLevelTest) { int battery_value = mediaTray->m_pMediaDbus->getBatteryLevel("TEST"); } /* -------------------- UkmediaOsdDisplayWidget ----------------------- */ // 获取osd弹窗位置测试 TEST_F(ukuiMediaTrayTest, osd_caculatePositionTest) { mediaTray->osdWidget->caculatePosition(); } // 设置osd弹窗图标测试 TEST_F(ukuiMediaTrayTest, osd_setIconTest) { mediaTray->osdWidget->setIcon("audio-headphones-symbolic"); } // osd弹窗定时器函数测试 TEST_F(ukuiMediaTrayTest, osd_timeoutHandleTest) { mediaTray->osdWidget->timeoutHandle(); } // osd弹窗重绘函数测试 TEST_F(ukuiMediaTrayTest, osd_repaintWidgetTest) { mediaTray->osdWidget->repaintWidget(); } // osd弹窗主题gsetting变化测试 TEST_F(ukuiMediaTrayTest, osd_ukuiThemeChangedSlotTest) { mediaTray->osdWidget->ukuiThemeChangedSlot("ukui-light"); } // osd弹窗获取透明度测试 TEST_F(ukuiMediaTrayTest, osd_getGlobalOpacityTest) { mediaTray->osdWidget->getGlobalOpacity(); } TEST_F(ukuiMediaTrayTest, Test_HeadsetWidget_headphoneButtonClickedSlot) { mediaTray->headsetWidget->headphoneButtonClickedSlot(); } TEST_F(ukuiMediaTrayTest, Test_HeadsetWidget_headsetButtonClickedSlot) { mediaTray->headsetWidget->headsetButtonClickedSlot(); } TEST_F(ukuiMediaTrayTest, Test_HeadsetWidget_microphoneButtonClickedSlot) { mediaTray->headsetWidget->microphoneButtonClickedSlot(); } TEST_F(ukuiMediaTrayTest, Test_HeadsetWidget_soundSettingButtonClickedSlot) { mediaTray->headsetWidget->soundSettingButtonClickedSlot(); } TEST_F(ukuiMediaTrayTest, Test_HeadsetWidget_cancelButtonClickedSlot) { mediaTray->headsetWidget->cancelButtonClickedSlot(); } TEST_F(ukuiMediaTrayTest, Test_HeadsetWidget_cset) { mediaTray->headsetWidget->cset("name= Capture Switch", HW_CARD, "0", 0, 0); } int main(int argc, char **argv) { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } ukui-media/tests/tests.pro0000664000175000017500000000041715170054730014564 0ustar fengfengTEMPLATE = subdirs SUBDIRS += unit_test_sound_theme_player \ unit_test_ukui_login_sound \ unit_test_ukui_media_tray \ unit_test_ukcc_audio_plugin \ unit_test_ukcc_app_dev_ctrl \ unit_test_ukcc_audio_dev_ctrl ukui-media/tests/unit_test_ukcc_app_dev_ctrl/0000775000175000017500000000000015170054730020443 5ustar fengfengukui-media/tests/unit_test_ukcc_app_dev_ctrl/main.cpp0000664000175000017500000001020315170054730022067 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #include #include "../../audio/ukmedia_main_widget.h" #include class ukccAppCtrlTest : public testing::Test { protected: static void SetUpTestCase() { int argc = 1; char *argv[] = {"ukccAppCtrlTest"}; //需加参数,否则因SDK Kwidget运行构造函数报错 app = new QApplication(argc, argv); ukccAppCtrl = new UkmediaAppCtrlWidget(); } static void TearDownTestCase() { delete ukccAppCtrl; delete app; } static UkmediaAppCtrlWidget* ukccAppCtrl; static QApplication* app; }; // 静态成员变量初始化测试 UkmediaAppCtrlWidget* ukccAppCtrlTest::ukccAppCtrl = nullptr; QApplication* ukccAppCtrlTest::app = nullptr; // 获取系统音量 TEST_F(ukccAppCtrlTest, Test_getSystemVolume) { int value = ukccAppCtrl->getSystemVolume(); } // 设置应用音量测试 TEST_F(ukccAppCtrlTest, Test_setAppVolume) { QSlider *slider = ukccAppCtrl->stackWidget->findChild("kylin-music"); if (slider) slider->setValue(58); else ukccAppCtrl->setAppVolume(100); } // 设置系统音量测试 TEST_F(ukccAppCtrlTest, Test_setSystemVolume) { bool state = ukccAppCtrl->setSystemVolume(100); } // 设置应用静音状态测试 TEST_F(ukccAppCtrlTest, Test_setAppMuteState) { UkuiButtonDrawSvg *btn = ukccAppCtrl->stackWidget->findChild("kylin-music"); if (btn) btn->click(); else ukccAppCtrl->setAppMuteState(); } // 设置系统输出设备静音状态测试 TEST_F(ukccAppCtrlTest, Test_setSystemOutputDevice) { bool state = ukccAppCtrl->setSystemOutputDevice("Speaker(sof-hda-dsp)"); } // 设置系统输入设备静音状态测试 TEST_F(ukccAppCtrlTest, Test_setSystemInputDevice) { bool state = ukccAppCtrl->setSystemInputDevice("Digital Microphone(sof-hda-dsp)"); } // 设置应用输出设备测试 TEST_F(ukccAppCtrlTest, Test_setAppOutputDevice) { QComboBox *box = ukccAppCtrl->stackWidget->findChild("kylin-music-output"); if (box) box->setCurrentIndex(1); else ukccAppCtrl->setAppOutputDevice("Speaker(sof-hda-dsp)"); } // 设置应用输入设备测试 TEST_F(ukccAppCtrlTest, Test_setAppInputDevice) { QComboBox *box = ukccAppCtrl->stackWidget->findChild("kylin-recorder-input"); if (box) box->setCurrentIndex(1); else ukccAppCtrl->setAppInputDevice("Digital Microphone(sof-hda-dsp)"); } // 更新端口测试 TEST_F(ukccAppCtrlTest, Test_updatePort) { ukccAppCtrl->updatePort(); } // 更新应用列表测试 TEST_F(ukccAppCtrlTest, Test_updateAppItem) { ukccAppCtrl->updateAppItem(SYSTEM_VOLUME_CTRL); } // 移除应用列表测试 TEST_F(ukccAppCtrlTest, Test_removeAppItem) { ukccAppCtrl->removeAppItem("Test"); } // 更新系统音量测试 TEST_F(ukccAppCtrlTest, Test_updateSystemVolume) { ukccAppCtrl->updateSystemVolume(50); } // 更新系统静音状态测试 TEST_F(ukccAppCtrlTest, Test_updateSystemMuteState) { ukccAppCtrl->updateSystemMuteState(true); } // 应用索引查询测试 TEST_F(ukccAppCtrlTest, Test_indexOfItemCombobox) { QComboBox *box = new QComboBox(); box->addItem(SYSTEM_VOLUME_CTRL); int index = ukccAppCtrl->indexOfItemCombobox(SYSTEM_VOLUME_CTRL, box); } int main(int argc, char **argv) { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } ukui-media/tests/unit_test_ukcc_app_dev_ctrl/unit_test_ukcc_app_dev_ctrl.pro0000664000175000017500000000203415170054730026731 0ustar fengfengQT += dbus xml testlib widgets TEMPLATE = app CONFIG += c++14 link_pkgconfig no_keywords PKGCONFIG += gio-2.0 \ libxml-2.0 \ dconf \ gsettings-qt6 \ kysdk-qtwidgets \ kysdk-sysinfo \ kysdk-hardware TARGET = unit_test_ukcc_app_dev_ctrl #代码覆盖率工具gcov QMAKE_LFLAGS +=-fprofile-arcs -ftest-coverage QMAKE_CXXFLAGS += --coverage -fno-inline -fno-access-control INCLUDEPATH += /usr/include/gtest LIBS += -lgtest_main -lpthread LIBS += -L$$[QT_INSTALL_LIBS] -lgsettings-qt6 -lgtest -lgcov LIBS += -lukcc HEADERS += \ ../../common/ukmedia_common.h \ ../../audio/ukui_custom_style.h \ ../../audio/app-device-control/ukmedia_app_device_ctrl.h \ ../../audio/app-device-control/ukmedia_app_item_widget.h SOURCES += \ ../../common/ukmedia_common.cpp \ ../../audio/ukui_custom_style.cpp \ ../../audio/app-device-control/ukmedia_app_device_ctrl.cpp \ ../../audio/app-device-control/ukmedia_app_item_widget.cpp \ main.cpp ukui-media/tests/unit_test_ukui_login_sound/0000775000175000017500000000000015170054730020351 5ustar fengfengukui-media/tests/unit_test_ukui_login_sound/unit_test_ukui_login_sound.pro0000664000175000017500000000201415170054730026543 0ustar fengfengQT += core gui testlib TEMPLATE = app CONFIG += c++17 link_pkgconfig no_keywords PKGCONFIG += alsa TARGET = unit_test_ukui_login_sound #代码覆盖率工具gcov QMAKE_LFLAGS +=-fprofile-arcs -ftest-coverage QMAKE_CXXFLAGS += --coverage -fno-inline -fno-access-control INCLUDEPATH += /usr/include/gtest LIBS += -lgtest_main -lpthread LIBS += -L$$[QT_INSTALL_LIBS] -lgsettings-qt6 -lgtest -lgcov SOURCES += ../../common/user_config.cpp \ ../../common/user_info_json.cpp \ ../../common/sound_effect_json.cpp \ ../../ukui-login-sound/ukui_login_sound.cpp \ ../../ukui-login-sound/ukui_login_sound_user_config.cpp \ main.cpp HEADERS += ../../common/json.h \ ../../common/user_config.h \ ../../common/user_info_json.h \ ../../common/sound_effect_json.h \ ../../ukui-login-sound/formats.h \ ../../ukui-login-sound/ukui_login_sound_h \ ../../ukui-login-sound/ukui_login_sound_user_config.h ukui-media/tests/unit_test_ukui_login_sound/main.cpp0000664000175000017500000000263415170054730022006 0ustar fengfeng/* * Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, 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 #include #include "../../ukui-login-sound/ukui_login_sound.h" class soundThemePlayerTest:public testing::Test { protected: void SetUp() override { player = new UkuiLoginSound(); sleep(1); } void TearDown() override { delete player; } UkuiLoginSound* player; }; // 播放音效测试 TEST_F(soundThemePlayerTest, playStartMusicTest) { QString defaultOutputDev = "default"; player->checkPcm(defaultOutputDev.toLatin1().data()); player->aplayMain(PLAY_STARTUP_WAV, defaultOutputDev.toLatin1().data()); } int main(int argc, char **argv) { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } ukui-media/configuration/0000775000175000017500000000000015170054730014403 5ustar fengfengukui-media/configuration/99-ukui-media.rules0000664000175000017500000000423415170054730017753 0ustar fengfeng# do not edit this file, it will be overwritten on update # This file is part of UKUI-MEDIA # Author: Zoey SUBSYSTEM!="sound", GOTO="pulseaudio_end" ACTION!="change", GOTO="pulseaudio_end" KERNEL!="card*", GOTO="pulseaudio_end" SUBSYSTEMS=="usb", GOTO="pulseaudio_check_usb" SUBSYSTEMS=="platform", DRIVERS=="thinkpad_acpi", ENV{PULSE_IGNORE}="1" SUBSYSTEMS=="platform", DRIVERS=="ft-hda", ENV{PULSE_PROFILE_SET}="pulse-ft-hda.conf" SUBSYSTEMS=="platform", DRIVERS=="pmdk_dp", ENV{PULSE_PROFILE_SET}="pulse-X100-hdmi.conf" GOTO="pulseaudio_end" LABEL="pulseaudio_check_usb" # Inspur Camera ATTRS{idVendor}=="f00f", ATTRS{idProduct}=="0e01", ENV{PULSE_PROFILE_SET}="kylin-inspur-usb-audio.conf" # Lenovo MH243A ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="4f18", ENV{PULSE_PROFILE_SET}="alc4080-mh243a-usb-audio.conf" # Lenovo M90H ATTRS{idVendor}=="17aa", ATTRS{idProduct}=="3501", ENV{PULSE_PROFILE_SET}="lenovo-m90h-usb-audio.conf" #LENOVO P90H ATTRS{idVendor}=="17aa", ATTRS{idProduct}=="3507", ENV{PULSE_PROFILE_SET}="lenovo-p90h-usb-audio.conf" # MCA500 usb audio ATTRS{idVendor}=="20b1", ATTRS{idProduct}=="3079", ENV{PULSE_PROFILE_SET}="mca500-usb-audio.conf" # Audio fs 1.0 ATTRS{idVendor}=="31b2", ATTRS{idProduct}=="0020", ENV{PULSE_PROFILE_SET}="fs-hygon-usb-audio.conf" # HUAWEI Kunpeng 920 0bda~4987 ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="4987", ENV{PULSE_PROFILE_SET}="0bda-4987-usb-audio.conf" # 三诺一体机 ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="4f39", ENV{PULSE_PROFILE_SET}="0bda-4f39-usb-audio.conf" # 海康威视P514p ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="4f36", ENV{PULSE_PROFILE_SET}="0bda-4f36-usb-audio.conf" # Douyu G591 ATTRS{idVendor}=="2b0d", ATTRS{idProduct}=="0031", ENV{PULSE_VOLUME_IGNORE_DB}="1" # C-media ATTRS{idVendor}=="0d8c", ATTRS{idProduct}=="0012", ENV{PULSE_VOLUME_IGNORE_DB}="1" # AEEVISION Camera ATTRS{idVendor}=="058e", ATTRS{idProduct}=="3863", ENV{PULSE_VOLUME_IGNORE_DB}="1" # EDIFIER M16 ATTRS{idVendor}=="0000", ATTRS{idProduct}=="0201", ENV{PULSE_VOLUME_IGNORE_DB}="1" # JBL USB ATTRS{idVendor}=="e5b7", ATTRS{idProduct}=="0811", ENV{PULSE_VOLUME_IGNORE_DB}="1" GOTO="pulseaudio_end" LABEL="pulseaudio_end" ukui-media/configuration/kylin-paths/0000775000175000017500000000000015170054730016646 5ustar fengfengukui-media/configuration/kylin-paths/analog-output-jbl.conf0000664000175000017500000000431215170054730023061 0ustar fengfeng# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Intended for the 'default' output. Note that a-o-speaker.conf has a ; higher priority than this ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 99 [Element Hardware Master] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Master] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element PCM] switch = mute volume = 216 override-map.1 = all override-map.2 = all-left,all-right [Element Headphone] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Front Microphone] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Master Mono] switch = off volume = off [Element Front] switch = mute volume = merge override-map.1 = all-front override-map.2 = front-left,front-right [Element Rear] switch = mute volume = merge override-map.1 = all-rear override-map.2 = rear-left,rear-right [Element Surround] switch = mute volume = merge override-map.1 = all-rear override-map.2 = rear-left,rear-right [Element Side] switch = mute volume = merge override-map.1 = all-side override-map.2 = side-left,side-right [Element Center] switch = mute volume = merge override-map.1 = all-center override-map.2 = all-center,all-center [Element LFE] switch = mute volume = merge override-map.1 = lfe override-map.2 = lfe,lfe [Element CLFE] switch = mute volume = merge override-map.1 = all-center override-map.2 = all-center,lfe #.include analog-output.conf.common ukui-media/configuration/kylin-paths/analog-input-rear-mic-hikvision.conf0000664000175000017500000000212415170054730025610 0ustar fengfeng# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; For devices where a 'Mic' or 'Mic Boost' element exists ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 87 description-key = analog-input-microphone-rear [Jack Mic - Input] required-any = any [Jack Mic - Input,1] state.plugged = no state.unplugged = unknown [Element Mic] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right ukui-media/configuration/kylin-paths/iec958-stereo-output-jbl.conf0000664000175000017500000000207015170054730024124 0ustar fengfeng# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . [Element IEC958] switch = mute [Element Headphone] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element PCM] required-any = any switch = mute volume = 216 override-map.1 = all override-map.2 = all-left,all-right [Element Speaker] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right ukui-media/configuration/kylin-paths/Desktop-speakers.conf0000664000175000017500000001116615170054730022746 0ustar fengfeng# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Path for mixers that don't have a 'Speaker' control, but where we ; force enable the speaker paths nonetheless. ; Needed for some older Dell laptops. ; See analog-output.conf.common for an explanation on the directives [General] priority = 100 description-key = analog-output-speaker [Properties] device.icon_name = audio-speakers [Jack Headphone - Output] state.plugged = no state.unplugged = unknown [Jack Dock Headphone] state.plugged = no state.unplugged = unknown [Jack Front Headphone] state.plugged = no state.unplugged = unknown [Jack Line Out] state.plugged = no state.unplugged = unknown [Jack Line Out Front] state.plugged = no state.unplugged = unknown [Jack Front Line Out] state.plugged = no state.unplugged = unknown [Jack Rear Line Out] state.plugged = no state.unplugged = unknown [Jack Dock Line Out] state.plugged = no state.unplugged = unknown [Jack Speaker - Output] state.plugged = no state.unplugged = unknown [Jack Speaker Phantom] required-any = any state.plugged = unknown state.unplugged = unknown [Jack Speaker Front Phantom] required-any = any state.plugged = unknown state.unplugged = unknown [Element Hardware Master] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Master] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Master Mono] switch = off volume = off ; This profile path is intended to control the speaker, not the ; headphones. But it should not hurt if we leave the headphone jack ; enabled nonetheless. [Element Headphone] switch = mute volume = zero [Element Headphone2] switch = mute volume = zero [Element Headphone+LO] switch = off volume = off [Element Speaker+LO] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Speaker] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Desktop Speaker] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Front] switch = mute volume = merge override-map.1 = all-front override-map.2 = front-left,front-right [Element Front Speaker] switch = mute volume = merge override-map.1 = all-front override-map.2 = front-left,front-right required-any = any [Element Speaker Front] switch = mute volume = merge override-map.1 = all-front override-map.2 = front-left,front-right required-any = any [Element Rear] switch = mute volume = merge override-map.1 = all-rear override-map.2 = rear-left,rear-right [Element Surround] switch = mute volume = merge override-map.1 = all-rear override-map.2 = rear-left,rear-right [Element Surround Speaker] switch = mute volume = merge override-map.1 = all-rear override-map.2 = rear-left,rear-right required-any = any [Element Speaker Surround] switch = mute volume = merge override-map.1 = all-rear override-map.2 = rear-left,rear-right required-any = any [Element Side] switch = mute volume = merge override-map.1 = all-side override-map.2 = side-left,side-right [Element Speaker Side] switch = mute volume = merge override-map.1 = all-side override-map.2 = side-left,side-right [Element Center] switch = mute volume = merge override-map.1 = all-center override-map.2 = all-center,all-center [Element Center Speaker] switch = mute volume = merge override-map.1 = all-center override-map.2 = all-center,all-center required-any = any [Element LFE] switch = mute volume = merge override-map.1 = lfe override-map.2 = lfe,lfe [Element LFE Speaker] switch = mute volume = merge override-map.1 = lfe override-map.2 = lfe,lfe required-any = any [Element Bass Speaker] switch = mute volume = merge override-map.1 = lfe override-map.2 = lfe,lfe required-any = any [Element CLFE] switch = mute volume = merge override-map.1 = all-center override-map.2 = all-center,lfe [Element Speaker CLFE] switch = mute volume = merge override-map.1 = all-center override-map.2 = all-center,lfe .include analog-output.conf.common ukui-media/configuration/kylin-paths/analog-output-headphones-enabled-AutoMute.conf0000664000175000017500000000713415170054730027566 0ustar fengfeng# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Path for mixers that have a 'Headphone' control ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 99 description-key = analog-output-headphones [Properties] device.icon_name = audio-headphones [Jack Dock Headphone] required-any = any [Jack Dock Headphone Phantom] required-any = any state.plugged = unknown state.unplugged = unknown [Jack Front Headphone] required-any = any [Jack Headphone - Output] required-any = any [Jack Front Headphone,1] required-any = any [Jack Front Headphone Front] required-any = any [Jack Front Headphone Surround] required-any = any [Jack Front Headphone Phantom] required-any = any state.plugged = unknown state.unplugged = unknown [Jack Headphone] required-any = any [Jack Headphone Phantom] required-any = any state.plugged = unknown state.unplugged = unknown # This jack can be either a headphone *or* a mic. Used on some ASUS netbooks. [Jack Headphone Mic] required-any = any [Element Hardware Master] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Master] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element DAC] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Master Mono] switch = off volume = off [Element Speaker+LO] switch = off volume = off [Element Headphone+LO] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Headphone] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Front] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Headset] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Line HP Swap] switch = on required-any = any [Element Auto-Mute Mode] enumeration = select [Option Auto-Mute Mode:Disabled] name = analog-output-headphones ; This profile path is intended to control the first headphones, not ; the second headphones. But it should not hurt if we leave the second ; headphone jack enabled nonetheless. [Element Headphone2] switch = mute volume = zero [Element Speaker] switch = off volume = off [Element Desktop Speaker] switch = off volume = off ; On some machines Front is actually a part of the Headphone path #[Element Front] #switch = off #volume = off [Element Rear] switch = off volume = off [Element Surround] switch = off volume = off [Element Side] switch = off volume = off [Element Center] switch = off volume = off [Element LFE] switch = off volume = off [Element Bass Speaker] switch = off volume = off [Element Speaker Front] switch = off volume = off [Element Speaker Surround] switch = off volume = off [Element Speaker Side] switch = off volume = off [Element Speaker CLFE] switch = off volume = off .include analog-output.conf.common ukui-media/configuration/kylin-paths/analog-input-lenveo-p90h.conf0000664000175000017500000000531415170054730024162 0ustar fengfeng# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; For devices where a 'Rear Mic' or 'Rear Mic Boost' element exists ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 82 description-key = analog-input [Jack Rear Mic] required-any = any [Jack Rear Mic - Input] required-any = any [Jack Rear Mic Phantom] required-any = any state.plugged = unknown state.unplugged = unknown [Jack Front Mic] state.plugged = no state.unplugged = unknown [Jack Mic] state.plugged = no state.unplugged = unknown [Element Capture] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Rear Mic Boost] required-any = any switch = select volume = merge override-map.1 = all override-map.2 = all-left,all-right [Option Rear Mic Boost:on] name = input-boost-on [Option Rear Mic Boost:off] name = input-boost-off [Element Rear Mic] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Rear Panel Mic in] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right ###适配Rear Stereo Mic 输入设备 [Element Rear Stereo Mic] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Channel Mode] enumeration = select [Option Channel Mode:2ch] name = analog-input-microphone-rear required-any = any [Element Input Source] enumeration = select [Option Input Source:Rear Mic] name = analog-input-microphone-rear required-any = any [Element Capture Source] enumeration = select [Option Capture Source:Rear Mic] name = analog-input-microphone-rear required-any = any [Element Mic] switch = off volume = off [Element Internal Mic] switch = off volume = off [Element Front Mic] switch = off volume = off [Element Dock Mic] switch = off volume = off [Element Mic Boost] switch = off volume = off [Element Dock Mic Boost] switch = off volume = off [Element Internal Mic Boost] switch = off volume = off [Element Front Mic Boost] switch = off volume = off .include analog-input-mic.conf.common ukui-media/configuration/kylin-paths/iec958-stereo-output-inspur.conf0000664000175000017500000000171215170054730024677 0ustar fengfeng# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . [General] description-key = iec958-stereo-output [Element IEC958] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element PCM] required-any = any switch = mute volume = 999 override-map.1 = all override-map.2 = all-left,all-right ukui-media/configuration/kylin-paths/analog-input-front-mic-hikvision.conf0000664000175000017500000000202615170054730026010 0ustar fengfeng# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; For devices where a 'Mic' or 'Mic Boost' element exists ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 85 description-key = analog-input-microphone-front [Jack Mic - Input,1] required-any = any [Element Mic,1] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right ukui-media/configuration/kylin-paths/analog-output-headphones-hikvision.conf0000664000175000017500000000212515170054730026431 0ustar fengfeng# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Path for mixers that have a 'Headphone' control ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 99 description-key = analog-output-headphones [Properties] device.icon_name = audio-headphones [Jack Headphone - Output] required-any = any [Element PCM,1] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element PCM] switch = off volume = off ukui-media/configuration/kylin-profiles/0000775000175000017500000000000015170054730017352 5ustar fengfengukui-media/configuration/kylin-profiles/alc4080-mh243a-usb-audio.conf0000664000175000017500000000323415170054730024160 0ustar fengfeng# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; ; ; See default.conf for an explanation on the directives used here. [General] auto-profiles = yes [Mapping analog-stereo-speaker] description = Analog Stereo Speaker device-strings = hw:%f,0,0 channel-map = left,right direction = output paths-output = analog-output-speaker [Mapping analog-stereo-headphone] description = Analog Stereo Headphones device-strings = hw:%f,1,0 channel-map = left,right direction = output paths-output = analog-output-headphones [Mapping analog-stereo-desktop-speaker] description = Analog Stereo Desktop Speaker device-strings = hw:%f,2,0 channel-map = left,right direction = output paths-output = Desktop-speakers [Mapping analog-mic] description = Analog Microphone device-strings = hw:%f,2,0 channel-map = left,right direction = input paths-input = analog-input-mic [Mapping analog-line-in] description = Analog Line-In device-strings = hw:%f,1,0 channel-map = left,right direction = input paths-input = analog-input-linein [DecibelFix Mic] db-values = 30:-37.25 63:0 ukui-media/configuration/kylin-profiles/UNIS-D3830-G3-audio.conf0000664000175000017500000000324215170054730023045 0ustar fengfeng# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; ; ; See default.conf for an explanation on the directives used here. [General] auto-profiles = yes [Mapping analog-stereo-speaker] description = Analog Stereo Speaker device-strings = hw:%f,0,0 channel-map = left,right direction = output paths-output = analog-output-speaker [Mapping analog-stereo-headphone] description = Analog Stereo Headphones device-strings = hw:%f,1,0 channel-map = left,right direction = output paths-output = analog-output-headphones #[Mapping analog-stereo-desktop-speaker] #description = Analog Stereo Desktop Speaker #device-strings = hw:%f,2,0 #channel-map = left,right #direction = output #paths-output = Desktop-speakers [Mapping analog-mic] description = Analog Microphone device-strings = hw:%f,2,0 channel-map = left,right direction = input paths-input = analog-input-mic [Mapping analog-line-in] description = Analog Line-In device-strings = hw:%f,1,0 channel-map = left,right direction = input paths-input = analog-input-linein [DecibelFix Mic] db-values = 30:-37.25 63:0 ukui-media/configuration/kylin-profiles/0bda-4f36-usb-audio.conf0000664000175000017500000000353015170054730023376 0ustar fengfeng# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . # vendor.id = 0bda product.id = 4f36 # 海康P514P # Speaker = device:0 # Headphone = device:1 # Front Mic = device:2 # Rear Mic = device:0 # Linein = device:1 # 声卡输出control项只有PCM和PCM1,还需搭配定制端口配置文件 # 声卡输入Jack事件与通用也不一致,还需搭配定制端口配置文件 [General] auto-profiles = yes [Mapping analog-stereo-headphone] description = Front Headphone paths-output = analog-output-headphones-hikvision device-strings = hw:%f,1 channel-map = left,right direction = output [Mapping analog-stereo-speaker] description = Rear Speaker paths-output = analog-output-speaker device-strings = hw:%f,0 channel-map = left,right direction = output [Mapping analog-front-mic] description = Front Microphone paths-input = analog-input-front-mic-hikvision device-strings = hw:%f,2 channel-map = left,right direction = input [Mapping analog-rear-mic] description = Rear Microphone paths-input = analog-input-rear-mic-hikvision device-strings = hw:%f,0 channel-map = left,right direction = input [Mapping analog-linein] description = Linein paths-input = analog-input-linein device-strings = hw:%f,1 channel-map = left,right direction = input ukui-media/configuration/kylin-profiles/pulse-ft-hda.conf0000664000175000017500000000234515170054730022516 0ustar fengfeng[General] auto-profiles = yes #[Mapping analog-stereo] #device-strings = hw:%f #channel-map = left,right #paths-output = analog-output analog-output-lineout analog-output-speaker analog-output-headphones analog-output-headphones-2 #paths-input = analog-input-front-mic analog-input-rear-mic analog-input-alc269vc-internal-mic analog-input-dock-mic analog-input analog-input-alc269vc-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line analog-input-headphone-mic analog-input-headset-mic #priority = 15 [Mapping stereo-fallback] device-strings = hw:%f fallback = yes channel-map = front-left,front-right paths-output = analog-output-lineout analog-output-speaker analog-output-headphones paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-mic analog-input-linein priority = 9 #[Mapping hdmi-stereo] #description = Digital Stereo (HDMI) #device-strings = hw:%f #paths-output = hdmi-output-0 #channel-map = left,right #priority = 7 #direction = output #[Mapping hdmi-stereo-extra1] #description = Digital Stereo (HDMI 2) #device-strings = hw:%f,1 #paths-output = hdmi-output-1 #channel-map = left,right #priority = 7 #direction = output ukui-media/configuration/kylin-profiles/0bda-4f39-usb-audio.conf0000664000175000017500000000246115170054730023403 0ustar fengfeng# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . # vendor.id = 0bda product.id = 4f39 # 三诺一体机 # Speaker = device:1 # Headphone = device:0 # Headphone Mic = device:0 [General] auto-profiles = yes [Mapping analog-stereo-headphone] description = Headphone paths-output = analog-output-headphones device-strings = hw:%f,0 channel-map = left,right direction = output [Mapping analog-stereo-speaker] description = Speaker paths-output = analog-output-speaker device-strings = hw:%f,1 channel-map = left,right direction = output [Mapping analog-stereo-mic] description = Headphone Microphone paths-input = analog-input-mic device-strings = hw:%f,0 channel-map = left,right direction = input ukui-media/configuration/kylin-profiles/fs-hygon-usb-audio.conf0000664000175000017500000000275215170054730023647 0ustar fengfeng# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; M-Audio FastTrack Pro ; ; This card has one duplex stereo channel called A and an additional ; stereo output channel called B. ; ; We knowingly only define a subset of the theoretically possible ; mapping combinations as profiles here. ; ; See default.conf for an explanation on the directives used here. [General] auto-profiles = yes [Mapping analog-stereo] device-strings = front:%f channel-map = left,right paths-output = analog-output paths-input = analog-input-mic priority = 15 [Mapping analog-mono] device-strings = hw:%f channel-map = mono #paths-output = analog-output paths-input = analog-input-mic priority = 15 [Mapping iec958-stereo] device-strings = iec958:%f channel-map = left,right paths-input = iec958-stereo-input #paths-output = iec958-stereo-output priority = 5 [DecibelFix PCM] db-values = 0:-65.25 100:0 ukui-media/configuration/kylin-profiles/mca500-usb-audio.conf0000664000175000017500000000262715170054730023103 0ustar fengfeng# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . [General] auto-profiles = yes [Mapping mono-fallback] device-strings = hw:%f fallback = yes channel-map = mono paths-output = analog-output analog-output-lineout analog-output-speaker analog-output-headphones analog-output-headphones-2 analog-output-mono paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line analog-input-headset-mic priority = 1 [Mapping iec958-stereo] device-strings = iec958:%f channel-map = left,right paths-input = iec958-stereo-input paths-output = iec958-stereo-output priority = 5 [DecibelFix PCM] db-values = 0:-65.62 100:0 ukui-media/configuration/kylin-profiles/lenovo-p90h-usb-audio.conf0000664000175000017500000000436015170054730024172 0ustar fengfeng# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; ; ; See default.conf for an explanation on the directives used here. [General] auto-profiles = yes [Mapping analog-stereo-speaker-output] description = Analog Stereo Speaker device-strings = hw:%f,0,0 channel-map = left,right direction = output paths-output = analog-output-speaker [Mapping analog-stereo-headphone-output] description = Analog Stereo Headphones device-strings = hw:%f,1,0 channel-map = left,right direction = output paths-output = analog-output-headphones [Mapping analog-stereo-desktop-speaker] description = Analog Stereo Desktop Speaker device-strings = hw:%f,2,0 channel-map = left,right direction = output paths-output = Desktop-speakers #[Mapping analog-stereo-spdif-output] #description = Analog Stereo SPDIF #device-strings = hw:%f,2,0 #channel-map = left,right #direction = output #paths-output = iec958-stereo-output #[Mapping analog-stereo-spdif-input] #description = Analog Stereo SPDIF #device-strings = hw:%f,0,0 #channel-map = left,right #direction = input #paths-input = iec958-stereo-input [Mapping analog-stereo-rear-mic-input] #description = Analog Stereo Rear Input device-strings = hw:%f,2,0 channel-map = left,right direction = input paths-input = analog-input-lenveo-p90h #paths-input = analog-input-rear-mic [Mapping analog-stereo-line-input] description = Analog Stereo Line Input device-strings = hw:%f,1,0 channel-map = left,right direction = input paths-input = analog-input-linein [Mapping analog-stereo-mic-input] description = Analog Stereo Input device-strings = hw:%f,2,0 channel-map = left,right direction = input paths-input = analog-input-mic ukui-media/configuration/kylin-profiles/lenovo-m90h-usb-audio.conf0000664000175000017500000000350615170054730024170 0ustar fengfeng# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; ; ; See default.conf for an explanation on the directives used here. [General] auto-profiles = yes [Mapping analog-stereo-speaker-output] description = Analog Stereo Speaker device-strings = hw:%f,0,0 channel-map = left,right direction = output paths-output = analog-output-speaker [Mapping analog-stereo-headphone-output] description = Analog Stereo Headphones device-strings = hw:%f,1,0 channel-map = left,right direction = output paths-output = analog-output-headphones [Mapping analog-stereo-spdif-output] description = Analog Stereo SPDIF device-strings = hw:%f,2,0 channel-map = left,right direction = output paths-output = iec958-stereo-output [Mapping analog-stereo-spdif-input] description = Analog Stereo SPDIF device-strings = hw:%f,0,0 channel-map = left,right direction = input paths-input = iec958-stereo-input [Mapping analog-stereo-line-input] description = Analog Stereo Line Input device-strings = hw:%f,1,0 channel-map = left,right direction = input paths-input = analog-input-linein [Mapping analog-stereo-mic-input] description = Analog Stereo Input device-strings = hw:%f,2,0 channel-map = left,right direction = input paths-input = analog-input-mic ukui-media/configuration/kylin-profiles/0bda-4987-usb-audio.conf0000664000175000017500000000304115170054730023324 0ustar fengfeng# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . # vendor.id = 0bda product.id = 4987 # Front Mic = device:0 # Rear Mic = device:1 # Line in = device:2 # 输出默认模拟耳机、数字输出端口一直存在 [General] auto-profiles = yes [Mapping analog-stereo-headphone] description = Headphone paths-output = analog-output-headphones device-strings = hw:%f,0 channel-map = left,right direction = output [Mapping mono-fallback-front-mic] description = Front Microphone paths-input = analog-input analog-input-front-mic device-strings = hw:%f,0 channel-map = mono direction = input [Mapping analog-stereo-rear-mic] description = Rear Microphone paths-input = analog-input analog-input-rear-mic device-strings = hw:%f,1 channel-map = left,right direction = input [Mapping analog-stereo-linein] description = Linein paths-input = analog-input analog-input-linein device-strings = hw:%f,2 channel-map = left,right direction = input ukui-media/configuration/kylin-profiles/pulse-x100-hdmi.conf0000664000175000017500000001252015170054730022756 0ustar fengfeng# This file is part of PulseAudio. ; [General] ; auto-profiles = no | yes # Instead of defining all profiles manually, autogenerate ; # them by combining every input mapping with every output mapping. ; ; [Mapping id] ; device-strings = ... # ALSA device string. %f will be replaced by the card identifier. ; channel-map = ... # Channel mapping to use for this device ; description = ... ; paths-input = ... # A list of mixer paths to use. Every path in this list will be probed. ; # If multiple are found to be working they will be available as device ports ; paths-output = ... ; element-input = ... # Instead of configuring a full mixer path simply configure a single ; # mixer element for volume/mute handling ; element-output = ... ; priority = ... ; direction = any | input | output # Only useful for? ; ; exact-channels = yes | no # If no, and the exact number of channels is not supported, ; # allow device to be opened with another channel count ; fallback = no | yes # This mapping will only be considered if all non-fallback mappings fail ; [Profile id] ; input-mappings = ... # Lists mappings for sources on this profile, those mapping must be ; # defined in this file too ; output-mappings = ... # Lists mappings for sinks on this profile, those mappings must be ; # defined in this file too ; description = ... ; priority = ... # Numeric value to deduce priority for this profile ; skip-probe = no | yes # Skip probing for availability? If this is yes then this profile ; # will be assumed as working without probing. Makes initialization ; # a bit faster but only works if the card is really known well. ; ; fallback = no | yes # This profile will only be considered if all non-fallback profiles fail ; [DecibelFix element] # Decibel fixes can be used to work around missing or incorrect dB ; # information from alsa. A decibel fix is a table that maps volume steps ; # to decibel values for one volume element. The "element" part in the ; # section title is the name of the volume element. ; # ; # NOTE: This feature is meant just as a help for figuring out the correct ; # decibel values. PulseAudio is not the correct place to maintain the ; # decibel mappings! ; # ; # If you need this feature, then you should make sure that when you have ; # the correct values figured out, the alsa driver developers get informed ; # too, so that they can fix the driver. ; ; db-values = ... # The option value consists of pairs of step numbers and decibel values. ; # The pairs are separated with whitespace, and steps are separated from ; # the corresponding decibel values with a colon. The values must be in an ; # increasing order. Here's an example of a valid string: ; # ; # "0:-40.50 1:-38.70 3:-33.00 11:0" ; # ; # The lowest step imposes a lower limit for hardware volume and the ; # highest step correspondingly imposes a higher limit. That means that ; # that the mixer will never be set outside those values - the rest of the ; # volume scale is done using software volume. ; # ; # As can be seen in the example, you don't need to specify a dB value for ; # each step. The dB values for skipped steps will be linearly interpolated ; # using the nearest steps that are given. [General] auto-profiles = yes [Mapping hdmi-stereo] description = Digital Stereo (HDMI) device-strings = hw:%f paths-output = hdmi-output-0 channel-map = left,right priority = 9 direction = output [Mapping hdmi-stereo-extra1] description = Digital Stereo (HDMI 2) device-strings = hw:%f,1 paths-output = hdmi-output-1 channel-map = left,right priority = 7 direction = output [Mapping hdmi-stereo-extra2] description = Digital Stereo (HDMI 3) device-strings = hw:%f,2 paths-output = hdmi-output-2 channel-map = left,right priority = 5 direction = output ukui-media/configuration/kylin-profiles/kylin-inspur-usb-audio.conf0000664000175000017500000000155715170054730024563 0ustar fengfeng# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . [General] auto-profiles = yes [Mapping iec958-stereo] device-strings = iec958:%f channel-map = left,right paths-input = iec958-stereo-input paths-output = iec958-stereo-output-inspur priority = 5 ukui-media/configuration/kylin-profiles/pulse-X100-hdmi.conf0000664000175000017500000001252015170054730022716 0ustar fengfeng# This file is part of PulseAudio. ; [General] ; auto-profiles = no | yes # Instead of defining all profiles manually, autogenerate ; # them by combining every input mapping with every output mapping. ; ; [Mapping id] ; device-strings = ... # ALSA device string. %f will be replaced by the card identifier. ; channel-map = ... # Channel mapping to use for this device ; description = ... ; paths-input = ... # A list of mixer paths to use. Every path in this list will be probed. ; # If multiple are found to be working they will be available as device ports ; paths-output = ... ; element-input = ... # Instead of configuring a full mixer path simply configure a single ; # mixer element for volume/mute handling ; element-output = ... ; priority = ... ; direction = any | input | output # Only useful for? ; ; exact-channels = yes | no # If no, and the exact number of channels is not supported, ; # allow device to be opened with another channel count ; fallback = no | yes # This mapping will only be considered if all non-fallback mappings fail ; [Profile id] ; input-mappings = ... # Lists mappings for sources on this profile, those mapping must be ; # defined in this file too ; output-mappings = ... # Lists mappings for sinks on this profile, those mappings must be ; # defined in this file too ; description = ... ; priority = ... # Numeric value to deduce priority for this profile ; skip-probe = no | yes # Skip probing for availability? If this is yes then this profile ; # will be assumed as working without probing. Makes initialization ; # a bit faster but only works if the card is really known well. ; ; fallback = no | yes # This profile will only be considered if all non-fallback profiles fail ; [DecibelFix element] # Decibel fixes can be used to work around missing or incorrect dB ; # information from alsa. A decibel fix is a table that maps volume steps ; # to decibel values for one volume element. The "element" part in the ; # section title is the name of the volume element. ; # ; # NOTE: This feature is meant just as a help for figuring out the correct ; # decibel values. PulseAudio is not the correct place to maintain the ; # decibel mappings! ; # ; # If you need this feature, then you should make sure that when you have ; # the correct values figured out, the alsa driver developers get informed ; # too, so that they can fix the driver. ; ; db-values = ... # The option value consists of pairs of step numbers and decibel values. ; # The pairs are separated with whitespace, and steps are separated from ; # the corresponding decibel values with a colon. The values must be in an ; # increasing order. Here's an example of a valid string: ; # ; # "0:-40.50 1:-38.70 3:-33.00 11:0" ; # ; # The lowest step imposes a lower limit for hardware volume and the ; # highest step correspondingly imposes a higher limit. That means that ; # that the mixer will never be set outside those values - the rest of the ; # volume scale is done using software volume. ; # ; # As can be seen in the example, you don't need to specify a dB value for ; # each step. The dB values for skipped steps will be linearly interpolated ; # using the nearest steps that are given. [General] auto-profiles = yes [Mapping hdmi-stereo] description = Digital Stereo (HDMI) device-strings = hw:%f paths-output = hdmi-output-0 channel-map = left,right priority = 9 direction = output [Mapping hdmi-stereo-extra1] description = Digital Stereo (HDMI 2) device-strings = hw:%f,1 paths-output = hdmi-output-1 channel-map = left,right priority = 7 direction = output [Mapping hdmi-stereo-extra2] description = Digital Stereo (HDMI 3) device-strings = hw:%f,2 paths-output = hdmi-output-2 channel-map = left,right priority = 5 direction = output ukui-media/NEWS0000664000175000017500000000005115170052044012223 0ustar fengfeng### ukui-media 1.1.0 Fork mate-media ukui-media/data/0000775000175000017500000000000015170054730012445 5ustar fengfengukui-media/data/ukui-media-control-mute-led.service0000664000175000017500000000027715170052044021253 0ustar fengfeng[Unit] Description=Mate media control leds daemon After=lightdm.service [Service] Restart=always RestartSec=2 ExecStart=/usr/bin/ukui-media-control-led [Install] WantedBy=multi-user.target ukui-media/data/Makefile.am0000664000175000017500000000161215170052044014475 0ustar fengfengNULL = SUBDIRS = \ icons \ $(NULL) @INTLTOOL_DESKTOP_RULE@ autostartdir = $(sysconfdir)/xdg/autostart autostart_in_files = ukui-volume-control-applet.desktop.in autostart_DATA = $(autostart_in_files:.desktop.in=.desktop) appsdir = $(datadir)/applications apps_in_files = ukui-volume-control.desktop.in apps_DATA = $(apps_in_files:.desktop.in=.desktop) gsettingsschema_in_files = org.ukui.media.gschema.xml.in gsettings_SCHEMAS = $(gsettingsschema_in_files:.xml.in=.xml) @GSETTINGS_RULES@ @INTLTOOL_XML_RULE@ EXTRA_DIST = \ $(autostart_in_files) \ ukui-volume-control.desktop.in.in \ $(gsettingsschema_in_files) \ $(NULL) CLEANFILES = \ ukui-volume-control.desktop \ $(gsettings_SCHEMAS) \ $(NULL) DISTCLEANFILES = \ ukui-volume-control-applet.desktop \ $(NULL) MAINTAINERCLEANFILES = \ Makefile.in \ $(gsettings_SCHEMAS:.xml=.valid) -include $(top_srcdir)/git.mk ukui-media/data/images/0000775000175000017500000000000015170052044013706 5ustar fengfengukui-media/data/images/blank.png0000664000175000017500000000016315170052044015503 0ustar fengfengPNG  IHDRKm)IDATc?6Ā N V ah~tEXtSoftwaregnome-screenshot>IENDB`ukui-media/data/images/audio-volume-high.png0000664000175000017500000000037715170052044017746 0ustar fengfengPNG  IHDRa pHYs  ~IDAT8˭[ 0 Ea (B1(B08XEj-q|t9'5M|͋, :8D YV@m&IF @PC.lHl${ExHzV(U$)"n{mLl6 i|2^㏩|IENDB`ukui-media/data/images/audio-input-microphone-medium.png0000664000175000017500000000125715170052044022276 0ustar fengfengPNG  IHDRatEXtSoftwareAdobe ImageReadyqe<QIDATxb?!n# .?###|8&!߼}ϟ?߾{?Į }:;<8pMUÞG6 LPVg8s<î$%rLw7l?U [ ;Šc8ȋL }AHop 68_ ּei)80Yxw AsÁ',L=bR\uGHXP9,"hB e)[v 0U+ot4<`mmSD ><{k ^`g8z($€ϟ>GG :,]JkT4T]fΚ  6 v^.wo?D}zm.5$H6t0L2a6pd4 EٯSmxfm``c$c(H]fTރ۰h@Jb$'@<hp#1jPv*x *|00 RLIENDB`ukui-media/data/images/audio-volume-muted.png0000664000175000017500000000036315170052044020140 0ustar fengfengPNG  IHDRa pHYs  ~IDAT8˝ 0EU#xC s6ĪEmK\@<}02E/]~ ms!A)Y+dkAxH~f}soϤz}gB(vFbIENDB`ukui-media/data/images/images.qrc0000664000175000017500000000107015170052044015660 0ustar fengfeng blank.png emblem-unreadable.png audio-volume-muted.png audio-volume-medium.png audio-volume-low.png audio-volume-high.png application-audio.png audio-input-microphone-muted.png audio-input-microphone-medium.png audio-input-microphone-low.png audio-input-microphone-high.png ukui-media/data/images/audio-input-microphone-high.png0000664000175000017500000000152515170052044021733 0ustar fengfengPNG  IHDRatEXtSoftwareAdobe ImageReadyqe<IDATxb?wG*ʁ\ &HB<2@!A@Рn$wdO/v n>7._)h8\SU %)?~2w `adfy\]ƈl<Ùv= )!c6?/?俿أx [ ;Šc8ȋL }AHop 77 n^d8_ ּei)80Yxw 8e RsÁ',L=bR\uGHXP_/XXDƅ  `47fؽg/(q N\U+ot4<`mmSD ><{k|9 piIqG2JK" s+>|o߭/_/ w))aPUVdPU`pwg9k.H|6؀[+ y߽շ_~25$H6t0L2a6pd4 EٯSŴJ)p5D@>̨)_=f^\L\YY / S4H% \ Ԥ}WnfJ nj"^4=.<Aj-d_JIENDB`ukui-media/data/images/emblem-unreadable.png0000664000175000017500000000021115170052044017747 0ustar fengfengPNG  IHDR pHYs  ~;IDATc?> &W͈bL׊VDSHțSIENDB`ukui-media/data/images/audio-input-microphone-low.png0000664000175000017500000000104415170052044021611 0ustar fengfengPNG  IHDRatEXtSoftwareAdobe ImageReadyqe<IDATxb?!n# .?###|8&!߼}ϟ?߾{?Į 5Ue R {a'CXXH|0(+39AQAaC p9 jp)= A^dR%v6󗮂5oپAFZ , DV&>>%4\p  S#g_䣘$ih8kZ`gt1={DaÀV'߾3h1?yښ㧈@|vy `3:{AZRѣ Ғ> <]JkT4T]fΚ  v= @ IjҀ@ 4hH1"P>H(3 hR@V\ FAm@ہ`DZb<IENDB`ukui-media/data/images/audio-input-microphone-muted.png0000664000175000017500000000101015170052044022117 0ustar fengfengPNG  IHDRatEXtSoftwareAdobe ImageReadyqe<IDATxb?!n# .?###|8&!߼}ϟ?߾{?Į 5Ue R {a'CXXH|0(+39AQAaC p9dC Yl2d0cXn Gr /Y5ѣ /]k޲}X835>`aj Xhxp/]`in̰{^q1Q&B6:j Oefx)\]riIq𐕖 t ݐkB ʊ 323̜5$<S%專իV &e w7% fq- 4thHА XcK5ˌ2Ѐ 2HOx.Fbr#Ԡ6 U @Ucf r-IENDB`ukui-media/data/images/audio-volume-low.png0000664000175000017500000000046315170052044017624 0ustar fengfengPNG  IHDRa pHYs  ~IDAT8˭A 1 Ea t%xo7QoH +.L1ΠBi ؖؗZKTXR [)@m3H9;38=gATR^$Ƨb_ '0h)o];`+v s`쎥4*q!^` PY/EÝ4Qᤣ{̞i WK#IENDB`ukui-media/data/images/audio-volume-medium.png0000664000175000017500000000042415170052044020300 0ustar fengfengPNG  IHDRa pHYs  ~IDAT8˭ 0 E.CO#t2Jؠ#d6aFCa$_O$2_,;m(Ws${$!AtqdPq#>5 $'d]ULr@Yd0>hk]Mܒt5TU<xY)-mLm_2[ nc9+VIENDB`ukui-media/data/images/application-audio.png0000664000175000017500000000061015170052044020013 0ustar fengfengPNG  IHDRa pHYs  ~:IDAT8ˍNAF 411Jeb>?D}C!AFcQԬ dXvvźslvMDۼ 9w^NwXyG;*ێ @l3{gIpJzSGU+fj ydQ4-|dT2 Bc}bi0 Jyy/50mR`>ŇrۓA(Xj鸽@1RVWs,25;{@aVHԛ9o_ö >a?"¬4IENDB`ukui-media/data/org.ukui.media.sound.gschema.xml0000664000175000017500000000077615170052044020553 0ustar fengfeng '' Filename Prompt sound file name. '' Name Description associated with a custom keybinding. ukui-media/data/org.ukui.sound.gschema.xml0000664000175000017500000000254115170052044017465 0ustar fengfeng '' Default mixer device The default mixer device used by the multimedia key bindings. [] Default mixer tracks The default mixer tracks used by the multimedia key bindings. false Enable ESD Enable sound server startup. false Sounds for events Whether to play sounds on user events. 'freedesktop' Sound theme name The XDG sound theme to use for event sounds. false Input feedback sounds Whether to play sounds on input events. ukui-media/data/ukui-volume-control.desktop.in.in0000664000175000017500000000072715170052044021014 0ustar fengfeng[Desktop Entry] _Name=Sound _Comment=Change sound volume and sound events Exec=ukui-volume-control Icon=multimedia-volume-control Terminal=false Type=Application StartupNotify=true Categories=AudioVideo;Mixer;Settings;HardwareSettings; Keywords=UKUI;volume;control;mixer;settings;sound;events; OnlyShowIn=UKUI; X-UKUI-Bugzilla-Bugzilla=UKUI X-UKUI-Bugzilla-Product=ukui-media X-UKUI-Bugzilla-Component=ukui-volume-control X-UKUI-Bugzilla-Version=@VERSION@ NoDisplay=true ukui-media/data/icons/0000775000175000017500000000000015170052044013554 5ustar fengfengukui-media/data/icons/Makefile.am0000664000175000017500000000100015170052044015577 0ustar fengfengNULL = SUBDIRS = \ scalable \ 16x16 \ 22x22 \ 24x24 \ 32x32 \ $(NULL) gtk_update_icon_cache = gtk-update-icon-cache -f -t $(datadir)/icons/ukui install-data-hook: update-icon-cache uninstall-hook: update-icon-cache update-icon-cache: @-if test -z "$(DESTDIR)"; then \ echo "Updating Gtk icon cache."; \ $(gtk_update_icon_cache); \ else \ echo "*** Icon cache not updated. After (un)install, run this:"; \ echo "*** $(gtk_update_icon_cache)"; \ fi -include $(top_srcdir)/git.mk ukui-media/data/icons/32x32/0000775000175000017500000000000015170052044014335 5ustar fengfengukui-media/data/icons/32x32/Makefile.am0000664000175000017500000000006115170052044016366 0ustar fengfengSUBDIRS = status -include $(top_srcdir)/git.mk ukui-media/data/icons/32x32/status/0000775000175000017500000000000015170052044015660 5ustar fengfengukui-media/data/icons/32x32/status/Makefile.am0000664000175000017500000000057015170052044017716 0ustar fengfengNULL = themedir = $(pkgdatadir)/icons/hicolor size = 32x32 context = status iconsdir = $(themedir)/$(size)/$(context) icons_DATA = \ audio-input-microphone-high.png \ audio-input-microphone-low.png \ audio-input-microphone-medium.png \ audio-input-microphone-muted.png \ $(NULL) EXTRA_DIST = \ $(icons_DATA) \ $(NULL) -include $(top_srcdir)/git.mk ukui-media/data/icons/32x32/status/audio-input-microphone-medium.png0000664000175000017500000000367515170052044024256 0ustar fengfengPNG  IHDR szztEXtSoftwareAdobe ImageReadyqe<_IDATxWkl~v.& qmFJ2Z~D$Y&&[Y?6kU4MZ`I~LTH 0mҪNcũ퐆v Ihb; ]o$Nu>;syǔ( (*ދ=Y-sOMQ@ lKi%ҏ?{.+H4"/r<'g"zϾ ן)ךLrB3LwQ*p9]h6իdLӋ̇d|[|weZhhhxݿyp8)E 1ٳo'"ńb|I"ڵEjѾ+QUQ"TpCbP)022\5j/ 9./ P5Ȝ ,Yz]?o=;oO ]Ծa}qAuu53Kґ$p٪?{aC}#]#8Ec{7NJhiC_(1xsw*+W/Jd")I@z4ؼEz`vvfV+xRxnJ9.RiTݲ03D zX,2 yk w#5'}Sb˱k!9|* XV$ t!Z@0cpkl+|igg_;9Ԍ2|?tg%GE+v|#]кlAroO3"u-rC;el歃] \s>!+36rsr >//r>PB0mmm1<|c#bҤ^+~4/X EJZ[ C%rL+'vnMVV,L!OF3>iZ@|"TӱHrǙ3g>OkJWvvO`!LLLɗ{{.DA\攸 =r  #19qK:j;UO(T*) {0i?xxs}Xglݶ-;+gwNvVJFpz,Gc|(-,,v~]Q96!ǘ{UYYY!ʎ)1|/(p^.2MXz!7: ;wH;.Bvv89`(Ҙy0/x:<\aWIx eF0>,-N L& ?l怾:2}+o-`-JdZ "B's~'LTW ٔ7y|HLE&41Ջ̄4۽Η:K22>ٮ!u@~Akj >F JVS0 vnfv*~$1=>ʊ*E(4 Nx.|r`XCZZx<Izf5CA퇭Si?M~j @?qAtqpq֯ ==:;;,C#^$buc9g;0XQ{8]3 쁓ŋVO48Mt ҖZ߬^NIIA:YE&!Q&Bm(Ƣ1]у'ZZZz=nݮjkʶliZr^^7闆#Ly,xù+~E%t, :۾es(@ǶoGgpּOu횲p\)=Cɵ=Ф'lnj][Vt0CSՕv5OCSceuuu g(L{7X25eаBwOw@FG3r Y>"]oڢnR޶Ά؞3ɾ}od|sgdc2끾^{ +E׾T[[{kZ<ֱQ1pT 7>|('7;]]Nh4EIQC!؉#C޺z) HIVTTXTTBXH[3<@81DURj߂t  n02 rWZA% /P`tS[C.CsP~c >Om iۿvSs m,|"' z6j~LMMN"Zw1$=ZdJpS!87pR+%ɤj%: ˯:\wYa*6rmRt!`(##p~x+d[[o ׎;heロ-{hR{pa؝O_ye{7tE:"#-[;w|s#ܾW~o͍b2؈Lf|&|Q0jjZsfNŤ&-e[K#ϪcڪjSklhT^5H԰j5wF}_6[*bV*ZQȁOLnIFƣxG ?s˦tc;xmojl`-MMa +I"Jn "? t"Cҗ{M[dW^nyфƤ8%<Ս  FG:`JJK 9w za͵b@PY@3HDĠdefT8/5dZ>>X=U j5H>% A< a4Q+@(RagffA;HI.6\g8a4f,Ɋ!ќ:!bE2FSg΍tpK@v/X/}hw8L";Mx؋%L'3s/b;Z6[ۜ9+3s-qKt:v$QD\vL ӡO;O'z9^T/^\III'ДFg|TKsٳIENDB`ukui-media/data/icons/32x32/status/audio-input-microphone-muted.png0000664000175000017500000000372515170052044024110 0ustar fengfengPNG  IHDR szztEXtSoftwareAdobe ImageReadyqe<wIDATxW{lS?qmqb6ImE6^ E$I'& V jjmZutǤc-cRNP@C^$!4v8/il'߾BH!I{9sϴ$I Qp팂 ?"S@ P/!V6| C /o'}{lL5g[g~Rհs@0Ur-~I~8SA^***tA$Aќup8P^Q._KKK94~}H3&Z >KV3NR/;pHPVV}}}P\\ ]PPPi@$$J5ʇa%~r{g  08eytED@\ 6 @=Jw:-?` 0yN6;!`QQtvvBNNܺ}B<'!$9wJWڷOj^:Ef!%+8}e2l6 qbx'c`h(؃};LaIN?~dX %Ǹ uAqI `Xd0Iz$`DPSW3U\L6}Z={@mok55zeaJ#'^kP~|_ l6\rdyK7&n7 ^Y͛7+1wp3[Oe({|řE(~I&$+d$r{~/_9GGǾlݜmp1NssXԛkude lU`vvfVN=&G67J͖ݺ ff}p}Gҽ{wEZJi`pІ;}{MHkiy( )ZlH0̰n~011cH^<+MMMZvaZa^s6l{qcȝ %w9d_n֨H2Sc׽wK>{y r h4͕U_6j/ӟ3~Mɨ~R+Mj:RښZk Ű *Rag{~;w7!99\EzQI+h9C&(sD'Lݞ u:v:uҗvסƍO))AH0!>M&Ш5 4Q! -:=SPDiSzE)LZX GT@렻3Mi(PTw+LdpMU{~?~2wA1 PU$eEyW01\yAQAaC P-`f?5Z=7 @VXH r2 Gb`x1JXp{[k!~~_~Az薄4q1<~A^V`÷l #-> n0hJ5r\r_HO"Y) 1[w0h*18|Ԉ0A^^@L\䪜L7'ĿN@7䰈W\ 99=yƠ(/pUKsc{22 [%נN<|\-j@߾3HI0ܺsAWGa ?6؂y~X7Tp#?`njȰsNIqQ9Ƶ;@>|V| >'I Wodfx-v{9Up|9+Y_ 1(2{؀aa-YgΤ6Km'^-cKIy 802g fl #zFs 2X7M-HʧaRVj.l n#(k\~FBjg.O Ž<`ҞLVq 7Pв.]woO>?.,d1p{%NLԆϞ=3>~Ƞ5ޘ ( #&ӗn700s1u2(~5f 2޾^(@ e̞7o0445~VHU6D0muIENDB`ukui-media/data/icons/24x24/status/audio-input-microphone-high.png0000664000175000017500000000227615170052044023713 0ustar fengfengPNG  IHDRw=tEXtSoftwareAdobe ImageReadyqe<`IDATxڴmL[U~HQ1(E6`24ll&!L )dF s35SpLt JH:3mP ݨҖzﳭ&ƓwϽ|{>x]uU:ɚULϖ?~y9_47x?' NyGGD3r8H=Ed\@c퇻X(M+9-eHaPDA==}`;h9>vֽ}YqY$|N CAeܛ5@D h@z(y?J\@xOu@q sl *bZF:~ɠ'V(PwV$i[YeΆ<%~r^X9/ p[]=Iȉ?*EL*96\\FEba;-xxZ0B#㐼!£[`zFN\n) y2aak0i VXcy ,Ӡ[o  Χ iSlMrJ"PV*( iE,M ``]AB#1Hݜ} ɩU(XvqNwbq8;yA$akf:T*\Ǟɷ53_7oq#̩eq/&$ԇ૶vX6dh4PCt&%f]dZ^^_oÆD%z!+Puu? ^vܙU֔97}^+նj]GA# &`( :NtU:w+LdpMU{~?~2wA1 R<ëoDݼà ϰk!I qde[T 2ܽAANȱS z: =F>| syYiW ߲}(رRb >`PUb8pó/`nU91n\&E]zZ03EyY2X3޳A\LYu- M\5Dod$nݹǠɰQkkkb[3?\ < 榆 ;wdŞo\ ̲s/$ 2\W[a V ߼w=gMep"ν̌ vnߊRuLo"w))F @Cb|4Y@MRa^Pyұl|F.%l n#2S6q.H?#V\53'aG0iO&(hYXݻ7N~2 PZxb& jCgϞ?~dpx`iioLDI [7oc`dl乀:L? 3 o_/qfϛ7o?`}M|Q !S~:fhIENDB`ukui-media/data/icons/24x24/status/audio-input-microphone-muted.png0000664000175000017500000000152315170052044024104 0ustar fengfengPNG  IHDRw=tEXtSoftwareAdobe ImageReadyqe<IDATxڴU_HQ}փfsS[DPj.!%챇D"$2%jD! -ik_~lM?'8{=we8v +NF=B3J'ʍONss:7ډads㤘@woN\󝾱aFG0>eOJ6Z&,4os`գd/$òTr$Z,~ n(d僿l{ MI?ҋҘT}jbbzIЫDNV&);J݁WMP*0+rUAϵ~xg[LM 9K115zV1g\dy>!dge@"N3PP<4cG0[~˲&7~ c$QB:6е_3l2 `G"@~aT5afCqQ쨼zORm.s-dRbGdzBDzAWUU !Z-\H=j#NnlU=Ġ[]pr֋(n)n+!]%cZ|.h'&߭`YoVxv?!SQIȟLy:}rB>űX,|~~D 7$,HIENDB`ukui-media/data/icons/16x16/0000775000175000017500000000000015170052044014341 5ustar fengfengukui-media/data/icons/16x16/Makefile.am0000664000175000017500000000006115170052044016372 0ustar fengfengSUBDIRS = status -include $(top_srcdir)/git.mk ukui-media/data/icons/16x16/status/0000775000175000017500000000000015170052044015664 5ustar fengfengukui-media/data/icons/16x16/status/Makefile.am0000664000175000017500000000057015170052044017722 0ustar fengfengNULL = themedir = $(pkgdatadir)/icons/hicolor size = 16x16 context = status iconsdir = $(themedir)/$(size)/$(context) icons_DATA = \ audio-input-microphone-high.png \ audio-input-microphone-low.png \ audio-input-microphone-medium.png \ audio-input-microphone-muted.png \ $(NULL) EXTRA_DIST = \ $(icons_DATA) \ $(NULL) -include $(top_srcdir)/git.mk ukui-media/data/icons/16x16/status/audio-input-microphone-medium.png0000664000175000017500000000125715170052044024254 0ustar fengfengPNG  IHDRatEXtSoftwareAdobe ImageReadyqe<QIDATxb?!n# .?###|8&!߼}ϟ?߾{?Į }:;<8pMUÞG6 LPVg8s<î$%rLw7l?U [ ;Šc8ȋL }AHop 68_ ּei)80Yxw AsÁ',L=bR\uGHXP9,"hB e)[v 0U+ot4<`mmSD ><{k ^`g8z($€ϟ>GG :,]JkT4T]fΚ  6 v^.wo?D}zm.5$H6t0L2a6pd4 EٯSmxfm``c$c(H]fTރ۰h@Jb$'@<hp#1jPv*x *|00 RLIENDB`ukui-media/data/icons/16x16/status/audio-input-microphone-high.png0000664000175000017500000000152515170052044023711 0ustar fengfengPNG  IHDRatEXtSoftwareAdobe ImageReadyqe<IDATxb?wG*ʁ\ &HB<2@!A@Рn$wdO/v n>7._)h8\SU %)?~2w `adfy\]ƈl<Ùv= )!c6?/?俿أx [ ;Šc8ȋL }AHop 77 n^d8_ ּei)80Yxw 8e RsÁ',L=bR\uGHXP_/XXDƅ  `47fؽg/(q N\U+ot4<`mmSD ><{k|9 piIqG2JK" s+>|o߭/_/ w))aPUVdPU`pwg9k.H|6؀[+ y߽շ_~25$H6t0L2a6pd4 EٯSŴJ)p5D@>̨)_=f^\L\YY / S4H% \ Ԥ}WnfJ nj"^4=.<Aj-d_JIENDB`ukui-media/data/icons/16x16/status/audio-input-microphone-low.png0000664000175000017500000000104415170052044023567 0ustar fengfengPNG  IHDRatEXtSoftwareAdobe ImageReadyqe<IDATxb?!n# .?###|8&!߼}ϟ?߾{?Į 5Ue R {a'CXXH|0(+39AQAaC p9 jp)= A^dR%v6󗮂5oپAFZ , DV&>>%4\p  S#g_䣘$ih8kZ`gt1={DaÀV'߾3h1?yښ㧈@|vy `3:{AZRѣ Ғ> <]JkT4T]fΚ  v= @ IjҀ@ 4hH1"P>H(3 hR@V\ FAm@ہ`DZb<IENDB`ukui-media/data/icons/16x16/status/audio-input-microphone-muted.png0000664000175000017500000000101015170052044024075 0ustar fengfengPNG  IHDRatEXtSoftwareAdobe ImageReadyqe<IDATxb?!n# .?###|8&!߼}ϟ?߾{?Į 5Ue R {a'CXXH|0(+39AQAaC p9dC Yl2d0cXn Gr /Y5ѣ /]k޲}X835>`aj Xhxp/]`in̰{^q1Q&B6:j Oefx)\]riIq𐕖 t ݐkB ʊ 323̜5$<S%專իV &e w7% fq- 4thHА XcK5ˌ2Ѐ 2HOx.Fbr#Ԡ6 U @Ucf r-IENDB`ukui-media/data/icons/scalable/0000775000175000017500000000000015170052044015322 5ustar fengfengukui-media/data/icons/scalable/Makefile.am0000664000175000017500000000007115170052044017354 0ustar fengfengSUBDIRS = status devices -include $(top_srcdir)/git.mk ukui-media/data/icons/scalable/devices/0000775000175000017500000000000015170052044016744 5ustar fengfengukui-media/data/icons/scalable/devices/audio-speaker-left.svg0000664000175000017500000004654415170052044023163 0ustar fengfeng image/svg+xml audio device speaker output left audio-speaker-left Evangeline McGlynn ukui-media/data/icons/scalable/devices/audio-speaker-left-testing.svg0000664000175000017500000005050215170052044024623 0ustar fengfeng image/svg+xml audio device speaker output left testing highlighted audio-speaker-left-testing Evangeline McGlynn ukui-media/data/icons/scalable/devices/audio-speaker-left-back-testing.svg0000664000175000017500000005051215170052044025522 0ustar fengfeng image/svg+xml audio device speaker output left-back testing highlighted audio-speaker-left-b-testing Evangeline McGlynn ukui-media/data/icons/scalable/devices/Makefile.am0000664000175000017500000000150615170052044021002 0ustar fengfengNULL = themedir = $(pkgdatadir)/icons/hicolor size = 48x48 context = devices iconsdir = $(themedir)/$(size)/$(context) icons_DATA = \ audio-speaker-center.svg \ audio-speaker-center-testing.svg \ audio-speaker-left-back.svg \ audio-speaker-left-back-testing.svg \ audio-speaker-left.svg \ audio-speaker-left-side.svg \ audio-speaker-left-side-testing.svg \ audio-speaker-left-testing.svg \ audio-speaker-right-back.svg \ audio-speaker-right-back-testing.svg \ audio-speaker-right.svg \ audio-speaker-right-side.svg \ audio-speaker-right-side-testing.svg \ audio-speaker-right-testing.svg \ audio-speaker-center-back-testing.svg \ audio-speaker-center-back.svg \ audio-subwoofer.svg \ audio-subwoofer-testing.svg \ $(NULL) EXTRA_DIST = \ $(icons_DATA) \ $(NULL) -include $(top_srcdir)/git.mk ukui-media/data/icons/scalable/devices/audio-speaker-center-back-testing.svg0000664000175000017500000004706415170052044026060 0ustar fengfeng image/svg+xml audio device speaker output center testing highlighted audio-speaker-center-testing Evangeline McGlynn ukui-media/data/icons/scalable/devices/audio-speaker-right.svg0000664000175000017500000004654115170052044023343 0ustar fengfeng image/svg+xml audio device speaker output right audio-speaker-right Evangeline McGlynn ukui-media/data/icons/scalable/devices/audio-speaker-left-back.svg0000664000175000017500000004656315170052044024062 0ustar fengfeng image/svg+xml audio device speaker output left-back audio-speaker-left-back Evangeline McGlynn ukui-media/data/icons/scalable/devices/audio-subwoofer.svg0000664000175000017500000003353015170052044022603 0ustar fengfeng image/svg+xml audio device subwoofer output audio-subwoofer Evangeline McGlynn ukui-media/data/icons/scalable/devices/audio-speaker-left-side.svg0000664000175000017500000004653115170052044024101 0ustar fengfeng image/svg+xml audio device speaker output left-side audio-speaker-left-side Evangeline McGlynn ukui-media/data/icons/scalable/devices/audio-speaker-center.svg0000664000175000017500000004650715170052044023510 0ustar fengfeng image/svg+xml audio device speaker output center audio-speaker-center Evangeline McGlynn ukui-media/data/icons/scalable/devices/audio-speaker-right-side.svg0000664000175000017500000004653315170052044024266 0ustar fengfeng image/svg+xml audio device speaker output right-side audio-speaker-right-side Evangeline McGlynn ukui-media/data/icons/scalable/devices/audio-speaker-right-side-testing.svg0000664000175000017500000005046515170052044025740 0ustar fengfeng image/svg+xml audio device speaker output right-side testing highlighted audio-speaker-right-side-testing Evangeline McGlynn ukui-media/data/icons/scalable/devices/audio-speaker-right-back.svg0000664000175000017500000004657315170052044024246 0ustar fengfeng image/svg+xml audio device speaker output right-back audio-speaker-right-back Evangeline McGlynn ukui-media/data/icons/scalable/devices/audio-subwoofer-testing.svg0000664000175000017500000002625315170052044024262 0ustar fengfeng image/svg+xml audio device subwoofer output testing highlighted audio-subwoofer-testing Evangeline McGlynn ukui-media/data/icons/scalable/devices/audio-speaker-center-back.svg0000664000175000017500000004513615170052044024403 0ustar fengfeng image/svg+xml audio device speaker output center audio-speaker-center Evangeline McGlynn ukui-media/data/icons/scalable/devices/audio-speaker-right-testing.svg0000664000175000017500000007767115170052044025026 0ustar fengfeng image/svg+xml audio device speaker output right testing highlighted audio-speaker-right-testing Evangeline McGlynn ukui-media/data/icons/scalable/devices/audio-speaker-right-back-testing.svg0000664000175000017500000005052615170052044025712 0ustar fengfeng image/svg+xml audio device speaker output right-back testing highlighted audio-speaker-right-back-testing Evangeline McGlynn ukui-media/data/icons/scalable/devices/audio-speaker-center-testing.svg0000664000175000017500000005044515170052044025157 0ustar fengfeng image/svg+xml audio device speaker output center testing highlighted audio-speaker-center-testing Evangeline McGlynn ukui-media/data/icons/scalable/devices/audio-speaker-left-side-testing.svg0000664000175000017500000005046415170052044025554 0ustar fengfeng image/svg+xml audio device speaker output left-side testing highlighted audio-speaker-left-side-testing Evangeline McGlynn ukui-media/data/icons/scalable/status/0000775000175000017500000000000015170052044016645 5ustar fengfengukui-media/data/icons/scalable/status/Makefile.am0000664000175000017500000000057315170052044020706 0ustar fengfengNULL = themedir = $(pkgdatadir)/icons/hicolor size = scalable context = status iconsdir = $(themedir)/$(size)/$(context) icons_DATA = \ audio-input-microphone-high.svg \ audio-input-microphone-low.svg \ audio-input-microphone-medium.svg \ audio-input-microphone-muted.svg \ $(NULL) EXTRA_DIST = \ $(icons_DATA) \ $(NULL) -include $(top_srcdir)/git.mk ukui-media/data/icons/scalable/status/audio-input-microphone-muted.svg0000664000175000017500000015623115170052044025111 0ustar fengfeng image/svg+xml ukui-media/data/icons/scalable/status/audio-input-microphone-low.svg0000664000175000017500000015615715170052044024603 0ustar fengfeng image/svg+xml ukui-media/data/icons/scalable/status/audio-input-microphone-high.svg0000664000175000017500000016126115170052044024711 0ustar fengfeng image/svg+xml ukui-media/data/icons/scalable/status/audio-input-microphone-medium.svg0000664000175000017500000016007415170052044025253 0ustar fengfeng image/svg+xml ukui-media/data/icons/22x22/0000775000175000017500000000000015170052044014333 5ustar fengfengukui-media/data/icons/22x22/Makefile.am0000664000175000017500000000006115170052044016364 0ustar fengfengSUBDIRS = status -include $(top_srcdir)/git.mk ukui-media/data/icons/22x22/status/0000775000175000017500000000000015170052044015656 5ustar fengfengukui-media/data/icons/22x22/status/Makefile.am0000664000175000017500000000057015170052044017714 0ustar fengfengNULL = themedir = $(pkgdatadir)/icons/hicolor size = 22x22 context = status iconsdir = $(themedir)/$(size)/$(context) icons_DATA = \ audio-input-microphone-high.png \ audio-input-microphone-low.png \ audio-input-microphone-medium.png \ audio-input-microphone-muted.png \ $(NULL) EXTRA_DIST = \ $(icons_DATA) \ $(NULL) -include $(top_srcdir)/git.mk ukui-media/data/icons/22x22/status/audio-input-microphone-medium.png0000664000175000017500000000171015170052044024240 0ustar fengfengPNG  IHDRĴl;tEXtSoftwareAdobe ImageReadyqe<jIDATxڜkHQ6̜[6.jYTfַ.dtEEч(#!(ˈĒȈ,:PQZ^.ֲs^l:ܞyeXTaI{R֒ҁ7W<̬Uff_VL&WTadFrnz^jr3-鮐C[uTR?[Q0u-%|g^DDIw"4iZW<*mZ ӻBVl"_:gAjLm@HP>AK #vmaX$3K@bL*A}S " VǬ WI|k%覷XX3w-1ynӇqlwAkr5$ak _{R/oOH,ۂȃCIQ،eQPWT#!!v+F,, s#Iaq=y\=bVBRo{mc!ޢ|~3p_Zli8=|5 `0t054c0w/XP+*y61!Pm4 ,t]t& X$LPJܥRprܧ)!-`@? I oX+Q+&BsBQġI-=O ğ_E&67| MtE<C q E*!>Wb(A qUzE'!Z Wu0>a+ְc>&bȥۘt?&O.عfqaqIWݰa}i!&z9X)kSO*Eb_L4o)$:g8 BTжwAvv6 | &p`qpS8Np92)qaph>34 ,{{O`3d37c^m c[U-BƊp{嫗U,K QUe &QSx|rlʕ#ԝEFISS95sx ޡc0 7,N2'd2A[=+(vGGE":vg~ju4e/15Ϲs8Ep8M{gK~IcC Da=p8(e۩aE="8UU^8NQl6:b>owsc\3Dc`pDڵ'zoL ( HL'NW0a<".,>yva!0N! D_•սD`75wbCq(wK`2OaL@ZXtKƆb rY<#7GN-XxqSI+K+EEFz*]=PT0':Wlu:VEDxF'{>|FNv&4 ℱ^~#I=b _XdJfJWݯwG=[oHNCar4mܱz{,^b 'u"ΟTudhypDZwFj JTM[0ݡaq+${AM4XfR9\ kLMM_#J,V+RG6_9vW{4j[stq c'h}KKsddePq?j7Sh(-yǏyz*hpŊ 6>:OPpIENDB`ukui-media/data/icons/22x22/status/audio-input-microphone-muted.png0000664000175000017500000000146115170052044024101 0ustar fengfengPNG  IHDRĴl;tEXtSoftwareAdobe ImageReadyqe<IDATxڜ_HSqǿrn\%I#|K"# %"EheDb!ljԜ-m߽쎹?zׁw>;seM( ]k8CSҶgGHPeb]e-YOLn7Bj yx< bEuu%wnqTU`*] #Rd0 AQ{]}>i[vg;k+q9~X~Nfa~ 'vertical' ukui-volume-control-applet orientation Used to set the orientation of ukui-volume-control-applet ukui-media/data/ukui-volume-control-applet.desktop0000664000175000017500000000164615170054730021272 0ustar fengfeng[Desktop Entry] _Name=Volume Control _Comment=Show desktop volume control Name[mn]=ᠳᠠᠭᠤᠨ ᠤ ᠬᠡᠮᠵᠢᠶ᠎ᠡ ᠶᠢ ᠡᠵᠡᠮᠳᠡᠬᠦ Name[zh_CN]=音量控制 Name[zh_HK]=音量控制 Comment[mn]=ᠰᠢᠷᠡᠭᠡᠨ ᠭᠠᠳᠠᠷᠭᠤ ᠶᠢᠨ ᠳᠠᠭᠤᠨ ᠤ ᠬᠡᠮᠵᠢᠶ᠎ᠡ ᠶᠢ ᠡᠵᠡᠮᠳᠡᠬᠦ ᠶᠢ ᠢᠯᠡᠷᠡᠭᠦᠯᠵᠡᠢ Comment[zh_CN]=显示桌面音量控制 Comment[zh_HK]=顯示桌面音量控制 Icon=multimedia-volume-control Exec=ukui-volume-control-applet-qt Terminal=false Type=Application Categories=AudioVideo;Mixer;Settings;HardwareSettings; Keywords=UKUI;volume;control;mixer;settings;sound; NoDisplay=true OnlyShowIn=UKUI; X-UKUI-Bugzilla-Bugzilla=UKUI X-UKUI-Bugzilla-Product=ukui-media X-UKUI-Bugzilla-Component=ukui-volume-control # See http://bugzilla.ukui.org/show_bug.cgi?id=568320 X-UKUI-AutoRestart=true X-UKUI-Autostart-Phase=Application