ukui-session-manager-4.0.0.1/0000755000175000017500000000000014565543367014362 5ustar fengfengukui-session-manager-4.0.0.1/CMakeLists.txt0000644000175000017500000000337214553704175017120 0ustar fengfengcmake_minimum_required(VERSION 3.1.0) project(ukui-session-manager) # Find includes in corresponding build directories set(CMAKE_INCLUDE_CURRENT_DIR ON) # Instruct CMake to run moc automatically when needed set(CMAKE_AUTOMOC ON) # Handle the Qt rcc code generator automatically. set(CMAKE_AUTORCC ON) # Create code from a list of Qt designer ui files set(CMAKE_AUTOUIC ON) set(CMAKE_CXX_STANDARD 11) include(GNUInstallDirs) # Enable funcion names and line numbers even for release builds add_definitions("-DQT_MESSAGELOGCONTEXT") # Find the QtWidgets library find_package(Qt5 COMPONENTS Widgets Core Multimedia X11Extras REQUIRED) find_package(Qt5LinguistTools) find_package(KF5IdleTime) find_package(Qt5DBus) find_package(KF5CoreAddons) find_package(KF5Config) find_package(KF5WindowSystem) # For debug: cmake -DCMAKE_BUILD_TYPE=debug set(CMAKE_CXX_FLAGS_DEBUG "-g -O0") set(CMAKE_C_FLAGS_DEBUG "-g -O0") # i18n set(UKUI_TRANSLATIONS_DIR ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/ukui/translations) add_definitions( -DUKUI_TRANSLATIONS_DIR="${UKUI_TRANSLATIONS_DIR}" ) if (NOT DEFINED UPDATE_TRANSLATIONS) set(UPDATE_TRANSLATIONS "No") endif() # To create a new ts file: lupdate -recursive . -target-language en_US -ts translations/en_US.ts -no-ui-lines file(GLOB TS_FILES "${CMAKE_CURRENT_SOURCE_DIR}/translations/*.ts") # cmake -DUPDATE_TRANSLATIONS=yes if (UPDATE_TRANSLATIONS) qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES}) else() qt5_add_translation(QM_FILES ${TS_FILES}) endif() add_custom_target(translations ALL DEPENDS ${QM_FILES}) install(FILES ${QM_FILES} DESTINATION ${UKUI_TRANSLATIONS_DIR}/${PROJECT_NAME}) add_subdirectory(ukui-session) add_subdirectory(tools) add_subdirectory(ukuismserver) add_subdirectory(data) ukui-session-manager-4.0.0.1/translations/0000755000175000017500000000000014553704175017074 5ustar fengfengukui-session-manager-4.0.0.1/translations/bo_CN.ts0000644000175000017500000003320414553704175020426 0ustar fengfeng MainWindow Logout ད་ལྟ་རྩིས་ཐེམ་ནས་བསུབ་པ། Reboot བསྐྱར་སློང་། Lock Screen བརྙན་ཡོལ་ཟྭ་རྒྱག།(_L) cancel ལེན་པ། Hebernate ངལ་གསོ། Switch User སྤྱོད་མཁན་བརྗེ་རེས། confirm གཏན་འཁེལ། Suspend འགེལ་འཇོག Shut Down སྒོ་རྒྱག་པ། Form རྣམ་པ། Hibernate ཧིན་རྡུ་ཉི་ཞི་ཡ། <html><head/><body><p><br/></p></body></html> <html> <head/> <body> <p> <br/> </p> </body> </html> QApplication (user),yhkylin-backup-tools is performing a system backup or restore. (བཀོལ་མི།)ཆིས་ལིན་གྲབས་ཉར་སླར་གསོ་ཡོ་ཆས་ཀྱིས་གྲབས་ཉར་རམ་སླར་གསོ་བྱེད་སྒང་རེད། (user),ukui-control-center is performing a system update or package installation. (བཀོལ་མི།)ཚོད་འཛིན་ངོས་པང་གིས་བརྒྱུད་ཁོངས་རིམ་སྤོར་རམ་མཉེན་ཆས་ནང་འཇུག་བྱེད་སྒང་རེད། For system security,Reboot、Shutdown、Logout and Hibernate are temporarily unavailable. བརྒྱུད་ཁོངས་བདེ་འཇགས་ལ་དམིགས་ཏེ་སྒོ་བསྐྱར་འབྱེད་དང་སྒོ་གཏན་པ། ཐོ་གསུབ་པ། ངལ་གསོ་བྱེད་པ་སོགས་ཀྱི་བྱེད་ནུས་གནས་སྐབས་རིང་བཀོལ་མི་ཐུབ། Multiple users are logged in at the same time.Are you sure you want to close this system? བཀོལ་མི་མང་པོ་དུས་མཉམ་དུ་ཐོ་ཞུགས་རྣམ་པར་གནས་འདུག་པས། བརྒྱུད་ཁོངས་སྒོ་གཏན་ནམ། For system security,Reboot、Shutdown and Hibernate are temporarily unavailable. བརྒྱུད་ཁོངས་བདེ་འཇགས་ལ་དམིགས་ཏེ་སྒོ་བསྐྱར་འབྱེད་དང་སྒོ་གཏན་པ། ཐོ་གསུབ་པ། ངལ་གསོ་བྱེད་པ་སོགས་ཀྱི་བྱེད་ནུས་གནས་སྐབས་རིང་བཀོལ་མི་ཐུབ། UKUI session tools, show the shutdown dialog without any arguments. UKUIཚོགས་འདུའི་ཡོ་བྱད་ལ་རྩོད་གླེང་ཅི་ཡང་མེད་པར་ལས་མཚམས་འཇོག་པའི་གླེང་མོལ་མངོན་པར་བྱས་ཡོད། Switch the user of this computer. རྩིས་འཁོར་འདིའི་སྤྱོད་མཁན་བརྗེ་རེས་བྱེད་དགོས། Hibernate this computer. གློག་ཀླད་འདི་ལ་ཧི་པི་ཝེ་སི་ཡོད། Suspend this computer. གློག་ཀླད་འདི་གནས་སྐབས་མཚམས་འཇོག་ཏུ་ Logout this computer. རྩིས་འཁོར་འདི་ཐོ་འགོད་བྱེད་དགོས། Restart this computer. རྩིས་འཁོར་འདི་བསྐྱར་དུ་འགོ་བརྩམས། Shutdown this computer. གློག་ཀླད་འདི་སྒོ་རྒྱག་དགོས། Switch User གློག་སྒོ་འབྱེད་པའི་སྤྱོད་མཁན Hibernate ཧིན་རྡུ་ཉི་ཞི་ཡ། Suspend གནས་སྐབས་མཚམས་འཇོག་ Logout ཐོ་འགོད་བྱེད་པ། Reboot བསྐྱར་དུ་འཁོར་བ། Shut Down ལས་མཚམས་འཇོག Lock Screen སྒོ་ལྕགས་ཀྱི་བརྙན་ཤེལ། cancel ཕྱིར་འཐེན། confirm ངོས་འཛིན་བྱས་པ། UKUI Session Manager UKUIཚོགས་འདུའི་སྤྱི་གཉེར་བ། system-monitor མ་ལག་ལྟ་ཞིབ་ཚད་ལེན Log Out ཐོ་འགོད་བྱེད་པ། Restart བསྐྱར་དུ་འཁོར་བ། QObject Still to do! ལག་བསྟར་བྱེད་པ། some applications are running and they dont want you to do this. བྱ་རིམ་རེ་འགའ་འཁོར་སྐྱོད་བྱེད་སྒང་ཡིན་པར་མ་ཟད། དེས་བྱ་བ་འདི་མི་བསྒྲུབ་པའི་རེ་འདོན་བྱ་བཞིག་འདུག System update or package installation in progress,this function is temporarily unavailable. བརྒྱུད་ཁོངས་རིམ་སྤོར་རམ་མཉེན་ཆས་ནང་འཇུག་བྱེད་སྒང་ཡིན་པས། བྱེད་ནུས་འདི་གནས་སྐབས་རིང་བཀོལ་མི་ཐུབ། System backup or restore in progress,this function is temporarily unavailable. བརྒྱུད་ཁོངས་གྲབས་ཉར་རམ་སླར་གསོ་བྱེད་སྒང་ཡིན་པས། བྱེད་ནུས་འདི་གནས་སྐབས་རིང་བཀོལ་མི་ཐུབ། cancel ལེན་པ། notice གསལ་བརྡ། confirm གཏན་འཁེལ། give up བློས་གཏང་བ། Multiple users are logged in at the same time.Are you sure you want to close this system? བཀོལ་མི་མང་པོ་དུས་མཉམ་དུ་ཐོ་ཞུགས་རྣམ་པར་གནས་འདུག་པས། བརྒྱུད་ཁོངས་སྒོ་གཏན་ནམ། OK འགྲིགས། The following program is running to prevent the system from hibernate! གཤམ་གྱི་གོ་རིམ་ནི་མ་ལག་ལ་བཀག་འགོག་བྱེད་པའི་ཆེད་དུ་ཡིན། The following program is running to prevent the system from suspend! གཤམ་གྱི་གོ་རིམ་ནི་མ་ལག་གནས་སྐབས་མཚམས་འཇོག་པར་བཀག་འགོག་བྱེད་པའི་ཆེད་དུ་ཡིན། The following program is running to prevent the system from logout! གཤམ་གྱི་གོ་རིམ་ནི་མ་ལག་གིས་ཐོ་འགོད་བྱེད་པར་བཀག་འགོག་བྱེད་པའི་ཆེད་དུ་ཡིན། The following program is running to prevent the system from reboot! གཤམ་གྱི་གོ་རིམ་ནི་མ་ལག་བསྐྱར་དུ་འབྱུང་བར་སྔོན་འགོག་བྱེད་ཆེད་ཡིན། The following program is running to prevent the system from shutting down! གཤམ་གྱི་གོ་རིམ་ནི་མ་ལག་གི་སྒོ་རྒྱག་པར་བཀག་འགོག་བྱེད་པའི་ཆེད་དུ་ཡིན། Still Hibernate སྔར་བཞིན་ཧིན་རྡུ་ཉི་ཞི་ཡ། Still Suspend སྔར་བཞིན་གནས་སྐབས་མཚམས་འཇོག Still Reboot སྔར་བཞིན་བསྐྱར་དུ་བྱུང་བ་རེད། Still Shutdown སྔར་བཞིན་ལས་མཚམས་བཞག་པ Cancel ཕྱིར་འཐེན། some applications are running and they don't want you to do this. རེ་འདུན་ཞུ་ཡིག་ཁ་ཤས་འཁོར་སྐྱོད་བྱེད་བཞིན་ཡོད་པས་ཁོ་ཚོས་ཁྱེད་ཚོར་འདི་ལྟར་བྱེད་དུ་འཇུག་མི་འདོད། ukui-session-manager-4.0.0.1/translations/zh_CN.ts0000644000175000017500000003027714553704175020456 0ustar fengfeng MainWindow Switch User 切换用户 Hibernate 休眠 Suspend 睡眠 Logout 注销 Reboot 重启 Shut Down 关机 Lock Screen 锁屏 cancel 取消 confirm 确认 QApplication UKUI session tools, show the shutdown dialog without any arguments. Switch the user of this computer. Hibernate this computer. Suspend this computer. Logout this computer. Restart this computer. Shutdown this computer. system-monitor 系统监视器 Switch User 切换用户 Hibernate 休眠 Suspend 睡眠 Logout 注销 Reboot 重启 Log Out 注销 Restart 重启 Shut Down 关机 Lock Screen 锁屏 Multiple users are logged in at the same time.Are you sure you want to close this system? 同时有多个用户处于登录状态,你确定要退出系统吗? cancel 取消 confirm 确认 (user),ukui-control-center is performing a system update or package installation. (用户),系统正在进行升级或软件包安装/卸载相关操作。 (user),yhkylin-backup-tools is performing a system backup or restore. (用户),备份还原工具正在进行备份或还原。 For system security,Reboot、Shutdown、Logout and Hibernate are temporarily unavailable. 为了系统安全,重启、关机、注销和休眠功能暂时不可用。 For system security,Reboot、Shutdown and Hibernate are temporarily unavailable. 为了系统安全,重启、关机和休眠功能暂时不可用。 UKUI Session Manager QObject is block system 阻止系统 into sleep for reason 休眠,因为 is block system 阻止系统 into sleep for reason 睡眠,因为 Are you sure 你确定 you want to get system into sleep? 要让系统进入休眠吗? Are you sure you want to get system into sleep? 你确定要让系统进入睡眠吗? cancel 取消 confirm 确认 is block system into reboot for reason 阻止系统重启,因为 is block system into shutdown for reason 阻止系统关机,因为 Are you sure you want to reboot? 你确定要重启系统吗? Are you sure you want to shutdown? 你确定要退出系统吗? Multiple users are logged in at the same time.Are you sure you want to close this system? 同时有多个用户处于登录状态,你确定要退出系统吗? System update or package installation in progress,this function is temporarily unavailable. 正在进行升级或安装/卸载(相关操作),这个功能暂时不可用。 System backup or restore in progress,this function is temporarily unavailable. 正在进行系统备份或还原,这个功能暂时不可用。 OK 确定 The following program is running to prevent the system from hibernate! 以下程序阻止休眠,您可以点击“取消”然后关闭这些程序。 The following program is running to prevent the system from suspend! 以下程序阻止睡眠,您可以点击“取消”然后关闭这些程序。 The following program is running to prevent the system from logout! 以下程序阻止注销,您可以点击“取消”然后关闭这些程序。 The following program is running to prevent the system from reboot! 以下程序阻止重启,您可以点击“取消”然后关闭这些程序。 The following program is running to prevent the system from shutting down! 以下程序阻止关机,您可以点击“取消”然后关闭这些程序。 Still Hibernate 休眠 Still Suspend 睡眠 Still Reboot 重启 Still Shutdown 关机 Cancel 取消 some applications are running and they don't want you to do this. 一些程序正在运行,而且它不希望你继续该操作。 Still to do! 仍然执行! give up 放弃 ukui-session-manager-4.0.0.1/translations/tr.ts0000644000175000017500000000607114277116241020067 0ustar fengfeng MainWindow Form Switch User Kullanıcı Değiştir Hebernate Hazırda Beklet Suspend Askıya Al Logout Çıkış Reboot Yeniden Başlat Shut Down Kapat Lock Screen Ekranı Kilitle QApplication UKUI session tools, show the shutdown dialog without any arguments. UKUI oturum araçları, herhangi bir argüman olmadan kapatma iletişim kutusunu gösterir. Switch the user of this computer. Bu bilgisayardaki kullanıcıyı değiştir. Hibernate this computer. Bu bilgisayarı hazırda beklet. Suspend this computer. Bu bilgisayarı askıya al. Logout this computer. Bu bilgisayardan çık. Restart this computer. Bu bilgisayarı yeniden başlat. Shutdown this computer. Bu bilgisayarı kapat. ukui-session-manager-4.0.0.1/translations/bo.ts0000644000175000017500000000433714277116241020045 0ustar fengfeng MainWindow Form Suspend Switch User Log Out Restart Power Off QApplication UKUI session tools, show the shutdown dialog without any arguments. Logout this computer. Shutdown this computer. Switch the user of this computer. Restart this computer. ukui-session-manager-4.0.0.1/ukui-session/0000755000175000017500000000000014567236155017014 5ustar fengfengukui-session-manager-4.0.0.1/ukui-session/modulemanager.h0000644000175000017500000000751614553704753022015 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * 2010-2016 LXQt team. * * 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, 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 MODULEMANAGER_H #define MODULEMANAGER_H #include "ukuimodule.h" #include #include #include #include #include #include #include #include #include #include #include class XdgDesktopFile; class IdleWatcher; typedef QMap ModulesMap; typedef QMapIterator ModulesMapIterator; class ModuleManager : public QObject, public QAbstractNativeEventFilter { Q_OBJECT public: ModuleManager(QObject* parent = nullptr); ~ModuleManager() override; void startProcess(const QString& name, bool detach); void stopProcess(const QString& name); bool startProcess(const QString &name, const QStringList &args); void startup(); void dostartwm(); void startupfinished(const QString& appName ,const QString& string); // Qt5 users native event filter bool nativeEventFilter(const QByteArray &eventType, void* message, long* result) override; static void insertStartupList(QString &&str); static bool isProgramStarted(const QString &&str); void ChkScreenLockStartup(); void setIsShutingDown(bool value); bool getIsShutingDown() const; private: bool startModuleTimer(QTimer *timer,int i); void playBootMusic(bool arg); void startProcess(const XdgDesktopFile &file, bool required); void constructStartupList(); bool autoRestart(const XdgDesktopFile &file); bool openAppWithAppManager(const XdgDesktopFile &file); public slots: void startCompsite(); void logout(bool doExit); void timerUpdate(); void timeup(); void weakup(bool arg); //void stateChanged(QMediaPlayer::State state); void onServiceStatusChanged(const QString &name, const QString &oldOwner, const QString &newOwner); void onSLStartupModeChanged(bool isStartup); private slots: void restartModules(int exitCode, QProcess::ExitStatus exitStatus); Q_SIGNALS: void moduleStateChanged(QString moduleName, bool state); void finished(); void usdfinished(); void wmfinished(); void panelfinished(); void desktopfinished(); private: QTimer *tusd = new QTimer(); QTimer *twm = new QTimer(); QTimer *tpanel = new QTimer(); QTimer *tdesktop = new QTimer(); bool isPanelStarted = false; bool isDesktopStarted = false; bool isWMStarted = false; bool isCompsiteStarted = false; bool isScreenLockStartup = false; bool isWayland = false; bool wmFound = false; bool isDirectInstall = false; bool isShutingDown = false; static std::map m_startupMap; ModulesMap mNameMap; XdgDesktopFileList mInitialization; XdgDesktopFile mWindowManager; XdgDesktopFile mPanel; XdgDesktopFile mFileManager; XdgDesktopFileList mDesktop; XdgDesktopFileList mApplication; XdgDesktopFileList mForceApplication; XdgDesktopFileList m_userAutoStartApp; }; #endif // MODULEMANAGER_H ukui-session-manager-4.0.0.1/ukui-session/idleadbusdaptor.h0000644000175000017500000000275514553704753022343 0ustar fengfeng/* BEGIN_COMMON_COPYRIGHT_HEADER * * Copyright: 2023, KylinSoft Co., Ltd. * Copyright: 2012 Razor team * * Authors: * Christian Surlykke * * This program or library 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. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA * * END_COMMON_COPYRIGHT_HEADER */ #ifndef IDLEDBUSADAPTOR_H #define IDLEDBUSADAPTOR_H #include #include "idlewatcher.h" class IdleDBusAdaptor : public QDBusAbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.gnome.SessionManager.Presence") public: IdleDBusAdaptor(IdleWatcher *mIdleWatch) : QDBusAbstractAdaptor(mIdleWatch) { connect(mIdleWatch, &IdleWatcher::StatusChanged, this , &IdleDBusAdaptor::StatusChanged); } Q_SIGNALS: void StatusChanged(uint status); private: IdleWatcher *mIdleWatch; }; #endif // IDLEDBUSADAPTOR_H ukui-session-manager-4.0.0.1/ukui-session/usminhibit.h0000644000175000017500000000505114553704753021340 0ustar fengfeng/***************************************************************** ukuismserver - the UKUI session management server Copyright 2000 Matthias Ettrich Copyright 2023, KylinSoft Co., Ltd. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ******************************************************************/ #ifndef USMINHIBIT_H #define USMINHIBIT_H #include "ukuilockinfo.h" #include #include #include class inhibit{ public: inhibit(QString app_id, quint32 toplevel_xid, QString reason, quint32 flags ,quint32 cookie ,QString inhibitorName); ~inhibit(); QString app_id; quint32 toplevel_xid; QString reason; quint32 flags; quint32 cookie; QString inhibitorName; }; class usminhibit : public QObject { Q_OBJECT public: enum InhibitorFlag{ GSM_INHIBITOR_FLAG_LOGOUT = 1 << 0, GSM_INHIBITOR_FLAG_SWITCH_USER = 1 << 1, GSM_INHIBITOR_FLAG_SUSPEND = 1 << 2, GSM_INHIBITOR_FLAG_IDLE = 1 << 3 }; usminhibit(); ~usminhibit(); QStringList getInhibitor(); quint32 addInhibit(QString app_id, quint32 toplevel_xid, QString reason, quint32 flags); uint unInhibit(quint32 cookie); uint generateCookie(); bool isInhibited(quint32 flags); public: QHash hash; int inhibitor_serial; int inhibit_logout_num; int inhibit_switchuser_num; int inhibit_suspend_num; int inhibit_idle_num; private: //QDBusInterface dbus; uint get_next_inhibitor_serial(); Q_SIGNALS: void inhibitAdd(); void inhibitRemove(); }; #endif // USMINHIBIT_H ukui-session-manager-4.0.0.1/ukui-session/xdgautostart.h0000644000175000017500000000465714553704753021731 0ustar fengfeng/* BEGIN_COMMON_COPYRIGHT_HEADER * (c)LGPL2+ * * Copyright: 2012 Razor team * Copyright: 2023, KylinSoft Co., Ltd. * * Authors: * Alexander Sokoloff * * This program or library 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. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA * * END_COMMON_COPYRIGHT_HEADER */ #ifndef QTXDG_XDGAUTOSTART_H #define QTXDG_XDGAUTOSTART_H #include "xdgmacros.h" #include "xdgdesktopfile.h" #include /*! @brief The XdgAutoStart class implements the "Desktop Application Autostart Specification" * from freedesktop.org. * This specification defines a method for automatically starting applications during the startup * of a desktop environment and after mounting a removable medium. * Now we impliment only startup. * * @sa http://standards.freedesktop.org/autostart-spec/autostart-spec-latest.html */ class QTXDG_API XdgAutoStart { public: /*! Returns a list of XdgDesktopFile objects for all the .desktop files in the Autostart directories When the .desktop file has the Hidden key set to true, the .desktop file must be ignored. But you can change this behavior by setting excludeHidden to false. */ static XdgDesktopFileList desktopFileList(bool excludeHidden=true); /*! Returns a list of XdgDesktopFile objects for .desktop files in the specified Autostart directories When the .desktop file has the Hidden key set to true, the .desktop file must be ignored. But you can change this behavior by setting excludeHidden to false. */ static XdgDesktopFileList desktopFileList(QStringList dirs, bool excludeHidden=true); /// For XdgDesktopFile returns the file path of the same name in users personal autostart directory. static QString localPath(const XdgDesktopFile& file); }; #endif // XDGAUTOSTART_H ukui-session-manager-4.0.0.1/ukui-session/sessiondbusadaptor.h0000644000175000017500000001214014553704753023076 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * 2010-2016 LXQt team. * * 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, 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 SESSIONDBUSADAPTOR_H #define SESSIONDBUSADAPTOR_H #include "../tools/ukuipower.h" #include "../tools/powerprovider.h" #include "modulemanager.h" #include "usminhibit.h" #include "sessionmanagercontext.h" #include #include class SessionDBusAdaptor : public QDBusAbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.gnome.SessionManager") public: SessionDBusAdaptor(SessionManagerDBusContext *context) :QDBusAbstractAdaptor(context) { connect(parent(), &SessionManagerDBusContext::moduleStateChanged, this, &SessionDBusAdaptor::moduleStateChanged); connect(parent(), &SessionManagerDBusContext::inhibitadded, this, &SessionDBusAdaptor::inhibitadded); connect(parent(), &SessionManagerDBusContext::inhibitremove, this, &SessionDBusAdaptor::inhibitremove); connect(parent(), &SessionManagerDBusContext::StartLogout, this, &SessionDBusAdaptor::StartLogout); connect(parent(), &SessionManagerDBusContext::PrepareForSwitchuser, this, &SessionDBusAdaptor::PrepareForSwitchuser); connect(parent(), &SessionManagerDBusContext::PrepareForPhase2, this, &SessionDBusAdaptor::PrepareForPhase2); } virtual ~SessionDBusAdaptor(){} inline SessionManagerDBusContext *parent() const { return static_cast(QObject::parent()); } Q_SIGNALS: void moduleStateChanged(QString moduleName, bool state); void inhibitadded(quint32 flags); void inhibitremove(quint32 flags); void StartLogout(); void PrepareForSwitchuser(); void PrepareForPhase2(); public slots: void startupfinished(const QString &appName ,const QString &string) { return parent()->startupfinished(appName, string); } bool canSwitch() { return parent()->canSwitch(); } bool canHibernate() { return parent()->canHibernate(); } bool canSuspend() { return parent()->canSuspend(); } bool canLockscreen() { return parent()->canLockscreen(); } bool canLogout() { return parent()->canLogout(); } bool canReboot() { return parent()->canReboot(); } bool canPowerOff() { return parent()->canPowerOff(); } Q_NOREPLY void switchUser() { return parent()->switchUser(); } Q_NOREPLY void hibernate() { return parent()->hibernate(); } Q_NOREPLY void suspend() { return parent()->suspend(); } Q_NOREPLY void logout() { return parent()->logout(); } Q_NOREPLY void reboot() { return parent()->reboot(); } Q_NOREPLY void powerOff() { return parent()->powerOff(); } Q_NOREPLY void startModule(const QString& name) { return parent()->startModule(name); } Q_NOREPLY void stopModule(const QString& name) { return parent()->stopModule(name); } bool startApp(const QString &name, const QStringList &args) { return parent()->startApp(name, args); } uint Inhibit(QString app_id, quint32 toplevel_xid, QString reason, quint32 flags) { return parent()->Inhibit(app_id,toplevel_xid, reason, flags); } Q_NOREPLY void Uninhibit(uint cookie) { return parent()->Uninhibit(cookie); } QStringList GetInhibitors() { return parent()->GetInhibitors(); } QVector ListInhibitor(QString type) { return parent()->ListInhibitor(type); } bool IsSessionRunning(){ return parent()->IsSessionRunning(); } QString GetSessionName() { return parent()->GetSessionName(); } bool IsInhibited(quint32 flags) { return parent()->IsInhibited(flags); } Q_NOREPLY void setSessionEnv(const QString &key, const QString &value) { return parent()->setSessionEnv(key, value); } Q_NOREPLY void emitStartLogout() { return parent()->emitStartLogout(); } Q_NOREPLY void emitPrepareForSwitchuser() { return parent()->emitPrepareForSwitchuser(); } Q_NOREPLY void emitPrepareForPhase2() { return parent()->emitPrepareForPhase2(); } Q_NOREPLY void simulateUserActivity(){ return parent()->simulateUserActivity(); } }; #endif // SESSIONDBUSADAPTOR_H ukui-session-manager-4.0.0.1/ukui-session/CMakeLists.txt0000644000175000017500000000317014553704753021554 0ustar fengfeng# Populate a CMake variable with the sources set(ukui-session_SRCS main.cpp sessionapplication.cpp modulemanager.cpp ukuimodule.cpp idlewatcher.cpp sessiondbusadaptor.h idlewatcher.cpp idlewatcher.h idleadbusdaptor.h xdgautostart.cpp xdgautostart.h xdgdesktopfile.cpp xdgdesktopfile.h xdgdirs.cpp xdgdirs.h xdgmacros.h usminhibit.cpp usminhibit.h ../tools/ukuipower.cpp ../tools/powerprovider.cpp ukuisessiondebug.cpp ukuisessiondebug.h sessionmanagercontext.h sessionmanagercontext.cpp ukuilockinfo.cpp ) #find_package(QT5XDG ) find_package(PkgConfig) pkg_search_module(GSETTINGS_QT REQUIRED gsettings-qt) include_directories(${GSETTINGS_QT_INCLUDES}) pkg_check_modules(GLIB2 REQUIRED glib-2.0) pkg_check_modules(GIO2 REQUIRED gio-2.0) pkg_search_module(X11 REQUIRED x11) pkg_check_modules(KDKINFO kysdk-sysinfo) if (KDKINFO_FOUND) ADD_DEFINITIONS(-DKDKINFO_FOUND="true") endif() include_directories(${X11_INCLUDES}) include_directories(${GLIB2_INCLUDE_DIRS}) include_directories(${GIO2_INCLUDE_DIRS}) include_directories(${KDKINFO_INCLUDE_DIRS}) link_directories(${KDKINFO_LIBRARY_DIRS}) add_executable(ukui-session ${ukui-session_SRCS}) target_link_libraries(ukui-session Qt5::Widgets Qt5::Core Qt5::DBus Qt5::Multimedia KF5::IdleTime KF5::CoreAddons KF5::ConfigCore KF5::ConfigGui KF5::WindowSystem -lSM -lICE ${GSETTINGS_QT_LIBRARIES} ${X11_LIBRARIES} ${GLIB2_LIBRARIES} ${GIO2_LIBRARIES} ${KDKINFO_LIBRARIES}) #${GSETTINGS_QT_LIBRARIES} install(TARGETS ukui-session DESTINATION bin) ukui-session-manager-4.0.0.1/ukui-session/org.ukui.KWin.Session.xml0000644000175000017500000000073514553704175023574 0ustar fengfeng ukui-session-manager-4.0.0.1/ukui-session/ukuimodule.cpp0000644000175000017500000000422414553704753021704 0ustar fengfeng/* BEGIN_COMMON_COPYRIGHT_HEADER * * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program or library 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. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA * * END_COMMON_COPYRIGHT_HEADER */ #include "ukuimodule.h" #include #include #include UkuiModule::UkuiModule(const XdgDesktopFile& file, QObject* parent) : QProcess(parent) , file(file) , fileName(QFileInfo(file.fileName()).fileName()) , mIsTerminating(false) { restartNum = 0; QProcess::setProcessChannelMode(QProcess::ForwardedChannels); connect(this, SIGNAL(stateChanged(QProcess::ProcessState)), SLOT(updateState(QProcess::ProcessState))); } void UkuiModule::startUKUIModule() { mIsTerminating = false; QStringList args = file.expandExecString(); QString command = args.takeFirst(); qDebug() << "Start ukui module: " << command << "args: " << args; QProcess::start(command, args); ModuleManager::insertStartupList(std::move(command)); } void UkuiModule::terminate() { mIsTerminating = true; QProcess::terminate(); } bool UkuiModule::isTerminating() { return mIsTerminating; } void UkuiModule::updateState(QProcess::ProcessState newState) { if (newState != QProcess::Starting) emit moduleStateChanged(fileName, (newState == QProcess::Running)); } ukui-session-manager-4.0.0.1/ukui-session/xdgdesktopfile.h0000644000175000017500000003152714553704753022210 0ustar fengfeng/* BEGIN_COMMON_COPYRIGHT_HEADER * (c)LGPL2+ * * Copyright: 2010-2011 Razor team * Copyright: 2023, KylinSoft Co., Ltd. * * Authors: * Alexander Sokoloff * * This program or library 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. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA * * END_COMMON_COPYRIGHT_HEADER */ #ifndef QTXDG_XDGDESKTOPFILE_H #define QTXDG_XDGDESKTOPFILE_H #include "xdgmacros.h" #include #include #include #include #include #include class XdgDesktopFileData; /** \brief Desktop files handling. XdgDesktopFile class gives the interface for reading the values from the XDG .desktop file. The interface of this class is similar on QSettings. XdgDesktopFile objects can be passed around by value since the XdgDesktopFile class uses implicit data sharing. The Desktop Entry Specification defines 3 types of desktop entries: Application, Link and Directory. The format of .desktop file is described on http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html Note that not all methods in this class make sense for all types of desktop files. \author Alexander Sokoloff */ class QTXDG_API XdgDesktopFile { public: /*! The Desktop Entry Specification defines 3 types of desktop entries: Application, Link and Directory. File type is determined by the "Type" tag. */ enum Type { UnknownType, //! Unknown desktop file type. Maybe is invalid. ApplicationType, //! The file describes application. LinkType, //! The file describes URL. DirectoryType //! The file describes directory settings. }; //! Constructs an empty XdgDesktopFile XdgDesktopFile(); /*! Constructs a copy of other. This operation takes constant time, because XdgDesktopFile is implicitly shared. This makes returning a XdgDesktopFile from a function very fast. If a shared instance is modified, it will be copied (copy-on-write), and that takes linear time. */ XdgDesktopFile(const XdgDesktopFile& other); /*! Constructs a new basic DesktopFile. If type is: - ApplicationType, "value" should be the Exec value; - LinkType, "value" should be the URL; - DirectoryType, "value" should be omitted */ XdgDesktopFile(XdgDesktopFile::Type type, const QString& name, const QString& value = QString()); //! Destroys the object. virtual ~XdgDesktopFile(); //! Assigns other to this DesktopFile and returns a reference to this DesktopFile. XdgDesktopFile& operator=(const XdgDesktopFile& other); //! Returns true if both files contain the identical key-value pairs bool operator==(const XdgDesktopFile &other) const; //! Returns false if both files contain the identical key-value pairs inline bool operator!=(const XdgDesktopFile &other) const { return !operator==(other); } //! Loads an DesktopFile from the file with the given fileName. virtual bool load(const QString& fileName); //! Saves the DesktopFile to the file with the given fileName. Returns true if successful; otherwise returns false. virtual bool save(const QString &fileName) const; /*! This is an overloaded function. This function writes a DesktopFile to the given device. */ virtual bool save(QIODevice *device) const; /*! Returns the value for key. If the key doesn't exist, returns defaultValue. If no default value is specified, a default QVariant is returned. */ QVariant value(const QString& key, const QVariant& defaultValue = QVariant()) const; /*! Returns the localized value for key. In the desktop file keys may be postfixed by [LOCALE]. If the localized value doesn't exist, returns non lokalized value. If non localized value doesn't exist, returns defaultValue. LOCALE must be of the form lang_COUNTRY.ENCODING@MODIFIER, where _COUNTRY, .ENCODING, and @MODIFIER may be omitted. If no default value is specified, a default QVariant is returned. */ QVariant localizedValue(const QString& key, const QVariant& defaultValue = QVariant()) const; //! Sets the value of setting key to value. If the key already exists, the previous value is overwritten. void setValue(const QString &key, const QVariant &value); /*! Sets the value of setting key to value. If a localized version of the key already exists, the previous value is overwritten. Otherwise, it overwrites the the un-localized version. */ void setLocalizedValue(const QString &key, const QVariant &value); //! Removes the entry with the specified key, if it exists. void removeEntry(const QString& key); //! Returns the entry Categories. It supports X-Categories extensions. QStringList categories() const; //! Returns list of values in entry Actions. QStringList actions() const; //! Returns true if there exists a setting called key; returns false otherwise. bool contains(const QString& key) const; //! Returns true if the XdgDesktopFile is valid; otherwise returns false. bool isValid() const; /*! Returns the file name of the desktop file. * Returns QString() if the file wasn't found when load was called. */ QString fileName() const; // //! Returns an icon specified in this file. // QIcon const icon(const QIcon& fallback = QIcon()) const; // //! Returns an icon for application action \param action. // QIcon const actionIcon(const QString & action, const QIcon& fallback = QIcon()) const; // //! Returns an icon name specified in this file. // QString const iconName() const; // //! Returns an icon name for application action \param action. // QString const actionIconName(const QString & action) const; //! Returns an list of mimetypes specified in this file. /*! @return Returns a list of the "MimeType=" entries. * If the file doens't contain the MimeType entry, an empty QStringList is * returned. Empty values are removed from the returned list. */ QStringList mimeTypes() const; //! This function is provided for convenience. It's equivalent to calling localizedValue("Name").toString(). QString name() const { return localizedValue(QLatin1String("Name")).toString(); } //! Returns an (localized) name for application action \param action. QString actionName(const QString & action) const; //! This function is provided for convenience. It's equivalent to calling localizedValue("Comment").toString(). QString comment() const { return localizedValue(QLatin1String("Comment")).toString(); } /*! Returns the desktop file type. @see XdgDesktopFile::Type */ Type type() const; /*! For file with Application type. Starts the program with the optional urls in a new process, and detaches from it. Returns true on success; otherwise returns false. @par urls - A list of files or URLS. Each file is passed as a separate argument to the executable program. For file with Link type. Opens URL in the associated application. Parametr urls is not used. For file with Directory type, do nothing. */ bool startDetached(const QStringList& urls) const; //! This function is provided for convenience. It's equivalent to calling startDetached(QStringList(url)). bool startDetached(const QString& url = QString()) const; /*! For file with Application type. Activates action defined by the \param action. Action is activated * either with the [Desktop Action %s]/Exec or by the D-Bus if the [Desktop Entry]/DBusActivatable is set. * \note Starting is done the same way as \sa startDetached() * * \return true on success; otherwise returns false. * \param urls - A list of files or URLS. Each file is passed as a separate argument to the executable program. * * For file with Link type, do nothing. * * For file with Directory type, do nothing. */ bool actionActivate(const QString & action, const QStringList & urls) const; /*! A Exec value consists of an executable program optionally followed by one or more arguments. This function expands this arguments and returns command line string parts. Note this method make sense only for Application type. @par urls - A list of files or URLS. Each file is passed as a separate argument to the result string program.*/ QStringList expandExecString(const QStringList& urls = QStringList()) const; /*! Returns the URL for the Link desktop file; otherwise an empty string is returned. */ QString url() const; /*! Computes the desktop file ID. It is the identifier of an installed * desktop entry file. * @par fileName - The desktop file complete name. * @par checkFileExists If true and the file doesn't exist the computed ID * will be an empty QString(). Defaults to true. * @return The computed ID. Returns an empty QString() if it's impossible to * compute the ID. Reference: * https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id */ static QString id(const QString &fileName, bool checkFileExists = true); /*! The desktop entry specification defines a number of fields to control the visibility of the application menu. Thisfunction checks whether to display a this application or not. @par environment - User supplied desktop environment name. If not supplied the desktop will be detected reading the XDG_CURRENT_DESKTOP environment variable. If not set, "UNKNOWN" will be used as the desktop name. All operations envolving the desktop environment name are case insensitive. */ bool isShown(const QString &environment = QString()) const; /*! This fuction returns true if the desktop file is applicable to the current environment. @par excludeHidden - if set to true (default), files with "Hidden=true" will be considered "not applicable". Setting this to false is be useful when the user wants to enable/disable items and wants to see those that are Hidden @par environment - User supplied desktop environment name. If not supplied the desktop will be detected reading the XDG_CURRENT_DESKTOP environment variable. If not set, "UNKNOWN" will be used as the desktop name. All operations envolving the desktop environment name are case insensitive. */ bool isSuitable(bool excludeHidden = true, const QString &environment = QString()) const; protected: virtual QString prefix() const { return QLatin1String("Desktop Entry"); } virtual bool check() const { return true; } private: /*! Returns the localized version of the key if the Desktop File already contains a localized version of it. If not, returns the same key back */ QString localizedKey(const QString& key) const; QSharedDataPointer d; }; /// Synonym for QList typedef QList XdgDesktopFileList; class QTXDG_API XdgDesktopFileCache { public: static XdgDesktopFile* getFile(const QString& fileName); static QList getAllFiles(); static QList getApps(const QString & mimeType); static XdgDesktopFile* getDefaultApp(const QString& mimeType); static QSettings::Format desktopFileSettingsFormat(); /*! Return all desktop apps that have category for their Categories key * Note that, according to xdg's spec, for non-standard categories "X-" * is added to the beginning of the category's name. This method takes care * of both cases. * See http://standards.freedesktop.org/menu-spec/menu-spec-latest.html#desktop-entry-extensions */ static QList getAppsOfCategory(const QString &category); private: static XdgDesktopFileCache & instance(); static XdgDesktopFile* load(const QString & fileName); XdgDesktopFileCache(); ~XdgDesktopFileCache(); void initialize(); void initialize(const QString & dirName); bool m_IsInitialized; QHash > m_defaultAppsCache; QHash m_fileCache; }; #endif // QTXDG_XDGDESKTOPFILE_H ukui-session-manager-4.0.0.1/ukui-session/sessionmanagercontext.h0000644000175000017500000000660514553704753023616 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * 2014 Hong Jen Yee (PCMan) . * * This library 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. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef SESSIONINHIBITCONTEXT_H #define SESSIONINHIBITCONTEXT_H #include "../tools/ukuipower.h" #include "../tools/powerprovider.h" #include "modulemanager.h" #include "usminhibit.h" #include "ukuilockinfo.h" #include #include #include #include #include #ifdef signals #undef signals #endif class SessionDBusAdaptor; class SessionManagerDBusContext : public QObject, protected QDBusContext { Q_OBJECT public: SessionManagerDBusContext(ModuleManager *manager,QObject *object = nullptr); ~SessionManagerDBusContext() override; Q_SIGNALS: void moduleStateChanged(QString moduleName, bool state); void inhibitadded(quint32 flags); void inhibitremove(quint32 flags); void StartLogout(); void PrepareForSwitchuser(); void PrepareForPhase2(); public Q_SLOTS: Q_NOREPLY void startupfinished(const QString& appName ,const QString& string); bool canLogout(); bool canSwitch(); bool canLockscreen(); bool canHibernate(); bool canSuspend(); bool canReboot(); bool canPowerOff(); Q_NOREPLY void switchUser(); Q_NOREPLY void hibernate(); Q_NOREPLY void suspend(); Q_NOREPLY void logout(); Q_NOREPLY void reboot(); Q_NOREPLY void powerOff(); Q_NOREPLY void startModule(const QString& name); Q_NOREPLY void stopModule(const QString& name); bool startApp(const QString &name, const QStringList &args); uint Inhibit(QString app_id, quint32 toplevel_xid, QString reason, quint32 flags); Q_NOREPLY void Uninhibit(uint cookie); QStringList GetInhibitors(); QVector ListInhibitor(QString type); bool IsSessionRunning(); QString GetSessionName(); bool IsInhibited(quint32 flags); Q_NOREPLY void setSessionEnv(const QString &key, const QString &value); Q_NOREPLY void emitStartLogout(); Q_NOREPLY void emitPrepareForSwitchuser(); Q_NOREPLY void emitPrepareForPhase2(); Q_NOREPLY void simulateUserActivity(); void on_serviceUnregistered(const QString &serviceName); private: void logoutComplete(); void rebootComplete(); void shutdownComplete(); QStringList listFileList(); void execPro(QStringList proList); private: ModuleManager *mManager; usminhibit *minhibit; PowerProvider *m_systemdProvider; PowerProvider *m_ukuiProvider; QHash> m_hashInhibitionServices; QDBusServiceWatcher *m_serviceWatcher = nullptr; }; #endif // SESSIONINHIBITCONTEXT_H ukui-session-manager-4.0.0.1/ukui-session/ukuimodule.h0000644000175000017500000000272714553704753021357 0ustar fengfeng/* BEGIN_COMMON_COPYRIGHT_HEADER * * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program or library 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. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA * * END_COMMON_COPYRIGHT_HEADER */ #ifndef UKUIMODULE_H #define UKUIMODULE_H #include #include "xdgautostart.h" #include "xdgdesktopfile.h" #include "xdgdirs.h" class UkuiModule : public QProcess { Q_OBJECT public: UkuiModule(const XdgDesktopFile& file, QObject *parent = nullptr); void startUKUIModule(); void terminate(); bool isTerminating(); int restartNum; const XdgDesktopFile file; const QString fileName; Q_SIGNALS: void moduleStateChanged(QString name, bool state); private slots: void updateState(QProcess::ProcessState newState); private: bool mIsTerminating; }; #endif // UKUIMODULE_H ukui-session-manager-4.0.0.1/ukui-session/modulemanager.cpp0000644000175000017500000006323214553704753022345 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * 2010-2016 LXQt team. * * 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, 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 "modulemanager.h" #include "ukuimodule.h" #include "idlewatcher.h" #include "ukuisessiondebug.h" #include "xdgautostart.h" #include "xdgdesktopfile.h" #include "xdgdirs.h" #include #include #include #include #include #include #include // #include #include #include #include #include #include /* qt会将glib里的signals成员识别为宏,所以取消该宏 * 后面如果用到signals时,使用Q_SIGNALS代替即可 **/ #ifdef signals #undef signals #endif #define SESSION_REQUIRED_COMPONENTS "org.ukui.session.required-components" #define SESSION_REQUIRED_COMPONENTS_PATH "/org/ukui/desktop/session/required-components/" #define SSWND_DBUS_SERVICE "org.ukui.ScreenSaverWnd" #define SSWND_DBUS_PATH "/" #define SSWND_DBUS_INTERFACE "org.ukui.ScreenSaverWnd" #define FD_DBUS_SERVICE "org.freedesktop.DBus" #define FD_DBUS_PATH "/org/freedesktop/DBus" #define FD_DBUS_INTERFACE "org.freedesktop.DBus" std::map ModuleManager::m_startupMap = {}; void ModuleManager::playBootMusic(bool arg) { if( arg ) return; //set default value of whether boot-music is opened bool play_music = true; if (QGSettings::isSchemaInstalled("org.ukui.session")) { QGSettings *gset = new QGSettings("org.ukui.session", "/org/ukui/desktop/session/", this); if (gset == NULL) { qDebug() << "QGSettings init error"; delete gset; return; } QString xdg_session_type = qgetenv("XDG_SESSION_TYPE"); if (arg) { play_music = gset->get("startup-music").toBool(); if (play_music) { if (xdg_session_type == "wayland") { //非华为机器没有声音放大器,默认不设置音量大小 QProcess::startDetached("paplay /usr/share/ukui/ukui-session-manager/startup.wav"); } else { QProcess::startDetached("aplay /usr/share/ukui/ukui-session-manager/startup.wav"); } } } else { play_music = gset->get("weakup-music").toBool(); if (play_music) { if (xdg_session_type == "wayland") { QProcess::startDetached("paplay /usr/share/ukui/ukui-session-manager/weakup.wav"); } else { QProcess::startDetached("aplay /usr/share/ukui/ukui-session-manager/weakup.wav"); } } } } } //睡眠转休眠判断 bool isSuspendToHibernate() { QString path = "/sys/power/wakeup_reason"; if (!QFile::exists(path)) { return false; } QFile wakeup_reason_file(path); wakeup_reason_file.open(QIODevice::ReadOnly | QIODevice::Text); QString wakeupReason = wakeup_reason_file.readAll().simplified(); wakeup_reason_file.close(); if (QString::compare(wakeupReason,"0xaa") == 0) { qDebug()<< "Suspend to Hibernate, wakeup_reason is "<get("windowmanager").toString() + ".desktop"; panel = gs->get("panel").toString() + ".desktop"; file_manager = gs->get("filemanager").toString() + ".desktop"; wm_notfound = gs->get("windowmanager").toString(); } else { //gsetting安装失败,或无法获取,设置默认值 qDebug() << "从gsettings 中或取值失败,设置默认值"; window_manager = "ukwm.desktop"; panel = "ukui-panel.desktop"; file_manager = "peony-qt-desktop.desktop"; } QString xdg_session_type = qgetenv("XDG_SESSION_TYPE"); if (xdg_session_type == "wayland") { isWayland = true; } QStringList desktop_paths; desktop_paths << "/usr/share/applications"; desktop_paths << "/etc/xdg/autostart"; bool panel_found = false; bool fm_found = false; bool wm_found = false; //const auto files = XdgAutoStart::desktopFileList(desktop_paths, false); for (const QString &dirName : const_cast(desktop_paths)) { QDir dir(dirName); if (!dir.exists()) continue; const QFileInfoList files = dir.entryInfoList(QStringList(QLatin1String("*.desktop")), QDir::Files | QDir::Readable); for (const QFileInfo &fi : files) { if (fi.fileName() == panel) { mPanel.load(fi.absoluteFilePath()); panel_found = true; qDebug() << "panel has been found"; } if (fi.fileName() == file_manager) { mFileManager.load(fi.absoluteFilePath()); fm_found = true; qDebug() << "filemanager has been found"; } if (fi.fileName() == window_manager) { mWindowManager.load(fi.absoluteFilePath()); wm_found = true; qDebug() << "windowmanager has been found"; } if (fm_found && panel_found && wm_found) break; } } if (!panel_found || !fm_found || !wm_found) isDirectInstall = true; if (isDirectInstall) { wmFound = wm_found; } if (wm_found == false) { QFileInfo check_ukwm("/usr/share/applications/ukwm.desktop"); QFileInfo check_ukuikwin("/usr/share/applications/ukui-kwin.desktop"); if (check_ukwm.exists()) { window_manager = "ukwm.desktop"; mWindowManager.load("/usr/share/applications/ukwm.desktop"); } else if (check_ukuikwin.exists()) { window_manager = "ukui-kwin.desktop"; mWindowManager.load("/usr/share/applications/ukui-kwin.desktop"); } wm_found = true; } //配置文件所给的窗口管理器找不到.desktop文件时,将所给QString设为可执行命令,创建一个desktop文件赋给mWindowManager // if (wm_found == false) { // mWindowManager = XdgDesktopFile(XdgDesktopFile::ApplicationType,"window-manager", wm_notfound); // qDebug() << "windowmanager has been created"; // } QString desktop_phase = "X-UKUI-Autostart-Phase"; QString desktop_type = "Type"; //设置excludeHidden为true,判断所有desktop文件的Hidden值,若为true,则将其从自启列表中去掉 const XdgDesktopFileList all_file_list = XdgAutoStart::desktopFileList(true); for (XdgDesktopFileList::const_iterator i = all_file_list.constBegin(); i != all_file_list.constEnd(); ++i) { QString filePath = i->fileName(); QString filename = QFileInfo(filePath).fileName(); if (filename == panel || filename == file_manager || filename == window_manager) { continue; } const XdgDesktopFile file = *i; if (i->contains(desktop_phase)) { QStringList s1 = file.value(desktop_phase).toString().split(QLatin1Char(';')); if (s1.contains("Initialization")) { mInitialization << file; } else if (s1.contains("Desktop")) { mDesktop << file; } else if (s1.contains("Application")) { //用户设置的自启应用要单独判断,有可能会存在问题,但暂时想不到更好的判断方法 if (filePath.contains(".config/autostart")) { m_userAutoStartApp << file; } else { mApplication << file; } } } else if (i->contains(desktop_type)) { QStringList s2 = file.value(desktop_type).toString().split(QLatin1Char(';')); if (s2.contains("Application")) { if (filePath.contains(".config/autostart")) { m_userAutoStartApp << file; } else { mApplication << file; } } } } QStringList force_app_paths; force_app_paths << "/usr/share/ukui/applications"; const XdgDesktopFileList force_file_list = XdgAutoStart::desktopFileList(force_app_paths, true); for (XdgDesktopFileList::const_iterator i = force_file_list.constBegin(); i != force_file_list.constEnd(); ++i) { qDebug() << (*i).fileName(); mForceApplication << *i; } } /* Startup Phare: * Initialization * WindowManager * Panel * FileManager * Desktop * Application * */ bool ModuleManager::startModuleTimer(QTimer *timer, int i) { timer->setSingleShot(true); connect(timer, SIGNAL(timeout()), this, SLOT(timeup())); timer->start(i * 1000); return true; } void ModuleManager::startupfinished(const QString &appName, const QString &string) { qDebug() << "moudle :" + appName + " startup finished, and it want to say " + string; if (appName == "ukui-settings-daemon") { tusd->stop(); emit usdfinished(); return; } if (appName == "ukui-kwin") { twm->stop(); isWMStarted = true; emit wmfinished(); return; } if (appName == "ukui-panel") { tpanel->stop(); isPanelStarted = true; emit panelfinished(); return; } if (appName == "peony-qt-desktop") { tdesktop->stop(); isDesktopStarted = true; emit desktopfinished(); return; } } void ModuleManager::timeup() { QTimer *time_out = qobject_cast(sender()); if (time_out == tusd) { qDebug() << "usd超时"; emit usdfinished(); return; } if (time_out == twm) { qDebug() <<"wm超时"; isWMStarted = true; emit wmfinished(); return; } if (time_out == tpanel) { qDebug() << "panel超时"; isPanelStarted = true; emit panelfinished(); return; } if (time_out == tdesktop) { qDebug() << "peony-qt-desktop超时"; isDesktopStarted = true; emit desktopfinished(); return; } } void ModuleManager::startCompsite() { if (isWayland) return; qDebug() << "Enter:: startCompsite"; if (!isPanelStarted || !isDesktopStarted || !isWMStarted) return;// || !isWMStarted if (isCompsiteStarted) return; isCompsiteStarted = true; timerUpdate(); /* //按kwin要求,kwin现在启动后自动开混成 // start composite QDBusInterface dbus("org.ukui.KWin", "/Compositor", "org.ukui.kwin.Compositing", QDBusConnection::sessionBus()); if (!dbus.isValid()) { qWarning() << "dbusCall: QDBusInterface is invalid ; kwin do not exit!"; timerUpdate(); } else { qDebug() << "Start composite"; dbus.call("resume"); timerUpdate(); }*/ } void ModuleManager::startup() { //直接安装进入的流程 const QFile file_installer("/etc/xdg/autostart/kylin-os-installer.desktop"); if (file_installer.exists() && isDirectInstall) { if (wmFound) { isPanelStarted = true; isDesktopStarted = true; connect(this, &ModuleManager::wmfinished, [=](){ startCompsite(); }); // startProcess(mWindowManager, true); startModuleTimer(twm, 18); } else { timerUpdate(); } return; } if (isWayland) { startProcess("hwaudioservice", true); } if (!isWayland) { connect(this, &ModuleManager::panelfinished, [=](){ startCompsite(); }); connect(this, &ModuleManager::wmfinished, [=](){ startCompsite(); }); connect(this, &ModuleManager::desktopfinished, [=](){ startCompsite(); }); // dostartwm(); } else { QTimer::singleShot(500, this, SLOT(timerUpdate())); } qDebug() << "Start Initialization app: "; for (XdgDesktopFileList::const_iterator i = mInitialization.constBegin(); i != mInitialization.constEnd(); ++i) { startProcess(*i, true); } startModuleTimer(twm,3); startProcess(mPanel, true); startModuleTimer(tpanel,5); startProcess(mFileManager, true); startModuleTimer(tdesktop,5); if (!isWayland) { qDebug() << "Start desktop: "; for (XdgDesktopFileList::const_iterator i = mDesktop.constBegin(); i != mDesktop.constEnd(); ++i) { startProcess(*i, true); } } } void ModuleManager::dostartwm() { qDebug() << "Start window manager: " << mWindowManager.name(); if (mWindowManager.name() == "UKUI-KWin") { startProcess(mWindowManager, true); startModuleTimer(twm, 18); } else { startProcess(mWindowManager, true); isWMStarted = true; } } void ModuleManager::timerUpdate() { QTimer::singleShot(500, this, [&](){ emit finished(); }); if (isWayland) { qDebug() << "Start desktop: "; for (XdgDesktopFileList::const_iterator i = mDesktop.constBegin(); i != mDesktop.constEnd(); ++i) { startProcess(*i, true); } } qDebug() << "Start application: "; QFile file_nm("/etc/xdg/autostart/kylin-nm.desktop"); QFile file_sogou("/usr/bin/sogouImeService"); for (XdgDesktopFileList::const_iterator i = mApplication.constBegin(); i != mApplication.constEnd(); ++i) { qDebug() << i->fileName(); if (i->fileName() == "/etc/xdg/autostart/nm-applet.desktop" && file_nm.exists()) { qDebug() << "the kylin-nm exist so the nm-applet will not start"; continue; } if (i->fileName() == "/etc/xdg/autostart/fcitx-qimpanel-autostart.desktop" && file_sogou.exists()) { qDebug() << "the sogouImeService exist so the fcitx-ui-qimpanel will not start"; continue; } startProcess(*i, false); } qDebug() << "Start force application: "; for (XdgDesktopFileList::const_iterator i = mForceApplication.constBegin(); i != mForceApplication.constEnd(); ++i) { startProcess(*i, true); } //启动用户自定义的开机自启应用 for (XdgDesktopFileList::const_iterator i = m_userAutoStartApp.constBegin(); i != m_userAutoStartApp.constEnd(); ++i) { qDebug() << "start user's autostart application " << i->fileName(); openAppWithAppManager(*i); } if (QGSettings::isSchemaInstalled("org.ukui.session")) { QGSettings *gset = new QGSettings("org.ukui.session", "/org/ukui/desktop/session/", this); bool restoreSession = gset->get("restore-session").toBool(); if (restoreSession) { //此处设置800ms延迟的原因是,当程序运行到这里时,已经启动了很多应用,根据测试,所有这些应用启动完并且完成与xmspserver的通信一般就需要800ms,如果这里不等待800ms, //然后直接调用xsmpserver的restoreSession接口,则xsmpserver的主循环会被用来启动那些待恢复的应用,从而无暇顾及其他应用的信号请求,造成阻塞,影响整个启动流程 //之前觉得把xsmpserver分离出去可以解决这个问题,现在看来还是不行,因为ukuismserver还是要跟ukui-session交互并且判断是否该应用是否已经已经启动过,真正要解决这个 //问题,还是要把启动阶段划分好,在restoreSession阶段保证就不会有别的应用干扰。 QTimer::singleShot(800, [](){ //调用ukuismserver恢复会话的接口 qDebug(UKUI_SESSION) << "began restore session"; QDBusInterface ukuismserverFace("org.ukui.ukuismserver", "/UKUISMServer", "org.ukui.ukuismserver", QDBusConnection::sessionBus()); if (!ukuismserverFace.isValid()) { qDebug() << "ukuisminterface not valid"; return; } ukuismserverFace.call(QDBus::CallMode::NoBlock, "restoreSession"); }); } } } void ModuleManager::startProcess(const XdgDesktopFile &file, bool required) { QStringList args = file.expandExecString(); if (args.isEmpty()) { qWarning() << "Wrong desktop file: " << file.fileName(); return; } QString name = QFileInfo(file.fileName()).fileName(); if (!mNameMap.contains(name)) { UkuiModule *proc = new UkuiModule(file, this); connect(proc, &UkuiModule::moduleStateChanged, this, &ModuleManager::moduleStateChanged); proc->startUKUIModule(); mNameMap[name] = proc; if (required || autoRestart(file)) { connect(proc, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(restartModules(int, QProcess::ExitStatus))); } } } void ModuleManager::startProcess(const QString& name, bool required) { QString desktop_name = name + ".desktop"; QStringList desktop_paths; desktop_paths << "/usr/share/applications"; const auto files = XdgAutoStart::desktopFileList(desktop_paths, false); for (const XdgDesktopFile& file : files) { if (QFileInfo(file.fileName()).fileName() == desktop_name) { startProcess(file, required); return; } } } void ModuleManager::stopProcess(const QString& name) { if (mNameMap.contains(name)) { mNameMap[name]->terminate(); } } bool ModuleManager::startProcess(const QString &name, const QStringList &args) { if (isProgramStarted(std::move(name))) { return false; } //这个接口供外部应用通过session打开应用使用,需要使用那个进程非正常死亡再拉起机制吗? QProcess *proc = new QProcess(this); proc->start(name, args); proc->closeReadChannel(QProcess::StandardOutput); proc->closeReadChannel(QProcess::StandardError); m_startupMap[std::forward(name)] = 1; return true; } bool ModuleManager::nativeEventFilter(const QByteArray &eventType, void *message, long *result) { if (eventType != "xcb_generic_event_t") // We only want to handle XCB events return false; return false; } void ModuleManager::insertStartupList(QString &&str) { if (!str.isEmpty()) { m_startupMap[std::forward(str)] = 1; } } bool ModuleManager::isProgramStarted(const QString &&str) { //需要对str做处理,取出可执行程序名,str一般是一个完整的二进制路径,所以既需要判断二进制路径也要判断二进制名 QString appName = str.mid(str.lastIndexOf(QDir::separator()) + 1); auto itFullPath = m_startupMap.find(str); auto itName = m_startupMap.find(appName); qDebug() << "judge if start before " << appName << " " << str; if (itFullPath != m_startupMap.end() || itName != m_startupMap.end()) { qDebug() << str << "has start up before"; return true; } return false; } bool ModuleManager::autoRestart(const XdgDesktopFile &file) { QString auto_restart = "X-UKUI-AutoRestart"; return file.value(auto_restart).toBool(); } bool ModuleManager::openAppWithAppManager(const XdgDesktopFile &file) { QDBusInterface face("com.kylin.AppManager", "/com/kylin/AppManager", "com.kylin.AppManager", QDBusConnection::sessionBus()); if (!face.isValid()) { qCDebug(UKUI_SESSION()) << "AppManager interface not valid"; return false; } QString name = file.fileName(); QVariant desktopFilePath(name); QDBusReply reply = face.call("LaunchApp", desktopFilePath); if (reply.isValid()) { return reply.value(); } return true; } void ModuleManager::restartModules(int /*exitCode*/, QProcess::ExitStatus exitStatus) { if (isShutingDown) { qDebug() << "Logout phase, don't Restart"; return; } UkuiModule *proc = qobject_cast(sender()); if (nullptr == proc) { qWarning() << "Got an invalid (null) module to restart, Ignoring it"; return; } //需要做出修改 if (proc->restartNum > 10) { mNameMap.remove(proc->fileName); disconnect(proc, SIGNAL(finished(int, QProcess::ExitStatus)), nullptr, nullptr); proc->deleteLater(); return; } if (!proc->isTerminating()) { //根据退出码来判断程序是否属于异常退出。 QString procName = proc->file.name(); if (proc->exitCode() == 0) { qDebug() << "Process" << procName << "(" << proc << ") exited correctly. " << "With the exitcode = " << proc->exitCode() << ",exitStatus = " << exitStatus; } else { qDebug() << "Process" << procName << "(" << proc << ") has to be restarted. " << "With the exitcode = " << proc->exitCode() << ",exitStatus = " << exitStatus; proc->startUKUIModule(); proc->restartNum++; return; } } mNameMap.remove(proc->fileName); proc->deleteLater(); } bool ModuleManager::getIsShutingDown() const { return isShutingDown; } void ModuleManager::setIsShutingDown(bool value) { isShutingDown = value; } void ModuleManager::logout(bool doExit) { // /org/freedesktop/login1/session/self 和 /org/freedesktop/login1/session/auto //有什么区别 QDBusInterface face("org.freedesktop.login1",\ "/org/freedesktop/login1/session/self",\ "org.freedesktop.login1.Session",\ QDBusConnection::systemBus()); if (doExit) { face.call("Terminate"); exit(0); } } void ModuleManager::ChkScreenLockStartup() { QDBusInterface *iface = new QDBusInterface(SSWND_DBUS_SERVICE, SSWND_DBUS_PATH, SSWND_DBUS_INTERFACE, QDBusConnection::sessionBus(), this); if (iface && iface->isValid()) { QDBusReply reply = iface->call("IsStartupMode"); if (reply.isValid()) { qDebug()<<"ScreenLockStartup:"< * * This program or library 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. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA * * END_COMMON_COPYRIGHT_HEADER */ #include "xdgautostart.h" #include "xdgdirs.h" #include #include #include #include /** * The Autostart Directories are $XDG_CONFIG_DIRS/autostart. If the same filename is * located under multiple Autostart Directories only the file under the most * important directory should be used. * * When multiple .desktop files with the same name exists in multiple directories * then only the Hidden key in the most important .desktop file must be considered: * If it is set to true all .desktop files with the same name in the other * directories MUST be ignored as well. */ XdgDesktopFileList XdgAutoStart::desktopFileList(bool excludeHidden) { QStringList dirs; dirs << XdgDirs::autostartHome(false) << XdgDirs::autostartDirs(); return desktopFileList(dirs, excludeHidden); } XdgDesktopFileList XdgAutoStart::desktopFileList(QStringList dirs, bool excludeHidden) { dirs.removeDuplicates(); QSet processed; XdgDesktopFileList ret; for (const QString &dirName : const_cast(dirs)) { QDir dir(dirName); if (!dir.exists()) continue; const QFileInfoList files = dir.entryInfoList(QStringList(QLatin1String("*.desktop")), QDir::Files | QDir::Readable); for (const QFileInfo &fi : files) { if (processed.contains(fi.fileName())) { continue; } processed << fi.fileName(); XdgDesktopFile desktop; if (!desktop.load(fi.absoluteFilePath())) { continue; } if (!desktop.isSuitable(excludeHidden)) { continue; } ret << desktop; } } return ret; } QString XdgAutoStart::localPath(const XdgDesktopFile& file) { QFileInfo fi(file.fileName()); return QString::fromLatin1("%1/%2").arg(XdgDirs::autostartHome(), fi.fileName()); } ukui-session-manager-4.0.0.1/ukui-session/ukuilockinfo.h0000644000175000017500000000475714553704753021703 0ustar fengfeng/* * Copyright (C) 2023, 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 2, 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 UKUILOCKINFO_H #define UKUILOCKINFO_H #include #include #include namespace InhibitInfo { struct InhibitorInfo { QString name; QString icon; }; QDBusArgument &operator<<(QDBusArgument &argument, const InhibitInfo::InhibitorInfo &mystruct); const QDBusArgument &operator>>(const QDBusArgument &argument, InhibitInfo::InhibitorInfo &mystruct); } Q_DECLARE_METATYPE(InhibitInfo::InhibitorInfo) namespace SystemdInhibitor { struct Inhibitor { QString action; QString name; QString reason; QString mode; int uid; int pid; }; QDBusArgument &operator<<(QDBusArgument &argument, const SystemdInhibitor::Inhibitor &mystruct); const QDBusArgument &operator>>(const QDBusArgument &argument, SystemdInhibitor::Inhibitor &mystruct); } Q_DECLARE_METATYPE(SystemdInhibitor::Inhibitor) class Ukuilockinfo { public: Ukuilockinfo(); ~Ukuilockinfo(); public: enum InhibitorType { logout, suspend, shutdown }; enum buttonType { switchuserBtn, hibernateBtn, suspendBtn, lockscreenBtn, logoutBtn, rebootBtn, shutdownBtn }; static QVector listInhibitorInfo(Ukuilockinfo::InhibitorType type); static bool getCfgValue(Ukuilockinfo::buttonType button); private: static void getLogoutInhibitor(QVector &inhibitorVec); static void getSystemdInhibitor(QString type, QVector &inhibitorVec); static void findNameAndIcon(QString &inhibitorName, InhibitInfo::InhibitorInfo &inhibitor); static QString getAppLocalName(QString desktopfp); }; #endif // UKUILOCKINFO_H ukui-session-manager-4.0.0.1/ukui-session/usminhibit.cpp0000644000175000017500000001360714553704753021701 0ustar fengfeng/***************************************************************** ukuismserver - the UKUI session management server Copyright 2000 Matthias Ettrich Copyright 2023, KylinSoft Co., Ltd. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ******************************************************************/ #include "usminhibit.h" #include "QDBusConnection" #include #include #include inhibit::inhibit(QString app_id, quint32 toplevel_xid, QString reason, quint32 flags ,quint32 cookie ,QString inhibitorName) { this->app_id = app_id; this->toplevel_xid = toplevel_xid; this->reason = reason; this->flags = flags; this->cookie = cookie; this->inhibitorName = inhibitorName; } inhibit::~inhibit() { } uint usminhibit::get_next_inhibitor_serial() { uint serial; serial = inhibitor_serial++; if ((int32_t)inhibitor_serial < 0) { inhibitor_serial = 1; } return serial; } usminhibit::usminhibit() { // dbus = new QDBusInterface("org.gnome.SessionManager", // "/org/gnome/SessionManager", // "org.gnome.SessionManager", // QDBusConnection::sessionBus()); inhibitor_serial = 1;//默认inhibitor序号从1开始 inhibit_logout_num = 0; inhibit_switchuser_num = 0; inhibit_suspend_num = 0; inhibit_idle_num = 0; } usminhibit::~usminhibit() { } bool usminhibit::isInhibited(quint32 flags) { bool isinhib = false; if ((flags & GSM_INHIBITOR_FLAG_LOGOUT) == GSM_INHIBITOR_FLAG_LOGOUT) { if (inhibit_logout_num > 0) isinhib = true; } if ((flags & GSM_INHIBITOR_FLAG_SWITCH_USER) == GSM_INHIBITOR_FLAG_SWITCH_USER) { if (inhibit_switchuser_num > 0) isinhib = true; } if ((flags & GSM_INHIBITOR_FLAG_SUSPEND) == GSM_INHIBITOR_FLAG_SUSPEND) { if (inhibit_suspend_num > 0) isinhib = true; } if ((flags & GSM_INHIBITOR_FLAG_IDLE) == GSM_INHIBITOR_FLAG_IDLE) { if (inhibit_idle_num > 0) isinhib = true; } return isinhib; } quint32 usminhibit::addInhibit(QString app_id, quint32 toplevel_xid, QString reason, quint32 flags) { if (app_id.isEmpty()) { return -1; } if (reason.isEmpty()) { return -1; } if (flags == 0) { return -1; } bool flag = false; if ((flags & GSM_INHIBITOR_FLAG_LOGOUT) == GSM_INHIBITOR_FLAG_LOGOUT) { inhibit_logout_num++; flag = true; } if ((flags & GSM_INHIBITOR_FLAG_SWITCH_USER) == GSM_INHIBITOR_FLAG_SWITCH_USER) { inhibit_switchuser_num++; flag = true; } if ((flags & GSM_INHIBITOR_FLAG_SUSPEND) == GSM_INHIBITOR_FLAG_SUSPEND) { inhibit_suspend_num++; flag = true; } if ((flags & GSM_INHIBITOR_FLAG_IDLE) == GSM_INHIBITOR_FLAG_IDLE) { inhibit_idle_num++; flag = true; } if (flag == false) { return -1; } quint32 cookie = generateCookie(); QString inhibitorName = "/org/gnome/SessionManager/inhibitor" + QString::number(get_next_inhibitor_serial()); inhibit a(app_id, toplevel_xid, reason, flags, cookie, inhibitorName); hash.insert(cookie, a); qDebug() << "app_id=" << app_id << "; toplevel_xid=" << QString::number(toplevel_xid) << "; reason=" << reason << "; flag=" << QString::number(flags); return cookie; } uint usminhibit::generateCookie() { quint32 cookie; do { cookie = QRandomGenerator::global()->bounded(1, 4294967295);//std::numeric_limits::max() } while (hash.contains(cookie)) ; return cookie; } uint usminhibit::unInhibit(quint32 cookie) { uint flags = 0; QHash::iterator i = hash.find(cookie); while (i != hash.end() && i.key() == cookie) { flags = i->flags; i = hash.erase(i); } if (flags == 0) { return flags; } if ((flags & GSM_INHIBITOR_FLAG_LOGOUT) == GSM_INHIBITOR_FLAG_LOGOUT) { inhibit_logout_num--; } if ((flags & GSM_INHIBITOR_FLAG_SWITCH_USER) == GSM_INHIBITOR_FLAG_SWITCH_USER) { inhibit_switchuser_num--; } if ((flags & GSM_INHIBITOR_FLAG_SUSPEND) == GSM_INHIBITOR_FLAG_SUSPEND) { inhibit_suspend_num--; } if ((flags & GSM_INHIBITOR_FLAG_IDLE) == GSM_INHIBITOR_FLAG_IDLE) { inhibit_idle_num--; } emit inhibitRemove(); return flags; } QStringList usminhibit::getInhibitor() { //do not show inhibitorName to user //in case we don't know who is inhibiting QHashIterator i(hash); QStringList inhibitors; while (i.hasNext()) { i.next(); QString messagelist; messagelist = i.value().app_id + "/" + QString::number(i.value().toplevel_xid) + "/" + i.value().reason + "/" + QString::number(i.value().flags) + "/" + QString::number(i.value().cookie); inhibitors << messagelist; } return inhibitors; } #include "usminhibit.moc" ukui-session-manager-4.0.0.1/ukui-session/sessionapplication.cpp0000644000175000017500000001472514553704753023437 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * 2014 Hong Jen Yee (PCMan) * * This library 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. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "sessionapplication.h" #include "modulemanager.h" #include "idleadbusdaptor.h" #include "idlewatcher.h" #include #include #include #include "../tools/ukuipower.h" #include extern "C" { #include #include } #define SESSION_DEFAULT_SETTINGS "org.ukui.session" #define SESSION_DEFAULT_SETTINGS_PATH "/org/ukui/desktop/session/" #define QT5_UKUI_STYLE "org.ukui.style" #define PERIPHERALS_MOUSE "org.ukui.peripherals-mouse" #define PERIPHERALS_MOUSE_PATH "/org/ukui/desktop/peripherals/mouse/" #define MOUSE_KEY "cursor-size" #define MOUSE_THEME "cursor-theme" #define XSETTINGS_PLUGIN_SCHEMA "org.ukui.SettingsDaemon.plugins.xsettings" #define SCALING_FACTOR_KEY "scaling-factor" #define FONT_RENDERING_SCHEMAS "org.ukui.font-rendering" #define FONT_REENDERING_PATH "/org/ukui/desktop/font-rendering/" #define DPI_KEY "dpi" QByteArray typeConver(int i) { QString str = QString::number(i); QByteArray byte; byte.append(str); return byte; } void SessionApplication::initialEnvironment() { SystemdProvider *sysProvider = new SystemdProvider(); if (gsettings_usable) { if (sysProvider->canAction(UkuiPower::PowerHibernate)) { gs->set("canhibernate", true); } else { gs->set("canhibernate", false); } //在打开关机管理界面后通过物理按键的方式关机/重启 //将导致win-key-release键值为true //造成大部分热键和组合键失效 //所以在登录进来时恢复默认值 gs->reset("win-key-release"); } //检查qt主题是否安装 const QByteArray qt_style(QT5_UKUI_STYLE); QByteArray QT_QPA_PLATFORMTHEME; if (QGSettings::isSchemaInstalled(qt_style)) { QT_QPA_PLATFORMTHEME = "ukui"; } else { QT_QPA_PLATFORMTHEME = "gtk2"; } qputenv("XDG_CURRENT_DESKTOP", "UKUI"); qputenv("QT_QPA_PLATFORMTHEME", QT_QPA_PLATFORMTHEME); qputenv("GTK_MODULES","ukuireload-gtk-module"); // qputenv("QT_QPA_PLATFORM", "xcb"); QString xdg_session_type = qgetenv("XDG_SESSION_TYPE"); // if (xdg_session_type == "wayland") { // QProcess::startDetached("dbus-update-activation-environment", QStringList() << "--systemd" << "DISPLAY" << "QT_QPA_PLATFORM"); // } qDebug() << "usd save-param process start"; QString user_name = qgetenv("USER"); QProcess::startDetached("save-param", QStringList() << "-u" << user_name ); //restart user's gvfs-daemon.service //QProcess::startDetached("systemctl", QStringList() << "--user" << "restart" << "gvfs-daemon.service"); } void SessionApplication::updateIdleDelay() { if (gsettings_usable) { const int idle = gs->get("idle-delay").toInt() * 60; if (lastIdleTime == idle ) { return; } mIdleWatcher->reset(idle); } } void SessionApplication::registerDBus() { QDBusConnection dbus = QDBusConnection::sessionBus(); if (!dbus.isConnected()) { qDebug() << "Fatal DBus Error"; QProcess *a = new QProcess(this); a->setProcessChannelMode(QProcess::ForwardedChannels); //这种启动方式是否就是在d-bus服务被杀死的情况下session启动两次的原因 a->start("dbus-launch", QStringList() << "--exit-with-session" << "ukui-session"); a->waitForFinished(-1); if (a->exitCode()) { qWarning() << "exited with code" << a->exitCode(); } } m_sessionManagerContext = new SessionManagerDBusContext(modman); if (!dbus.registerService(QStringLiteral("org.gnome.SessionManager"))) { qCritical() << "Can't register org.gnome.SessionManager, there is already a session manager!"; } if (!dbus.registerObject(("/org/gnome/SessionManager"), m_sessionManagerContext)) { qCritical() << "Can't register object, there is already an object registered at " << "/org/gnome/SessionManager"; } //idle-delay固定设置为1,不再被更改 lastIdleTime = 1 * 60; const QByteArray screensaverID("org.ukui.screensaver"); if (QGSettings::isSchemaInstalled(screensaverID) && gsettings_usable) { int idleNum = gs->get("idle-delay").toInt(); //兼容旧机制,避免升级出现异常 if (idleNum != 1) { QGSettings *screensaverGs = new QGSettings(screensaverID); screensaverGs->set("idle-delay",idleNum); gs->reset("idle-delay"); delete screensaverGs; } } mIdleWatcher = new IdleWatcher(lastIdleTime); new IdleDBusAdaptor(mIdleWatcher); if (!dbus.registerObject("/org/gnome/SessionManager/Presence", mIdleWatcher)) { qCritical() << "Cant' register object, there is already an object registered at " << "org/gnome/SessionManager/Presence"; } } SessionApplication::SessionApplication(int &argc, char* *argv) : QApplication(argc, argv) { const QByteArray id(SESSION_DEFAULT_SETTINGS); if (QGSettings::isSchemaInstalled(id)) { gsettings_usable = true; gs = new QGSettings(SESSION_DEFAULT_SETTINGS, SESSION_DEFAULT_SETTINGS_PATH, this); } else { qWarning() << "Failed to get default value from gsettings, set gsettings_usable to false!"; gsettings_usable = false; } initialEnvironment(); modman = new ModuleManager(); registerDBus(); // Wait until the event loop starts // QTimer::singleShot(0, this, SLOT(startup())); } SessionApplication::~SessionApplication() { delete modman; delete mIdleWatcher; delete gs; } bool SessionApplication::startup() { modman->startup(); return true; } ukui-session-manager-4.0.0.1/ukui-session/xdgdirs.h0000644000175000017500000001437114553704753020636 0ustar fengfeng/* BEGIN_COMMON_COPYRIGHT_HEADER * (c)LGPL2+ * * Copyright: 2010-2011 Razor team * Copyright: 2023, KylinSoft Co., Ltd. * * Authors: * Alexander Sokoloff cn> * * This program or library 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. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA * * END_COMMON_COPYRIGHT_HEADER */ #ifndef QTXDG_XDGDIRS_H #define QTXDG_XDGDIRS_H #include "xdgmacros.h" #include #include /*! @brief The XdgMenu class implements the "XDG Base Directory Specification" from freedesktop.org. * This specification defines where these files should be looked for by defining one or more base * directories relative to which files should be located. * * All postfix parameters should start with an '/' slash. * * @sa http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html */ class QTXDG_API XdgDirs { public: enum UserDirectory { Desktop, Download, Templates, PublicShare, Documents, Music, Pictures, Videos }; /*! @brief Returns the path to the user folder passed as parameter dir defined in * $XDG_CONFIG_HOME/user-dirs.dirs. Returns /tmp if no $HOME defined, $HOME/Desktop if * dir equals XdgDirs::Desktop or $HOME othewise. */ static QString userDir(UserDirectory dir); /*! @brief Returns the default path to the user specified directory. * Returns /tmp if no $HOME defined, $HOME/Desktop if dir equals * XdgDirs::Desktop or $HOME othewise. If dir value is invalid, an empty * QString is returned. */ static QString userDirDefault(UserDirectory dir); /*! @brief Returns true if writting into configuration file $XDG_CONFIG_HOME/user-dirs.dirs * the path in value for the directory in dir is succesfull. Returns false otherwise. If * createDir is true, dir will be created if it doesn't exist. */ static bool setUserDir(UserDirectory dir, const QString &value, bool createDir); /*! @brief Returns the path to the directory that corresponds to the $XDG_DATA_HOME. * If @i createDir is true, the function will create the directory. * * $XDG_DATA_HOME defines the base directory relative to which user specific data files * should be stored. If $XDG_DATA_HOME is either not set or empty, a default equal to * $HOME/.local/share should be used. */ static QString dataHome(bool createDir=true); /*! @brief Returns the path to the directory that corresponds to the $XDG_CONFIG_HOME. * If @i createDir is true, the function will create the directory. * * $XDG_CONFIG_HOME defines the base directory relative to which user specific configuration * files should be stored. If $XDG_CONFIG_HOME is either not set or empty, a default equal * to $HOME/.config should be used. */ static QString configHome(bool createDir=true); /*! @brief Returns a list of all directories that corresponds to the $XDG_DATA_DIRS. * $XDG_DATA_DIRS defines the preference-ordered set of base directories to search for data * files in addition to the $XDG_DATA_HOME base directory. If $XDG_DATA_DIRS is either not set * or empty, a value equal to /usr/local/share:/usr/share is used. * * If the postfix is not empty it will append to end of each returned directory. */ static QStringList dataDirs(const QString &postfix = QString()); /*! @brief Returns a list of all directories that corresponds to the $XDG_CONFIG_DIRS. * $XDG_CONFIG_DIRS defines the preference-ordered set of base directories to search for * configuration files in addition to the $XDG_CONFIG_HOME base directory. If $XDG_CONFIG_DIRS * is either not set or empty, a value equal to /etc/xdg should be used. * * If the postfix is not empty it will append to end of each returned directory. */ static QStringList configDirs(const QString &postfix = QString()); /*! @brief Returns the path to the directory that corresponds to the $XDG_CACHE_HOME. * If @i createDir is true, the function will create the directory. * * $XDG_CACHE_HOME defines the base directory relative to which user specific non-essential * data files should be stored. If $XDG_CACHE_HOME is either not set or empty, * a default equal to $HOME/.cache should be used. */ static QString cacheHome(bool createDir=true); /*! @brief Returns the path to the directory that corresponds to the $XDG_RUNTIME_DIR. * $XDG_RUNTIME_DIR defines the base directory relative to which user-specific non-essential * runtime files and other file objects (such as sockets, named pipes, ...) should be stored. * The directory MUST be owned by the user, and he MUST be the only one having read and write * access to it. Its Unix access mode MUST be 0700. */ static QString runtimeDir(); /*! @brief Returns the path to the directory that corresponds to the $XDG_CONFIG_HOME/autostart * * If $XDG_CONFIG_HOME is not set, the Autostart Directory in the user's home directory is * ~/.config/autostart/ */ static QString autostartHome(bool createDir=true); /*! @brief Returns a list of all directories that correspond to $XDG_CONFIG_DIRS/autostart * If $XDG_CONFIG_DIRS is not set, the system wide Autostart Directory is /etc/xdg/autostart * * If the postfix is not empty it will append to end of each returned directory. * * Note: this does not include the user's autostart directory * @sa autostartHome() */ static QStringList autostartDirs(const QString &postfix = QString()); }; #endif // QTXDG_XDGDIRS_H ukui-session-manager-4.0.0.1/ukui-session/idlewatcher.h0000644000175000017500000000315114553704753021457 0ustar fengfeng/* BEGIN_COMMON_COPYRIGHT_HEADER * * Copyright: 2023, KylinSoft Co., Ltd. * Copyright: 2012 Razor team * * Authors: * Christian Surlykke * * This program or library 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. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA * * END_COMMON_COPYRIGHT_HEADER */ #ifndef IDLEWATCHER_H #define IDLEWATCHER_H #include #include #include //class ScreenSaver; class IdleWatcher : public QObject { Q_OBJECT public: explicit IdleWatcher(int idle, QObject *parent = nullptr); virtual ~IdleWatcher(); void reset(int idle); private: bool isWemeetappRunning(); private slots: void resumingFromIdle(); void timeoutReached(int identifier, int timeout); void setup(); void weakupFromSleep(bool a); Q_SIGNALS: void StatusChanged(uint status); public: QDBusInterface *interface; private: int mSecsidle = 1 * 60; QList args; }; #endif // IDLEWATCHER_H ukui-session-manager-4.0.0.1/ukui-session/sessionmanagercontext.cpp0000644000175000017500000004375314553704753024156 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * 2014 Hong Jen Yee (PCMan) . * * This library 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. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "sessionmanagercontext.h" #include "sessiondbusadaptor.h" #include #include #include SessionManagerDBusContext::SessionManagerDBusContext(ModuleManager *manager, QObject *parent) : QObject(parent) , mManager(manager) , minhibit(new usminhibit()) , m_systemdProvider(new SystemdProvider()) , m_ukuiProvider(new UKUIProvider()) , m_serviceWatcher(new QDBusServiceWatcher(this)) { new SessionDBusAdaptor(this); connect(mManager, &ModuleManager::moduleStateChanged, this , &SessionManagerDBusContext::moduleStateChanged); connect(mManager, &ModuleManager::finished, this, &SessionManagerDBusContext::emitPrepareForPhase2); connect(minhibit, &usminhibit::inhibitRemove, this, &SessionManagerDBusContext::simulateUserActivity); connect(minhibit, &usminhibit::inhibitAdd, this, &SessionManagerDBusContext::simulateUserActivity); m_serviceWatcher->setConnection(QDBusConnection::sessionBus()); m_serviceWatcher->setWatchMode(QDBusServiceWatcher::WatchForUnregistration); connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &SessionManagerDBusContext::on_serviceUnregistered); //注册类型 qDBusRegisterMetaType(); qDBusRegisterMetaType>(); qDBusRegisterMetaType(); } SessionManagerDBusContext::~SessionManagerDBusContext() { } Q_NOREPLY void SessionManagerDBusContext::startupfinished(const QString &appName ,const QString &string) { return mManager->startupfinished(appName, string); } bool SessionManagerDBusContext::canSwitch() { if (Ukuilockinfo::getCfgValue(Ukuilockinfo::buttonType::switchuserBtn) && m_systemdProvider->canAction(UkuiPower::PowerSwitchUser)) { return true; } return false; } bool SessionManagerDBusContext::canLockscreen() { if (Ukuilockinfo::getCfgValue(Ukuilockinfo::buttonType::lockscreenBtn)) { return true; } return false; } bool SessionManagerDBusContext::canHibernate() { if (Ukuilockinfo::getCfgValue(Ukuilockinfo::buttonType::hibernateBtn) && m_systemdProvider->canAction(UkuiPower::PowerHibernate)) { return true; } return false; } bool SessionManagerDBusContext::canSuspend() { if (Ukuilockinfo::getCfgValue(Ukuilockinfo::buttonType::suspendBtn) && m_systemdProvider->canAction(UkuiPower::PowerSuspend)) { return true; } return false; } bool SessionManagerDBusContext::canLogout() { //暂时都返回true if (Ukuilockinfo::getCfgValue(Ukuilockinfo::buttonType::logoutBtn)) { return true; } return false; } bool SessionManagerDBusContext::canReboot() { if (Ukuilockinfo::getCfgValue(Ukuilockinfo::buttonType::rebootBtn) && m_systemdProvider->canAction(UkuiPower::PowerReboot)) { return true; } return false; } bool SessionManagerDBusContext::canPowerOff() { if (Ukuilockinfo::getCfgValue(Ukuilockinfo::buttonType::shutdownBtn) && m_systemdProvider->canAction(UkuiPower::PowerShutdown)) { return true; } return false; } Q_NOREPLY void SessionManagerDBusContext::switchUser() { m_systemdProvider->doAction(UkuiPower::PowerSwitchUser); } Q_NOREPLY void SessionManagerDBusContext::hibernate() { m_systemdProvider->doAction(UkuiPower::PowerHibernate); } Q_NOREPLY void SessionManagerDBusContext::suspend() { m_systemdProvider->doAction(UkuiPower::PowerSuspend); } Q_NOREPLY void SessionManagerDBusContext::logout() { qDebug() << "ukui-session logout inteface called"; if (mManager->getIsShutingDown()) { qDebug() << "already performing logout"; return; } mManager->setIsShutingDown(true); QDBusInterface ukuismserverFace("org.ukui.ukuismserver", "/UKUISMServer", "org.ukui.ukuismserver", QDBusConnection::sessionBus()); if (!ukuismserverFace.isValid()) { qDebug() << "ukuisminterface not valid"; return; } // QDBusReply isCloseSession = ukuismserverFace.call("isCloseSession"); // if (isCloseSession.value()) { // qDebug() << "already performing close session"; // return; // } // OrgKdeKSMServerInterfaceInterface ksmserverIface(QStringLiteral("org.kde.ksmserver"), QStringLiteral("/KSMServer"), QDBusConnection::sessionBus()); //QDBusAbstractInterface有超时机制,默认是25秒,25秒后没有返回值,会给出一个带有错误信息的返回值,供调用方判断做出下一步动作,也正是因此KDE桌面不会在某个应用的提醒保存页面一直 //停留,而是会继续往下执行注销操作,可以通过setTimerout设置超时时间,实验证明确实如此 ukuismserverFace.setTimeout(15000); QList argumentList; QDBusPendingReply closeSessionReply = ukuismserverFace.asyncCallWithArgumentList(QStringLiteral("closeSession"), argumentList); auto watcher = new QDBusPendingCallWatcher(closeSessionReply, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, [closeSessionReply, watcher, this]() { watcher->deleteLater(); if (closeSessionReply.isError()) { qDebug() << "ksmserver failed to complete close session"; } if (closeSessionReply.value()) { qDebug() << "ukuismserver close session completed"; logoutComplete(); } else { mManager->setIsShutingDown(false); qDebug() << "logout cancelled"; } }); } Q_NOREPLY void SessionManagerDBusContext::reboot() { qDebug() << "ukui-session reboot inteface called"; if (mManager->getIsShutingDown()) { qDebug() << "already performing reboot"; return; } mManager->setIsShutingDown(true); QDBusInterface ukuismserverFace("org.ukui.ukuismserver", "/UKUISMServer", "org.ukui.ukuismserver", QDBusConnection::sessionBus()); if (!ukuismserverFace.isValid()) { qDebug() << "ukuisminterface not valid"; return; } // OrgKdeKSMServerInterfaceInterface ksmserverIface(QStringLiteral("org.kde.ksmserver"), QStringLiteral("/KSMServer"), QDBusConnection::sessionBus()); //QDBusAbstractInterface有超时机制,默认是25秒,25秒后没有返回值,会给出一个带有错误信息的返回值,供调用方判断做出下一步动作,也正是因此KDE桌面不会在某个应用的提醒保存页面一直 //停留,而是会继续往下执行注销操作,可以通过setTimerout设置超时时间,实验证明确实如此 ukuismserverFace.setTimeout(15000); QList argumentList; QDBusPendingReply closeSessionReply = ukuismserverFace.asyncCallWithArgumentList(QStringLiteral("closeSession"), argumentList); auto watcher = new QDBusPendingCallWatcher(closeSessionReply, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, [closeSessionReply, watcher, this]() { watcher->deleteLater(); if (closeSessionReply.isError()) { qDebug() << "ksmserver failed to complete close session"; } if (closeSessionReply.value()) { qDebug() << "ukuismserver close session completed"; rebootComplete(); } else { mManager->setIsShutingDown(false); qDebug() << "logout cancelled"; } }); } Q_NOREPLY void SessionManagerDBusContext::powerOff() { qDebug() << "ukui-session reboot inteface called"; if (mManager->getIsShutingDown()) { qDebug() << "already performing poweroff"; return; } mManager->setIsShutingDown(true); QDBusInterface ukuismserverFace("org.ukui.ukuismserver", "/UKUISMServer", "org.ukui.ukuismserver", QDBusConnection::sessionBus()); if (!ukuismserverFace.isValid()) { qDebug() << "ukuisminterface not valid"; return; } // OrgKdeKSMServerInterfaceInterface ksmserverIface(QStringLiteral("org.kde.ksmserver"), QStringLiteral("/KSMServer"), QDBusConnection::sessionBus()); //QDBusAbstractInterface有超时机制,默认是25秒,25秒后没有返回值,会给出一个带有错误信息的返回值,供调用方判断做出下一步动作,也正是因此KDE桌面不会在某个应用的提醒保存页面一直 //停留,而是会继续往下执行注销操作,可以通过setTimerout设置超时时间,实验证明确实如此 ukuismserverFace.setTimeout(15000); QList argumentList; QDBusPendingReply closeSessionReply = ukuismserverFace.asyncCallWithArgumentList(QStringLiteral("closeSession"), argumentList); auto watcher = new QDBusPendingCallWatcher(closeSessionReply, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, [closeSessionReply, watcher, this]() { watcher->deleteLater(); if (closeSessionReply.isError()) { qDebug() << "ksmserver failed to complete close session"; } if (closeSessionReply.value()) { qDebug() << "ukuismserver close session completed"; shutdownComplete(); } else { mManager->setIsShutingDown(false); qDebug() << "logout cancelled"; } }); } Q_NOREPLY void SessionManagerDBusContext::startModule(const QString& name) { mManager->startProcess(name, true); } Q_NOREPLY void SessionManagerDBusContext::stopModule(const QString& name) { mManager->stopProcess(name); } bool SessionManagerDBusContext::startApp(const QString &name, const QStringList &args) { return mManager->startProcess(name, args); } uint SessionManagerDBusContext::Inhibit(QString app_id, quint32 toplevel_xid, QString reason, quint32 flags) { uint result = minhibit->addInhibit(app_id, toplevel_xid, reason, flags); if (result < 0) { return 0; } QString dbusService = message().service(); qDebug() << "SessionManagerDBusContext message().service():" << dbusService; QStringList keys = m_hashInhibitionServices.keys(); if (!keys.contains(dbusService)) { QList cookies; cookies.append(result); m_hashInhibitionServices.insert(dbusService, cookies); m_serviceWatcher->addWatchedService(dbusService); qDebug() << "m_serviceWatcher services:..." << m_serviceWatcher->watchedServices(); } else { for (auto iter = m_hashInhibitionServices.begin(); iter != m_hashInhibitionServices.end(); iter++) { qDebug() << "Inhibit iter.key()" << iter.key() << dbusService; if (iter.key() == dbusService) { QList cookies = iter.value(); if (!cookies.contains(result)) { cookies.append(result); m_hashInhibitionServices[dbusService] = cookies; break; } } } } qDebug() << "Inhibit m_hashInhibitionServices..." << m_hashInhibitionServices; emit this->inhibitadded(flags); return result; } Q_NOREPLY void SessionManagerDBusContext::Uninhibit(uint cookie) { uint result = minhibit->unInhibit(cookie); if (result > 0) { emit inhibitremove(result); } for (auto iter = m_hashInhibitionServices.begin(); iter != m_hashInhibitionServices.end(); iter++) { QList cookies = iter.value(); for (int i = 0; i < cookies.length(); i++) { if (cookie == cookies[i]) { qDebug() << "Uninhibit cookies:" << cookies << "cookie:" << cookie; if (cookies.length() > 1) { cookies.removeAt(i); m_hashInhibitionServices[iter.key()] = cookies; } else { m_serviceWatcher->removeWatchedService(iter.key()); m_hashInhibitionServices.remove(iter.key()); qDebug() << "Uninhibit m_hashInhibitionServices...:" << m_hashInhibitionServices; } qDebug() << "Uninhibit m_hashInhibitionServices:" << m_hashInhibitionServices; return; } } } } QStringList SessionManagerDBusContext::GetInhibitors() { return minhibit->getInhibitor(); } QVector SessionManagerDBusContext::ListInhibitor(QString type) { QVector result; if (type == QString("logout")) { result = Ukuilockinfo::listInhibitorInfo(Ukuilockinfo::InhibitorType::logout); } else if (type == QString("sleep")) { result = Ukuilockinfo::listInhibitorInfo(Ukuilockinfo::InhibitorType::suspend); } else if (type == QString("shutdown")) { result = Ukuilockinfo::listInhibitorInfo(Ukuilockinfo::InhibitorType::shutdown); } return result; } bool SessionManagerDBusContext::IsSessionRunning() { QString xdg_session_desktop = qgetenv("XDG_SESSION_DESKTOP").toLower(); if (xdg_session_desktop == "ukui" || xdg_session_desktop == "ukui-wayland") { return true; } return false; } QString SessionManagerDBusContext::GetSessionName() { QString xdg_session_desktop = qgetenv("XDG_SESSION_DESKTOP").toLower(); if (xdg_session_desktop == "ukui" || xdg_session_desktop == "ukui-wayland") { return "ukui-session"; } return "error"; } bool SessionManagerDBusContext::IsInhibited(quint32 flags) { return minhibit->isInhibited(flags); } void SessionManagerDBusContext::setSessionEnv(const QString &key, const QString &value) { qDebug() << "set env " << key << " as " << value; qputenv(key.toLatin1(), value.toLatin1()); } Q_NOREPLY void SessionManagerDBusContext::emitStartLogout() { qDebug() << "emit StartLogout"; emit StartLogout(); } Q_NOREPLY void SessionManagerDBusContext::emitPrepareForSwitchuser() { qDebug() << "emit PrepareForSwitchuser"; emit PrepareForSwitchuser(); } Q_NOREPLY void SessionManagerDBusContext::emitPrepareForPhase2() { qDebug() << "emit PrepareForPhase2"; emit PrepareForPhase2(); } Q_NOREPLY void SessionManagerDBusContext::simulateUserActivity() { qDebug() << "simulate User Activity"; KIdleTime::instance()->simulateUserActivity(); } void SessionManagerDBusContext::on_serviceUnregistered(const QString &serviceName) { qDebug() << "onServiceUnregistered..." << serviceName; for (auto iter = m_hashInhibitionServices.begin(); iter != m_hashInhibitionServices.end(); iter++) { if (iter.key() == serviceName) { QList cookies = iter.value(); for (auto i = 0; i < cookies.length(); i++) { quint32 cookie = cookies[i]; qDebug() << "on_serviceUnregistered cookie:" << cookie; Uninhibit(cookie); } return; } } qDebug() << "on_serviceUnregistered unfind serviceName:" << serviceName; } void SessionManagerDBusContext::logoutComplete() { //注销前执行指定目录脚本 QStringList list = listFileList(); if (list.isEmpty()) { qDebug() << "logouting file list is empty or dir is not exits."; } else { execPro(list); } //调用systemd的注销 QDBusInterface face("org.freedesktop.login1", "/org/freedesktop/login1/session/self", "org.freedesktop.login1.Session", QDBusConnection::systemBus()); qDebug() << "call sytemd login1 teminate session"; face.call("Terminate"); } void SessionManagerDBusContext::rebootComplete() { //注销前执行指定目录脚本 QStringList list = listFileList(); if (list.isEmpty()) { qDebug() << "logouting file list is empty or dir is not exits."; } else { execPro(list); } //调用systemd的reboot qDebug() << "complete xsmp logout, call Reboot"; this->m_systemdProvider->doAction(UkuiPower::PowerReboot); } void SessionManagerDBusContext::shutdownComplete() { //注销前执行指定目录脚本 QStringList list = listFileList(); if (list.isEmpty()) { qDebug() << "logouting file list is empty or dir is not exits."; } else { execPro(list); } //调用systemd的shutdown qDebug() << "complete xsmp logout, call powerOff"; this->m_systemdProvider->doAction(UkuiPower::PowerShutdown); } QStringList SessionManagerDBusContext::listFileList() { QDir dir("/etc/ukui/ukui-session/logout/"); if (!dir.exists()) { qWarning("Cannot find the example directory"); return QStringList(); } dir.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks); dir.setSorting(QDir::Size | QDir::Reversed); QFileInfoList list = dir.entryInfoList(); QStringList filelist; for (int i = list.size() - 1 ; i >= 0 ; --i) { QFileInfo fileInfo = list.at(i); filelist< #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef KDKINFO_FOUND #include #endif extern "C" { #include #include } #define XSETTINGS_SCHEMA "org.ukui.SettingsDaemon.plugins.xsettings" #define MOUSE_SCHEMA "org.ukui.peripherals-mouse" #define SCALING_KEY "scaling-factor" #define CURSOR_SIZE "cursor-size" #define CURSOR_THEME "cursor-theme" void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) { QString logPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/ukui-session/ukui-session-xsmp.log"; //判断log文件是否存在 if (!QFile::exists(logPath)) { QString logDir = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/ukui-session"; //不存在时,创建ukui-session文件夹 QDir dir(logDir); if (!dir.exists(logDir)) { if (!dir.mkdir(logDir)) { return; } } //创建log文件 QFile file(logPath); if (!file.open(QIODevice::WriteOnly)) { return; } file.close(); } if (!QFile::exists(logPath)) { return; } QByteArray localMsg = msg.toLocal8Bit(); QDateTime dateTime = QDateTime::currentDateTime(); QByteArray time = QString("[%1] ").arg(dateTime.toString("MM-dd hh:mm:ss.zzz")).toLocal8Bit(); QString logMsg; switch (type) { case QtDebugMsg: logMsg = QString("%1 Debug: %2 (%3:%4, %5)\n") .arg(time.constData()) .arg(localMsg.constData()) .arg(context.file) .arg(context.line) .arg(context.function); break; case QtInfoMsg: logMsg = QString("%1 Info: %2 (%3:%4, %5)\n") .arg(time.constData()) .arg(localMsg.constData()) .arg(context.file) .arg(context.line) .arg(context.function); break; case QtWarningMsg: logMsg = QString("%1 Warning: %2 (%3:%4, %5)\n") .arg(time.constData()) .arg(localMsg.constData()) .arg(context.file) .arg(context.line) .arg(context.function); break; case QtCriticalMsg: logMsg = QString("%1 Critical: %2 (%3:%4, %5)\n") .arg(time.constData()) .arg(localMsg.constData()) .arg(context.file) .arg(context.line) .arg(context.function); break; case QtFatalMsg: logMsg = QString("%1 Fatal: %2 (%3:%4, %5)\n") .arg(time.constData()) .arg(localMsg.constData()) .arg(context.file) .arg(context.line) .arg(context.function); break; } //clear file content when it is too large QFile file(logPath); qint64 fileSize = file.size(); if (fileSize >= 1024 * 1024 * 10) { file.open(QFile::WriteOnly | QFile::Truncate); file.flush(); file.close(); } QFile logFile(logPath); logFile.open(QIODevice::WriteOnly | QIODevice::Append); QTextStream ts(&logFile); ts << logMsg << endl; logFile.flush(); logFile.close(); } /* 设置DPI环境变量 */ void setXresources(double scale) { Display *dpy; QGSettings *mouse_settings = new QGSettings(MOUSE_SCHEMA); QString str = QString("Xft.dpi:\t%1\nXcursor.size:\t%2\nXcursor.theme:\t%3\n") .arg(scale * 96) .arg(mouse_settings->get(CURSOR_SIZE).toInt() * scale) .arg(mouse_settings->get(CURSOR_THEME).toString()); dpy = XOpenDisplay(NULL); XChangeProperty(dpy, RootWindow(dpy, 0), XA_RESOURCE_MANAGER, XA_STRING, 8, PropModeReplace, (unsigned char *) str.toLatin1().data(), str.length()); XCloseDisplay(dpy); qDebug() << "setXresources:" << str; delete mouse_settings; } /* 过滤低分辨率高缩放比情况 */ void screenScaleJudgement(QGSettings *settings) { qreal scaling = qApp->devicePixelRatio(); double scale; scale = settings->get(SCALING_KEY).toDouble(); if (scale > 1.25) { bool state = false; bool mScale = false; for (QScreen *screen : QGuiApplication::screens()) { int width = screen->geometry().width() * scaling; int height = screen->geometry().height() * scaling; if (width < 1920 && height < 1080) { state = true; } else if (width == 1920 && height == 1080 && scale > 1.5) { state = true; } /* else if (width > 2560 && height > 1440) { mScale = true; } */ } // if (state && !mScale) { if (state) { settings->set(SCALING_KEY, 1.0); scale = 1.0; } } setXresources(scale); } /* 判断文件是否存在 */ bool isFileExist(QString XresourcesFile) { QFileInfo fileInfo(XresourcesFile); if (fileInfo.isFile()) { qDebug() << "File exists"; return true; } qDebug() << "File does not exis"; return false; } /* 编写判断标志文件,更改 鼠标/DPI 配置大小*/ void writeXresourcesFile(QString XresourcesFile, QGSettings *settings, double scaling) { QFile file(XresourcesFile); QString content = QString("Xft.dpi:%1\nXcursor.size:%2").arg(96.0 * scaling).arg(24.0 * scaling); QByteArray str = content.toLatin1().data(); file.open(QIODevice::ReadWrite | QIODevice::Text); file.write(str); file.close(); QGSettings *Font = new QGSettings("org.ukui.font-rendering"); Font->set("dpi", 96.0); settings->set(SCALING_KEY, scaling); qDebug() << " writeXresourcesFile: content = " << content << " scalings = " << settings->get(SCALING_KEY).toDouble(); delete Font; } /* 判断是否为首次登陆 */ bool isTheFirstLogin(QGSettings *settings) { QString homePath = getenv("HOME"); QString XresourcesFile = homePath+"/.config/xresources"; QString Xresources = homePath+"/.Xresources"; qreal scaling = qApp->devicePixelRatio(); bool zoom1 = false, zoom2 = false, zoom3 = false; double mScaling; bool xres, Xres; Xres = isFileExist(Xresources); xres = isFileExist(XresourcesFile); //判断标志文件是否存在 if (xres && !Xres) { return false; } else if (xres && Xres) { QFile::remove(Xresources); return false; } else if (Xres && !xres) { QFile::rename(Xresources, XresourcesFile); return false; } for (QScreen *screen : QGuiApplication::screens()) { int width = screen->geometry().width() * scaling; int height = screen->geometry().height() * scaling; if (width <= 1920 && height <= 1080) { zoom1 = true; } else if (width > 1920 && height > 1080 && width <= 2560 && height <=1500) { zoom2 = true; } else if (width > 2560 && height > 1440) { zoom3 = true; } } if (zoom1) { mScaling = 1.0; } else if (!zoom1 && zoom2) { mScaling = 1.5; //考虑新版缩放,设置默认150%暂时停止设置; } else if (!zoom1 && !zoom2 && zoom3) { mScaling = 2.0; } writeXresourcesFile(XresourcesFile, settings, mScaling); setXresources(mScaling); return true; } /* 配置新装系统、新建用户第一次登陆时,4K缩放功能*/ void setHightResolutionScreenZoom(bool platForm) { QGSettings *settings; double scale; int ScreenNum = QApplication::screens().length(); if (!QGSettings::isSchemaInstalled(XSETTINGS_SCHEMA) || !QGSettings::isSchemaInstalled("org.ukui.font-rendering") || !QGSettings::isSchemaInstalled(MOUSE_SCHEMA)) { qDebug() << "Error: ukui-settings-daemon's Schema is not installed, will not setting dpi!"; delete settings; return; } settings = new QGSettings(XSETTINGS_SCHEMA); scale = settings->get(SCALING_KEY).toDouble(); if (platForm) { setXresources(scale); goto end; } if (isTheFirstLogin(settings)) { qDebug() << "Set the default zoom value when logging in for the first time."; goto end; } /* 过滤单双屏下小分辨率大缩放值 */ if (ScreenNum > 1) { setXresources(scale); goto end; } screenScaleJudgement(settings); end: delete settings; } bool requireDbusSession() { QString env_dbus = qgetenv("DBUS_SESSION_BUS_ADDRESS"); if (!env_dbus.isEmpty()) return true; qDebug() << "Fatal DBus Error"; QProcess *a = new QProcess; a->setProcessChannelMode(QProcess::ForwardedChannels); a->start("dbus-launch", QStringList() << "--exit-with-session" << "ukui-session"); a->waitForFinished(-1); if (a->exitCode()) { qWarning() << "exited with code" << a->exitCode(); } delete a; return true; } void signalHandler(int sig) { QDBusInterface face("org.freedesktop.login1", "/org/freedesktop/login1/user/self", "org.freedesktop.login1.User", QDBusConnection::systemBus()); face.call("Kill", 9); } void openDubug() { //#ifdef QT_NO_DEBUG // UKUI_SESSION().setFilterRules(QLatin1Literal("org.ukui.ukuisession=false")); //#else // UKUI_SESSION().setFilterRules(QLatin1Literal("org.ukui.ukuisession=true")); //#endif UKUI_SESSION().setFilterRules(QLatin1Literal("org.ukui.ukuisession=true")); qInstallMessageHandler(myMessageOutput); qCDebug(UKUI_SESSION) << "=================================================== UKUI session manager start. ==================================================="; } void launchUKUISMServer() { QGSettings *sSettings; QString wm = "ukui-kwin"; if (!QGSettings::isSchemaInstalled("org.ukui.session.required-components") ) { qDebug() << "Error: ukui-session-manager's Schema is not installed!"; } else { sSettings = new QGSettings("org.ukui.session.required-components"); wm = sSettings->get("windowmanager").toString(); } delete sSettings; qDebug() << "start ukuismserver with windowmanager=" << wm ; QProcess *p = new QProcess; QString fullPath = "/usr/bin/" + wm; QFile file(fullPath); if(file.exists()) { QStringList arg; arg << "-w"; arg << wm; p->start("ukuismserver",arg); } else { qDebug() << "Can not find file " << fullPath << ", start ukwm instead" ; QStringList arg; arg << "-w"; arg << "ukwm"; p->start("ukuismserver",arg); } p->closeReadChannel(QProcess::StandardOutput); p->closeReadChannel(QProcess::StandardError); } int main(int argc, char **argv) { //initUkuiLog4qt("ukui-session"); //加上这个处理会在终端一直输出信息,原因不明 // signal(SIGTERM, signalHandler); openDubug(); requireDbusSession(); //qputenv("QT_QPA_PLATFORM", "xcb"); SessionApplication app(argc, argv); //后续需要细化区分,部分wayland环境也需要缩放设置 QString xdg_session_type = qgetenv("XDG_SESSION_TYPE"); if (xdg_session_type != "wayland") { #ifdef KDKINFO_FOUND QString platForm = kdk_system_get_hostCloudPlatform(); #else QString platForm = "none"; #endif if (platForm == "none") { qDebug() << "platForm=" << platForm << ", 系统环境为实体机"; setHightResolutionScreenZoom(false); } else { qDebug() << "platForm=" << platForm << ", 系统环境为云环境"; setHightResolutionScreenZoom(true); } } app.setQuitOnLastWindowClosed(false); //启动ukuismserver launchUKUISMServer(); // Load ts files const QString locale = QLocale::system().name(); QTranslator translator; qDebug() << "local: " << locale; qDebug() << "path: " << QStringLiteral(UKUI_TRANSLATIONS_DIR) + QStringLiteral("/ukui-session-manager"); if (translator.load(locale, QStringLiteral(UKUI_TRANSLATIONS_DIR) + QStringLiteral("/ukui-session-manager"))) { app.installTranslator(&translator); } else { qDebug() << "Load translations file failed!"; } QCommandLineParser parser; parser.setApplicationDescription(QApplication::tr("UKUI Session Manager")); const QString VERINFO = QStringLiteral("2.0.11-7"); app.setApplicationVersion(VERINFO); parser.addHelpOption(); parser.addVersionOption(); parser.process(app); QString serviceId = "org.ukui.ukuismserver"; auto watcher = new QDBusServiceWatcher(serviceId, QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForRegistration); QObject::connect(watcher, &QDBusServiceWatcher::serviceRegistered, [&app]() { qDebug() << "ukuismserver service registered, begin startup."; app.startup(); });//服务注册成功会发出信号 return app.exec(); } ukui-session-manager-4.0.0.1/ukui-session/sessionapplication.h0000644000175000017500000000340714553704753023077 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * 2014 Hong Jen Yee (PCMan) . * * This library 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. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef SESSIONAPPLICATION_H #define SESSIONAPPLICATION_H #include #include #include #include #include "sessionmanagercontext.h" /* qt会将glib里的signals成员识别为宏,所以取消该宏 * 后面如果用到signals时,使用Q_SIGNALS代替即可 **/ #ifdef signals #undef signals #endif class ModuleManager; class IdleWatcher; class SessionApplication : public QApplication { Q_OBJECT public: SessionApplication(int& argc, char** argv); ~SessionApplication(); bool startup(); private Q_SLOTS: void registerDBus(); void updateIdleDelay(); private: void initialEnvironment(); private: int lastIdleTime; bool gsettings_usable; QGSettings * gs; ModuleManager* modman; IdleWatcher* mIdleWatcher; SessionManagerDBusContext* m_sessionManagerContext = nullptr; }; #endif // SESSIONAPPLICATION_H ukui-session-manager-4.0.0.1/ukui-session/xdgmacros.h0000644000175000017500000000331414553704753021154 0ustar fengfeng/* * Copyright (C) 2014 Luís Pereira * 2023, KylinSoft Co., Ltd. * * This library 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. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA */ #ifndef QTXDG_MACROS_H #define QTXDG_MACROS_H #ifdef __cplusplus # include # ifndef QTXDG_DEPRECATED # define QTXDG_DEPRECATED Q_DECL_DEPRECATED # endif #endif #ifdef QTXDG_COMPILATION #define QTXDG_API Q_DECL_EXPORT #else #define QTXDG_API Q_DECL_IMPORT #endif #if defined(QTXDG_COMPILATION) && defined(QTXDG_TESTS) # define QTXDG_AUTOTEST Q_DECL_EXPORT /* Build library,tests enabled */ #elif defined(QTXDG_BUILDING_TESTS) /* Build the tests */ # define QTXDG_AUTOTEST Q_DECL_IMPORT #else # define QTXDG_AUTOTEST /* Building library, tests disabled */ #endif #ifndef QL1S #define QL1S(x) QLatin1String(x) #endif #ifndef QL1C #define QL1C(x) QLatin1Char(x) #endif #ifndef QSL #define QSL(x) QStringLiteral(x) #endif #ifndef QBAL #define QBAL(x) QByteArrayLiteral(x) #endif #endif // QTXDG_MACROS_H ukui-session-manager-4.0.0.1/ukui-session/idlewatcher.cpp0000644000175000017500000001370414553704753022017 0ustar fengfeng/* BEGIN_COMMON_COPYRIGHT_HEADER * * Copyright: 2023, KylinSoft Co., Ltd. * Copyright: 2012 Razor team * * Authors: * Christian Surlykke * * This program or library 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. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA * * END_COMMON_COPYRIGHT_HEADER */ #include "idlewatcher.h" #include #include #include #define SYSTEMD_SERVICE "org.freedesktop.login1" #define SYSTEMD_PATH "/org/freedesktop/login1/session/auto" #define SYSTEMD_INTERFACE "org.freedesktop.login1.Session" #define PROPERTY "Active" IdleWatcher::IdleWatcher(int idle, QObject *parent) : QObject(parent) { if (idle > 0) { mSecsidle = idle; } connect(KIdleTime::instance(), &KIdleTime::resumingFromIdle, this, &IdleWatcher::resumingFromIdle); connect(KIdleTime::instance(), SIGNAL(timeoutReached(int, int)), this, SLOT(timeoutReached(int, int))); QDBusConnection::systemBus().connect(QString("org.freedesktop.login1"), QString("/org/freedesktop/login1"), QString("org.freedesktop.login1.Manager"), QString("PrepareForSleep"), this, SLOT(weakupFromSleep(bool))); setup(); interface = new QDBusInterface("org.gnome.SessionManager", "/org/gnome/SessionManager", "org.gnome.SessionManager", QDBusConnection::sessionBus()); args.append(QVariant(SYSTEMD_INTERFACE)); args.append(QVariant(PROPERTY)); } IdleWatcher::~IdleWatcher() { KIdleTime::instance()->removeAllIdleTimeouts(); } void IdleWatcher::setup() { if (mSecsidle > 0) { KIdleTime::instance()->addIdleTimeout(1000 * mSecsidle); } } void IdleWatcher::weakupFromSleep(bool a) { if (!a) { qDebug() << "模拟用户操作"; KIdleTime::instance()->simulateUserActivity(); } } void IdleWatcher::timeoutReached(int identifier, int timeout) { quint32 inhibit_idle = 8; bool isinhibited = false; QDBusReply reply = interface->call("IsInhibited", inhibit_idle); if (reply.isValid()) { // use the returned value qDebug() << "Is inhibit by someone: " << reply.value(); isinhibited = reply.value(); } else { qDebug() << reply.value(); } if (isinhibited == true) { qDebug() << "some applications inhibit idle."; return; } if (isinhibited == false) { //判断腾讯会议是否在进行 if (isWemeetappRunning()) { return; } //Inactive user do not send StatusChanged signal. QDBusMessage mesg = QDBusMessage::createMethodCall(SYSTEMD_SERVICE,SYSTEMD_PATH, "org.freedesktop.DBus.Properties", "Get"); mesg.setArguments(args); QDBusMessage ret = QDBusConnection::systemBus().call(mesg); //if system do not support swithchuser, SYSTEMD_PATH will not exist. //Then make the only user be active all the time. bool value = true; if (ret.type() == QDBusMessage::ErrorMessage) { qWarning() << "Error getting active value , do not check whether session is active."; } else { value = ret.arguments()[0].value().variant().value(); } if (!value) return; KIdleTime::instance()->catchNextResumeEvent(); if (timeout == 1000 * mSecsidle) { qDebug() << "idle Timeout Reached, emit StatusChanged 3 signal!"; emit StatusChanged(3); } } } void IdleWatcher::resumingFromIdle() { qDebug() << "Somethings happened, emit StatusChanged 0 signal!"; emit StatusChanged(0); } void IdleWatcher::reset(int idle) { KIdleTime::instance()->removeAllIdleTimeouts(); if (idle > 0) { qDebug() << "Idle timeout reset to " << idle; mSecsidle = idle; setup(); } } bool IdleWatcher::isWemeetappRunning() { bool result = false; FILE *fp; const char *command; //这条命令的含义是查询腾讯会议打开的端口,经过实验发现,腾讯会议在进行会议的时候,会存在一个特殊的UDP连接, //lsof命令会打印出该连接的详细信息,输出信息中用来标识的字符是"UDP *:",通过这个特殊标识符来过滤我们需要的信息 //进而用来判断腾讯会议是否在会议中 //反斜杠注意前面还要加一个反斜杠转义,grep用\s\来表示空格,而\在字符串中是转义符号,所以为了能够在字符串中正确使用\,需要对\使用转义符\。 command = "lsof -i -P -n | grep wemeetapp|grep 'UDP\\s\\*:'"; //执行command并通过fp获取输出 fp = popen(command, "r"); if (fp == nullptr) { return false; } //将command的输出存入outpur数组 QString str; char c; while (!feof(fp)) { c = fgetc(fp); try { str += c; } catch (std::bad_alloc) { str.clear(); qDebug() << "loop error"; break; }; } qDebug() << str; if (str.contains("wemeetapp")) { qDebug() << "wemeetapp is running"; result = true; } else { result = false; } fclose(fp); return result; } ukui-session-manager-4.0.0.1/ukui-session/xdgdirs.cpp0000644000175000017500000002175414553704753021174 0ustar fengfeng/* BEGIN_COMMON_COPYRIGHT_HEADER * (c)LGPL2+ * * Copyright: 2010-2011 Razor team * Copyright: 2023, KylinSoft Co., Ltd. * * Authors: * Alexander Sokoloff * * This program or library 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. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA * * END_COMMON_COPYRIGHT_HEADER */ #include "xdgdirs.h" #include #include #include #include static const QString userDirectoryString[8] = { QLatin1String("Desktop"), QLatin1String("Download"), QLatin1String("Templates"), QLatin1String("Publicshare"), QLatin1String("Documents"), QLatin1String("Music"), QLatin1String("Pictures"), QLatin1String("Videos") }; // Helper functions prototypes void fixBashShortcuts(QString &s); void removeEndingSlash(QString &s); QString createDirectory(const QString &dir); void cleanAndAddPostfix(QStringList &dirs, const QString& postfix); QString userDirFallback(XdgDirs::UserDirectory dir); /************************************************ Helper func. ************************************************/ void fixBashShortcuts(QString &s) { if (s.startsWith(QLatin1Char('~'))) s = QFile::decodeName(qgetenv("HOME")) + (s).mid(1); } void removeEndingSlash(QString &s) { // We don't check for empty strings. Caller must check it. // Remove the ending slash, except for root dirs. if (s.length() > 1 && s.endsWith(QLatin1Char('/'))) s.chop(1); } QString createDirectory(const QString &dir) { QDir d(dir); if (!d.exists()) { if (!d.mkpath(QLatin1String("."))) { qWarning() << QString::fromLatin1("Can't create %1 directory.").arg(d.absolutePath()); } } QString r = d.absolutePath(); removeEndingSlash(r); return r; } void cleanAndAddPostfix(QStringList &dirs, const QString& postfix) { const int N = dirs.count(); for (int i = 0; i < N; ++i) { fixBashShortcuts(dirs[i]); removeEndingSlash(dirs[i]); dirs[i].append(postfix); } } QString userDirFallback(XdgDirs::UserDirectory dir) { QString fallback; const QString home = QFile::decodeName(qgetenv("HOME")); if (home.isEmpty()) { return QString::fromLatin1("/tmp"); } else if (dir == XdgDirs::Desktop) { fallback = QString::fromLatin1("%1/%2").arg(home, QLatin1String("Desktop")); } else { fallback = home; } return fallback; } QString XdgDirs::userDirDefault(XdgDirs::UserDirectory dir) { // possible values for UserDirectory Q_ASSERT(!(dir < XdgDirs::Desktop || dir > XdgDirs::Videos)); if (dir < XdgDirs::Desktop || dir > XdgDirs::Videos) return QString(); return userDirFallback(dir); } QString XdgDirs::userDir(XdgDirs::UserDirectory dir) { // possible values for UserDirectory Q_ASSERT(!(dir < XdgDirs::Desktop || dir > XdgDirs::Videos)); if (dir < XdgDirs::Desktop || dir > XdgDirs::Videos) return QString(); QString folderName = userDirectoryString[dir]; const QString fallback = userDirFallback(dir); QString configDir(configHome()); QFile configFile(configDir + QLatin1String("/user-dirs.dirs")); if (!configFile.exists()) return fallback; if (!configFile.open(QIODevice::ReadOnly | QIODevice::Text)) return fallback; QString userDirVar(QLatin1String("XDG_") + folderName.toUpper() + QLatin1String("_DIR")); QTextStream in(&configFile); QString line; while (!in.atEnd()) { line = in.readLine(); if (line.contains(userDirVar)) { configFile.close(); // get path between quotes line = line.section(QLatin1Char('"'), 1, 1); if (line.isEmpty()) return fallback; line.replace(QLatin1String("$HOME"), QLatin1String("~")); fixBashShortcuts(line); return line; } } configFile.close(); return fallback; } bool XdgDirs::setUserDir(XdgDirs::UserDirectory dir, const QString& value, bool createDir) { // possible values for UserDirectory Q_ASSERT(!(dir < XdgDirs::Desktop || dir > XdgDirs::Videos)); if (dir < XdgDirs::Desktop || dir > XdgDirs::Videos) return false; const QString home = QFile::decodeName(qgetenv("HOME")); if (!(value.startsWith(QLatin1String("$HOME")) || value.startsWith(QLatin1String("~/")) || value.startsWith(home) || value.startsWith(QDir(home).canonicalPath()))) return false; QString folderName = userDirectoryString[dir]; QString configDir(configHome()); QFile configFile(configDir + QLatin1String("/user-dirs.dirs")); // create the file if doesn't exist and opens it if (!configFile.open(QIODevice::ReadWrite | QIODevice::Text)) return false; QTextStream stream(&configFile); QVector lines; QString line; bool foundVar = false; while (!stream.atEnd()) { line = stream.readLine(); if (line.indexOf(QLatin1String("XDG_") + folderName.toUpper() + QLatin1String("_DIR")) == 0) { foundVar = true; QString path = line.section(QLatin1Char('"'), 1, 1); line.replace(path, value); lines.append(line); } else if (line.indexOf(QLatin1String("XDG_")) == 0) { lines.append(line); } } stream.reset(); configFile.resize(0); if (!foundVar) stream << QString::fromLatin1("XDG_%1_DIR=\"%2\"\n").arg(folderName.toUpper(),(value)); for (QVector::iterator i = lines.begin(); i != lines.end(); ++i) { stream << *i << QLatin1Char('\n'); } configFile.close(); if (createDir) { QString path = QString(value).replace(QLatin1String("$HOME"), QLatin1String("~")); fixBashShortcuts(path); QDir().mkpath(path); } return true; } QString XdgDirs::dataHome(bool createDir) { QString s = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); fixBashShortcuts(s); if (createDir) return createDirectory(s); removeEndingSlash(s); return s; } QString XdgDirs::configHome(bool createDir) { QString s = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); fixBashShortcuts(s); if (createDir) return createDirectory(s); removeEndingSlash(s); return s; } QStringList XdgDirs::dataDirs(const QString &postfix) { QString d = QFile::decodeName(qgetenv("XDG_DATA_DIRS")); QStringList dirs = d.split(QLatin1Char(':'), QString::SkipEmptyParts); if (dirs.isEmpty()) { dirs.append(QString::fromLatin1("/usr/local/share")); dirs.append(QString::fromLatin1("/usr/share")); } else { QMutableListIterator it(dirs); while (it.hasNext()) { const QString dir = it.next(); if (!dir.startsWith(QLatin1Char('/'))) it.remove(); } } dirs.removeDuplicates(); cleanAndAddPostfix(dirs, postfix); return dirs; } QStringList XdgDirs::configDirs(const QString &postfix) { QStringList dirs; const QString env = QFile::decodeName(qgetenv("XDG_CONFIG_DIRS")); if (env.isEmpty()) { dirs.append(QString::fromLatin1("/etc/xdg")); } else { dirs = env.split(QLatin1Char(':'), QString::SkipEmptyParts); } cleanAndAddPostfix(dirs, postfix); return dirs; } QString XdgDirs::cacheHome(bool createDir) { QString s = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation); fixBashShortcuts(s); if (createDir) return createDirectory(s); removeEndingSlash(s); return s; } QString XdgDirs::runtimeDir() { QString result = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); fixBashShortcuts(result); removeEndingSlash(result); return result; } QString XdgDirs::autostartHome(bool createDir) { QString s = QString::fromLatin1("%1/autostart").arg(configHome(createDir)); fixBashShortcuts(s); if (createDir) return createDirectory(s); QDir d(s); QString r = d.absolutePath(); removeEndingSlash(r); return r; } QStringList XdgDirs::autostartDirs(const QString &postfix) { QStringList dirs; const QStringList s = configDirs(); for (const QString &dir : s) { dirs << QString::fromLatin1("%1/autostart").arg(dir) + postfix; } return dirs; } ukui-session-manager-4.0.0.1/ukui-session/xdgdesktopfile.cpp0000644000175000017500000015045614553704753022546 0ustar fengfeng/* BEGIN_COMMON_COPYRIGHT_HEADER * (c)LGPL2+ * * Copyright: 2010-2011 Razor team * Copyright: 2023, KylinSoft Co., Ltd. * * Authors: * Alexander Sokoloff * * This program or library 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. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA * * END_COMMON_COPYRIGHT_HEADER */ #include "xdgdesktopfile.h" #include "xdgdirs.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //add from this function: static inline QByteArray detectDesktopEnvironment() { const QByteArray _desktop = qgetenv("XDG_CURRENT_DESKTOP"); if (!_desktop.isEmpty()) { return _desktop.toUpper(); } return QByteArray("UNKNOWN"); } /** * See: http://standards.freedesktop.org/desktop-entry-spec */ // A list of executables that can't be run with QProcess::startDetached(). They // will be run with QProcess::start() static const QStringList nonDetachExecs = QStringList() << QLatin1String("pkexec"); static const QLatin1String onlyShowInKey("OnlyShowIn"); static const QLatin1String notShowInKey("NotShowIn"); static const QLatin1String categoriesKey("Categories"); static const QLatin1String actionsKey("Actions"); static const QLatin1String extendPrefixKey("X-"); static const QLatin1String mimeTypeKey("MimeType"); static const QLatin1String applicationsStr("applications"); static const QLatin1String nameKey("Name"); static const QLatin1String typeKey("Type"); static const QLatin1String ApplicationStr("Application"); static const QLatin1String LinkStr("Link"); static const QLatin1String DirectoryStr("Directory"); static const QLatin1String execKey("Exec"); static const QLatin1String urlKey("URL"); static const QLatin1String iconKey("Icon"); static const QLatin1String initialPreferenceKey("InitialPreference"); // Helper functions prototypes bool checkTryExec(const QString& progName); QString &doEscape(QString& str, const QHash &repl); QString &doUnEscape(QString& str, const QHash &repl); QString &escape(QString& str); QString &escapeExec(QString& str); QString expandDynamicUrl(QString url); QString expandEnvVariables(const QString str); QStringList expandEnvVariables(const QStringList strs); QString findDesktopFile(const QString& dirName, const QString& desktopName); QString findDesktopFile(const QString& desktopName); static QStringList parseCombinedArgString(const QString &program); bool read(const QString &prefix); void replaceVar(QString &str, const QString &varName, const QString &after); QString &unEscape(QString& str); QString &unEscapeExec(QString& str); void loadMimeCacheDir(const QString& dirName, QHash > & cache); QString &doEscape(QString& str, const QHash &repl) { // First we replace slash. str.replace(QLatin1Char('\\'), QLatin1String("\\\\")); QHashIterator i(repl); while (i.hasNext()) { i.next(); if (i.key() != QLatin1Char('\\')) str.replace(i.key(), QString::fromLatin1("\\\\%1").arg(i.value())); } return str; } /************************************************ The escape sequences \s, \n, \t, \r, and \\ are supported for values of type string and localestring, meaning ASCII space, newline, tab, carriage return, and backslash, respectively. ************************************************/ QString &escape(QString& str) { QHash repl; repl.insert(QLatin1Char('\n'), QLatin1Char('n')); repl.insert(QLatin1Char('\t'), QLatin1Char('t')); repl.insert(QLatin1Char('\r'), QLatin1Char('r')); return doEscape(str, repl); } /************************************************ Quoting must be done by enclosing the argument between double quotes and escaping the double quote character, backtick character ("`"), dollar sign ("$") and backslash character ("\") by preceding it with an additional backslash character. Implementations must undo quoting before expanding field codes and before passing the argument to the executable program. Note that the general escape rule for values of type string states that the backslash character can be escaped as ("\\") as well and that this escape rule is applied before the quoting rule. As such, to unambiguously represent a literal backslash character in a quoted argument in a desktop entry file requires the use of four successive backslash characters ("\\\\"). Likewise, a literal dollar sign in a quoted argument in a desktop entry file is unambiguously represented with ("\\$"). ************************************************/ QString &escapeExec(QString& str) { QHash repl; // The parseCombinedArgString() splits the string by the space symbols, // we temporarily replace them on the special characters. // Replacement will reverse after the splitting. repl.insert(QLatin1Char('"'), QLatin1Char('"')); // double quote, repl.insert(QLatin1Char('\''), QLatin1Char('\'')); // single quote ("'"), repl.insert(QLatin1Char('\\'), QLatin1Char('\\')); // backslash character ("\"), repl.insert(QLatin1Char('$'), QLatin1Char('$')); // dollar sign ("$"), return doEscape(str, repl); } QString &doUnEscape(QString& str, const QHash &repl) { int n = 0; while (1) { n=str.indexOf(QLatin1String("\\"), n); if (n < 0 || n > str.length() - 2) break; if (repl.contains(str.at(n+1))) { str.replace(n, 2, repl.value(str.at(n+1))); } n++; } return str; } /************************************************ The escape sequences \s, \n, \t, \r, and \\ are supported for values of type string and localestring, meaning ASCII space, newline, tab, carriage return, and backslash, respectively. ************************************************/ QString &unEscape(QString& str) { QHash repl; repl.insert(QLatin1Char('\\'), QLatin1Char('\\')); repl.insert(QLatin1Char('s'), QLatin1Char(' ')); repl.insert(QLatin1Char('n'), QLatin1Char('\n')); repl.insert(QLatin1Char('t'), QLatin1Char('\t')); repl.insert(QLatin1Char('r'), QLatin1Char('\r')); return doUnEscape(str, repl); } /************************************************ Quoting must be done by enclosing the argument between double quotes and escaping the double quote character, backtick character ("`"), dollar sign ("$") and backslash character ("\") by preceding it with an additional backslash character. Implementations must undo quoting before expanding field codes and before passing the argument to the executable program. Reserved characters are space (" "), tab, newline, double quote, single quote ("'"), backslash character ("\"), greater-than sign (">"), less-than sign ("<"), tilde ("~"), vertical bar ("|"), ampersand ("&"), semicolon (";"), dollar sign ("$"), asterisk ("*"), question mark ("?"), hash mark ("#"), parenthesis ("(") and (")") backtick character ("`"). Note that the general escape rule for values of type string states that the backslash character can be escaped as ("\\") as well and that this escape rule is applied before the quoting rule. As such, to unambiguously represent a literal backslash character in a quoted argument in a desktop entry file requires the use of four successive backslash characters ("\\\\"). Likewise, a literal dollar sign in a quoted argument in a desktop entry file is unambiguously represented with ("\\$"). ************************************************/ QString &unEscapeExec(QString& str) { unEscape(str); QHash repl; // The parseCombinedArgString() splits the string by the space symbols, // we temporarily replace them on the special characters. // Replacement will reverse after the splitting. repl.insert(QLatin1Char(' '), 01); // space repl.insert(QLatin1Char('\t'), 02); // tab repl.insert(QLatin1Char('\n'), 03); // newline, repl.insert(QLatin1Char('"'), QLatin1Char('"')); // double quote, repl.insert(QLatin1Char('\''), QLatin1Char('\'')); // single quote ("'"), repl.insert(QLatin1Char('\\'), QLatin1Char('\\')); // backslash character ("\"), repl.insert(QLatin1Char('>'), QLatin1Char('>')); // greater-than sign (">"), repl.insert(QLatin1Char('<'), QLatin1Char('<')); // less-than sign ("<"), repl.insert(QLatin1Char('~'), QLatin1Char('~')); // tilde ("~"), repl.insert(QLatin1Char('|'), QLatin1Char('|')); // vertical bar ("|"), repl.insert(QLatin1Char('&'), QLatin1Char('&')); // ampersand ("&"), repl.insert(QLatin1Char(';'), QLatin1Char(';')); // semicolon (";"), repl.insert(QLatin1Char('$'), QLatin1Char('$')); // dollar sign ("$"), repl.insert(QLatin1Char('*'), QLatin1Char('*')); // asterisk ("*"), repl.insert(QLatin1Char('?'), QLatin1Char('?')); // question mark ("?"), repl.insert(QLatin1Char('#'), QLatin1Char('#')); // hash mark ("#"), repl.insert(QLatin1Char('('), QLatin1Char('(')); // parenthesis ("(") repl.insert(QLatin1Char(')'), QLatin1Char(')')); // parenthesis (")") repl.insert(QLatin1Char('`'), QLatin1Char('`')); // backtick character ("`"). return doUnEscape(str, repl); } namespace { /*! * Helper class for getting the keys for "Additional applications actions" * ([Desktop Action %s] sections) */ class XdgDesktopAction : public XdgDesktopFile { public: XdgDesktopAction(const XdgDesktopFile & parent, const QString & action) : XdgDesktopFile(parent) , m_prefix(QString{QLatin1String("Desktop Action %1")}.arg(action)) {} protected: virtual QString prefix() const { return m_prefix; } private: const QString m_prefix; }; } class XdgDesktopFileData: public QSharedData { public: XdgDesktopFileData(); inline void clear() { mFileName.clear(); mIsValid = false; mValidIsChecked = false; mIsShow.clear(); mItems.clear(); mType = XdgDesktopFile::UnknownType; } bool read(const QString &prefix); XdgDesktopFile::Type detectType(XdgDesktopFile *q) const; bool startApplicationDetached(const XdgDesktopFile *q, const QString & action, const QStringList& urls) const; bool startLinkDetached(const XdgDesktopFile *q) const; bool startByDBus(const QString & action, const QStringList& urls) const; QStringList getListValue(const XdgDesktopFile * q, const QString & key, bool tryExtendPrefix) const; QString mFileName; bool mIsValid; mutable bool mValidIsChecked; mutable QHash mIsShow; QMap mItems; XdgDesktopFile::Type mType; }; XdgDesktopFileData::XdgDesktopFileData() : mFileName() , mIsValid(false) , mValidIsChecked(false) , mIsShow() , mItems() , mType(XdgDesktopFile::UnknownType) { } bool XdgDesktopFileData::read(const QString &prefix) { QFile file(mFileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return false; QString section; QTextStream stream(&file); bool prefixExists = false; while (!stream.atEnd()) { QString line = stream.readLine().trimmed(); // Skip comments ...................... if (line.startsWith(QLatin1Char('#'))) continue; // Section .............................. if (line.startsWith(QLatin1Char('[')) && line.endsWith(QLatin1Char(']'))) { section = line.mid(1, line.length()-2); if (section == prefix) prefixExists = true; continue; } QString key = line.section(QLatin1Char('='), 0, 0).trimmed(); QString value = line.section(QLatin1Char('='), 1).trimmed(); if (key.isEmpty()) continue; mItems[section + QLatin1Char('/') + key] = QVariant(value); } // Not check for empty prefix mIsValid = (prefix.isEmpty()) || prefixExists; return mIsValid; } XdgDesktopFile::Type XdgDesktopFileData::detectType(XdgDesktopFile *q) const { QString typeStr = q->value(typeKey).toString(); if (typeStr == ApplicationStr) return XdgDesktopFile::ApplicationType; if (typeStr == LinkStr) return XdgDesktopFile::LinkType; if (typeStr == DirectoryStr) return XdgDesktopFile::DirectoryType; if (!q->value(execKey).toString().isEmpty()) return XdgDesktopFile::ApplicationType; return XdgDesktopFile::UnknownType; } bool XdgDesktopFileData::startApplicationDetached(const XdgDesktopFile *q, const QString & action, const QStringList& urls) const { //DBusActivatable handling if (q->value(QLatin1String("DBusActivatable"), false).toBool()) { /* WARNING: We fallback to use Exec when the DBusActivatable fails. * * This is a violation of the standard and we know it! * * From the Standard: * DBusActivatable A boolean value specifying if D-Bus activation is * supported for this application. If this key is missing, the default * value is false. If the value is true then implementations should * ignore the Exec key and send a D-Bus message to launch the * application. See D-Bus Activation for more information on how this * works. Applications should still include Exec= lines in their desktop * files for compatibility with implementations that do not understand * the DBusActivatable key. * * So, why are we doing it ? In the benefit of user experience. * We first ignore the Exec line and in use the D-Bus to lauch the * application. But if it fails, we try the Exec method. * * We consider that this violation is more acceptable than an failure * in launching an application. */ if (startByDBus(action, urls)) return true; } QStringList args = action.isEmpty() ? q->expandExecString(urls) : XdgDesktopAction{*q, action}.expandExecString(urls); if (args.isEmpty()) return false; if (q->value(QLatin1String("Terminal")).toBool()) { QString term = QString::fromLocal8Bit(qgetenv("TERM")); if (term.isEmpty()) term = QLatin1String("xterm"); args.prepend(QLatin1String("-e")); args.prepend(term); } bool nonDetach = false; for (const QString &s : nonDetachExecs) { for (const QString &a : const_cast(args)) { if (a.contains(s)) { nonDetach = true; } } } QString cmd = args.takeFirst(); QString workingDir = q->value(QLatin1String("Path")).toString(); if (!workingDir.isEmpty() && !QDir(workingDir).exists()) workingDir = QString(); if (nonDetach) { QScopedPointer p(new QProcess); p->setStandardInputFile(QProcess::nullDevice()); p->setProcessChannelMode(QProcess::ForwardedChannels); if (!workingDir.isEmpty()) p->setWorkingDirectory(workingDir); p->start(cmd, args); bool started = p->waitForStarted(); if (started) { QProcess *proc = p.take(); //release the pointer(will be selfdestroyed upon finish) QObject::connect(proc, static_cast(&QProcess::finished), proc, &QProcess::deleteLater); } return started; } else { return QProcess::startDetached(cmd, args, workingDir); } } bool XdgDesktopFileData::startLinkDetached(const XdgDesktopFile *q) const { QString url = q->url(); if (url.isEmpty()) { qWarning() << "XdgDesktopFileData::startLinkDetached: url is empty."; return false; } QString scheme = QUrl(url).scheme(); if (scheme.isEmpty() || scheme == QLatin1String("file")) { // Local file QFileInfo fi(url); QMimeDatabase db; QMimeType mimeInfo = db.mimeTypeForFile(fi); XdgDesktopFile* desktopFile = XdgDesktopFileCache::getDefaultApp(mimeInfo.name()); if (desktopFile) return desktopFile->startDetached(url); } else { // Internet URL return QDesktopServices::openUrl(QUrl::fromEncoded(url.toLocal8Bit())); } return false; } bool XdgDesktopFileData::startByDBus(const QString & action, const QStringList& urls) const { QFileInfo f(mFileName); QString path(f.completeBaseName()); path = path.replace(QLatin1Char('.'), QLatin1Char('/')).prepend(QLatin1Char('/')); QVariantMap platformData; platformData.insert(QLatin1String("desktop-startup-id"), QString::fromLocal8Bit(qgetenv("DESKTOP_STARTUP_ID"))); QDBusObjectPath d_path(path); if (d_path.path().isEmpty()) { qWarning() << "XdgDesktopFileData::startByDBus: invalid name" << f.fileName() << "of DBusActivatable .desktop file" ", assembled DBus object path" << path << "is invalid!"; return false; } QDBusInterface app(f.completeBaseName(), path, QLatin1String("org.freedesktop.Application")); //Note: after the QDBusInterface construction, it can *invalid* (has reasonable lastError()) // but this can be due to some intermediate DBus call(s) which doesn't need to be fatal and // our next call() can succeed // see discussion https://github.com/lxqt/libqtxdg/pull/75 if (app.lastError().isValid()) { qWarning().noquote() << "XdgDesktopFileData::startByDBus: invalid interface:" << app.lastError().message() << ", but trying to continue..."; } QDBusMessage reply; if (!action.isEmpty()) { QList v_urls; for (const auto & url : urls) { v_urls.append(url); } reply = app.call(QLatin1String("ActivateAction"), action, v_urls, platformData); } else if (urls.isEmpty()) { reply = app.call(QLatin1String("Activate"), platformData); } else { reply = app.call(QLatin1String("Open"), urls, platformData); } return QDBusMessage::ErrorMessage != reply.type(); } QStringList XdgDesktopFileData::getListValue(const XdgDesktopFile * q, const QString & key, bool tryExtendPrefix) const { QString used_key = key; if (!q->contains(used_key) && tryExtendPrefix) { used_key = extendPrefixKey + key; if (!q->contains(used_key)) return QStringList(); } return q->value(used_key).toString().split(QLatin1Char(';'), QString::SkipEmptyParts); } XdgDesktopFile::XdgDesktopFile(): d(new XdgDesktopFileData) { } XdgDesktopFile::XdgDesktopFile(const XdgDesktopFile& other) : d(other.d) { } XdgDesktopFile::XdgDesktopFile(Type type, const QString& name, const QString &value) : d(new XdgDesktopFileData) { d->mFileName = name + QLatin1String(".desktop"); d->mType = type; setValue(QLatin1String("Version"), QLatin1String("1.0")); setValue(nameKey, name); if (type == XdgDesktopFile::ApplicationType) { setValue(typeKey, ApplicationStr); setValue(execKey, value); } else if (type == XdgDesktopFile::LinkType) { setValue(typeKey, LinkStr); setValue(urlKey, value); } else if (type == XdgDesktopFile::DirectoryType) { setValue(typeKey, DirectoryStr); } d->mIsValid = check(); } XdgDesktopFile::~XdgDesktopFile() { } XdgDesktopFile& XdgDesktopFile::operator=(const XdgDesktopFile& other) { d = other.d; return *this; } bool XdgDesktopFile::operator==(const XdgDesktopFile &other) const { return d->mItems == other.d->mItems; } bool XdgDesktopFile::load(const QString& fileName) { d->clear(); if (fileName.startsWith(QDir::separator())) { // absolute path QFileInfo f(fileName); if (f.exists()) { d->mFileName = f.canonicalFilePath(); } else { return false; } } else { // relative path const QString r = findDesktopFile(fileName); if (r.isEmpty()) return false; else d->mFileName = r; } d->read(prefix()); d->mIsValid = d->mIsValid && check(); d->mType = d->detectType(this); return isValid(); } bool XdgDesktopFile::save(QIODevice *device) const { QTextStream stream(device); QMap::const_iterator i = d->mItems.constBegin(); QString section; while (i != d->mItems.constEnd()) { QString path = i.key(); QString sect = path.section(QLatin1Char('/'),0,0); if (sect != section) { section = sect; stream << QLatin1Char('[') << section << QLatin1Char(']') << endl; } QString key = path.section(QLatin1Char('/'), 1); stream << key << QLatin1Char('=') << i.value().toString() << endl; ++i; } return true; } bool XdgDesktopFile::save(const QString &fileName) const { QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) return false; return save(&file); } QVariant XdgDesktopFile::value(const QString& key, const QVariant& defaultValue) const { QString path = (!prefix().isEmpty()) ? prefix() + QLatin1Char('/') + key : key; QVariant res = d->mItems.value(path, defaultValue); if (res.type() == QVariant::String) { QString s = res.toString(); return unEscape(s); } return res; } void XdgDesktopFile::setValue(const QString &key, const QVariant &value) { QString path = (!prefix().isEmpty()) ? prefix() + QLatin1Char('/') + key : key; if (value.type() == QVariant::String) { QString s=value.toString(); if (key.toUpper() == QLatin1String("EXEC")) { escapeExec(s); } else { escape(s); } d->mItems[path] = QVariant(s); if (key.toUpper() == QLatin1String("TYPE")) d->mType = d->detectType(this); } else { d->mItems[path] = value; } } void XdgDesktopFile::setLocalizedValue(const QString &key, const QVariant &value) { setValue(localizedKey(key), value); } /************************************************ LC_MESSAGES value Possible keys in order of matching lang_COUNTRY@MODIFIER lang_COUNTRY@MODIFIER, lang_COUNTRY, lang@MODIFIER, lang, default value lang_COUNTRY lang_COUNTRY, lang, default value lang@MODIFIER lang@MODIFIER, lang, default value lang lang, default value ************************************************/ QString XdgDesktopFile::localizedKey(const QString& key) const { QString lang = QString::fromLocal8Bit(qgetenv("LC_MESSAGES")); if (lang.isEmpty()) lang = QString::fromLocal8Bit(qgetenv("LC_ALL")); if (lang.isEmpty()) lang = QString::fromLocal8Bit(qgetenv("LANG")); QString modifier = lang.section(QLatin1Char('@'), 1); if (!modifier.isEmpty()) lang.truncate(lang.length() - modifier.length() - 1); QString encoding = lang.section(QLatin1Char('.'), 1); if (!encoding.isEmpty()) lang.truncate(lang.length() - encoding.length() - 1); QString country = lang.section(QLatin1Char('_'), 1); if (!country.isEmpty()) lang.truncate(lang.length() - country.length() - 1); //qDebug() << "LC_MESSAGES: " << qgetenv("LC_MESSAGES"); //qDebug() << "Lang:" << lang; //qDebug() << "Country:" << country; //qDebug() << "Encoding:" << encoding; //qDebug() << "Modifier:" << modifier; if (!modifier.isEmpty() && !country.isEmpty()) { QString k = QString::fromLatin1("%1[%2_%3@%4]").arg(key, lang, country, modifier); //qDebug() << "\t try " << k << contains(k); if (contains(k)) return k; } if (!country.isEmpty()) { QString k = QString::fromLatin1("%1[%2_%3]").arg(key, lang, country); //qDebug() << "\t try " << k << contains(k); if (contains(k)) { return k; } } if (!modifier.isEmpty()) { QString k = QString::fromLatin1("%1[%2@%3]").arg(key, lang, modifier); //qDebug() << "\t try " << k << contains(k); if (contains(k)) { return k; } } QString k = QString::fromLatin1("%1[%2]").arg(key, lang); //qDebug() << "\t try " << k << contains(k); if (contains(k)) { return k; } //qDebug() << "\t try " << key << contains(key); return key; } QVariant XdgDesktopFile::localizedValue(const QString& key, const QVariant& defaultValue) const { return value(localizedKey(key), defaultValue); } QStringList XdgDesktopFile::categories() const { return d->getListValue(this, categoriesKey, true); } QStringList XdgDesktopFile::actions() const { return d->getListValue(this, actionsKey, false); } void XdgDesktopFile::removeEntry(const QString& key) { QString path = (!prefix().isEmpty()) ? prefix() + QLatin1Char('/') + key : key; d->mItems.remove(path); } bool XdgDesktopFile::contains(const QString& key) const { QString path = (!prefix().isEmpty()) ? prefix() + QLatin1Char('/') + key : key; return d->mItems.contains(path); } bool XdgDesktopFile::isValid() const { return d->mIsValid; } QString XdgDesktopFile::fileName() const { return d->mFileName; } //QIcon const XdgDesktopFile::icon(const QIcon& fallback) const //{ // QIcon result = XdgIcon::fromTheme(value(iconKey).toString(), fallback); // if (result.isNull() && type() == ApplicationType) { // result = XdgIcon::fromTheme(QLatin1String("application-x-executable.png")); // // TODO Maybe defaults for other desktopfile types as well.. // } // return result; //} //QIcon const XdgDesktopFile::actionIcon(const QString & action, const QIcon& fallback) const //{ // return d->mType == ApplicationType // ? XdgDesktopAction{*this, action}.icon(icon(fallback)) // : fallback; //} //QString const XdgDesktopFile::iconName() const //{ // return value(iconKey).toString(); //} //QString const XdgDesktopFile::actionIconName(const QString & action) const //{ // return d->mType == ApplicationType // ? XdgDesktopAction{*this, action}.iconName() // : QString{}; //} QStringList XdgDesktopFile::mimeTypes() const { return value(mimeTypeKey).toString().split(QLatin1Char(';'), QString::SkipEmptyParts); } QString XdgDesktopFile::actionName(const QString & action) const { return d->mType == ApplicationType ? XdgDesktopAction{*this, action}.name() : QString{}; } XdgDesktopFile::Type XdgDesktopFile::type() const { return d->mType; } /************************************************ Starts the program defined in this desktop file in a new process, and detaches from it. Returns true on success; otherwise returns false. If the calling process exits, the detached process will continue to live. Urls - the list of URLs or files to open, can be empty (app launched without argument) If the function is successful then *pid is set to the process identifier of the started process. ************************************************/ bool XdgDesktopFile::startDetached(const QStringList& urls) const { switch (d->mType) { case ApplicationType: return d->startApplicationDetached(this, QString{}, urls); break; case LinkType: return d->startLinkDetached(this); break; default: return false; } } bool XdgDesktopFile::actionActivate(const QString & action, const QStringList& urls) const { return d->mType == ApplicationType ? d->startApplicationDetached(this, action, urls) : false; } /************************************************ This is an overloaded function. ************************************************/ bool XdgDesktopFile::startDetached(const QString& url) const { if (url.isEmpty()) { return startDetached(QStringList()); } else { return startDetached(QStringList(url)); } } static QStringList parseCombinedArgString(const QString &program) { QStringList args; QString tmp; int quoteCount = 0; bool inQuote = false; // handle quoting. tokens can be surrounded by double quotes // "hello world". three consecutive double quotes represent // the quote character itself. for (int i = 0; i < program.size(); ++i) { if (program.at(i) == QLatin1Char('"')) { ++quoteCount; if (quoteCount == 3) { // third consecutive quote quoteCount = 0; tmp += program.at(i); } continue; } if (quoteCount) { if (quoteCount == 1) { inQuote = !inQuote; } quoteCount = 0; } if (!inQuote && program.at(i).isSpace()) { if (!tmp.isEmpty()) { args += tmp; tmp.clear(); } } else { tmp += program.at(i); } } if (!tmp.isEmpty()) { args += tmp; } return args; } void replaceVar(QString &str, const QString &varName, const QString &after) { str.replace(QRegExp(QString::fromLatin1("\\$%1(?!\\w)").arg(varName)), after); str.replace(QRegExp(QString::fromLatin1("\\$\\{%1\\}").arg(varName)), after); } QString expandEnvVariables(const QString str) { QString scheme = QUrl(str).scheme(); if (scheme == QLatin1String("http") || scheme == QLatin1String("https") || scheme == QLatin1String("shttp") || scheme == QLatin1String("ftp") || scheme == QLatin1String("ftps") || scheme == QLatin1String("pop") || scheme == QLatin1String("pops") || scheme == QLatin1String("imap") || scheme == QLatin1String("imaps") || scheme == QLatin1String("mailto") || scheme == QLatin1String("nntp") || scheme == QLatin1String("irc") || scheme == QLatin1String("telnet") || scheme == QLatin1String("xmpp") || scheme == QLatin1String("nfs") ) { return str; } const QString homeDir = QFile::decodeName(qgetenv("HOME")); QString res = str; res.replace(QRegExp(QString::fromLatin1("~(?=$|/)")), homeDir); replaceVar(res, QLatin1String("HOME"), homeDir); replaceVar(res, QLatin1String("USER"), QString::fromLocal8Bit(qgetenv("USER"))); replaceVar(res, QLatin1String("XDG_DESKTOP_DIR"), XdgDirs::userDir(XdgDirs::Desktop)); replaceVar(res, QLatin1String("XDG_TEMPLATES_DIR"), XdgDirs::userDir(XdgDirs::Templates)); replaceVar(res, QLatin1String("XDG_DOCUMENTS_DIR"), XdgDirs::userDir(XdgDirs::Documents)); replaceVar(res, QLatin1String("XDG_MUSIC_DIR"), XdgDirs::userDir(XdgDirs::Music)); replaceVar(res, QLatin1String("XDG_PICTURES_DIR"), XdgDirs::userDir(XdgDirs::Pictures)); replaceVar(res, QLatin1String("XDG_VIDEOS_DIR"), XdgDirs::userDir(XdgDirs::Videos)); replaceVar(res, QLatin1String("XDG_PHOTOS_DIR"), XdgDirs::userDir(XdgDirs::Pictures)); return res; } QStringList expandEnvVariables(const QStringList strs) { QStringList res; for (const QString &s : strs) { res << expandEnvVariables(s); } return res; } QStringList XdgDesktopFile::expandExecString(const QStringList& urls) const { if (d->mType != ApplicationType) { return QStringList(); } QStringList result; QString execStr = value(execKey).toString(); unEscapeExec(execStr); const QStringList tokens = parseCombinedArgString(execStr); for (QString token : tokens) { // The parseCombinedArgString() splits the string by the space symbols, // we temporarily replaced them on the special characters. // Now we reverse it. token.replace(01, QLatin1Char(' ')); token.replace(02, QLatin1Char('\t')); token.replace(03, QLatin1Char('\n')); // ---------------------------------------------------------- // A single file name, even if multiple files are selected. if (token == QLatin1String("%f")) { if (!urls.isEmpty()) { result << expandEnvVariables(urls.at(0)); } continue; } // ---------------------------------------------------------- // A list of files. Use for apps that can open several local files at once. // Each file is passed as a separate argument to the executable program. if (token == QLatin1String("%F")) { result << expandEnvVariables(urls); continue; } // ---------------------------------------------------------- // A single URL. Local files may either be passed as file: URLs or as file path. if (token == QLatin1String("%u")) { if (!urls.isEmpty()) { QUrl url; url.setUrl(expandEnvVariables(urls.at(0))); result << ((!url.toLocalFile().isEmpty()) ? url.toLocalFile() : QString::fromUtf8(url.toEncoded())); } continue; } // ---------------------------------------------------------- // A list of URLs. Each URL is passed as a separate argument to the executable // program. Local files may either be passed as file: URLs or as file path. if (token == QLatin1String("%U")) { for (const QString &s : urls) { QUrl url(expandEnvVariables(s)); result << ((!url.toLocalFile().isEmpty()) ? url.toLocalFile() : QString::fromUtf8(url.toEncoded())); } continue; } // ---------------------------------------------------------- // The Icon key of the desktop entry expanded as two arguments, first --icon // and then the value of the Icon key. Should not expand to any arguments if // the Icon key is empty or missing. if (token == QLatin1String("%i")) { QString icon = value(iconKey).toString(); if (!icon.isEmpty()) { result << QLatin1String("-icon") << icon.replace(QLatin1Char('%'), QLatin1String("%%")); } continue; } // ---------------------------------------------------------- // The translated name of the application as listed in the appropriate Name key // in the desktop entry. if (token == QLatin1String("%c")) { result << localizedValue(nameKey).toString().replace(QLatin1Char('%'), QLatin1String("%%")); continue; } // ---------------------------------------------------------- // The location of the desktop file as either a URI (if for example gotten from // the vfolder system) or a local filename or empty if no location is known. if (token == QLatin1String("%k")) { result << fileName().replace(QLatin1Char('%'), QLatin1String("%%")); break; } // ---------------------------------------------------------- // Deprecated. // Deprecated field codes should be removed from the command line and ignored. if (token == QLatin1String("%d") || token == QLatin1String("%D") || token == QLatin1String("%n") || token == QLatin1String("%N") || token == QLatin1String("%v") || token == QLatin1String("%m")) { continue; } // ---------------------------------------------------------- result << expandEnvVariables(token); } return result; } bool checkTryExec(const QString& progName) { if (progName.startsWith(QDir::separator())) return QFileInfo(progName).isExecutable(); const QStringList dirs = QFile::decodeName(qgetenv("PATH")).split(QLatin1Char(':')); for (const QString &dir : dirs) { if (QFileInfo(QDir(dir), progName).isExecutable()) return true; } return false; } QString XdgDesktopFile::id(const QString &fileName, bool checkFileExists) { const QFileInfo f(fileName); if (checkFileExists) { if (!f.exists()) { return QString(); } } QString id = f.absoluteFilePath(); const QStringList dataDirs = XdgDirs::dataDirs(); for (const QString &d : dataDirs) { if (id.startsWith(d)) { // remove only the first occurence id.replace(id.indexOf(d), d.size(), QString()); } } const QLatin1Char slash('/'); const QString s = slash + applicationsStr + slash; if (!id.startsWith(s)) return QString(); id.replace(id.indexOf(s), s.size(), QString()); id.replace(slash, QLatin1Char('-')); return id; } bool XdgDesktopFile::isShown(const QString &environment) const { const QString env = environment.toUpper(); if (d->mIsShow.contains(env)) return d->mIsShow.value(env); d->mIsShow.insert(env, false); // Means "this application exists, but don't display it in the menus". if (value(QLatin1String("NoDisplay")).toBool()) return false; // The file is not suitable to the current environment if (!isSuitable(true, env)) return false; d->mIsShow.insert(env, true); return true; } bool XdgDesktopFile::isSuitable(bool excludeHidden, const QString &environment) const { // Hidden should have been called Deleted. It means the user deleted // (at his level) something that was present if (excludeHidden && value(QLatin1String("Hidden")).toBool()) return false; // A list of strings identifying the environments that should display/not // display a given desktop entry. // OnlyShowIn ........ QString env; if (environment.isEmpty()) { env = QString::fromLocal8Bit(detectDesktopEnvironment()); } else { env = environment.toUpper(); } QString key; bool keyFound = false; if (contains(onlyShowInKey)) { key = onlyShowInKey; keyFound = true; } else { key = extendPrefixKey + onlyShowInKey; keyFound = contains(key) ? true : false; } if (keyFound) { QStringList s = value(key).toString().toUpper().split(QLatin1Char(';')); if (!s.contains(env)) return false; } // NotShowIn ......... if (contains(notShowInKey)) { key = notShowInKey; keyFound = true; } else { key = extendPrefixKey + notShowInKey; keyFound = contains(key) ? true : false; } if (keyFound) { QStringList s = value(key).toString().toUpper().split(QLatin1Char(';')); if (s.contains(env)) return false; } // actually installed. If not, entry may not show in menus, etc. QString s = value(QLatin1String("TryExec")).toString(); if (!s.isEmpty() && ! checkTryExec(s)) return false; return true; } QString expandDynamicUrl(QString url) { const QStringList env = QProcess::systemEnvironment(); for (const QString &line : env) { QString name = line.section(QLatin1Char('='), 0, 0); QString val = line.section(QLatin1Char('='), 1); url.replace(QString::fromLatin1("$%1").arg(name), val); url.replace(QString::fromLatin1("${%1}").arg(name), val); } return url; } QString XdgDesktopFile::url() const { if (type() != LinkType) return QString(); QString url; url = value(urlKey).toString(); if (!url.isEmpty()) return url; // WTF? What standard describes it? url = expandDynamicUrl(value(QLatin1String("URL[$e]")).toString()); if (!url.isEmpty()) return url; return QString(); } QString findDesktopFile(const QString& dirName, const QString& desktopName) { QDir dir(dirName); QFileInfo fi(dir, desktopName); if (fi.exists()) return fi.canonicalFilePath(); // Working recursively ............ const QFileInfoList dirs = dir.entryInfoList(QStringList(), QDir::Dirs | QDir::NoDotAndDotDot); for (const QFileInfo &d : dirs) { QString cn = d.canonicalFilePath(); if (dirName != cn) { QString f = findDesktopFile(cn, desktopName); if (!f.isEmpty()) return f; } } return QString(); } QString findDesktopFile(const QString& desktopName) { QStringList dataDirs = XdgDirs::dataDirs(); dataDirs.prepend(XdgDirs::dataHome(false)); for (const QString &dirName : const_cast(dataDirs)) { QString f = findDesktopFile(dirName + QLatin1String("/applications"), desktopName); if (!f.isEmpty()) return f; } return QString(); } XdgDesktopFile* XdgDesktopFileCache::getFile(const QString& fileName) { if (fileName.isEmpty()) return nullptr; if (instance().m_fileCache.contains(fileName)) { return instance().m_fileCache.value(fileName); } QString file; if (!fileName.startsWith(QDir::separator())) { // Relative path // Search desktop file .................. file = findDesktopFile(fileName); if (file.isEmpty()) return nullptr; } else { file = fileName; } XdgDesktopFile* desktopFile; // The file was found if (!instance().m_fileCache.contains(file)) { desktopFile = load(file); if (desktopFile) { instance().m_fileCache.insert(file, desktopFile); return desktopFile; } else { return nullptr; } } else { // already in the cache desktopFile = instance().m_fileCache.value(file); return desktopFile; } } QList XdgDesktopFileCache::getAllFiles() { return instance().m_fileCache.values(); } XdgDesktopFileCache & XdgDesktopFileCache::instance() { static XdgDesktopFileCache cache; if (!cache.m_IsInitialized) { cache.initialize(); cache.m_IsInitialized = true; } return cache; } /*! * Handles files with a syntax similar to desktopfiles as QSettings files. * The differences between ini-files and desktopfiles are: * desktopfiles uses '#' as comment marker, and ';' as list-separator. * Every key/value must be inside a section (i.e. there is no 'General' pseudo-section) */ bool readDesktopFile(QIODevice & device, QSettings::SettingsMap & map) { QString section; QTextStream stream(&device); while (!stream.atEnd()) { QString line = stream.readLine().trimmed(); // Skip comments and empty lines if (line.startsWith(QLatin1Char('#')) || line.isEmpty()) continue; // Section .............................. if (line.startsWith(QLatin1Char('[')) && line.endsWith(QLatin1Char(']'))) { section = line.mid(1, line.length()-2); continue; } QString key = line.section(QLatin1Char('='), 0, 0).trimmed(); QString value = line.section(QLatin1Char('='), 1).trimmed(); if (key.isEmpty()) continue; if (section.isEmpty()) { qWarning() << "key=value outside section"; return false; } key.prepend(QLatin1Char('/')); key.prepend(section); if (value.contains(QLatin1Char(';'))) { map.insert(key, value.split(QLatin1Char(';'), QString::SkipEmptyParts)); } else { map.insert(key, value); } } return true; } /*! See readDesktopFile */ bool writeDesktopFile(QIODevice & device, const QSettings::SettingsMap & map) { QTextStream stream(&device); QString section; for (auto it = map.constBegin(); it != map.constEnd(); ++it) { bool isString = it.value().canConvert(); bool isStringList = (it.value().type() == QVariant::StringList); if ((! isString) && (! isStringList)) { return false; } QString thisSection = it.key().section(QLatin1Char('/'), 0, 0); if (thisSection.isEmpty()) { qWarning() << "No section defined"; return false; } if (thisSection != section) { stream << QLatin1Char('[') << thisSection << QLatin1Char(']') << QLatin1Char('\n'); section = thisSection; } QString remainingKey = it.key().section(QLatin1Char('/'), 1, -1); if (remainingKey.isEmpty()) { qWarning() << "Only one level in key..." ; return false; } stream << remainingKey << QLatin1Char('='); if (isString) { stream << it.value().toString() << QLatin1Char(';'); } else {/* if (isStringList) */ const auto values = it.value().toStringList(); for (const QString &value : values) { stream << value << QLatin1Char(';'); } } stream << QLatin1Char('\n'); } return true; } void XdgDesktopFileCache::initialize(const QString& dirName) { QDir dir(dirName); // Directories have the type "application/x-directory", but in the desktop file // are shown as "inode/directory". To handle these cases, we use this hash. QHash specials; specials.insert(QLatin1String("inode/directory"), QLatin1String("application/x-directory")); // Working recursively ............ const QFileInfoList files = dir.entryInfoList(QStringList(), QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); for (const QFileInfo &f : files) { if (f.isDir()) { initialize(f.absoluteFilePath()); continue; } XdgDesktopFile* df = load(f.absoluteFilePath()); if (!df) { continue; } if (! m_fileCache.contains(f.absoluteFilePath())) { m_fileCache.insert(f.absoluteFilePath(), df); } const QStringList mimes = df->value(mimeTypeKey).toString().split(QLatin1Char(';'), QString::SkipEmptyParts); for (const QString &mime : mimes) { int pref = df->value(initialPreferenceKey, 0).toInt(); // We move the desktopFile forward in the list for this mime, so that // no desktopfile in front of it have a lower initialPreference. int position = m_defaultAppsCache[mime].length(); while (position > 0 && m_defaultAppsCache[mime][position - 1]->value(initialPreferenceKey, 0).toInt() < pref) { position--; } m_defaultAppsCache[mime].insert(position, df); } } } XdgDesktopFile* XdgDesktopFileCache::load(const QString& fileName) { XdgDesktopFile* desktopFile = new XdgDesktopFile(); Q_CHECK_PTR(desktopFile); if (desktopFile && desktopFile->load(fileName)) return desktopFile; delete desktopFile; return nullptr; } void loadMimeCacheDir(const QString& dirName, QHash > & cache) { QDir dir(dirName); // Directories have the type "application/x-directory", but in the desktop file // are shown as "inode/directory". To handle these cases, we use this hash. QHash specials; specials.insert(QLatin1String("inode/directory"), QLatin1String("application/x-directory")); // Working recursively ............ const QFileInfoList files = dir.entryInfoList(QStringList(), QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); for (const QFileInfo &f : files) { if (f.isDir()) { loadMimeCacheDir(f.absoluteFilePath(), cache); continue; } XdgDesktopFile* df = XdgDesktopFileCache::getFile(f.absoluteFilePath()); if (!df) continue; const QStringList mimes = df->value(mimeTypeKey).toString().split(QLatin1Char(';'), QString::SkipEmptyParts); for (const QString &mime : mimes) { int pref = df->value(initialPreferenceKey, 0).toInt(); // We move the desktopFile forward in the list for this mime, so that // no desktopfile in front of it have a lower initialPreference. int position = cache[mime].length(); while (position > 0 && cache[mime][position - 1]->value(initialPreferenceKey, 0).toInt() < pref) { position--; } cache[mime].insert(position, df); } } } QSettings::Format XdgDesktopFileCache::desktopFileSettingsFormat() { static QSettings::Format format = QSettings::InvalidFormat; if (format == QSettings::InvalidFormat) format = QSettings::registerFormat(QLatin1String("*.list"), readDesktopFile, writeDesktopFile); return format; } XdgDesktopFileCache::XdgDesktopFileCache() : m_IsInitialized(false) , m_defaultAppsCache() , m_fileCache() { } XdgDesktopFileCache::~XdgDesktopFileCache() { } void XdgDesktopFileCache::initialize() { QStringList dataDirs = XdgDirs::dataDirs(); dataDirs.prepend(XdgDirs::dataHome(false)); for (const QString &dirname : const_cast(dataDirs)) { initialize(dirname + QLatin1String("/applications")); // loadMimeCacheDir(dirname + "/applications", m_defaultAppsCache); } } QList XdgDesktopFileCache::getAppsOfCategory(const QString& category) { QList list; const QString _category = category.toUpper(); const QHash fileCache = instance().m_fileCache; for (XdgDesktopFile *desktopFile : fileCache) { QStringList categories = desktopFile->value(categoriesKey).toString().toUpper().split(QLatin1Char(';')); if (!categories.isEmpty() && (categories.contains(_category) || categories.contains(QLatin1String("X-") + _category))) list.append(desktopFile); } return list; } QList XdgDesktopFileCache::getApps(const QString& mimetype) { return instance().m_defaultAppsCache.value(mimetype); } XdgDesktopFile* XdgDesktopFileCache::getDefaultApp(const QString& mimetype) { // First, we look in following places for a default in specified order: // ~/.config/mimeapps.list // /etc/xdg/mimeapps.list // ~/.local/share/applications/mimeapps.list // /usr/local/share/applications/mimeapps.list // /usr/share/applications/mimeapps.list QStringList mimeDirsList; mimeDirsList.append(XdgDirs::configHome(false)); mimeDirsList.append(XdgDirs::configDirs()); mimeDirsList.append(XdgDirs::dataHome(false) + QLatin1String("/applications")); mimeDirsList.append(XdgDirs::dataDirs(QLatin1String("/applications"))); for (const QString &mimeDir : const_cast(mimeDirsList)) { QString defaultsListPath = mimeDir + QLatin1String("/mimeapps.list"); if (QFileInfo::exists(defaultsListPath)) { QSettings defaults(defaultsListPath, desktopFileSettingsFormat()); defaults.beginGroup(QLatin1String("Default Applications")); if (defaults.contains(mimetype)) { QVariant value = defaults.value(mimetype); if (value.canConvert()) {// A single string can also convert to a stringlist const QStringList values = value.toStringList(); for (const QString &desktopFileName : values) { XdgDesktopFile* desktopFile = XdgDesktopFileCache::getFile(desktopFileName); if (desktopFile) { return desktopFile; } else { qWarning() << desktopFileName << "not a valid desktopfile"; } } } } defaults.endGroup(); } } // If we havent found anything up to here, we look for a desktopfile that declares // the ability to handle the given mimetype. See getApps. QList apps = getApps(mimetype); XdgDesktopFile* desktopFile = apps.isEmpty() ? 0 : apps[0]; return desktopFile; } ukui-session-manager-4.0.0.1/ukui-session/ukuisessiondebug.cpp0000644000175000017500000000167714553704753023122 0ustar fengfeng/* BEGIN_COMMON_COPYRIGHT_HEADER * * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program or library 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. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA * * END_COMMON_COPYRIGHT_HEADER */ #include "ukuisessiondebug.h" Q_LOGGING_CATEGORY(UKUI_SESSION, "org.ukui.ukuisession") ukui-session-manager-4.0.0.1/ukui-session/ukuilockinfo.cpp0000644000175000017500000002060414553704753022223 0ustar fengfeng/* * Copyright (C) 2023, 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 2, 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 "ukuilockinfo.h" #include "xdgdesktopfile.h" #include #include #include #include #include #include QDBusArgument &InhibitInfo::operator<<(QDBusArgument &argument, const InhibitInfo::InhibitorInfo &mystruct) { argument.beginStructure(); argument << mystruct.name << mystruct.icon; argument.endStructure(); return argument; } const QDBusArgument &InhibitInfo::operator>>(const QDBusArgument &argument, InhibitInfo::InhibitorInfo &mystruct) { argument.beginStructure(); argument >> mystruct.name >> mystruct.icon ; argument.endStructure(); return argument; } Ukuilockinfo::Ukuilockinfo() { } QVector Ukuilockinfo::listInhibitorInfo(Ukuilockinfo::InhibitorType type) { QVector result; switch (type) { case InhibitorType::logout: { getLogoutInhibitor(result); break; } case InhibitorType::suspend: { getSystemdInhibitor(QString("sleep"), result); break; } case InhibitorType::shutdown: { getSystemdInhibitor(QString("shutdown"), result); break; } } return result; } bool Ukuilockinfo::getCfgValue(Ukuilockinfo::buttonType button) { bool buttonActive = false; bool newIniFile = false;//ini文件是否为新建文件 QString iniDir = "/usr/share/ukui/ukui-session-manager/config"; if (!QFile::exists(iniDir + "/btnconfig.ini")) { qDebug() << "btnconfig.ini file is not exists!!!"; QDir dir(iniDir); if (!dir.exists(iniDir)) { if (dir.mkdir(iniDir)) {//目前创建不成功 没有权限 QFile iniFile(iniDir + "/btnconfig.ini"); if (iniFile.open(QIODevice::WriteOnly)) { newIniFile = true; iniFile.close(); } qDebug() << "inifile open faile!"; } else { qDebug() << "create inidir faile!"; } } } QSettings *cfgSettings = new QSettings("/usr/share/ukui/ukui-session-manager/config/btnconfig.ini", QSettings::IniFormat); if (newIniFile) {//貌似路径下文件只可读不可写 cfgSettings->setValue("btn/SwitchUserBtnHide", false); cfgSettings->setValue("btn/HibernateBtnHide", false); cfgSettings->setValue("btn/LockScreenBtnHide", false); cfgSettings->setValue("btn/LogoutBtnHide", false); cfgSettings->setValue("btn/RebootBtnHide", false); cfgSettings->setValue("btn/ShutDownBtnHide", false); cfgSettings->setValue("btn/SuspendBtnHide", false); } switch (button) { case switchuserBtn: buttonActive = !(cfgSettings->value("btn/SwitchUserBtnHide").toBool()); break; case hibernateBtn: buttonActive = !(cfgSettings->value("btn/HibernateBtnHide").toBool()); break; case suspendBtn: buttonActive = !(cfgSettings->value("btn/SuspendBtnHide").toBool()); break; case lockscreenBtn: buttonActive = !(cfgSettings->value("btn/LockScreenBtnHide").toBool()); break; case logoutBtn: buttonActive = !(cfgSettings->value("btn/LogoutBtnHide").toBool()); break; case rebootBtn: buttonActive = !(cfgSettings->value("btn/RebootBtnHide").toBool()); break; case shutdownBtn: buttonActive = !(cfgSettings->value("btn/ShutDownBtnHide").toBool()); break; default: break; } return buttonActive; } void Ukuilockinfo::getSystemdInhibitor(QString type, QVector &inhibitorVec) { QDBusInterface loginInterface("org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", QDBusConnection::systemBus()); if (loginInterface.isValid()) { qDebug() << "create interface success"; } QDBusMessage result = loginInterface.call("ListInhibitors"); QList outArgs = result.arguments(); QVariant first = outArgs.at(0); const QDBusArgument &dbusArgs = first.value(); dbusArgs.beginArray(); while (!dbusArgs.atEnd()) { SystemdInhibitor::Inhibitor inhibitor; dbusArgs >> inhibitor; if (inhibitor.mode == QString("block") && inhibitor.action.contains(type)) { InhibitInfo::InhibitorInfo inhibitInfo; findNameAndIcon(inhibitor.name, inhibitInfo); if (inhibitInfo.name.isEmpty()) { inhibitInfo.name = inhibitor.name; } inhibitorVec.push_back(inhibitInfo); } } dbusArgs.endArray(); } void Ukuilockinfo::findNameAndIcon(QString &inhibitorName, InhibitInfo::InhibitorInfo &inhibitor) { QString icon; QString name; QStringList desktop_paths = { "/usr/share/applications", "/etc/xdg/autostart" }; for (const QString &dirName : const_cast(desktop_paths)) { QDir dir(dirName); if (!dir.exists()) { continue; } const QFileInfoList files = dir.entryInfoList(QStringList(QLatin1String("*.desktop")), QDir::Files | QDir::Readable); for (const QFileInfo &fi : files) { QString base = fi.baseName(); if (base == inhibitorName) { XdgDesktopFile desktopFile; desktopFile.load(fi.absoluteFilePath()); icon = desktopFile.value("Icon").toString(); name = getAppLocalName(fi.absoluteFilePath());//根据系统的本地语言设置获取对应的名称 inhibitor.name = name; inhibitor.icon = icon; } } } } QString Ukuilockinfo::getAppLocalName(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; } void Ukuilockinfo::getLogoutInhibitor(QVector &inhibitorVec) { QDBusInterface sessionInterface("org.gnome.SessionManager", "/org/gnome/SessionManager", "org.gnome.SessionManager", QDBusConnection::sessionBus()); if (!sessionInterface.isValid()) { qDebug() << "interface not usable"; return; } QDBusMessage result = sessionInterface.call("GetInhibitors"); auto res = result.arguments().toVector(); for (auto it = res.begin(); it != res.end(); ++it) { QString info = it->toString(); if (info.isEmpty()) { continue; } QStringList infolist = info.split("/"); InhibitInfo::InhibitorInfo inhibitorInfo; inhibitorInfo.name = infolist[0]; inhibitorInfo.icon = infolist[2]; inhibitorVec.push_back(inhibitorInfo); } } QDBusArgument &SystemdInhibitor::operator<<(QDBusArgument &argument, const SystemdInhibitor::Inhibitor &mystruct) { argument.beginStructure(); argument << mystruct.action << mystruct.name << mystruct.reason << mystruct.mode << mystruct.uid << mystruct.pid; argument.endStructure(); return argument; } const QDBusArgument &SystemdInhibitor::operator>>(const QDBusArgument &argument, SystemdInhibitor::Inhibitor &mystruct) { argument.beginStructure(); argument >> mystruct.action >> mystruct.name >> mystruct.reason >> mystruct.mode >> mystruct.uid >> mystruct.pid; argument.endStructure(); return argument; } ukui-session-manager-4.0.0.1/ukui-session/ukuisessiondebug.h0000644000175000017500000000200414553704753022550 0ustar fengfeng/* BEGIN_COMMON_COPYRIGHT_HEADER * * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program or library 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. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA * * END_COMMON_COPYRIGHT_HEADER */ #ifndef UKUISESSIONDEBUG_H #define UKUISESSIONDEBUG_H #include Q_DECLARE_LOGGING_CATEGORY(UKUI_SESSION) #endif // UKUISESSIONDEBUG_H ukui-session-manager-4.0.0.1/man/0000755000175000017500000000000014553704753015130 5ustar fengfengukui-session-manager-4.0.0.1/man/ukuismserver.10000644000175000017500000000774114553704753017767 0ustar fengfeng.\" Manual page for ukui-session .\" .\" This is free software; you may 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, .\" or (at your option) any later version. .\" .\" This is distributed in the hope that it will be useful, but .\" WITHOUT ANY WARRANTY; without even the implied warranty of .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the .\" GNU 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. .\" .\" (C) 2000 Miguel de Icaza (miguel@helixcode.com) .\" (C) 2009-2010 Vincent Untz (vuntz@gnome.org) .\" (C) 2023, KylinSoft Co., Ltd. .\" .TH UKUI-SESSION 1 "11 February 2023" "UKUI Desktop Environment" .\" Please adjust this date when revising the manpage. .\" .SH "NAME" ukui-session \- Start the UKUI Desktop Environment. .SH "SYNOPSIS" .B ukui-session [OPTIONS] .SH "DESCRIPTION" The \fBukui-session\fP program starts up the UKUI desktop environment. This command is typically executed by your Login/Display Manager (like GDM, LXDM, XDM, SLiM, or from your X startup scripts like .xinitrc). It will load either your saved session, or it will provide a default session for the user as defined by the system administrator (or the default UKUI installation on your system). .PP The default session is defined in the dconf keys under .BI / org / ukui / desktop / session / .br When saving a session, \fBukui-session\fP saves the currently running applications in the \fB~/.config/ukui-session/saved-session\fP directory of the users /home. .PP \fBukui-session\fP is an X11 session manager. It can manage UKUI applications as well as any other X11 SM compliant ones. .SH "OPTIONS" .SS The following options are supported: .TP \fB\-a, \-\-autostart=DIR\fR Start all applications defined in "\fBDIR\fP", instead of starting the applications defined in .BI / org / ukui / desktop / session / default\-session .br Multiple \fB\-\-autostart\fP options can be passed. .TP \fB\-f, \-\-failsafe\fR Run in fail-safe mode. User-specified applications will not be started. .TP \fB\-\-debug\fR Enable debugging code. .TP \fB\-\-display=DISPLAY\fR X display to use. .TP \fB\-\-version\fR Output version information and exit. .TP \fB\-?, \-h, \-\-help\fR Print standard command line options. .TP \fB\-\-help\-all\fR Print all command line options. .SH "ENVIRONMENT" .SS \fBukui-session\fP sets several environment variables for the use of its child processes: .PP .B "SESSION_MANAGER" .IP This variable is used by session-manager aware clients to contact ukui-session. .PP .B "DISPLAY" .IP This variable is set to the X display being used by \fBukui-session\fP. Note that if the \-\-display option is used this might be different from the setting of the environment variable when ukui-session is invoked. .SH "FILES" .PP .nf .B ~/.config/autostart .B /usr/share/autostart .B /usr/share/ukui/autostart .fi .IP The applications defined in the above directories will be started on login. \fBukui-session-properties(1)\fP can be used to easily configure them. .PP .B ~/.config/ukui-session/saved-session .IP This directory contains the list of applications of the saved session. .SH "BUGS" .SS Should you encounter any bugs, they may be reported at: https://github.com/ukui/ukui-session-manager/issues .SH "AUTHORS" .SS This Manual page was originally written for gnome-session by: .nf Miguel de Icaza (2000) Vincent Untz (2009-2010) .fi .SS It has been updated for the MATE Desktop Environment by: Adam Erdman (2014) .SS It has been updated for the UKUI Desktop Environment by: yilei (2016) .SH "SEE ALSO" .SS Further information may also be available at: http://wiki.ukui.org/ .P .BR ukui-session-properties(1), .BR ukui-session-save(1), .BR ukui-wm(1) ukui-session-manager-4.0.0.1/man/ukui-session.10000644000175000017500000000774114553704753017661 0ustar fengfeng.\" Manual page for ukui-session .\" .\" This is free software; you may 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, .\" or (at your option) any later version. .\" .\" This is distributed in the hope that it will be useful, but .\" WITHOUT ANY WARRANTY; without even the implied warranty of .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the .\" GNU 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. .\" .\" (C) 2000 Miguel de Icaza (miguel@helixcode.com) .\" (C) 2009-2010 Vincent Untz (vuntz@gnome.org) .\" (C) 2023, KylinSoft Co., Ltd. .\" .TH UKUI-SESSION 1 "11 February 2023" "UKUI Desktop Environment" .\" Please adjust this date when revising the manpage. .\" .SH "NAME" ukui-session \- Start the UKUI Desktop Environment. .SH "SYNOPSIS" .B ukui-session [OPTIONS] .SH "DESCRIPTION" The \fBukui-session\fP program starts up the UKUI desktop environment. This command is typically executed by your Login/Display Manager (like GDM, LXDM, XDM, SLiM, or from your X startup scripts like .xinitrc). It will load either your saved session, or it will provide a default session for the user as defined by the system administrator (or the default UKUI installation on your system). .PP The default session is defined in the dconf keys under .BI / org / ukui / desktop / session / .br When saving a session, \fBukui-session\fP saves the currently running applications in the \fB~/.config/ukui-session/saved-session\fP directory of the users /home. .PP \fBukui-session\fP is an X11 session manager. It can manage UKUI applications as well as any other X11 SM compliant ones. .SH "OPTIONS" .SS The following options are supported: .TP \fB\-a, \-\-autostart=DIR\fR Start all applications defined in "\fBDIR\fP", instead of starting the applications defined in .BI / org / ukui / desktop / session / default\-session .br Multiple \fB\-\-autostart\fP options can be passed. .TP \fB\-f, \-\-failsafe\fR Run in fail-safe mode. User-specified applications will not be started. .TP \fB\-\-debug\fR Enable debugging code. .TP \fB\-\-display=DISPLAY\fR X display to use. .TP \fB\-\-version\fR Output version information and exit. .TP \fB\-?, \-h, \-\-help\fR Print standard command line options. .TP \fB\-\-help\-all\fR Print all command line options. .SH "ENVIRONMENT" .SS \fBukui-session\fP sets several environment variables for the use of its child processes: .PP .B "SESSION_MANAGER" .IP This variable is used by session-manager aware clients to contact ukui-session. .PP .B "DISPLAY" .IP This variable is set to the X display being used by \fBukui-session\fP. Note that if the \-\-display option is used this might be different from the setting of the environment variable when ukui-session is invoked. .SH "FILES" .PP .nf .B ~/.config/autostart .B /usr/share/autostart .B /usr/share/ukui/autostart .fi .IP The applications defined in the above directories will be started on login. \fBukui-session-properties(1)\fP can be used to easily configure them. .PP .B ~/.config/ukui-session/saved-session .IP This directory contains the list of applications of the saved session. .SH "BUGS" .SS Should you encounter any bugs, they may be reported at: https://github.com/ukui/ukui-session-manager/issues .SH "AUTHORS" .SS This Manual page was originally written for gnome-session by: .nf Miguel de Icaza (2000) Vincent Untz (2009-2010) .fi .SS It has been updated for the MATE Desktop Environment by: Adam Erdman (2014) .SS It has been updated for the UKUI Desktop Environment by: yilei (2016) .SH "SEE ALSO" .SS Further information may also be available at: http://wiki.ukui.org/ .P .BR ukui-session-properties(1), .BR ukui-session-save(1), .BR ukui-wm(1) ukui-session-manager-4.0.0.1/man/ukui-session-tools.10000644000175000017500000000774114553704753021017 0ustar fengfeng.\" Manual page for ukui-session .\" .\" This is free software; you may 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, .\" or (at your option) any later version. .\" .\" This is distributed in the hope that it will be useful, but .\" WITHOUT ANY WARRANTY; without even the implied warranty of .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the .\" GNU 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. .\" .\" (C) 2000 Miguel de Icaza (miguel@helixcode.com) .\" (C) 2009-2010 Vincent Untz (vuntz@gnome.org) .\" (C) 2023, KylinSoft Co., Ltd. .\" .TH UKUI-SESSION 1 "11 February 2023" "UKUI Desktop Environment" .\" Please adjust this date when revising the manpage. .\" .SH "NAME" ukui-session \- Start the UKUI Desktop Environment. .SH "SYNOPSIS" .B ukui-session [OPTIONS] .SH "DESCRIPTION" The \fBukui-session\fP program starts up the UKUI desktop environment. This command is typically executed by your Login/Display Manager (like GDM, LXDM, XDM, SLiM, or from your X startup scripts like .xinitrc). It will load either your saved session, or it will provide a default session for the user as defined by the system administrator (or the default UKUI installation on your system). .PP The default session is defined in the dconf keys under .BI / org / ukui / desktop / session / .br When saving a session, \fBukui-session\fP saves the currently running applications in the \fB~/.config/ukui-session/saved-session\fP directory of the users /home. .PP \fBukui-session\fP is an X11 session manager. It can manage UKUI applications as well as any other X11 SM compliant ones. .SH "OPTIONS" .SS The following options are supported: .TP \fB\-a, \-\-autostart=DIR\fR Start all applications defined in "\fBDIR\fP", instead of starting the applications defined in .BI / org / ukui / desktop / session / default\-session .br Multiple \fB\-\-autostart\fP options can be passed. .TP \fB\-f, \-\-failsafe\fR Run in fail-safe mode. User-specified applications will not be started. .TP \fB\-\-debug\fR Enable debugging code. .TP \fB\-\-display=DISPLAY\fR X display to use. .TP \fB\-\-version\fR Output version information and exit. .TP \fB\-?, \-h, \-\-help\fR Print standard command line options. .TP \fB\-\-help\-all\fR Print all command line options. .SH "ENVIRONMENT" .SS \fBukui-session\fP sets several environment variables for the use of its child processes: .PP .B "SESSION_MANAGER" .IP This variable is used by session-manager aware clients to contact ukui-session. .PP .B "DISPLAY" .IP This variable is set to the X display being used by \fBukui-session\fP. Note that if the \-\-display option is used this might be different from the setting of the environment variable when ukui-session is invoked. .SH "FILES" .PP .nf .B ~/.config/autostart .B /usr/share/autostart .B /usr/share/ukui/autostart .fi .IP The applications defined in the above directories will be started on login. \fBukui-session-properties(1)\fP can be used to easily configure them. .PP .B ~/.config/ukui-session/saved-session .IP This directory contains the list of applications of the saved session. .SH "BUGS" .SS Should you encounter any bugs, they may be reported at: https://github.com/ukui/ukui-session-manager/issues .SH "AUTHORS" .SS This Manual page was originally written for gnome-session by: .nf Miguel de Icaza (2000) Vincent Untz (2009-2010) .fi .SS It has been updated for the MATE Desktop Environment by: Adam Erdman (2014) .SS It has been updated for the UKUI Desktop Environment by: yilei (2016) .SH "SEE ALSO" .SS Further information may also be available at: http://wiki.ukui.org/ .P .BR ukui-session-properties(1), .BR ukui-session-save(1), .BR ukui-wm(1) ukui-session-manager-4.0.0.1/ukuismserver/0000755000175000017500000000000014553704753017121 5ustar fengfengukui-session-manager-4.0.0.1/ukuismserver/ukuismserverdbusadaptor.cpp0000644000175000017500000000105014553704175024614 0ustar fengfeng#include "ukuismserverdbusadaptor.h" #include "ukuismserver.h" #include "ukuismserverdebug.h" #include ukuismserverdbusadaptor::ukuismserverdbusadaptor(UKUISMServer *server) : QDBusAbstractAdaptor(server) { } ukuismserverdbusadaptor::~ukuismserverdbusadaptor() { } bool ukuismserverdbusadaptor::closeSession() { return parent()->closeSession(); } bool ukuismserverdbusadaptor::isCloseSession() { return parent()->isCloseSession(); } void ukuismserverdbusadaptor::restoreSession() { return parent()->restoreSession(); } ukui-session-manager-4.0.0.1/ukuismserver/ukuismconnection.h0000644000175000017500000000417014553704175022667 0ustar fengfeng/***************************************************************** ksmserver - the KDE session management server Copyright 2000 Matthias Ettrich Copyright 2005 Lubos Lunak Copyright 2021 KylinSoft Co., Ltd. relatively small extensions by Oswald Buddenhagen some code taken from the dcopserver (part of the KDE libraries), which is Copyright 1999 Matthias Ettrich Copyright 1999 Preston Brown Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ******************************************************************/ #ifndef UKUISMCONNECTION_H #define UKUISMCONNECTION_H #include #include class UKUISMConnection : public QSocketNotifier { public: UKUISMConnection(IceConn conn) : QSocketNotifier(IceConnectionNumber(conn), QSocketNotifier::Read ) { iceConn = conn; } IceConn iceConn; }; class UKUISMListener : public QSocketNotifier { public: UKUISMListener(IceListenObj obj) : QSocketNotifier(IceGetListenConnectionNumber(obj), QSocketNotifier::Read) { listenObj = obj; } IceListenObj listenObj; }; #endif // UKUISMCONNECTION_H ukui-session-manager-4.0.0.1/ukuismserver/ukuismserver.h0000644000175000017500000001171614553704175022042 0ustar fengfeng/***************************************************************** ukuismserver - the UKUI session management server Copyright 2000 Matthias Ettrich Copyright 2021 KylinSoft Co., Ltd. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ******************************************************************/ #ifndef UKUISMSERVER_H #define UKUISMSERVER_H extern "C" { #include } #include //该头文件来自kwindowsystem,主要作用是处理和Qt的头文件一起编译时的冲突 #include #include #include #include #include class UKUISMClient; class UKUISMListener; class UKUISMConnection; class KProcess; class OrgUkuiKWinSessionInterface; class UKUISMServer : public QObject, protected QDBusContext { Q_OBJECT public: explicit UKUISMServer(const QString &windowManager = ""); ~UKUISMServer(); UKUISMClient* newClient(SmsConn conn); void deleteClient(UKUISMClient *client); //callback void clientRegistered(const char *previousId); void interactRequest(UKUISMClient *client, int dialogType); void interactDone(UKUISMClient *client, bool cancelShutdown_); void phase2Request(UKUISMClient *client); void saveYourselfDone(UKUISMClient *client, bool success); void clientSetProgram(UKUISMClient *client); // error handling void ioError(IceConn iceConn); public: void* watchConnection(IceConn iceConn ); void removeConnection(UKUISMConnection *conn ); void restoreSession(); void restoreWM(const QString &sessionName); void startDefaultSession(); void shutdown(); void performLogout(); Q_SIGNALS: void logoutFinished(); void UKUISMServerStartFinished(); public Q_SLOTS: void cleanUp();//smserver析构前的清理工作 bool closeSession(); private Q_SLOTS: void newConnection(int socket); void processData(int socket); void wmProcessChange(); void protectionTimeout(); void timeoutQuit(); void timeoutWMQuit(); private: void completeShutdownOrCheckpoint(); void storeSession(); void completeKilling(); void startKilling(); void killWM(); void completeKillingWM(); void killingCompleted(); void cancelShutdown(UKUISMClient *c); KProcess* startApplication(const QStringList &command, bool wm = false); bool restoreApplication(const QStringList &command); void executeCommand(const QStringList& command); void handlePendingInteractions(); void startProtection(); void endProtection(); void launchWM(const QList &wmStartCommands); void tryRestoreNext(); bool isWM(const UKUISMClient *client) const; bool isWM(const QString &program) const; //改变客户端的顺序 void changeClientOrder(); bool syncDBusEnvironment(); void syncSessionEnv(const QString &key, const QString &value); inline void executeBoxadm(); bool isWPSWinActive(const QString &wpsAPP); void deleteInactiveClients(); private: enum State { Idle, LaunchingWM, Restoring, Shutdown, Killing, KillingWM, WaitingForKNotify, // shutdown }; State m_state; bool clean; bool m_saveSession; int m_wmPhase1WaitingCount; int m_appsToStart; int m_appRestored; int m_saveType; KProcess *m_wmProcess; OrgUkuiKWinSessionInterface *m_kwinInterface; UKUISMClient *m_clientInteracting; QList m_listener; QList m_clients; std::map m_activeClients; QTimer m_restoreTimer; QTimer m_protectionTimer; QString m_wm; QString m_lastIdRestore; QString m_sessionGroup; QStringList m_wmCommands; QDBusMessage m_performLogoutCall; public: bool isCloseSession(); bool isCancelLogout() const; void setIsCancelLogout(bool isCancelLogout); bool isCancelShutdown() const; void setIsCancelShutdown(bool isCancelShutdown); bool isCancelReboot() const; void setIsCancelReboot(bool isCancelReboot); }; #endif // UKUISMSERVER_H ukui-session-manager-4.0.0.1/ukuismserver/CMakeLists.txt0000644000175000017500000000114214553704175021655 0ustar fengfengset(UKUISMSERVER_SRCS main.cpp ukuismclient.cpp ukuismclient.h ukuismserver.cpp ukuismserver.h ukuismserverdebug.cpp ukuismserverdebug.h ukuismconnection.h ukuismserverdbusadaptor.cpp ukuismserverdbusadaptor.h ) qt5_add_dbus_interface(UKUISMSERVER_SRCS org.ukui.KWin.Session.xml ukuikwinsession_interface) add_executable(ukuismserver ${UKUISMSERVER_SRCS}) target_link_libraries(ukuismserver PRIVATE Qt5::Widgets Qt5::DBus KF5::WindowSystem KF5::ConfigCore KF5::ConfigGui KF5::CoreAddons -lSM -lICE) install(TARGETS ukuismserver DESTINATION bin) ukui-session-manager-4.0.0.1/ukuismserver/ukuismclient.h0000644000175000017500000000416314553704175022010 0ustar fengfeng/***************************************************************** ksmserver - the KDE session management server Copyright 2000 Matthias Ettrich Copyright 2021 KylinSoft Co., Ltd. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ******************************************************************/ #ifndef UKUISMCLIENT_H #define UKUISMCLIENT_H extern "C" { #include } #include #include #include class UKUISMClient { public: explicit UKUISMClient(SmsConn conn); ~UKUISMClient(); void resetState(); void registerClient(const char *previousId = nullptr); SmsConn connection() const { return m_smsConn; } QString program() const; QStringList restartCommand() const; QStringList discardCommand() const; int restartStyleHint() const; SmProp* property( const char* name ) const; const char* clientId() const { return m_id ? m_id : ""; } QString userId() const; public: QList m_properties; bool m_saveYourselfDone; bool m_pendingInteraction; bool m_waitForPhase2; bool m_wasPhase2; private: const char *m_id; SmsConn m_smsConn; }; #endif // UKUISMCLIENT_H ukui-session-manager-4.0.0.1/ukuismserver/org.ukui.KWin.Session.xml0000644000175000017500000000073514553704175023702 0ustar fengfeng ukui-session-manager-4.0.0.1/ukuismserver/ukuismserverdbusadaptor.h0000644000175000017500000000122514553704175024265 0ustar fengfeng#ifndef UKUISMSERVERDBUSADAPTOR_H #define UKUISMSERVERDBUSADAPTOR_H #include "ukuismserver.h" #include #include class ukuismserverdbusadaptor : public QDBusAbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.ukui.ukuismserver") public: ukuismserverdbusadaptor(UKUISMServer *server); ~ukuismserverdbusadaptor(); inline UKUISMServer *parent() const { return static_cast(QObject::parent()); } public slots: bool closeSession(); bool isCloseSession(); void restoreSession(); signals: void UKUISMServerStartFinished(); }; #endif // UKUISMSERVERDBUSADAPTOR_H ukui-session-manager-4.0.0.1/ukuismserver/ukuismserver.cpp0000644000175000017500000015176714553704753022412 0ustar fengfeng/***************************************************************** ksmserver - the KDE session management server Copyright 2000 Matthias Ettrich Copyright 2005 Lubos Lunak Copyright 2021 KylinSoft Co., Ltd. relatively small extensions by Oswald Buddenhagen some code taken from the dcopserver (part of the KDE libraries), which is Copyright 1999 Matthias Ettrich Copyright 1999 Preston Brown Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ******************************************************************/ #include "ukuismserver.h" #include "ukuismclient.h" #include "ukuismconnection.h" #include "ukuismserverdebug.h" #include "ukuismserverdbusadaptor.h" #include "ukuikwinsession_interface.h" #include extern "C" { #include #include #include #include } #include #include #include #include #include #include #include #include #include #include #include #include #include #define VendorString "ukuismserver" #define ReleaseString "1.0" #define SESSION_PREVIOUS_LOGOUT "saved at previous logout" enum KWinSessionState { Normal = 0, Saving = 1, Quitting = 2 }; IceAuthDataEntry *authDataEntries = nullptr; static QTemporaryFile *remTempFile = nullptr; static IceListenObj *listenObjs = nullptr; int numTransports = 0; static bool onlyLocal = false; extern "C" int _IceTransNoListen(const char *protocol); UKUISMServer*& getGlobalServer(const QString &wm = "") { static UKUISMServer *server = new UKUISMServer(wm); return server; } static Bool HostBasedAuthProc(char *hostname) { if (onlyLocal) { return true; } else { return false; } } Status RegisterClientProc(SmsConn smsConn, SmPointer managerData, char *previousId) { UKUISMClient *client = static_cast(managerData); client->registerClient(previousId); qCDebug(UKUISMSERVER) << "client " << client->clientId() << " registered."; return 1; } void InteractRequestProc(SmsConn smsConn, SmPointer managerData, int dialogType) { getGlobalServer()->interactRequest(static_cast(managerData), dialogType ); } void InteractDoneProc(SmsConn smsConn, SmPointer managerData, Bool cancelShutdown) { getGlobalServer()->interactDone(static_cast(managerData), cancelShutdown); } void SaveYourselfRequestProc(SmsConn smsConn, SmPointer managerData, int saveType, Bool shutdown, int interactStyle, Bool fast, Bool global) { //如果shutdown为true,则执行关机流程 if (shutdown) { getGlobalServer()->shutdown(); } else if (!global) { //如果global为false,则只向发送请求的客户端发送save yourself SmsSaveYourself(smsConn, saveType, false, interactStyle, fast); SmsSaveComplete(smsConn); } } void SaveYourselfPhase2RequestProc(SmsConn smsConn, SmPointer managerData) { getGlobalServer()->phase2Request(static_cast(managerData)); } void SaveYourselfDoneProc(SmsConn smsConn, SmPointer managerData, Bool success) { getGlobalServer()->saveYourselfDone(static_cast(managerData), success); } void CloseConnectionProc(SmsConn smsConn, SmPointer managerData, int count, char **reasonMsgs) { qCDebug(UKUISMSERVER) << "one app close connection"; getGlobalServer()->deleteClient(static_cast(managerData)); if (count) { SmFreeReasons(count, reasonMsgs); } IceConn iceConn = SmsGetIceConnection(smsConn); SmsCleanUp(smsConn); IceSetShutdownNegotiation(iceConn, False); IceCloseConnection(iceConn); } void SetPropertiesProc(SmsConn smsConn, SmPointer managerData, int numProps, SmProp **props) { UKUISMClient *client = static_cast(managerData); for (int i = 0; i < numProps; i++) { SmProp *p = client->property(props[i]->name); if (p) { client->m_properties.removeAll( p ); SmFreeProperty(p); } client->m_properties.append(props[i]); if (!qstrcmp(props[i]->name, SmProgram)) { getGlobalServer()->clientSetProgram(client); qCDebug(UKUISMSERVER) << client->clientId() << " and " << client->program(); } } if (numProps) { free(props); } } void DeletePropertiesProc(SmsConn smsConn, SmPointer managerData, int numProps, char **propNames) { UKUISMClient *client = static_cast(managerData); for (int i = 0; i < numProps; i++) { SmProp *p = client->property(propNames[i]); if (p) { client->m_properties.removeAll(p); SmFreeProperty(p); } } } void GetPropertiesProc(SmsConn smsConn, SmPointer managerData) { UKUISMClient *client = static_cast(managerData); SmProp **props = new SmProp*[client->m_properties.count()]; int i = 0; foreach (SmProp *prop, client->m_properties) { props[i++] = prop; } SmsReturnProperties(smsConn, i, props); delete[] props; } static Status NewClientProc(SmsConn conn, SmPointer manager_data, unsigned long *mask_ret, SmsCallbacks *cb, char **failure_reason_ret) { //根据XSMP协议的要求,出错时,这个函数应该返回状态0,并且错误原因要包含在failure_reason_ret中 *failure_reason_ret = nullptr; void *client = (static_cast(manager_data))->newClient(conn); cb->register_client.callback = RegisterClientProc; cb->register_client.manager_data = client; cb->interact_request.callback = InteractRequestProc; cb->interact_request.manager_data = client; cb->interact_done.callback = InteractDoneProc; cb->interact_done.manager_data = client; cb->save_yourself_request.callback = SaveYourselfRequestProc; cb->save_yourself_request.manager_data = client; cb->save_yourself_phase2_request.callback = SaveYourselfPhase2RequestProc; cb->save_yourself_phase2_request.manager_data = client; cb->save_yourself_done.callback = SaveYourselfDoneProc; cb->save_yourself_done.manager_data = client; cb->close_connection.callback = CloseConnectionProc; cb->close_connection.manager_data = client; cb->set_properties.callback = SetPropertiesProc; cb->set_properties.manager_data = client; cb->delete_properties.callback = DeletePropertiesProc; cb->delete_properties.manager_data = client; cb->get_properties.callback = GetPropertiesProc; cb->get_properties.manager_data = client; *mask_ret = SmsRegisterClientProcMask | SmsInteractRequestProcMask | SmsInteractDoneProcMask | SmsSaveYourselfRequestProcMask | SmsSaveYourselfP2RequestProcMask | SmsSaveYourselfDoneProcMask | SmsCloseConnectionProcMask | SmsSetPropertiesProcMask | SmsDeletePropertiesProcMask | SmsGetPropertiesProcMask; return 1; } /*用于打印16进制数据*/ static void fprintfhex(FILE *fp, unsigned int len, char *cp) { static const char hexchars[] = "0123456789abcdef"; for (; len > 0; len--, cp++) { unsigned char s = *cp; putc(hexchars[s >> 4], fp); putc(hexchars[s & 0x0f], fp); } } static void write_iceauth(FILE *addfp, FILE *removefp, IceAuthDataEntry *entry) { fprintf(addfp, "add %s \"\" %s %s ", entry->protocol_name, entry->network_id, entry->auth_name); fprintfhex(addfp, entry->auth_data_length, entry->auth_data); fprintf(addfp, "\n"); fprintf(removefp, "remove protoname=%s protodata=\"\" netid=%s authname=%s\n", entry->protocol_name, entry->network_id, entry->auth_name); } #define COOKIE_LEN 16 Status SetAuthentication_local (int count, IceListenObj *listenObjs) { int i; for (i = 0; i < count; i ++) { char *prot = IceGetListenConnectionString(listenObjs[i]); if (!prot) continue; char *host = strchr(prot, '/'); char *sock = nullptr; if (host) { *host=0; host++; sock = strchr(host, ':'); if (sock) { *sock = 0; sock++; } } if (sock && !strcmp(prot, "local")) { chmod(sock, 0700); } IceSetHostBasedAuthProc(listenObjs[i], HostBasedAuthProc); free(prot); } return 1; } Status SetAuthentication (int count, IceListenObj *listenObjs, IceAuthDataEntry **authDataEntries) { QTemporaryFile addTempFile; remTempFile = new QTemporaryFile; if (!addTempFile.open() || !remTempFile->open()) return 0; if ((*authDataEntries = (IceAuthDataEntry *) malloc(count * 2 * sizeof(IceAuthDataEntry))) == nullptr) return 0; FILE *addAuthFile = fopen(QFile::encodeName(addTempFile.fileName()).constData(), "r+"); FILE *remAuthFile = fopen(QFile::encodeName(remTempFile->fileName()).constData(), "r+"); for (int i = 0; i < numTransports * 2; i += 2) { (*authDataEntries)[i].network_id = IceGetListenConnectionString(listenObjs[i/2]); (*authDataEntries)[i].protocol_name = (char*)"ICE"; (*authDataEntries)[i].auth_name = (char*)"MIT-MAGIC-COOKIE-1"; (*authDataEntries)[i].auth_data = IceGenerateMagicCookie(COOKIE_LEN); (*authDataEntries)[i].auth_data_length = COOKIE_LEN; (*authDataEntries)[i+1].network_id = IceGetListenConnectionString(listenObjs[i/2]); (*authDataEntries)[i+1].protocol_name = (char*)"XSMP"; (*authDataEntries)[i+1].auth_name = (char*)"MIT-MAGIC-COOKIE-1"; (*authDataEntries)[i+1].auth_data = IceGenerateMagicCookie(COOKIE_LEN); (*authDataEntries)[i+1].auth_data_length = COOKIE_LEN; write_iceauth(addAuthFile, remAuthFile, &(*authDataEntries)[i]); write_iceauth(addAuthFile, remAuthFile, &(*authDataEntries)[i+1]); IceSetPaAuthData(2, &(*authDataEntries)[i]); IceSetHostBasedAuthProc(listenObjs[i/2], HostBasedAuthProc); } fclose(addAuthFile); fclose(remAuthFile); QString iceAuth = QStandardPaths::findExecutable(QStringLiteral("iceauth")); if (iceAuth.isEmpty()) { qCDebug(UKUISMSERVER) << "UKUISMServer: could not find iceauth"; return 0; } KProcess p; p << iceAuth << QStringLiteral("source") << addTempFile.fileName(); p.execute(); return (1); } void FreeAuthenticationData(int count, IceAuthDataEntry *authDataEntries) { /* Each transport has entries for ICE and XSMP */ if (onlyLocal) { return; } for (int i = 0; i < count * 2; i++) { free(authDataEntries[i].network_id); free(authDataEntries[i].auth_data); } free (authDataEntries); QString iceAuth = QStandardPaths::findExecutable(QStringLiteral("iceauth")); if (iceAuth.isEmpty()) { qCDebug(UKUISMSERVER) << "UKUISMServer: could not find iceauth"; return; } if (remTempFile) { KProcess p; p << iceAuth << QStringLiteral("source") << remTempFile->fileName(); p.execute(); } delete remTempFile; remTempFile = nullptr; } void UKUISMWatchProc(IceConn iceConn, IcePointer client_data, Bool opening, IcePointer *watch_data) { UKUISMServer *ds = static_cast(client_data); if (opening) { *watch_data = (IcePointer)ds->watchConnection(iceConn); } else { ds->removeConnection((UKUISMConnection*)*watch_data); } } UKUISMServer::UKUISMServer(const QString &windowManager) : m_kwinInterface(new OrgUkuiKWinSessionInterface(QStringLiteral("org.ukui.KWin"), QStringLiteral("/Session"), QDBusConnection::sessionBus(), this)) , m_state(Idle), m_saveSession(false), m_wmPhase1WaitingCount(0), m_clientInteracting(nullptr), m_sessionGroup(QStringLiteral("")) , m_wm(windowManager.isEmpty() ? QStringLiteral("ukui-kwin_x11") : windowManager), m_wmCommands(QStringList({m_wm})) { new ukuismserverdbusadaptor(this); bool success = QDBusConnection::sessionBus().registerObject(QStringLiteral("/UKUISMServer"), this);//注册d-bus对象 qCDebug(UKUISMSERVER) << "registered object is " << success; // connect(this, &UKUISMServer::UKUISMServerStartFinished, adaptor, &ukuismserverdbusadaptor::UKUISMServerStartFinished); onlyLocal = true; if (onlyLocal) { _IceTransNoListen("tcp"); } char errormsg[256]; if (!SmsInitialize((char*)VendorString, (char*)ReleaseString, NewClientProc, (SmPointer) this, HostBasedAuthProc, 256, errormsg)) { qCDebug(UKUISMSERVER) << "UKUISMServer: could not register XSM protocol"; } if (!IceListenForConnections(&numTransports, &listenObjs, 256, errormsg)) { qCDebug(UKUISMSERVER) << "UKUISMServer: Error listening for connections: " << errormsg; qCDebug(UKUISMSERVER) << "UKUISMServer: Aborting."; exit(1); } { QByteArray fName = QFile::encodeName(QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation) + QDir::separator() + QStringLiteral("UKUISMServer")); QString display = QString::fromLocal8Bit(::getenv("DISPLAY")); display.remove(QRegExp(QStringLiteral("\\.[0-9]+$"))); int i = 0; while ((i = display.indexOf(QLatin1Char(':'))) >= 0) { display[i] = '_'; } while ((i = display.indexOf(QLatin1Char('/'))) >= 0) { display[i] = '_'; } fName += '_'+display.toLocal8Bit(); FILE *f; //w+ 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件 f = ::fopen(fName.data(), "w+"); if (!f) { QString str = QString(QStringLiteral("UKUISMServer: cannot open %s: %s")).arg(fName.data()).arg(strerror(errno)); qCDebug(UKUISMSERVER) << str; qCDebug(UKUISMSERVER) << "UKUISMServer: Aborting."; exit(1); } char *session_manager = IceComposeNetworkIdList(numTransports, listenObjs); fprintf(f, "%s\n%i\n", session_manager, getpid()); fclose(f); setenv("SESSION_MANAGER", session_manager, true); //这里要将session_manager变量传递给其他需要的进程 //同步环境变量到D-Bus中 syncDBusEnvironment(); //同步环境变量到ukui-session中 syncSessionEnv("SESSION_MANAGER", session_manager); free(session_manager); } if (onlyLocal) { if (!SetAuthentication_local(numTransports, listenObjs)) { qFatal("ukuismserver : authentication setup failed."); } } else { if (!SetAuthentication(numTransports, listenObjs, &authDataEntries)) { qFatal("ukuismserver : authentication setup failed."); } } IceAddConnectionWatch(UKUISMWatchProc, (IcePointer)this); UKUISMListener *con; for (int i = 0; i < numTransports; i++) { fcntl(IceGetListenConnectionNumber(listenObjs[i]), F_SETFD, FD_CLOEXEC); con = new UKUISMListener(listenObjs[i]); m_listener.append(con); connect(con, &UKUISMListener::activated, this, &UKUISMServer::newConnection); } connect(qApp, &QApplication::aboutToQuit, this, &UKUISMServer::cleanUp); connect(&m_restoreTimer, &QTimer::timeout, this, &UKUISMServer::tryRestoreNext); connect(&m_protectionTimer, &QTimer::timeout, this, &UKUISMServer::protectionTimeout); qCDebug(UKUISMSERVER) << "finish construct ukuismserver"; // QTimer::singleShot(0, [this](){ // this->UKUISMServerStartFinished(); // }); } UKUISMServer::~UKUISMServer() { qDeleteAll(m_listener); cleanUp(); } UKUISMClient *UKUISMServer::newClient(SmsConn conn) { UKUISMClient *client = new UKUISMClient(conn); m_clients.append(client); m_activeClients[client] = 0; return client; } void UKUISMServer::deleteClient(UKUISMClient *client) { int num = m_clients.removeAll(client); qCDebug(UKUISMSERVER) << "m_clients remove client " << client->clientId() << " num is " << num << ", remain " << m_clients.size(); if (client == m_clientInteracting) { m_clientInteracting = nullptr; handlePendingInteractions(); } delete client; if (m_state == Shutdown) { completeShutdownOrCheckpoint(); } if (m_state == Killing) { completeKilling(); } if (m_state == KillingWM) { completeKillingWM(); } } void UKUISMServer::clientRegistered(const char *previousId) { if (previousId && m_lastIdRestore == QString::fromLocal8Bit(previousId)) { tryRestoreNext(); } } void UKUISMServer::interactRequest(UKUISMClient *client, int dialogType) { //如果是关机阶段,该客户端的请求就要暂时挂起 if (m_state == Shutdown) { //将pendingInteraction属性设置为true,以便在handlePendingInteractions中处理 qCDebug(UKUISMSERVER) << client->clientId() << "ask for interact" << "pending it"; client->m_pendingInteraction = true; } else { //非关机阶段,则直接授予客户端交互权限 qCDebug(UKUISMSERVER) << "sending interact permission to client " << client->clientId(); SmsInteract(client->connection()); } //处理被挂起的客户端请求 handlePendingInteractions(); } void UKUISMServer::interactDone(UKUISMClient *client, bool cancelShutdown_) { //如果交互完成的客户端与服务器保存的m_clientInteraction信息不一致,则返回,一般不会发生,因为退出时的交互对话框是模态的 if (client != m_clientInteracting) return; //重置m_clientInteraciton,以便处理下一个挂起的客户端 qCDebug(UKUISMSERVER) << m_clientInteracting->clientId() << "interact done"; m_clientInteracting = nullptr; //如果客户端取消关机,则向所有客户端发送取消关机信号 if (cancelShutdown_) { QString programPath = client->program(); QString programName = programPath.mid(programPath.lastIndexOf(QDir::separator()) + 1); if (programName != QLatin1String("ukui-screensaver-default")) { qCDebug(UKUISMSERVER) << client->clientId() << "cancel shutdown"; cancelShutdown(client); } else { //屏保程序不正常退出时,会在注销阶段发送一个取消注销信号过来,忽略这个信号,才能正常完成注销 qCDebug(UKUISMSERVER) << "ukui-screensaver-default send cancel shutdown, ignore it"; handlePendingInteractions(); } } else { //处理下一个挂起的客户端 handlePendingInteractions(); } } void UKUISMServer::phase2Request(UKUISMClient *client) { //这两个成员变量对于窗管才有用 client->m_waitForPhase2 = true; client->m_wasPhase2 = true; completeShutdownOrCheckpoint(); if (isWM(client) && m_wmPhase1WaitingCount > 0) { --m_wmPhase1WaitingCount; //窗管完成phase1保存,请求phase2,服务器先向所有其他客户端发送保存命令 if (m_wmPhase1WaitingCount == 0) { foreach (UKUISMClient *c, m_clients) { if (!isWM(c)) { qCDebug(UKUISMSERVER) << "wm done phase1, sending saveyourself to " << c->clientId(); SmsSaveYourself(c->connection(), m_saveType, m_saveType != SmSaveLocal, m_saveType != SmSaveLocal ? SmInteractStyleAny : SmInteractStyleNone, false); } } } } } void UKUISMServer::saveYourselfDone(UKUISMClient *client, bool success) { //此处是非shutdown状态时,客户端注册进来仍然会走saveyourselfdone,参考kde代码中的注释 if (m_state == Idle || m_state == LaunchingWM || m_state == Restoring) { m_activeClients[client] = 1; QStringList discard = client->discardCommand(); if (!discard.isEmpty()) { executeCommand(discard); } return; } if (success) { qCDebug(UKUISMSERVER) << client->clientId() << "successfully done save"; client->m_saveYourselfDone = true; completeShutdownOrCheckpoint(); } else { //即使保存不成功也要按照成功的方式进行下一步,否则无法保存下一个客户端 client->m_saveYourselfDone = true; completeShutdownOrCheckpoint(); } startProtection(); } void UKUISMServer::clientSetProgram(UKUISMClient *client) { //kde根据窗管开始设置client属性来确定窗管已经启动完成,ukui-session中不需要通过这种方式来确定,暂时注释这一段 // if (isWM(client)) { // qCDebug(UKUISMSERVER) << "windowManager loaded, wm set property"; // } } void UKUISMServer::ioError(IceConn iceConn) { //找出iceConn包含的信息,为什么一定需要这个 } void *UKUISMServer::watchConnection(IceConn iceConn) { UKUISMConnection *conn = new UKUISMConnection(iceConn); connect(conn, &UKUISMConnection::activated, this, &UKUISMServer::processData); return (void*)conn; } void UKUISMServer::restoreWM(const QString &sessionName) { if (m_state != Idle) return; m_state = LaunchingWM; KSharedConfig::Ptr config = KSharedConfig::openConfig(); m_sessionGroup = QLatin1String("Session: ") + sessionName; KConfigGroup configSessionGroup(config, m_sessionGroup); //如果以后要加上恢复会话功能,m_appsToStart会被用到 int count = configSessionGroup.readEntry("count", 0); m_appsToStart = count; //以下这段是从保存的会话文件中寻找wm的重启命令 QList wmStartCommands; if (!m_wm.isEmpty()) { for (int i = 1; i <= count; i++) { QString n = QString::number(i); if (isWM(configSessionGroup.readEntry(QStringLiteral("program") + n, QString()))) { wmStartCommands << configSessionGroup.readEntry(QStringLiteral("restartCommand") + n, QStringList()); } } } if (wmStartCommands.isEmpty()) { wmStartCommands << m_wmCommands; } launchWM(wmStartCommands); } void UKUISMServer::startDefaultSession() { if (m_state != Idle ) { return; } m_state = LaunchingWM; m_sessionGroup = QString(); launchWM(QList() << m_wmCommands); } void UKUISMServer::shutdown() { //保存关机 qCDebug(UKUISMSERVER) << "begin performlogout"; performLogout(); } QStringList listFileList() { QDir dir("/etc/ukui/ukui-session/logout/"); if (!dir.exists()) { qWarning("Cannot find the example directory"); return QStringList(); } dir.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks); dir.setSorting(QDir::Size | QDir::Reversed); QFileInfoList list = dir.entryInfoList(); QStringList filelist; for (int i = list.size() - 1 ; i >= 0 ; --i) { QFileInfo fileInfo = list.at(i); filelist<= Shutdown) { qCDebug(UKUISMSERVER) << "already perform Logout"; return; } QDBusInterface face("com.kylin.AppManager", "/com/kylin/AppManager", "com.kylin.AppManager", QDBusConnection::sessionBus()); if (!face.isValid()) { qDebug() << "AppManager inteface not valid"; } else { QDBusReply reply = face.call("ThawApps"); if (reply.isValid()) { qCDebug(UKUISMSERVER) << "ThawApps return " << reply.value(); } } deleteInactiveClients(); m_kwinInterface->setState(KWinSessionState::Saving); m_state = Shutdown; m_saveSession = true; if (m_saveSession) { m_sessionGroup = QStringLiteral("Session: ") + QString::fromLocal8Bit(SESSION_PREVIOUS_LOGOUT); } m_wmPhase1WaitingCount = 0; m_saveType = SmSaveBoth; //将该函数的调用放到注销开始阶段 //9a0的机器上session是由kwin启动的,所以kwin不会注册到ukuismserver中,注销的时候也不会先保存kwin,而是直接向所有客户端发送保存信号 changeClientOrder(); startProtection(); foreach (UKUISMClient *c, m_clients) { c->resetState(); if (isWM(c)) { ++m_wmPhase1WaitingCount; } } if (m_wmPhase1WaitingCount > 0) { foreach (UKUISMClient *c, m_clients) { //先向窗管发送保存自身的信号 if (isWM(c)) { qCDebug(UKUISMSERVER) << "sending saveyourself to wm first"; SmsSaveYourself(c->connection(), m_saveType, true, SmInteractStyleAny, false); } } } else { foreach (UKUISMClient *c, m_clients) { qCDebug(UKUISMSERVER) << "sending saveourself to client " << " " << c->clientId(); SmsSaveYourself(c->connection(), m_saveType, true, SmInteractStyleAny, false); } } if (m_clients.isEmpty()) { completeShutdownOrCheckpoint(); } return; } void UKUISMServer::cleanUp() { if (clean) { return; } clean = true; IceFreeListenObjs(numTransports, listenObjs); QByteArray fName = QFile::encodeName(QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation) + QLatin1Char('/') + QStringLiteral("UKUISMServer")); QString display = QString::fromLocal8Bit(::getenv("DISPLAY")); display.remove(QRegExp(QStringLiteral("\\.[0-9]+$"))); int i; while ((i = display.indexOf(QLatin1Char(':'))) >= 0) { display[i] = '_'; } while ((i = display.indexOf(QLatin1Char('/'))) >= 0) { display[i] = '_'; } fName += '_' + display.toLocal8Bit(); ::unlink(fName.data()); FreeAuthenticationData(numTransports, authDataEntries); } bool UKUISMServer::closeSession() { Q_ASSERT(calledFromDBus()); qCDebug(UKUISMSERVER) << "begin logout"; performLogout(); qCDebug(UKUISMSERVER) << "Setting D-Bus reply delayed"; setDelayedReply(true); //不清楚这一步是否有意义,但是kde目前是这么做的 m_performLogoutCall = message(); return false; } void UKUISMServer::newConnection(int socket) { IceAcceptStatus status; IceConn iceConn = IceAcceptConnection(((UKUISMListener*)sender())->listenObj, &status); if (iceConn == nullptr) return; IceSetShutdownNegotiation(iceConn, False); IceConnectStatus cstatus; while ((cstatus = IceConnectionStatus(iceConn)) == IceConnectPending) { (void)IceProcessMessages(iceConn, nullptr, nullptr); } if (cstatus != IceConnectAccepted) { if (cstatus == IceConnectIOError) { qCDebug(UKUISMSERVER) << "IO error opening ICE Connection!"; } else { qCDebug(UKUISMSERVER) << "ICE Connection rejected!"; } (void)IceCloseConnection(iceConn); return; } fcntl(IceConnectionNumber(iceConn), F_SETFD, FD_CLOEXEC); } void UKUISMServer::processData(int socket) { IceConn iceConn = ((UKUISMConnection*)sender())->iceConn; IceProcessMessagesStatus status = IceProcessMessages(iceConn, nullptr, nullptr); if (status == IceProcessMessagesIOError) { //如果有同类应用再次注册进来就会触发此处 IceSetShutdownNegotiation(iceConn, False); QList::iterator it = m_clients.begin(); QList::iterator const itEnd = m_clients.end(); //这里做的就是找出发出错误信息的那个client,然后从m_clients中移除掉 while ((it != itEnd) && *it && (SmsGetIceConnection((*it)->connection()) != iceConn)) { ++it; } if ((it != itEnd) && *it) { SmsConn smsConn = (*it)->connection(); qCDebug(UKUISMSERVER) << "earse duplicate app, or some app close connection"; deleteClient(*it); SmsCleanUp(smsConn); } (void)IceCloseConnection(iceConn); } } void UKUISMServer::wmProcessChange() { if (m_state != LaunchingWM) { m_wmProcess = nullptr; return; } if (m_wmProcess->state() == QProcess::NotRunning) { if (m_wm == QLatin1String("ukui-kwin_x11")) { return; } m_wm = QStringLiteral("ukui-kwin_x11"); m_wmCommands = (QStringList() << QStringLiteral("ukui-kwin_x11")); launchWM(QList() << m_wmCommands); return; } } void UKUISMServer::protectionTimeout() { if ((m_state != Shutdown) || m_clientInteracting) {//如果状态不是Shutdown,或者clientInteracing有值,则条件成立 //m_clientInteracting有可能为nullptr,所以在这里获取clientid不是很方便 qCDebug(UKUISMSERVER) << "protectionTimeout: state is " << m_state << "clientInteracting is " << m_clientInteracting << "protectionTimeout returned"; //如果有一个客户端正在interact,而这里已经超时了,则会直接return,protectiontimer不能用于更改正在interect的客户端的状态。 //也即protectionTimer只能作用于那些不响应saveyourself信号的客户端,但是现在因为加入了清理不响应信号客户端的机制,所以实际上protectionTimer可能不会再有起作用的机会 return; } foreach (UKUISMClient *c, m_clients) { if (!c->m_saveYourselfDone && !c->m_waitForPhase2) {//非窗管这类客户端且没有完成保存自身 qCDebug(UKUISMSERVER) << "protectionTimeout: client " << c->clientId(); c->m_saveYourselfDone = true; } } completeShutdownOrCheckpoint(); startProtection(); } void UKUISMServer::timeoutQuit() { // killWM(); //杀死客户端阶段有一个10秒的等待,若10秒过后还有客户端没有响应killconnection信号,则会强制调用killwm //由于现在改成了不杀死窗管,所以这里的强制操作也改为调用systemd的注销 killingCompleted(); } void UKUISMServer::timeoutWMQuit() { if (m_state == KillingWM) { qCDebug(UKUISMSERVER) << "SmsDie WM timeout"; } killingCompleted(); } void UKUISMServer::completeShutdownOrCheckpoint() { //只能在shutdown阶段调用 if (m_state != Shutdown) return; QList pendingClients; pendingClients = m_clients; //此处判断除窗管之外的客户端是否全部完成保存,没有的话就返回 foreach (UKUISMClient *c, pendingClients ) { if (!c->m_saveYourselfDone && !c->m_waitForPhase2) { qCDebug(UKUISMSERVER) << c->clientId() << " haven't save"; return; } } //窗管正在等待phase2阶段的保存,则向其发送保存phase2的信号 bool waitForPhase2 = false; foreach (UKUISMClient *c, pendingClients) { if (!c->m_saveYourselfDone && c->m_waitForPhase2) { c->m_waitForPhase2 = false; qCDebug(UKUISMSERVER) << "sending saveyourselfphase2 to " << c->clientId(); SmsSaveYourselfPhase2(c->connection()); waitForPhase2 = true; } } if (waitForPhase2) { return; } //运行到这里说明窗管和普通客户端都完成了保存,开始保存会话信息到磁盘中 if (m_saveSession) { qCDebug(UKUISMSERVER) << "store session informantion in rcfile"; storeSession(); } //会话信息保存完毕后开始退出,杀死客户端 if (m_state == Shutdown) { m_state = WaitingForKNotify; if (m_state == WaitingForKNotify) { qCDebug(UKUISMSERVER) << "begin killint client"; startKilling(); } } } void UKUISMServer::storeSession() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); config->reparseConfiguration();//更新配置文件 KConfigGroup configSessionGroup(config, m_sessionGroup); int count = configSessionGroup.readEntry("count", 0); for (int i = 1; i <= count; i++) { //读取discardcommand QStringList discardCommand = configSessionGroup.readPathEntry(QLatin1String("discardCommand") + QString::number(i), QStringList()); if (discardCommand.isEmpty()) { continue; } //这一步的目的是寻找是否有重复的discardcommand, 如果没有就直接执行discardcommand QList::iterator it = m_clients.begin(); QList::iterator const itEnd = m_clients.end(); while ((it != itEnd) && *it && (discardCommand != (*it)->discardCommand())) { ++it; } if ((it != itEnd)&& *it) { continue; } executeCommand(discardCommand); } //删除上一次保存的会话信息 config->deleteGroup(m_sessionGroup); KConfigGroup cg(config, m_sessionGroup); count = 0; //将wm移动到客户端列表的第一个 foreach (UKUISMClient *c, m_clients) { if (isWM(c)) { m_clients.removeAll(c); m_clients.prepend(c); break; } } //遍历每一个客户端,存储客户端的信息到文件中 foreach (UKUISMClient *c, m_clients) { int restartHint = c->restartStyleHint(); if (restartHint == SmRestartNever) { continue; } QString program = c->program(); QStringList restartCommand = c->restartCommand(); if (program.isEmpty() && restartCommand.isEmpty()) { continue; } count++; QString n = QString::number(count); cg.writeEntry(QStringLiteral("program") + n, program); cg.writeEntry(QStringLiteral("clientId") + n, c->clientId()); cg.writeEntry(QStringLiteral("restartCommand") + n, restartCommand); cg.writePathEntry(QStringLiteral("discardCommand") + n, c->discardCommand()); cg.writeEntry(QStringLiteral("restartStyleHint") + n, restartHint); cg.writeEntry(QStringLiteral("userId") + n, c->userId()); cg.writeEntry(QStringLiteral("wasWm") + n, isWM(c)); } cg.writeEntry( "count", count ); config->sync(); } void UKUISMServer::completeKilling() { if (m_state == Killing) { //这一段的含义是只要客户端列表中还有非窗管的客户端存在,则等待,直到客户端中只有一个窗管,则开始杀死窗管 bool wait = false; foreach (UKUISMClient *c, m_clients) { if (isWM(c)) { continue; } wait = true; } if (wait) { return; } // killWM(); //修改为不杀死窗管,直接结束会话 killingCompleted(); } } void UKUISMServer::startKilling() { if (m_state == Killing) { return; } m_state = Killing; m_kwinInterface->setState(KWinSessionState::Quitting); foreach (UKUISMClient *c, m_clients) { if (isWM(c)) {//最后再杀死窗管 continue; } qCDebug(UKUISMSERVER) << c->clientId() << "kill connection"; SmsDie(c->connection()); } completeKilling(); QTimer::singleShot(2000, this, &UKUISMServer::timeoutQuit); } void UKUISMServer::killWM() { if (m_state != Killing) { return; } m_state = KillingWM; bool iswm = false; foreach (UKUISMClient *c, m_clients) { if (isWM(c)) { iswm = true; qCDebug(UKUISMSERVER) << "wm kill connection"; SmsDie(c->connection()); } } if (iswm) { completeKillingWM(); //5秒后窗管没有杀死,就直接终止程序 QTimer::singleShot(5000, this, &UKUISMServer::timeoutWMQuit); } else { killingCompleted(); } } void UKUISMServer::completeKillingWM() { if (m_state == KillingWM) { if (m_clients.isEmpty()) { killingCompleted(); } } } void UKUISMServer::killingCompleted() { //这里构造d-bus的返回值,此处返回true,说明注销过程正常完成,用来通知plasma-session的关机接口,注销保存已经完成,可以开始关机 if (m_performLogoutCall.type() == QDBusMessage::MethodCallMessage) { auto reply = m_performLogoutCall.createReply(true); qCDebug(UKUISMSERVER) << "sending D-Bus reply to plasma shutdown"; QDBusConnection::sessionBus().send(reply); m_performLogoutCall = QDBusMessage(); } qApp->quit(); // emit logoutFinished(); //// qApp->quit(); // //目前不清楚如果不做清理会有什么影响,看日志没有发现问题,使用上也没有区别,但为了保险还是加上 // cleanUp(); // qCDebug(UKUISMSERVER) << "call boxadm -lockall"; // executeBoxadm(); // qCDebug(UKUISMSERVER) << "finish boxadm"; // qCDebug(UKUISMSERVER) << "call systemd Terminate"; // QDBusInterface face("org.freedesktop.login1", // "/org/freedesktop/login1/session/self", // "org.freedesktop.login1.Session", // QDBusConnection::systemBus()); // face.call("Terminate"); } void UKUISMServer::cancelShutdown(UKUISMClient *c) { m_clientInteracting = nullptr; foreach (UKUISMClient *c, m_clients) { qCDebug(UKUISMSERVER) << "sending cancel shutdown to client " << c->clientId(); SmsShutdownCancelled(c->connection()); if (c->m_saveYourselfDone) { QStringList discard = c->discardCommand(); if (!discard.isEmpty()) { qCDebug(UKUISMSERVER) << c->clientId() << "discard saveing state, discardCommand is " << discard; executeCommand(discard); } } } m_state = Idle; //取消注销会向D-Bus调用方返回值,此处返回false,说明注销被取消,保证不会被强制注销 m_kwinInterface->setState(KWinSessionState::Normal); if (m_performLogoutCall.type() == QDBusMessage::MethodCallMessage) { auto reply = m_performLogoutCall.createReply(false); QDBusConnection::sessionBus().send(reply); m_performLogoutCall = QDBusMessage(); } } KProcess *UKUISMServer::startApplication(const QStringList &command, bool wm) { if (wm) { KProcess *process = new KProcess(this); qCDebug(UKUISMSERVER) << "the wm start command is " << command; *process << command; //应该需要在此处更新m_wm,因为m_wm需要在后面用来判断应用是否是窗管 m_wm = command[0]; connect(process, static_cast(&KProcess::error), process, &KProcess::deleteLater); connect(process, static_cast(&KProcess::finished), process, &KProcess::deleteLater); process->start(); return process; } else { qCDebug(UKUISMSERVER) << "The Restart Command is :" << command; int n = command.count(); QString app = command[0]; QStringList argList; for (int i = 1; i < n; i++) { argList.append(command[i]); } //调用ukui-session的接口启动应用 QDBusInterface *sessionInterface = new QDBusInterface("org.gnome.SessionManager", "/org/gnome/SessionManager", "org.gnome.SessionManager", QDBusConnection::sessionBus(), this); if (!sessionInterface->isValid()) { qWarning() << "dbusCall: Session QDBusInterface is invalid"; return nullptr; } QList argumentList; QVariant appVar(app); QVariant arguVar(argList); argumentList.append(appVar); argumentList.append(arguVar); sessionInterface->callWithArgumentList(QDBus::CallMode::NoBlock, "startApp", argumentList); return nullptr; } } bool UKUISMServer::restoreApplication(const QStringList &command) { qCDebug(UKUISMSERVER) << "The Restart Command is :" << command; int n = command.count(); QString app = command[0]; QStringList argList; for (int i = 1; i < n; i++) { argList.append(command[i]); } //调用ukui-session的接口启动应用 QDBusInterface *sessionInterface = new QDBusInterface("org.gnome.SessionManager", "/org/gnome/SessionManager", "org.gnome.SessionManager", QDBusConnection::sessionBus(), this); if (!sessionInterface->isValid()) { qWarning() << "dbusCall: Session QDBusInterface is invalid"; return false; } QList argumentList; QVariant appVar(app); QVariant arguVar(argList); argumentList.append(appVar); argumentList.append(arguVar); //如果要使用dbus接口的返回值,则应设置为阻塞的模式 QDBusReply reply = sessionInterface->call(QDBus::CallMode::Block, "startApp", appVar, arguVar); if (!(reply.isValid())) { qCDebug(UKUISMSERVER) << "call startApp getting error " << reply.error().name(); } if (reply.value()) { qCDebug(UKUISMSERVER) << "success restore " << app; return true; } return false; } void UKUISMServer::executeCommand(const QStringList &command) { if (command.isEmpty()) { return; } KProcess::execute(command); } void UKUISMServer::handlePendingInteractions() { //该函数在保存退出阶段,第一次进入该函数时,clientInteracting一定是nullptr if (m_clientInteracting) { return; } //遍历客户端,找到第一个正在挂起的客户端,将其初始化为m_clientInteracting foreach (UKUISMClient *c, m_clients) { if (c->m_pendingInteraction) { m_clientInteracting = c; c->m_pendingInteraction = false; break; } } //向m_clientInteracting授予交互权限 if (m_clientInteracting) { endProtection(); qCDebug(UKUISMSERVER) << "sending interact to " << m_clientInteracting->clientId(); SmsInteract(m_clientInteracting->connection()); } else { qCDebug(UKUISMSERVER) << "no more client is pending"; startProtection(); } } void UKUISMServer::startProtection() { //m_protectionTimer用于在第一个客户端获得交互权限前的定时保护作用,若存在一个进程处于忙碌状态,不处理我们对其发送的保存自身信号 //则会造成服务器一直等待该进程的响应,当定时器走完的时候,我们直接将所有客户端的状态设置为保存完毕,然后进行下一阶段的保存 m_protectionTimer.setSingleShot(true); m_protectionTimer.start(5000); qCDebug(UKUISMSERVER) << "start protectionTimer"; } void UKUISMServer::endProtection() { m_protectionTimer.stop(); } void UKUISMServer::launchWM(const QList &wmStartCommands) { assert(m_state == LaunchingWM); if (!(qEnvironmentVariableIsSet("WAYLAND_DISPLAY") || qEnvironmentVariableIsSet("WAYLAND_SOCKET"))) { m_wmProcess = startApplication(wmStartCommands[0], true); connect(m_wmProcess, SIGNAL(error(QProcess::ProcessError)), SLOT(wmProcessChange())); connect(m_wmProcess, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(wmProcessChange())); } } void UKUISMServer::tryRestoreNext() { if (m_state != Restoring) { return; } m_restoreTimer.stop(); KConfigGroup config(KSharedConfig::openConfig(), m_sessionGroup); while (m_appRestored < m_appsToStart) { //最开始m_appRestored为0,而rc文件中为0的为kwin,kwin已经被处理过,所以这里将++放在前,等于从id为1开始处理 QString n = QString::number(++m_appRestored); QString clientId = config.readEntry(QLatin1String("clientId") + n, QString()); QString clientName = config.readEntry(QLatin1String("program") + n, QString()); bool alreadyStarted = false; foreach (UKUISMClient *c, m_clients) { if (QString::fromLocal8Bit(c->clientId()) == clientId) { qCDebug(UKUISMSERVER) << c->program() << " is already started"; alreadyStarted = true; break; } else if (c->program() == clientName) { qCDebug(UKUISMSERVER) << c->program() << " already started"; alreadyStarted = true; break; } } if (alreadyStarted) { continue; } QStringList restartCommand = config.readEntry(QLatin1String("restartCommand") + n, QStringList()); if (restartCommand.isEmpty() || (config.readEntry(QStringLiteral("restartStyleHint") + n, 0) == SmRestartNever)) { continue; } if (isWM(config.readEntry( QStringLiteral("program") + n, QString()))) { continue; } if (config.readEntry(QStringLiteral("wasWm") + n, false)) { continue; } if (restoreApplication(restartCommand)) { m_lastIdRestore = clientId; if (!m_lastIdRestore.isEmpty()) { m_restoreTimer.setSingleShot(true); m_restoreTimer.start(2000); return; } } else { QCoreApplication::processEvents(); continue; } } qCDebug(UKUISMSERVER) << "finish restore all clients"; m_appRestored = 0; m_lastIdRestore.clear(); m_state = Idle; } bool UKUISMServer::isWM(const UKUISMClient *client) const { return isWM(client->program()); } bool UKUISMServer::isWM(const QString &program) const { //m_wm一般情况下是个完整的二进制程序路径,此处获得程序名,如果不是完整二进制路径,获取到的也是程序名, QString wmName = m_wm.mid(m_wm.lastIndexOf(QDir::separator()) + 1); QString programName = program.mid(program.lastIndexOf(QDir::separator()) + 1); return programName == wmName; } void UKUISMServer::changeClientOrder() { //桌面和控制栏等没有实现xsmp信号保存的应用会在interect done后发过来一个保存完成的信号,然后不等server的kill connection信号 //自己退出,所以将他们从客户端列表中去掉。 //这只是一个暂时解决的方法,应该由desktop和panel做出修改,在收到服务器的退出信号后才退出进程 //经过测试,即使将桌面和任务栏放到最后杀死,任然会出现桌面和任务栏先消失,应用还在的情况,要防止这种情况 //要么在storeSession后直接调用systemd的注销接口,要么让桌面和任务栏按照xsmp规范修改。 foreach (UKUISMClient *c, m_clients) { QString programPath = c->program(); QString programName = programPath.mid(programPath.lastIndexOf(QDir::separator()) + 1); if (programName == QLatin1String("ukui-panel")) { m_clients.removeAll(c); //改为任务栏不退出 // m_clients.append(c); } else if (programName == QLatin1String("peony-qt-desktop")) { m_clients.removeAll(c); //改为桌面不退出 // m_clients.append(c); } else if (programName == QLatin1String("ukui-tablet-desktop")) { //平板桌面的进程 m_clients.removeAll(c); } else if (programName == QLatin1String("et")) { //wps表格 if (!isWPSWinActive(programName)) { m_clients.removeAll(c); } else { m_clients.removeAll(c); m_clients.prepend(c); } } else if (programName == QLatin1String("wps")) { //wps文字 if (!isWPSWinActive(programName)) { m_clients.removeAll(c); } else { m_clients.removeAll(c); m_clients.prepend(c); } } else if (programName == QLatin1String("wpp")) { //wps演示 if (!isWPSWinActive(programName)) { m_clients.removeAll(c); } else { m_clients.removeAll(c); m_clients.prepend(c); } } } } bool UKUISMServer::syncDBusEnvironment() { QString program; QStringList args = {QStringLiteral("--systemd"), QStringLiteral("--all")};; QStringList env; QProcess p; if (!QStandardPaths::findExecutable(QStringLiteral("dbus-update-activation-environment")).isEmpty()) { program = "dbus-update-activation-environment"; } p.setEnvironment(QProcess::systemEnvironment() << env); p.setProcessChannelMode(QProcess::ForwardedChannels); if (!program.isEmpty()) { p.start(program, args); } else { qWarning() << "dbus-update-activation-environment don't exist"; return false; } p.waitForFinished(-1);//等待程序执行完成 if (p.exitCode()) { qWarning() << program << args << "exited with code" << p.exitCode(); } return p.exitCode() == 0;//QProcess::NormalExit 0 QProcess::CrashExit 1 } void UKUISMServer::syncSessionEnv(const QString &key, const QString &value) { QDBusInterface *sessionInterface = new QDBusInterface("org.gnome.SessionManager", "/org/gnome/SessionManager", "org.gnome.SessionManager", QDBusConnection::sessionBus(), this); if (!sessionInterface->isValid()) { qWarning() << "dbusCall: Session QDBusInterface is invalid"; return; } QList argumentList; QVariant envKey(key); QVariant envValue(value); argumentList.append(envKey); argumentList.append(envValue); qCDebug(UKUISMSERVER) << "sync env to ukui-session"; QDBusMessage msg = sessionInterface->callWithArgumentList(QDBus::CallMode::NoBlock, "setSessionEnv", argumentList); QString error = msg.errorMessage(); if (!error.isEmpty()) { qCDebug(UKUISMSERVER) << "the error is " << error; } } void UKUISMServer::executeBoxadm() { QProcess *proc = new QProcess; QString argu = "--lockall"; proc->start("boxadm", QStringList{argu}); proc->waitForFinished(-1); delete proc; } bool UKUISMServer::isWPSWinActive(const QString &wpsAPP) { bool isActive = false; FILE *fp; const char *command; char c; QString str; //反斜杠注意前面还要加一个反斜杠转义 if (wpsAPP == "et") { command = "xwininfo -id `xwininfo -tree -root |grep -E \"WPS表格|WPS 表格|WPS Spreadsheets|Spreadsheets\"|awk '{ print $1 }'`|grep \"Map\\s\\State\"|awk '{ print $3 }'"; } else if (wpsAPP == "wps") { command = "xwininfo -id `xwininfo -tree -root |grep -E \"WPS文字|WPS 文字|WPS Writer|Writer\"|awk '{ print $1 }'`|grep \"Map\\s\\State\"|awk '{ print $3 }'"; } else if (wpsAPP == "wpp") { command = "xwininfo -id `xwininfo -tree -root |grep -E \"WPS演示|WPS 演示|WPS Presentation|Presentation\"|awk '{ print $1 }'`|grep \"Map\\s\\State\"|awk '{ print $3 }'"; } fp = popen(command, "r"); int i = 0; while (1) { c = fgetc(fp); if (c == 10 || c == EOF) { break; } str += c; ++i; } qDebug() << str; if (str.isEmpty()) { qDebug() << "wps未启动"; } else if (str == QString("IsUnMapped")) { qDebug() << "wps启动但窗口已关闭"; } else if (str == QString("IsViewable")) { qDebug() << "wps已启动且窗口开启"; isActive = true; } pclose(fp); return isActive; } void UKUISMServer::deleteInactiveClients() { for (const auto &iter : m_activeClients) { if (iter.second == 0) { // qCDebug(UKUISMSERVER) << "m_clients remove inactive client " << iter.first->program(); m_clients.removeAll(iter.first); } } } void UKUISMServer::removeConnection(UKUISMConnection *conn) { delete conn; } void UKUISMServer::restoreSession() { m_appRestored = 0; m_lastIdRestore.clear(); m_state = Restoring; tryRestoreNext(); } bool UKUISMServer::isCloseSession() { qCDebug(UKUISMSERVER) << "m_state = " << m_state; return m_state >= Shutdown; } ukui-session-manager-4.0.0.1/ukuismserver/ukuismserverdebug.cpp0000644000175000017500000000170614553704175023402 0ustar fengfeng/* BEGIN_COMMON_COPYRIGHT_HEADER * (c)LGPL2+ * Copyright 2021 KylinSoft Co., Ltd. * * This program or library 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. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA * * END_COMMON_COPYRIGHT_HEADER */ #include "ukuismserverdebug.h" Q_LOGGING_CATEGORY(UKUISMSERVER, "org.ukui.ukuismserver") ukui-session-manager-4.0.0.1/ukuismserver/ukuismclient.cpp0000644000175000017500000001220714553704175022341 0ustar fengfeng/***************************************************************** ksmserver - the KDE session management server Copyright 2000 Matthias Ettrich Copyright 2005 Lubos Lunak Copyright 2021 KylinSoft Co., Ltd. relatively small extensions by Oswald Buddenhagen some code taken from the dcopserver (part of the KDE libraries), which is Copyright 1999 Matthias Ettrich Copyright 1999 Preston Brown Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ******************************************************************/ #include "ukuismclient.h" #include "ukuismserver.h" #include #include extern UKUISMServer*& getGlobalServer(const QString &wm = ""); /*用于生成客户端的唯一ID*/ Q_GLOBAL_STATIC(QString, my_addr) char * safeSmsGenerateClientID(SmsConn c) { char *ret = nullptr; if (!ret) { if (my_addr->isEmpty()) { char hostname[256]; if (gethostname(hostname, 255) != 0) { my_addr->sprintf("0%.8x", rand()); } else { int addr[4] = {0, 0, 0, 0}; int pos = 0; for (unsigned int i = 0; i < strlen(hostname); ++i, ++pos) { addr[pos % 4] += hostname[i]; } *my_addr = QStringLiteral("0"); for (int i = 0; i < 4; ++i) { *my_addr += QString::number(addr[i], 16); } } } ret = (char*)malloc(1 + my_addr->length() + 13 + 10 + 4 + 1 + 0); static int sequence = 0; if (ret == nullptr) { return nullptr; } sprintf(ret, "1%s%.13ld%.10d%.4d", my_addr->toLatin1().constData(), (long)time(nullptr), getpid(), sequence); sequence = (sequence + 1) % 10000; } return ret; } UKUISMClient::UKUISMClient(SmsConn conn) : m_smsConn(conn), m_id(nullptr) { resetState(); } UKUISMClient::~UKUISMClient() { } void UKUISMClient::resetState() { m_saveYourselfDone = false; m_pendingInteraction = false; m_waitForPhase2 = false; m_wasPhase2 = false; } void UKUISMClient::registerClient(const char *previousId) { m_id = previousId; if (!m_id) { m_id = safeSmsGenerateClientID(m_smsConn); } SmsRegisterClientReply(m_smsConn, (char*)m_id); //此处向应用发出保存自身信号,应用收到信号后,调用SmcSetProperties将应用的名称等信息注册到server SmsSaveYourself(m_smsConn, SmSaveLocal, false, SmInteractStyleNone, false); SmsSaveComplete(m_smsConn); getGlobalServer()->clientRegistered(previousId); } QString UKUISMClient::program() const { SmProp *p = property(SmProgram); if (!p || qstrcmp(p->type, SmARRAY8) || p->num_vals < 1) { return QString(); } return QLatin1String((const char*)p->vals[0].value); } QStringList UKUISMClient::restartCommand() const { QStringList result; SmProp *p = property(SmRestartCommand); if (!p || qstrcmp(p->type, SmLISTofARRAY8) || p->num_vals < 1) { return result; } for (int i = 0; i < p->num_vals; i++) { result += QLatin1String((const char*)p->vals[i].value); } return result; } QStringList UKUISMClient::discardCommand() const { QStringList result; SmProp *p = property(SmDiscardCommand); if (!p || qstrcmp(p->type, SmLISTofARRAY8) || p->num_vals < 1) { return result; } for (int i = 0; i < p->num_vals; i++) { result += QLatin1String((const char*)p->vals[i].value); } return result; } int UKUISMClient::restartStyleHint() const { SmProp *p = property(SmRestartStyleHint); if (!p || qstrcmp(p->type, SmCARD8) || p->num_vals < 1) { return SmRestartIfRunning; } return *((unsigned char*)p->vals[0].value); } SmProp *UKUISMClient::property(const char *name) const { foreach (SmProp *prop, m_properties) { if (!qstrcmp(prop->name, name)) { return prop; } } return nullptr; } QString UKUISMClient::userId() const { SmProp *p = property(SmUserID); if (!p || qstrcmp(p->type, SmARRAY8) || p->num_vals < 1) { return QString(); } return QLatin1String((const char*)p->vals[0].value); } ukui-session-manager-4.0.0.1/ukuismserver/ukuismserverdebug.h0000644000175000017500000000201414553704175023040 0ustar fengfeng/* BEGIN_COMMON_COPYRIGHT_HEADER * (c)LGPL2+ * Copyright 2021 KylinSoft Co., Ltd. * * This program or library 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. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA * * END_COMMON_COPYRIGHT_HEADER */ #ifndef UKUISMSERVERDEBUG_H #define UKUISMSERVERDEBUG_H #include Q_DECLARE_LOGGING_CATEGORY(UKUISMSERVER) #endif // UKUISMSERVERDEBUG_H ukui-session-manager-4.0.0.1/ukuismserver/main.cpp0000644000175000017500000001224414553704175020552 0ustar fengfeng#include "ukuismserver.h" #include "ukuismserverdebug.h" #include #include #include #include #include #include #include extern UKUISMServer*& getGlobalServer(const QString &wm = ""); void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) { QString logPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/ukui-session/ukuismserver.log"; //判断log文件是否存在 if (!QFile::exists(logPath)) { QString logDir = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/ukui-session"; //不存在时,创建ukui-session文件夹 QDir dir(logDir); if (!dir.exists(logDir)) { if (!dir.mkdir(logDir)) { return; } } //创建log文件 QFile file(logPath); if (!file.open(QIODevice::WriteOnly)) { return; } file.close(); } if (!QFile::exists(logPath)) { return; } QByteArray localMsg = msg.toLocal8Bit(); QDateTime dateTime = QDateTime::currentDateTime(); QByteArray time = QString("[%1] ").arg(dateTime.toString("MM-dd hh:mm:ss.zzz")).toLocal8Bit(); QString logMsg; switch (type) { case QtDebugMsg: logMsg = QString("%1 Debug: %2 (%3:%4, %5)\n") .arg(time.constData()) .arg(localMsg.constData()) .arg(context.file) .arg(context.line) .arg(context.function); break; case QtInfoMsg: logMsg = QString("%1 Info: %2 (%3:%4, %5)\n") .arg(time.constData()) .arg(localMsg.constData()) .arg(context.file) .arg(context.line) .arg(context.function); break; case QtWarningMsg: logMsg = QString("%1 Warning: %2 (%3:%4, %5)\n") .arg(time.constData()) .arg(localMsg.constData()) .arg(context.file) .arg(context.line) .arg(context.function); break; case QtCriticalMsg: logMsg = QString("%1 Critical: %2 (%3:%4, %5)\n") .arg(time.constData()) .arg(localMsg.constData()) .arg(context.file) .arg(context.line) .arg(context.function); break; case QtFatalMsg: logMsg = QString("%1 Fatal: %2 (%3:%4, %5)\n") .arg(time.constData()) .arg(localMsg.constData()) .arg(context.file) .arg(context.line) .arg(context.function); break; } //clear file content when it is too large QFile file(logPath); qint64 fileSize = file.size(); if (fileSize >= 1024 * 1024 * 10) { file.open(QFile::WriteOnly | QFile::Truncate); file.flush(); file.close(); } QFile logFile(logPath); logFile.open(QIODevice::WriteOnly | QIODevice::Append); QTextStream ts(&logFile); ts << logMsg << endl; logFile.flush(); logFile.close(); } void IoErrorHandler(IceConn iceConn) { getGlobalServer()->ioError(iceConn); } void openDubug() { UKUISMSERVER().setFilterRules(QLatin1Literal("org.ukui.ukuismserver=true")); qInstallMessageHandler(myMessageOutput); qCDebug(UKUISMSERVER) << "=================================================== UKUISMServer start. ==================================================="; } int main(int argc, char **argv) { openDubug(); QApplication a(argc, argv); a.setQuitOnLastWindowClosed(false); QCommandLineParser parser;//用于解析命令行参数,当程序以命令行形式执行时,可以解析命令行参数 parser.setApplicationDescription("ukuismserver"); parser.addHelpOption(); parser.addVersionOption(); QCommandLineOption restoreOption(QStringList() << QStringLiteral("r") << QStringLiteral("restore"), "Restores the saved user session if available"); parser.addOption(restoreOption); QCommandLineOption wmOption(QStringList() << QStringLiteral("w") << QStringLiteral("windowmanager"), "Starts in case no other window manager is \nparticipating in the session. Default is 'kwin'", "wm"); parser.addOption(wmOption); QCommandLineOption nolocalOption(QStringLiteral("nolocal"), "Also allow remote connections"); parser.addOption(nolocalOption); parser.process(a);//从a获取命令行参数, 也就是argc和argv QString wm = parser.value(wmOption);//应该是为了获取传入的窗管的值 为空 IceSetIOErrorHandler(IoErrorHandler); getGlobalServer(wm)->restoreWM(QStringLiteral("saved at previous logout")); qCDebug(UKUISMSERVER) << "ukuismserver register service"; QDBusConnection::sessionBus().registerService(QStringLiteral("org.ukui.ukuismserver")); return a.exec(); } ukui-session-manager-4.0.0.1/tools/0000755000175000017500000000000014553704753015515 5ustar fengfengukui-session-manager-4.0.0.1/tools/myiconlabel.cpp0000644000175000017500000000727414553704753020531 0ustar fengfeng/* * Copyright (C) 2023, 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 2, 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 "myiconlabel.h" #include #include #include #include #include #include #include #include MyIconLabel::MyIconLabel(int labelWidth, int iconWidth, QString path, QWidget *parent) { this->setFixedSize(labelWidth, labelWidth); this->setObjectName("iconlabel"); //iconLabel设置icon有锯齿,所以使用PushButton设置icon,同时在PushButton中将其收到的事件截断 m_btnIcon = new PushButton(this); m_btnIcon->setIcon(QIcon(path)); m_btnIcon->setIconSize(QSize(iconWidth, iconWidth)); m_btnIcon->setObjectName("btn"); m_btnIcon->setStyleSheet("QPushButton#btn{background-color: transparent;border:none;} \ QPushButton:checked { background-color: transparent;border:none;}\ QPushButton:hover { background-color: transparent;border:none;}"); m_btnIcon->setGeometry(QRect((width() - iconWidth)/2, (width() - iconWidth)/2, iconWidth, iconWidth)); m_btnIcon->setCheckable(false); m_btnIcon->setChecked(false); m_btnIcon->setGeometry(QRect((width() - iconWidth)/2, (width() - iconWidth)/2, iconWidth, iconWidth)); m_btnIcon->setAttribute(Qt::WA_TransparentForMouseEvents,true); //不再裁剪圆形窗口 //已经通过设置border-radius的方式画圆 //setMask裁剪的圆形锯齿感特别严重 //mask不要与控件一样大 锯齿明显 稍微大一点 //this->setMask(QRegion(this->x() - 1, this->y() - 1, this->width() + 2, this->height() + 2,QRegion::Ellipse)); //this->setStyleSheet("QLabel#"+ this->objectName() + "{background-color: rgb(255,255,255,40);border-radius:" + QString::number(this->width()/2) + "px;}"); //this->setPixmap(m_pixMap); this->setAlignment(Qt::AlignCenter); //setMouseTracking(true); } MyIconLabel::~MyIconLabel() { } bool MyIconLabel::event(QEvent *event) { // qDebug() << m_showBackColor << "event..." << event->type(); // if (!m_showBackColor) { // return QWidget::event(event); // } if (event->type() == QEvent::MouseButtonPress) { QString str = "QLabel{background-color: rgb(255,255,255,100);border-radius: " + QString::number(this->width()/2) + "px;}"; this->setStyleSheet(str); this->setAttribute(Qt::WA_StyledBackground); } else if (event->type() == QEvent::MouseButtonRelease) { QString str = "QLabel{background-color: rgb(255,255,255,80);border-radius: " + QString::number(this->width()/2) + "px;}"; this->setStyleSheet(str); this->setAttribute(Qt::WA_StyledBackground); emit mouseEventSignals(event); } else { emit mouseEventSignals(event); } return QWidget::event(event); } bool MyIconLabel::containsPoint(QPoint p) { QPainterPath path; path.addEllipse(QRect(0, 0, width(), height())); //qDebug() << "containsPoint..." << width() << height() << p << QRect(0,0,width(),height()); return path.contains(p); } ukui-session-manager-4.0.0.1/tools/xeventmonitor.h0000644000175000017500000000310114553704753020602 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2011 ~ 2017 Deepin, Inc. * Copyright (C) 2011 ~ 2017 Wang Yong. * Copyright (C) 2023, KylinSoft Co., Ltd. * * Author: Wang Yong * Maintainer: Wang Yong * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef XEVENTMONITOR_H #define XEVENTMONITOR_H #include #include #include class XEventMonitorPrivate; class XEventMonitor : public QThread { Q_OBJECT public: XEventMonitor(QObject *parent = 0); ~XEventMonitor(); Q_SIGNALS: void buttonPress(int x, int y); void buttonDrag(int x, int y); void buttonRelease(int x, int y); void keyPress(int keyCode); void keyRelease(int keyCode); void keyPress(const QString &key); void keyRelease(const QString &key); protected: void run(); private: XEventMonitorPrivate *d_ptr; Q_DECLARE_PRIVATE(XEventMonitor) }; #endif ukui-session-manager-4.0.0.1/tools/grab-x11.h0000644000175000017500000000142114553704753017206 0ustar fengfeng/* * Copyright (C) 2023, 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, 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 . * **/ #ifndef GRABX11_H #define GRABX11_H bool establishGrab(); bool closeGrab(); #endif // GRABX11_H ukui-session-manager-4.0.0.1/tools/ukuipower.h0000644000175000017500000000302214553704753017715 0ustar fengfeng/* BEGIN_COMMON_COPYRIGHT_HEADER * * Copyright: 2023, KylinSoft Co., Ltd. * Copyright: 2012 Razor team * * Authors: * Christian Surlykke * * This program or library 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. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA * * END_COMMON_COPYRIGHT_HEADER */ #ifndef UKUIPOWER_H #define UKUIPOWER_H #include #include class PowerProvider; class UkuiPower : public QObject { Q_OBJECT public: enum Action { PowerSwitchUser, PowerHibernate, PowerSuspend, PowerMonitorOff, PowerLogout, PowerReboot, PowerShutdown, }; explicit UkuiPower(QObject *parent = nullptr); virtual ~UkuiPower(); bool canAction(Action action) const; public slots: bool doAction(Action action); private: PowerProvider *m_systemdProvider; }; #endif // UKUIPOWER_H ukui-session-manager-4.0.0.1/tools/mainwindow.cpp0000644000175000017500000017636514553704753020417 0ustar fengfeng/*Copyright (C) 2023, 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 2, 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 "mainwindow.h" #include "commonpushbutton.h" #include "powerprovider.h" #include "mylistview.h" //#include //XTest.h/Xlib.h/XInput.h/X.h中定义了一个None,QStyleOption中也定义了None,会造成冲突,把QListView的头文件放到前面 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "grab-x11.h" #include "xeventmonitor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "plasma-shell-manager.h" QT_BEGIN_NAMESPACE extern void qt_blurImage(QPainter *p, QImage &blurImage, qreal radius, bool quality, bool alphaOnly, int transposed = 0); QT_END_NAMESPACE #define BLUR_RADIUS 300 #define BACKGROUND_SETTINGS "org.mate.background" QPixmap blurPixmap(QPixmap pixmap) { QPainter painter(&pixmap); QImage srcImg = pixmap.toImage(); qt_blurImage(&painter, srcImg, BLUR_RADIUS, false, false); //在设置Qt::WA_TranslucentBackground属性后,模糊图片会导致锁屏界面透明 //不设置上面这个属性,在wayland模式下也出现了透明 //因此这里修改image图形的alpha值为255. for (int y = 0;y < srcImg.height();++y) { QRgb *row = (QRgb*)srcImg.scanLine(y); for (int x = 0; x < srcImg.width(); ++x) { ((unsigned char*)&row[x])[3] = 255; } } painter.end(); return QPixmap::fromImage(srcImg); } QString getUserName(QFile *a) { QString user = getenv("USER"); if (a->exists()) { a->open(QIODevice::ReadOnly | QIODevice::Text); QTextStream fileStream(a); int k = 0; while (!fileStream.atEnd()) { QString line = fileStream.readLine(); if (k == 0) { QString a = line; qDebug() << "uid=" << a; struct passwd *user1; user1 = getpwuid(a.toInt()); qDebug() << "name=" << user1->pw_name << ",uid=" << user1->pw_uid; if (user1->pw_name == NULL) { return user; } user = user1->pw_name; } k++; } } return user; } MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , m_power(new UkuiPower(this)) , xEventMonitor(new XEventMonitor(this)) { const QByteArray bid(BACKGROUND_SETTINGS); if (QGSettings::isSchemaInstalled(bid)) { QGSettings *gset = new QGSettings(BACKGROUND_SETTINGS, "", this); QString fullstr = gset->get("picture-filename").toString(); qDebug() << "picture path = " << fullstr; QFileInfo fileInfo(fullstr); if (fileInfo.isFile() && pix.load(fullstr)) { //增加对pix的判断,有些图片格式qt不支持,无法读取,导致pix为null,引起程序崩溃 #bug75856 if (pix.isNull()) { pix.load(":/images/background-ukui.png"); pix = blurPixmap(pix); } else { pix = blurPixmap(pix); } } else { QString imagefile = "/usr/share/backgrounds/1-warty-final-ubuntukylin.jpg"; QFileInfo fileimage(imagefile); if (fileimage.isFile() && fileimage.exists() && pix.load(imagefile)) { pix = blurPixmap(pix); } else{ pix.load("/usr/share/ukui/ukui-session-manager/images/background-ukui.png"); pix = blurPixmap(pix); } } gset->deleteLater(); } else { pix.load("/usr/share/ukui/ukui-session-manager/images/background-ukui.png"); pix = blurPixmap(pix); } this->setObjectName("widget"); m_screen = QApplication::desktop()->screenGeometry(QCursor::pos()); m_toolWidget = new QWidget(this); m_toolWidget->setGeometry(m_screen); qDebug() << "m_toolWidget width:" << m_toolWidget->width()<height(); m_vBoxLayout = new QVBoxLayout(); m_buttonHLayout = new QGridLayout(); m_dateTimeLayout = new QVBoxLayout(); m_timeLabel = new QLabel(); m_dateLabel = new QLabel(); m_judgeWidgetVLayout = new QVBoxLayout(); m_judgeBtnHLayout = new QHBoxLayout(); m_judgeLabel = new QLabel(); m_messageLabel1 = new QLabel(); m_messageLabel2 = new QLabel(); m_scrollArea = new QScrollArea; m_scrollArea->setObjectName("scrollArea"); m_scrollArea->removeEventFilter(this); m_btnWidget = new QWidget(); m_btnWidget->setObjectName("btnWidget"); m_judgeWidget = new QWidget(this); m_judgeWidget->setObjectName("judgeWidget"); //m_showWarningArea 作为该界面所有组件的父指针,方便排版 m_showWarningArea = new QWidget(this); m_showWarningArea->setObjectName(QString::fromUtf8("area")); initialSystemMonitor(); initialBtn(); initialJudgeWidget(); //Make a hash-map to store tableNum-to-lastWidget if (m_power->canAction(UkuiPower::PowerHibernate)) { isHibernateHide = false; } if (m_power->canAction(UkuiPower::PowerSuspend)) { isSuspendHide = false; } if (m_power->canAction(UkuiPower::PowerLogout)) { isLogoutHide = false; } if (m_power->canAction(UkuiPower::PowerMonitorOff)) { isLockscreenHide = false; } if (m_power->canAction(UkuiPower::PowerReboot)) { isRebootHide = false; } if (m_power->canAction(UkuiPower::PowerShutdown)) { isPowerOffHide = false; } if (LockChecker::getCachedUsers() > 1 && m_power->canAction(UkuiPower::PowerSwitchUser)) { isSwitchuserHide = false; } m_btnHideMap.insert(m_switchUserBtn, isSwitchuserHide); m_btnHideMap.insert(m_hibernateBtn, isHibernateHide); m_btnHideMap.insert(m_suspendBtn, isSuspendHide); m_btnHideMap.insert(m_lockScreenBtn, isLockscreenHide); m_btnHideMap.insert(m_logoutBtn, isLogoutHide); m_btnHideMap.insert(m_rebootBtn, isRebootHide); m_btnHideMap.insert(m_shutDownBtn, isPowerOffHide); // initialBtnCfg(); map.insert(0, m_switchUserBtn); map.insert(1, m_hibernateBtn); map.insert(2, m_suspendBtn); map.insert(3, m_lockScreenBtn); map.insert(4, m_logoutBtn); map.insert(5, m_rebootBtn); map.insert(6, m_shutDownBtn); gs = new QGSettings("org.ukui.session", "/org/ukui/desktop/session/"); tableNum = -1; changeBtnState("empty"); //更新显示时间的控件 initialDateTimeWidget(); QTimer *timeWidgetUpdateTimer = new QTimer(this); connect(timeWidgetUpdateTimer, &QTimer::timeout, this, &MainWindow::updateDateTimerWidget); timeWidgetUpdateTimer->start(1000); ResizeEvent(); m_vBoxLayout->addStretch(20); m_vBoxLayout->addLayout(m_dateTimeLayout, 80); m_vBoxLayout->addStretch(120); m_vBoxLayout->addWidget(m_scrollArea, 640); m_vBoxLayout->addStretch(120); m_vBoxLayout->addWidget(m_systemMonitorBtn, 48, Qt::AlignHCenter); m_vBoxLayout->addStretch(62); m_vBoxLayout->setSpacing(0); m_vBoxLayout->setContentsMargins((m_screen.width() - m_scrollArea->width() - 20)/2,0,(m_screen.width() - m_scrollArea->width() - 20)/2,0); qDebug() << "width..........." << m_judgeLabel->width() << m_scrollArea->width() << m_messageLabel1->width() << m_messageLabel2->width(); //根据屏幕分辨率与鼠标位置重设界面 //m_screen = QApplication::desktop()->screenGeometry(QCursor::pos()); //setFixedSize(QApplication::primaryScreen()->virtualSize()); setGeometry(0, 0, QApplication::primaryScreen()->virtualSize().width(), QApplication::primaryScreen()->virtualSize().height()); move(0, 0);//设置初始位置的值 //设置窗体无边框,不可拖动拖拽拉伸;为顶层窗口,无法被切屏;不使用窗口管理器 if(QString(qgetenv("XDG_SESSION_TYPE")) == "wayland") { setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); } else if (QString(qgetenv("XDG_SESSION_TYPE")) == "x11") { setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint); } setFocusPolicy(Qt::NoFocus); // setAttribute(Qt::WA_TranslucentBackground, true);//设定该窗口透明显示 m_toolWidget->setLayout(m_vBoxLayout); //setCentralWidget(m_toolWidget); qDebug() << "m_toolWidget...." << m_toolWidget->geometry(); qDebug() << "pos..." << QCursor::pos() << this->geometry(); qDebug() << "m_screen..." << m_screen; QString platform = QGuiApplication::platformName(); if(QString(qgetenv("XDG_SESSION_TYPE")) == "wayland") { PlasmaShellManager::getInstance()->setAppWindowKeepAbove(true); } //screencount changed QDesktopWidget *desktop = QApplication::desktop(); connect(desktop, &QDesktopWidget::screenCountChanged, this, &MainWindow::screenCountChanged); connect(desktop, &QDesktopWidget::resized, this, &MainWindow::screenCountChanged); connect(desktop, &QDesktopWidget::workAreaResized, this, &MainWindow::screenCountChanged); connect(desktop, &QDesktopWidget::primaryScreenChanged, this, &MainWindow::screenCountChanged); qDebug() << "m_btnWidget FixedHeight000:" << m_btnWidget->width() << m_scrollArea->width(); if (platform.startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) { //wayland interface } else { /*捕获键盘,如果捕获失败,那么模拟一次esc按键来退出菜单,如果仍捕获失败,则放弃捕获*/ if (establishGrab()) { qDebug() << "establishGrab : true"; } else { qDebug() << "establishGrab : false"; XTestFakeKeyEvent(QX11Info::display(), XKeysymToKeycode(QX11Info::display(), XK_Escape), True, 1); XTestFakeKeyEvent(QX11Info::display(), XKeysymToKeycode(QX11Info::display(), XK_Escape), False, 1); XFlush(QX11Info::display()); sleep(1); if (!establishGrab()) { qDebug() << "establishGrab : false again!"; // exit(1); } } // KeyPress, KeyRelease, ButtonPress, ButtonRelease and MotionNotify events has been redirected connect(xEventMonitor, SIGNAL(keyPress(const QString &)), this, SLOT(onGlobalKeyPress(const QString &))); connect(xEventMonitor, SIGNAL(keyRelease(const QString &)), this, SLOT(onGlobalkeyRelease(const QString &))); xEventMonitor->start(); qApp->installNativeEventFilter(this); } } MainWindow::~MainWindow() { delete m_power; delete xEventMonitor; } void MainWindow::initialSystemMonitor() { m_systemMonitorHLayout = new QHBoxLayout(); m_systemMonitorBtn = new QWidget(this); m_systemMonitorIconLabel = new QLabel(); m_systemMonitorLabel = new QLabel(m_systemMonitorBtn); m_systemMonitorBtn->setObjectName("systemMonitor"); QFont font = QApplication::font(); QFontMetrics fm(font); m_systemMonitorLabel->setText(QApplication::tr("system-monitor")); m_systemMonitorLabel->setStyleSheet("color: white");//; font: 12pt int btnWidth = fm.boundingRect(m_systemMonitorLabel->text()).width() + 40; qDebug() << "btnWidth:" << btnWidth << fontMetrics().width(m_systemMonitorLabel->width()) ; m_systemMonitorBtn->setFixedSize(btnWidth, 48); QString str = "QWidget#systemMonitor{background-color: transparent;border-radius: " + QString::number(m_systemMonitorBtn->height()/2) + "px;}"; //QString str = "QWidget#systemMonitor{background-color: rgb(255,255,255,40);border-radius: " + QString::number(m_systemMonitorBtn->height()/2) + "px;}"; m_systemMonitorBtn->setStyleSheet(str); m_systemMonitorBtn->setAttribute(Qt::WA_StyledBackground); QRegion region0(m_systemMonitorBtn->x() + m_systemMonitorBtn->height()/2 + 1, m_systemMonitorBtn->y() - 1, m_systemMonitorBtn->width()-m_systemMonitorBtn->height(), m_systemMonitorBtn->height() + 2); QRegion region1(m_systemMonitorBtn->x() - 1, m_systemMonitorBtn->y() - 1, m_systemMonitorBtn->height() + 2, m_systemMonitorBtn->height() + 2, QRegion::Ellipse); QRegion region2(m_systemMonitorBtn->x() + m_systemMonitorBtn->width() - m_systemMonitorBtn->height(), m_systemMonitorBtn->y() - 1, m_systemMonitorBtn->height() + 2, m_systemMonitorBtn->height() + 2, QRegion::Ellipse); QRegion region = region0 + region1 + region2; m_systemMonitorBtn->setMask(region); m_systemMonitorLabel->setAlignment(Qt::AlignCenter); m_systemMonitorHLayout->setAlignment(Qt::AlignCenter); m_systemMonitorHLayout->addWidget(m_systemMonitorLabel,fm.boundingRect(m_systemMonitorLabel->text()).width()); m_systemMonitorBtn->setLayout(m_systemMonitorHLayout); m_systemMonitorBtn->installEventFilter(this); qDebug() << "m_systemMonitorIcon:" << m_systemMonitorIcon.width() << m_systemMonitorIcon.height() << m_systemMonitorIconLabel->width() << m_systemMonitorIconLabel->height(); qDebug() << "m_systemMonitorBtn:" << m_systemMonitorBtn->height(); } void MainWindow::initialBtn() { m_switchUserBtn = new MyPushButton(m_btnImagesPath+"/switchuser.svg", QApplication::tr("Switch User"), "switchuser", m_scrollArea); m_hibernateBtn = new MyPushButton(m_btnImagesPath+"/hibernate.svg", QApplication::tr("Hibernate"), "hibernate", m_scrollArea); m_suspendBtn = new MyPushButton(m_btnImagesPath+"/suspend.svg", QApplication::tr("Suspend"), "suspend", m_scrollArea); m_logoutBtn = new MyPushButton(m_btnImagesPath+"/logout.svg", QApplication::tr("Log Out"), "logout", m_scrollArea); m_rebootBtn = new MyPushButton(m_btnImagesPath+"/reboot.svg", QApplication::tr("Restart"), "reboot", m_scrollArea); m_shutDownBtn = new MyPushButton(m_btnImagesPath+"/shutdown.svg", QApplication::tr("Shut Down"), "shutdown", m_scrollArea); m_lockScreenBtn = new MyPushButton(m_btnImagesPath+"/lockscreen.svg", QApplication::tr("Lock Screen"), "lockscreen", m_scrollArea); //ui->setupUi(this); m_switchUserBtn->installEventFilter(this); m_hibernateBtn->installEventFilter(this); m_suspendBtn->installEventFilter(this); m_lockScreenBtn->installEventFilter(this); m_logoutBtn->installEventFilter(this); m_rebootBtn->installEventFilter(this); m_shutDownBtn->installEventFilter(this); connect(m_switchUserBtn, &MyPushButton::mouseRelase, this, &MainWindow::mouseReleaseSlots); connect(m_hibernateBtn, &MyPushButton::mouseRelase, this, &MainWindow::mouseReleaseSlots); connect(m_suspendBtn, &MyPushButton::mouseRelase, this, &MainWindow::mouseReleaseSlots); connect(m_lockScreenBtn, &MyPushButton::mouseRelase, this, &MainWindow::mouseReleaseSlots); connect(m_logoutBtn, &MyPushButton::mouseRelase, this, &MainWindow::mouseReleaseSlots); connect(m_rebootBtn, &MyPushButton::mouseRelase, this, &MainWindow::mouseReleaseSlots); connect(m_shutDownBtn, &MyPushButton::mouseRelase, this, &MainWindow::mouseReleaseSlots); } void MainWindow::initialJudgeWidget() { QStringList userlist = LockChecker::getLoginedUsers(); if (userlist.count() > 1) { close_system_needed_to_confirm = true; } QString tips = QApplication::tr("Multiple users are logged in at the same time.Are you sure " "you want to close this system?"); m_judgeLabel->setText(tips); m_judgeLabel->setStyleSheet("color:white");//;font:12pt; m_judgeLabel->setObjectName("label"); //m_judgeLabel->setGeometry(0,0,m_screen.width(),50); //m_judgeLabel->setFixedHeight(60); qDebug() << "m_judgeLabel width:" << m_judgeLabel->width() << m_judgeLabel->height(); m_judgeLabel->setAlignment(Qt::AlignHCenter); m_judgeLabel->setWordWrap(true); m_cancelBtn = new CommonPushButton(QApplication::tr("cancel"), QString::fromUtf8("cancelButton"), 120, 48, 8); m_confirmBtn = new CommonPushButton(QApplication::tr("confirm"), QString::fromUtf8("confirmButton"), 120, 48, 8); m_judgeBtnHLayout->setAlignment(Qt::AlignHCenter); m_judgeBtnHLayout->setSpacing(0); m_judgeBtnHLayout->addWidget(m_cancelBtn); m_judgeBtnHLayout->addSpacing(24); m_judgeBtnHLayout->addWidget(m_confirmBtn); m_judgeWidgetVLayout->addSpacing(10); m_judgeWidgetVLayout->addWidget(m_judgeLabel); m_judgeWidgetVLayout->addSpacing(10); m_judgeWidgetVLayout->addLayout(m_judgeBtnHLayout); m_judgeWidgetVLayout->setAlignment(Qt::AlignCenter); m_judgeWidgetVLayout->setContentsMargins(0,0,0,0); connect(m_cancelBtn, &CommonPushButton::clicked, this, &MainWindow::exitt); connect(m_confirmBtn, &CommonPushButton::clicked, [&]() { emit confirmButtonclicked(); }); m_judgeWidget->setLayout(m_judgeWidgetVLayout); m_judgeWidget->setStyleSheet("QWidget#judgeWidget{background-color: transparent;}"); m_judgeWidget->setVisible(false); m_judgeWidget->setGeometry(m_screen.x() + (m_screen.width() - 800 * m_screen.width()/1920)/2, m_screen.y() + (m_screen.height() - 200)/2 - 10, 800 * m_screen.width()/1920, 200); } void MainWindow::initialDateTimeWidget() { QDateTime current_date_time = QDateTime::currentDateTime(); const QByteArray id_control("org.ukui.control-center.panel.plugins"); QString current_date; QString current_time; if (QGSettings::isSchemaInstalled(id_control)) { QGSettings *controlSetting = new QGSettings(id_control, QByteArray(), this); QString formate_a = controlSetting->get("date").toString(); QString formate_b = controlSetting->get("hoursystem").toString(); if (formate_a == "en") current_date = current_date_time.toString("yyyy-MM-dd ddd"); else if (formate_a == "cn") current_date = current_date_time.toString("yyyy/MM/dd ddd"); else current_date = current_date_time.toString("yyyy-MM-dd ddd"); if (formate_b == "12") current_time = current_date_time.toString("A hh:mm"); else if (formate_b == "24") current_time = current_date_time.toString("hh:mm"); else current_time = current_date_time.toString("hh:mm"); } else { current_date = current_date_time.toString("yyyy-MM-dd ddd"); current_time = current_date_time.toString("hh:mm"); } m_timeLabel->setText(current_time); m_dateLabel->setText(current_date); // m_timeLabel->setFont(QFont("Noto Sans CJK SC", 28, 50)); // m_dateLabel->setFont(QFont("Noto Sans CJK SC", 12, 50)); m_timeLabel->setStyleSheet("color: white; font: 28pt"); m_dateLabel->setStyleSheet("color: white; font: 12pt"); m_dateLabel->setAlignment(Qt::AlignHCenter | Qt::AlignTop); m_timeLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); m_dateLabel->setObjectName("date_label"); m_timeLabel->setObjectName("time_lable"); m_dateTimeLayout->addStretch(); m_dateTimeLayout->addWidget(m_timeLabel); // m_messageVLayout->addSpacing(10); //m_dateTimeLayout->setStretch(0,1); m_dateTimeLayout->addWidget(m_dateLabel); //m_dateTimeLayout->setStretch(1,2); } void MainWindow::updateDateTimerWidget() { QDateTime current_date_time = QDateTime::currentDateTime(); const QByteArray id_control("org.ukui.control-center.panel.plugins"); QString current_date; QString current_time; if (QGSettings::isSchemaInstalled(id_control)) { QGSettings *controlSetting = new QGSettings(id_control, QByteArray(), this); QString formate_a = controlSetting->get("date").toString(); QString formate_b = controlSetting->get("hoursystem").toString(); if (formate_a == "en") current_date = current_date_time.toString("yyyy-MM-dd ddd"); else if (formate_a == "cn") current_date = current_date_time.toString("yyyy/MM/dd ddd"); else current_date = current_date_time.toString("yyyy-MM-dd ddd"); if (formate_b == "12") current_time = current_date_time.toString("A hh:mm"); else if (formate_b == "24") current_time = current_date_time.toString("hh:mm"); else current_time = current_date_time.toString("hh:mm"); } else { current_date = current_date_time.toString("yyyy-MM-dd ddd"); current_time = current_date_time.toString("hh:mm"); } m_timeLabel->setText(current_time); m_dateLabel->setText(current_date); } void MainWindow::initialBtnCfg() { bool newIniFile = false;//ini文件是否为新建文件 QString iniDir = "/usr/share/ukui/ukui-session-manager/config"; if(!QFile::exists(iniDir + "/btnconfig.ini")){ qDebug() << "btnconfig.ini file is not exists!!!"; QDir dir(iniDir); if(!dir.exists(iniDir)){ if(dir.mkdir(iniDir)){//目前创建不成功 没有权限 QFile iniFile(iniDir + "/btnconfig.ini"); if(iniFile.open(QIODevice::WriteOnly)){ newIniFile = true; iniFile.close(); } qDebug() << "inifile open faile!"; } else qDebug() << "create inidir faile!"; } } m_btnCfgSetting = new QSettings("/usr/share/ukui/ukui-session-manager/config/btnconfig.ini", QSettings::IniFormat); if (newIniFile) {//貌似路径下文件只可读不可写 m_btnCfgSetting->setValue("btn/SwitchUserBtnHide", false); m_btnCfgSetting->setValue("btn/HibernateBtnHide", false); m_btnCfgSetting->setValue("btn/LockScreenBtnHide", false); m_btnCfgSetting->setValue("btn/LogoutBtnHide", false); m_btnCfgSetting->setValue("btn/RebootBtnHide", false); m_btnCfgSetting->setValue("btn/ShutDownBtnHide", false); m_btnCfgSetting->setValue("btn/SuspendBtnHide", false); } m_btnCfgSetting->setValue("btn/SwitchUserBtnHide", isSwitchuserHide); m_btnCfgSetting->setValue("btn/HibernateBtnHide", isHibernateHide); m_btnCfgSetting->setValue("btn/SuspendBtnHide", isSuspendHide); m_btnCfgSetting->setValue("btn/LogoutBtnHide", isLogoutHide); m_btnCfgSetting->setValue("btn/RebootBtnHide", isRebootHide); m_btnCfgSetting->setValue("btn/ShutDownBtnHide", isPowerOffHide); qDebug() << "isHibernateHide..." << isHibernateHide; m_btnHideMap.insert(m_switchUserBtn, isSwitchuserHide); m_btnHideMap.insert(m_hibernateBtn, isHibernateHide); m_btnHideMap.insert(m_suspendBtn, isSuspendHide); m_btnHideMap.insert(m_lockScreenBtn, m_btnCfgSetting->value("btn/LockScreenBtnHide").toBool()); m_btnHideMap.insert(m_logoutBtn, isLogoutHide); m_btnHideMap.insert(m_rebootBtn, isRebootHide); m_btnHideMap.insert(m_shutDownBtn, isPowerOffHide); } void MainWindow::setLayoutWidgetVisible(QLayout* layout, bool show) { for (int i = 0;i < layout->count(); i++) { QLayoutItem*item = layout->layout()->itemAt(i); if (item->widget() != nullptr) { item->widget()->setVisible(show); } } } void MainWindow::changeBtnState(QString btnName, bool isKeySelect) { for(auto item = map.begin(); item != map.end(); item++) { if (btnName == QString("empty")) { item.value()->changeIconBackColor((item.value()->objectName() == btnName), isKeySelect); } if (isKeySelect) { item.value()->changeIconBackColor((item.value()->objectName() == btnName), isKeySelect); } else { if (item.value()->objectName() == btnName) { item.value()->changeIconBackColor((item.value()->objectName() == btnName), isKeySelect); } if (lastWidget && lastWidget->objectName() == item.value()->objectName() && lastWidget->objectName() != btnName) { item.value()->changeIconBackColor(false, isKeySelect); } } } } void MainWindow::mouseReleaseSlots(QEvent *event, QString objName) { for (auto iter = map.begin(); iter != map.end(); iter++) { if (iter.value()->getIconLabel()->objectName() == objName) { changePoint(iter.value(), event); if (event->type() == QEvent::MouseButtonRelease) { qDebug() << "mouseReleaseSlots..." << objName; doEvent(objName, iter.key()); return; } } } } void MainWindow::screenCountChanged() { QDesktopWidget *desktop = QApplication::desktop(); qDebug() << "inside screenCountChanged,screenCount = " << desktop->screenCount() << QApplication::desktop()->screenGeometry(QCursor::pos()).width() << QApplication::desktop()->screenGeometry(QCursor::pos()).height(); //setGeometry(desktop->geometry()); //updateGeometry(); //move(0,0); //setFixedSize(QApplication::primaryScreen()->virtualSize()); setGeometry(0, 0, QApplication::primaryScreen()->virtualSize().width(), QApplication::primaryScreen()->virtualSize().height()); ResizeEvent(); update(); } void MainWindow::calculateBtnSpan(int allNum, int lineMaxNum, MyPushButton *btn, int &row, int &colum) { int afterHideNum = 0; for (int i = 0; i < m_btnHideMap.count(); i++) { if (map.key(btn) > i) { if (m_btnHideMap.value(map.value(i))) { afterHideNum++; } } else if(map.key(btn) == i) { colum = (i - afterHideNum) % lineMaxNum; row = (i - afterHideNum) / lineMaxNum; return; } } } void MainWindow::showNormalBtnWidget(int hideNum) { int margins = 0; margins = (m_screen.width() -160 - 128 * (7 - hideNum)) / (6-hideNum); qDebug() << "showNormalBtnWidget hideNum:" << hideNum << "margins:" << margins; m_lineNum = 1; int btnWidgetWidth = 0; if (margins > 60) { //m_buttonHLayout->addWidget(m_listView); btnWidgetWidth = (128 * (7 - hideNum) + 60 * (6 - hideNum)); m_buttonHLayout->setHorizontalSpacing(60); } else { btnWidgetWidth = 128 * (7 - hideNum) + margins * (6 - hideNum); m_buttonHLayout->setHorizontalSpacing(margins); } m_btnWidget->setGeometry(QRect(m_btnWidget->x(),m_btnWidget->y(),btnWidgetWidth+ 24, 632 * m_screen.height()/1080)); m_btnWidget->setContentsMargins(0,0,0,100 * m_screen.height()/1080); m_scrollArea->setGeometry(QRect(m_scrollArea->x(),m_scrollArea->y(),btnWidgetWidth + 24, 632 * m_screen.height()/1080)); m_scrollArea->setContentsMargins(0,0,0,0); m_scrollArea->verticalScrollBar()->setVisible(false); m_scrollArea->verticalScrollBar()->setDisabled(true); m_scrollArea->verticalScrollBar()->setStyleSheet("QScrollBar{ background: transparent; margin-top:0px;margin-bottom:0px ; }"\ "QScrollBar:vertical{width: 0px;background: transparent;border-radius:3px;}"\ "QScrollBar::handle:vertical{width: 0px; background: rgba(255,255,255, 40); border-radius:3px;}"\ "QScrollBar::handle:vertical:hover{width: 0px; background: rgba(255,255,255, 60); border-radius:3px;}"\ "QScrollBar::add-line:vertical{width:0px;height:0px}"\ "QScrollBar::sub-line:vertical{width:0px;height:0px}"); //m_buttonHLayout->setContentsMargins(0,0,0,(m_scrollArea->height() - m_switchUserBtn->height()) * 3/5); for (int i = 0;i < m_buttonHLayout->count(); i++) { QLayoutItem*item = m_buttonHLayout->layout()->itemAt(i); if (item->widget() != nullptr) { m_buttonHLayout->removeWidget(item->widget()); } } m_buttonHLayout->addWidget(m_switchUserBtn,0,0); m_buttonHLayout->addWidget(m_hibernateBtn,0,1); m_buttonHLayout->addWidget(m_suspendBtn,0,2); m_buttonHLayout->addWidget(m_lockScreenBtn,0,3); m_buttonHLayout->addWidget(m_logoutBtn,0,4); m_buttonHLayout->addWidget(m_rebootBtn,0,5); m_buttonHLayout->addWidget(m_shutDownBtn,0,6); m_btnWidgetNeedScrollbar = false; } void MainWindow::showHasScrollBarBtnWidget(int hideNum) { int allBtnNum = 7 - hideNum; int lineWidth = m_screen.width() - 160 - 6; int lineMaxBtnNum = (lineWidth - 128)/188 + 1; m_lineNum = allBtnNum/lineMaxBtnNum + ((allBtnNum%lineMaxBtnNum > 0) ? 1 : 0); int needHeight = (m_lineNum * 171 + (m_lineNum - 1) * 32); int btnWidgetHeight = 632; qDebug() << "showHasScrollBarBtnWidget lineWidth:" << lineWidth << "lineMaxBtnNum:" << lineMaxBtnNum << "needHeight:" << needHeight << "lineNum:" << m_lineNum<< allBtnNum/lineMaxBtnNum << allBtnNum%lineMaxBtnNum; calculateBtnSpan(allBtnNum, lineMaxBtnNum, m_switchUserBtn, m_switchRow, m_switchColumn); calculateBtnSpan(allBtnNum, lineMaxBtnNum, m_hibernateBtn, m_hibernateRow, m_hibernateColumn); calculateBtnSpan(allBtnNum, lineMaxBtnNum, m_suspendBtn, m_suspendRow, m_suspendColumn); calculateBtnSpan(allBtnNum, lineMaxBtnNum, m_lockScreenBtn, m_lockScreenRow, m_lockScreenColumn); calculateBtnSpan(allBtnNum, lineMaxBtnNum, m_logoutBtn, m_logoutRow, m_logoutColumn); calculateBtnSpan(allBtnNum, lineMaxBtnNum, m_rebootBtn, m_rebootRow, m_rebootColumn); calculateBtnSpan(allBtnNum, lineMaxBtnNum, m_shutDownBtn, m_shutDownRow, m_shutDownColumn); // qDebug() << "switchRow:" << m_switchRow << m_switchColumn; // qDebug() << "hibernateRow:" << m_hibernateRow << m_hibernateColumn; // qDebug() << "suspendRow:" << m_suspendRow << m_suspendColumn; // qDebug() << "lockScreenRow:" << m_lockScreenRow << m_lockScreenColumn; // qDebug() << "logoutRow:" << m_logoutRow << m_logoutColumn; // qDebug() << "rebootRow:" << m_rebootRow << m_rebootColumn; // qDebug() << "shutDownRow:" << m_shutDownRow << m_shutDownColumn; { m_scrollArea->verticalScrollBar()->setVisible(!m_judgeLabel->isVisible()); m_scrollArea->verticalScrollBar()->setDisabled(false); m_scrollArea->verticalScrollBar()->setStyleSheet("QScrollBar{ background: transparent; margin-top:0px;margin-bottom:0px ; }"\ "QScrollBar:vertical{width: 6px;background: transparent;border-radius:3px;}"\ "QScrollBar::handle:vertical{width: 6px; background: rgba(255,255,255, 40); border-radius:3px;}"\ "QScrollBar::handle:vertical:hover{width: 6px; background: rgba(255,255,255, 60); border-radius:3px;}"\ "QScrollBar::add-line:vertical{width:0px;height:0px}"\ "QScrollBar::sub-line:vertical{width:0px;height:0px}"); qDebug() << "set bar pos..."; //m_scrollArea->verticalScrollBar()->setGeometry(QRect(lineWidth + 20, 0, 6, 100)); m_scrollArea->setContentsMargins(0,0,0,0); m_scrollArea->setGeometry(QRect(0,0,128 * lineMaxBtnNum + 60 * (lineMaxBtnNum-1) + 6, btnWidgetHeight * m_screen.height()/1080)); m_buttonHLayout->setContentsMargins(0,0,0,0);// (needHeight > 632 ? 0 : (632 - needHeight)) * m_screen.height()/1080); m_btnWidget->setGeometry(QRect(0,0,128 * lineMaxBtnNum + 60 * (lineMaxBtnNum-1), needHeight)); m_btnWidget->setContentsMargins(6,0,12,0); qDebug() << "m_btnWidget FixedHeight:" << needHeight << btnWidgetHeight << m_btnWidget->width() << m_scrollArea->width(); qDebug() << "isSwitchuserHide:" << isSwitchuserHide << "isHibernateHide:" << isHibernateHide << "isSuspendHide:" << isSuspendHide; for (int i = 0;i < m_buttonHLayout->count(); i++) { QLayoutItem*item = m_buttonHLayout->layout()->itemAt(i); if (item->widget() != nullptr) { m_buttonHLayout->removeWidget(item->widget()); } } m_buttonHLayout->addWidget(m_switchUserBtn, m_switchRow, m_switchColumn); m_buttonHLayout->addWidget(m_hibernateBtn, m_hibernateRow, m_hibernateColumn); m_buttonHLayout->addWidget(m_suspendBtn, m_suspendRow, m_suspendColumn); m_buttonHLayout->addWidget(m_lockScreenBtn, m_lockScreenRow, m_lockScreenColumn); m_buttonHLayout->addWidget(m_logoutBtn, m_logoutRow, m_logoutColumn); m_buttonHLayout->addWidget(m_rebootBtn, m_rebootRow, m_rebootColumn); m_buttonHLayout->addWidget(m_shutDownBtn, m_shutDownRow, m_shutDownColumn); m_buttonHLayout->setHorizontalSpacing(60); m_buttonHLayout->setAlignment(Qt::AlignHCenter); m_btnWidgetNeedScrollbar = true; } } void MainWindow::ResizeEvent() { m_screen = QApplication::desktop()->screenGeometry(QCursor::pos()); setGeometry(0, 0, QApplication::primaryScreen()->virtualSize().width(), QApplication::primaryScreen()->virtualSize().height()); //处理界面停留在阻止提醒界面时拔插屏幕出现的问题 if (m_showWarningMesg) { showInhibitWarning(m_inhibitList, defaultnum); m_toolWidget->setGeometry(m_screen); return; } m_judgeWidget->setGeometry(m_screen.x() + (m_screen.width() - 800 * m_screen.width()/1920)/2, m_screen.y() + (m_screen.height() - 200)/2 - 10,800 * m_screen.width()/1920, 200); if(m_judgeWidget->isVisible()) return; qDebug() << "ResizeEvent moveWidget m_screen:" << m_screen.width() << m_screen.height(); int hideNum = 0; for (int i = 0; i < m_btnHideMap.count(); i++) { if (m_btnHideMap.value(map.value(i))) { hideNum++; } } // Move the widget to the direction where they should be for (int i = 0; i <= 6; i++) { if (m_btnHideMap.value(map.value(i))) { map[i]->hide(); } else { map[i]->show(); } } if((m_screen.width() -160 - 128 * (7 - hideNum))/(6-hideNum) >= 16) { showNormalBtnWidget(hideNum); } else { showHasScrollBarBtnWidget(hideNum); } m_btnWidget->setStyleSheet("QWidget#btnWidget{background-color: transparent;}"); m_btnWidget->setLayout(m_buttonHLayout); m_scrollArea->horizontalScrollBar()->setVisible(false); m_scrollArea->horizontalScrollBar()->setDisabled(true); m_scrollArea->setWidget(m_btnWidget); m_scrollArea->setStyleSheet("QScrollArea#scrollArea{background-color: transparent;}"); m_scrollArea->setAlignment(Qt::AlignCenter); qDebug() << "m_scrollArea geometry:" << m_scrollArea->geometry(); qDebug() << "m_btnWidget geometry:" << m_btnWidget->geometry(); qDebug() << "m_buttonHLayout geometry:" << m_buttonHLayout->geometry(); m_vBoxLayout->setContentsMargins((m_screen.width() - m_scrollArea->width() - 20)/2,0,(m_screen.width() - m_scrollArea->width() - 20)/2,0); //m_scrollArea->adjustSize(); //m_scrollArea->setWidgetResizable(true); //行 rowMap.clear(); rowMap.insert(0, m_switchRow); rowMap.insert(1, m_hibernateRow); rowMap.insert(2, m_suspendRow); rowMap.insert(3, m_lockScreenRow); rowMap.insert(4, m_logoutRow); rowMap.insert(5, m_rebootRow); rowMap.insert(6, m_shutDownRow); //列 columMap.clear(); columMap.insert(0, m_switchColumn); columMap.insert(1, m_hibernateColumn); columMap.insert(2, m_suspendColumn); columMap.insert(3, m_lockScreenColumn); columMap.insert(4, m_logoutColumn); columMap.insert(5, m_rebootColumn); columMap.insert(6, m_shutDownColumn); m_toolWidget->setGeometry(m_screen); } // Paint the background picture void MainWindow::paintEvent(QPaintEvent *e) { QPainter painter(this); painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing); painter.setPen(Qt::transparent); painter.setBrush(QColor(0, 0, 0, 78)); // 178 for (QScreen *screen: QApplication::screens()) { // draw picture to every screen QRect rect = screen->geometry(); painter.drawPixmap(rect, pix.scaled(screen->size())); //drawRect可以避免在白色壁纸的情况下模糊背景造成的问题 painter.drawRect(rect); } QWidget::paintEvent(e); } //lock screen void MainWindow::doLockscreen() { gs->set("win-key-release", false); QString SS_DBUS_SERVICE = "org.ukui.ScreenSaver"; QString SS_DBUS_PATH = "/"; QString SS_DBUS_INTERFACE = "org.ukui.ScreenSaver"; QString displayNum = QString(qgetenv("DISPLAY")).replace(":", "").replace(".", "_"); qDebug() << "dispaly = " << displayNum ; QString sessionDbus = QString("%1%2").arg(SS_DBUS_SERVICE).arg(displayNum); QDBusInterface *interface = new QDBusInterface(sessionDbus, SS_DBUS_PATH, SS_DBUS_INTERFACE); if (!interface->isValid()) { qDebug() << "interface not valid"; delete interface; interface = new QDBusInterface(SS_DBUS_SERVICE, SS_DBUS_PATH, SS_DBUS_INTERFACE); } /*监听锁屏起来的信号,再执行界面退出操作,规避点击锁屏后先显示桌面再打开锁屏 QDBusConnection::sessionBus().connect(QString("org.ukui.ScreenSaver"), QString("/"), QString("org.ukui.ScreenSaver"), QString("lock"), this, SLOT(exitt()));*/ QDBusMessage msg = interface->call("Lock"); //延迟界面退出操作,规避点击锁屏后先显示桌面再打开锁屏 // QTimer::singleShot(500, this, SLOT(exitt())); exit(0); } void MainWindow::doSystemMonitor() { qDebug() << "doSystemMonitor...."; QProcess::startDetached("ukui-system-monitor", QStringList()); exitt(); } // handle mouse-clicked event bool MainWindow::eventFilter(QObject *obj, QEvent *event) { if (obj->objectName() == "systemMonitor") { if (event->type() == QEvent::MouseButtonRelease) { doSystemMonitor(); } else { if (event->type() == QEvent::Leave) { QString str = "QWidget#systemMonitor{background-color: transparent;border-radius: " + QString::number(m_systemMonitorBtn->height()/2) + "px;}"; m_systemMonitorBtn->setStyleSheet(str); m_systemMonitorBtn->setAttribute(Qt::WA_StyledBackground); //tableNum = -1; flag = false; changeBtnState("empty"); } else if (event->type() == QEvent::Enter) { QString str = "QWidget#systemMonitor{background-color: rgb(255,255,255,40);border-radius: " + QString::number(m_systemMonitorBtn->height()/2) + "px;}"; m_systemMonitorBtn->setStyleSheet(str); m_systemMonitorBtn->setAttribute(Qt::WA_StyledBackground); //tableNum = -1; flag = true; changeBtnState("empty"); } else if (event->type() == QEvent::MouseButtonPress) { QString str = "QWidget#systemMonitor{background-color: rgb(255,255,255,80);border-radius: " + QString::number(m_systemMonitorBtn->height()/2) + "px;}"; m_systemMonitorBtn->setStyleSheet(str); m_systemMonitorBtn->setAttribute(Qt::WA_StyledBackground); } } } if(event->type() == QEvent::WindowActivate){ if(QString(qgetenv("XDG_SESSION_TYPE")) == "wayland") { PlasmaShellManager::getInstance()->setAppWindowKeepAbove(true); } //QTimer::singleShot(20,this,&MainWindow::laterActivate); } return QWidget::eventFilter(obj, event); } void MainWindow::changePoint(QWidget *widget, QEvent *event) { if (event->type() == QEvent::Enter) { changeBtnState(widget->objectName()); } if (event->type() == QEvent::Leave) { flag = false; lastWidget = widget; } } void MainWindow::doEvent(QString test, int i) { qDebug() << "doevent... i:" << i << test << inhibitShutdown << inhibitSleep << close_system_needed_to_confirm; defaultnum = i; QString platform = QGuiApplication::platformName(); if (platform.startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) { //wayland interface } else { if (click_blank_space_need_to_exit) { if (closeGrab()) { qDebug() << "success to close Grab"; } else { qDebug() << "failure to close Grab"; } } } switch (i) { case (1) : case (2) : { //获取inhibitor,不为空展示界面,为空直接执行动作 QVector res = LockChecker::listInhibitor("sleep"); if (res.isEmpty()) { qDebug() << "Start do action" << test << defaultnum; this->hide(); emit signalTostart(); } else { showInhibitWarning(res, i); } break; } case (3) : { doLockscreen(); break; } case (4) : { QVector res = LockChecker::listInhibitor("shutdown"); if (res.isEmpty()) { qDebug() << "Start do action" << test << defaultnum; this->hide(); emit signalTostart(); } else { showInhibitWarning(res, i); } break; } case (5) : case (6) : { QVector res = LockChecker::listInhibitor("shutdown"); if (res.isEmpty()) { if (close_system_needed_to_confirm) { connect(this, &MainWindow::confirmButtonclicked, [&](){ gs->set("win-key-release", false); qDebug() << "Start do action" << defaultnum; emit signalTostart(); }); this->judgeboxShow(); } else { gs->set("win-key-release", false); qDebug() << "Start do action" << defaultnum; emit signalTostart(); } } else { showInhibitWarning(res, i); } break; } default : { qDebug() << "Start do action" << test << defaultnum; this->hide(); emit signalTostart(); break; } } } // handle the blank-area mousePressEvent void MainWindow::mousePressEvent(QMouseEvent *event) { if (click_blank_space_need_to_exit) { QPainterPath path; QRect rect(m_systemMonitorBtn->geometry()); const qreal radius = m_systemMonitorBtn->height()/2; path.moveTo(rect.topRight() - QPointF(radius, 0)); path.lineTo(rect.topLeft() + QPointF(radius, 0)); path.quadTo(rect.topLeft(), rect.topLeft() + QPointF(0, radius)); path.lineTo(rect.bottomLeft() + QPointF(0, -radius)); path.quadTo(rect.bottomLeft(), rect.bottomLeft() + QPointF(radius, 0)); path.lineTo(rect.bottomRight() - QPointF(radius, 0)); path.quadTo(rect.bottomRight(), rect.bottomRight() + QPointF(0, -radius)); path.lineTo(rect.topRight() + QPointF(0, radius)); path.quadTo(rect.topRight(), rect.topRight() + QPointF(-radius, -0)); qDebug() << "mousePressEvent" << m_switchUserBtn->geometry() << m_scrollArea->mapFromGlobal(event->pos()); if (!m_suspendBtn->getIconLabel()->containsPoint(m_suspendBtn->getIconLabel()->mapFromGlobal(event->pos())) && !m_hibernateBtn->getIconLabel()->containsPoint(m_hibernateBtn->getIconLabel()->mapFromGlobal(event->pos())) && !m_lockScreenBtn->getIconLabel()->containsPoint(m_lockScreenBtn->getIconLabel()->mapFromGlobal(event->pos())) && !m_switchUserBtn->getIconLabel()->containsPoint(m_switchUserBtn->getIconLabel()->mapFromGlobal(event->pos())) && !m_logoutBtn->getIconLabel()->containsPoint(m_logoutBtn->getIconLabel()->mapFromGlobal(event->pos())) && !m_rebootBtn->getIconLabel()->containsPoint(m_rebootBtn->getIconLabel()->mapFromGlobal(event->pos())) && !m_shutDownBtn->getIconLabel()->containsPoint(m_shutDownBtn->getIconLabel()->mapFromGlobal(event->pos())) && !path.contains(m_toolWidget->mapFromGlobal(event->pos()))) { exitt(); } } } bool MainWindow::exitt() { gs->set("win-key-release", false); if(QString(qgetenv("XDG_SESSION_TYPE")) == "xcb") { if (closeGrab()) { qDebug() << "success to close Grab"; } else { qDebug() << "failure to close Grab"; } } close(); exit(0); } bool MainWindow::judgeBtnIsEnable(int index) { if (!m_btnHideMap.value(map.value(index))) { return true; } return false; } bool MainWindow::matchKeyBtn(int row, int colum) { for (int j = 6; j >= 0; j--) { if (rowMap.value(j) == row && columMap.value(j) == colum && judgeBtnIsEnable(j)) { tableNum = j; return true; } } return false; } void MainWindow::calculateKeyBtn(const QString &key) { if (key == "Left") { if (tableNum == 0 || tableNum == -1) { for (int i = 6; i >= 0; i--) { if (judgeBtnIsEnable(i)) { tableNum = i; return; } } } else { for (int i = tableNum - 1; i >= 0; i--) { if (judgeBtnIsEnable(i)) { tableNum = i; return; } } for (int i = 6; i >= tableNum + 1; i--) { if (judgeBtnIsEnable(i)) { tableNum = i; return; } } } } else if (key == "Right") { if (tableNum == 6 || tableNum == -1) { for (int i = 0; i <= 6; i++) { if (judgeBtnIsEnable(i)) { tableNum = i; return; } } } else { for (int i = tableNum + 1; i <= 6; i++) { if (judgeBtnIsEnable(i)) { tableNum = i; return; } } for (int i = 0; i <= tableNum - 1; i++) { if (judgeBtnIsEnable(i)) { tableNum = i; return; } } } } else if (m_lineNum > 1 && key == "Up") { if (tableNum == -1) { for (int i = m_lineNum - 1; i >= 0; i--) { if(matchKeyBtn(i, 0)) return; } } int tableRow = rowMap.value(tableNum); int tableColum = columMap.value(tableNum); for (int i = 1; i < tableRow + 1; i++) { if (matchKeyBtn(tableRow - i, tableColum)) return; } for (int i = m_lineNum - 1; i > tableRow; i--) { if(matchKeyBtn(i, tableColum)) return; } } else if (m_lineNum > 1 && key == "Down") { if (tableNum == -1) { for (int i = 0; i < m_lineNum; i++) { if (matchKeyBtn(i, 0)) return; } } int tableRow = rowMap.value(tableNum); int tableColum = columMap.value(tableNum); for (int i = tableRow + 1; i < m_lineNum; i++) { if (matchKeyBtn(i, tableColum)) return; } for (int i = 0; i < tableRow + 1; i++) { if (matchKeyBtn(i, tableColum)) return; } } } void MainWindow::onGlobalKeyPress(const QString &key) { } // handle "Esc","Left","Right","Enter" keyPress event void MainWindow::onGlobalkeyRelease(const QString &key) { if(QString(qgetenv("XDG_SESSION_TYPE")) != "wayland") { return; } qDebug() << "key: " << key << "flag:" << flag << "click_blank_space_need_to_exit:" << click_blank_space_need_to_exit; if (!click_blank_space_need_to_exit && m_enterJudgeBox) { return; } /*else if (!click_blank_space_need_to_exit && m_enterInhibitList) { //处理阻止列表界面的键盘逻辑 int totalRow = m_inhibitDataModel->rowCount(); if (key == "Escape") { exitt(); } else if (key == "Tab") { if (m_isFirstEnterInhibitList) { m_inhibitAppList->setCurrentIndex(m_inhibitDataModel->index(0, 0)); m_currentListSelect = 0; m_isFirstEnterInhibitList = false; } else { if (!m_isCancelBtnSelect) { QString str = "QWidget#cancelBtn{background-color: rgb(255,255,255,80);border: 1px solid #296CD9; border-radius: " + QString::number(m_inhibitCancelBtn->height()/2) + "px;}"; m_inhibitCancelBtn->setStyleSheet(str); m_inhibitCancelBtn->setAttribute(Qt::WA_StyledBackground); m_inhibitAppList->clearSelection(); m_isCancelBtnSelect = true; } else { QString str = "QWidget#cancelBtn{background-color: rgb(255,255,255,40);border-radius: " + QString::number(m_inhibitCancelBtn->height()/2) + "px;}"; m_inhibitCancelBtn->setStyleSheet(str); m_inhibitCancelBtn->setAttribute(Qt::WA_StyledBackground); m_inhibitAppList->setCurrentIndex(m_inhibitDataModel->index(0, 0)); m_currentListSelect = 0; m_isCancelBtnSelect = false; } } } else if (key == "Down") { if (!m_isCancelBtnSelect) { m_isFirstEnterInhibitList = false; if (m_currentListSelect < totalRow - 1) { m_inhibitAppList->setCurrentIndex(m_inhibitDataModel->index(++m_currentListSelect, 0)); } else if (m_currentListSelect == totalRow - 1) { m_inhibitAppList->setCurrentIndex(m_inhibitDataModel->index(0, 0)); m_currentListSelect = 0; } } } else if (key == "Up") { if (!m_isCancelBtnSelect) { m_isFirstEnterInhibitList = false; if (m_currentListSelect > 0) { m_inhibitAppList->setCurrentIndex(m_inhibitDataModel->index(--m_currentListSelect, 0)); } else if (m_currentListSelect <= 0) { m_inhibitAppList->setCurrentIndex(m_inhibitDataModel->index(totalRow - 1, 0)); m_currentListSelect = totalRow - 1; } } } else if (key == "Return" || key == "KP_Enter") { if (m_isCancelBtnSelect) { exitt(); } } }*/ else { if (key == "Escape") { exitt(); } else if (key == "Left" || key == "Right" || key == "Up" || key == "Down") { if (!m_isSystemMonitorSelect) { m_isFirstEnter = false; int oldNum = tableNum; calculateKeyBtn(key); qDebug() << "key...." << oldNum << tableNum; if (oldNum == tableNum) return; QString button = map[tableNum]->objectName(); if (m_btnWidgetNeedScrollbar) { int currentLine = rowMap[tableNum]; QScrollBar *scrollBar = m_scrollArea->verticalScrollBar(); if (currentLine == 0) { scrollBar->setValue(0); } else { int sValue = scrollBar->maximum() - scrollBar->minimum(); float scale = (currentLine * 180 + currentLine * m_buttonHLayout->verticalSpacing() + 128) * 1.0 / (m_btnWidget->height() * 1.0); scrollBar->setValue(sValue * scale); } } changeBtnState(button, true); } } else if (key == "Return" || key == "KP_Enter") { // space,KP_Enter if (m_isSystemMonitorSelect) { doSystemMonitor(); } else { for (auto item = map.begin(); item != map.end(); item++) { if (item.value()->getIsKeySelect()) return doEvent(item.value()->objectName(), item.key()); } for (auto item = map.begin(); item != map.end(); item++) { if (item.value()->getIsMouseSelect()) { return doEvent(item.value()->objectName(), item.key()); } } } } /*else if (key == "Tab") { if (m_isFirstEnter) { tableNum = -1; calculateKeyBtn("Right"); QString button = map[tableNum]->objectName(); changeBtnState(button, true); m_isFirstEnter = false; } else { if (!m_isSystemMonitorSelect) { QString str = "QWidget#systemMonitor{background-color: rgb(255,255,255,80);border: 1px solid #296CD9; border-radius: " + QString::number(m_systemMonitorBtn->height()/2) + "px;}"; m_systemMonitorBtn->setStyleSheet(str); m_systemMonitorBtn->setAttribute(Qt::WA_StyledBackground); flag = true; changeBtnState("empty", true); m_isSystemMonitorSelect = true; } else { tableNum = -1; calculateKeyBtn("Right"); QString button = map[tableNum]->objectName(); changeBtnState(button, true); QString str = "QWidget#systemMonitor{background-color: transparent;border-radius: " + QString::number(m_systemMonitorBtn->height()/2) + "px;}"; m_systemMonitorBtn->setStyleSheet(str); m_systemMonitorBtn->setAttribute(Qt::WA_StyledBackground); flag = false; m_isSystemMonitorSelect = false; } } }*/ } } void MainWindow::showInhibitWarning(QVector &list, int action) { QRect mainScreen; QList screens = QApplication::screens(); QPoint ptf(QCursor::pos()); for (QScreen *screen : screens) { QRect rec = screen->geometry(); if (rec.contains(ptf)) { mainScreen = rec;//获取鼠标所在屏幕 } } //进入应用阻止列表后点击一些事件不会再被响应 click_blank_space_need_to_exit = false; m_enterInhibitList = true; for (int j = 0; j < 7; j++) { map[j]->hide();//隐藏界面上原有的部件 } m_systemMonitorBtn->hide(); m_scrollArea->verticalScrollBar()->setVisible(false); drawWarningWindow(mainScreen, list, action); } void MainWindow::drawWarningWindow(QRect &rect, QVector &list, int action) { int xx = rect.x(); int yy = rect.y();//用于设置相对位置 bool isEnoughBig = m_screen.height() - 266 - 467 > 0 ? true : false; m_showWarningArea->setGeometry(0, 0, isEnoughBig ? 740 : 1200 * m_screen.width()/1920, isEnoughBig ? 467 : (500 * m_screen.height()/1080)); QVBoxLayout *vBoxLayout = new QVBoxLayout(); //顶部提醒信息 QLabel *tips = new QLabel(m_showWarningArea); tips->setObjectName(QString::fromUtf8("tips")); tips->setGeometry(0, 0, isEnoughBig ? 740 : m_showWarningArea->width(), 27); tips->setWordWrap(true); QString str; //defaultnum会在doevent中初始化为按钮的编号,结合defaultnum判断可以保证sleep和shutdown都被阻止时能够正确显示信息 switch (action) { case 1 : str = QObject::tr("The following program is running to prevent the system from hibernate!"); break; case 2 : str = QObject::tr("The following program is running to prevent the system from suspend!"); break; case 4: str = QObject::tr("The following program is running to prevent the system from logout!"); break; case 5 : str = QObject::tr("The following program is running to prevent the system from reboot!"); break; case 6 : str = QObject::tr("The following program is running to prevent the system from shutting down!"); break; } tips->setText(str); tips->setAlignment(Qt::AlignCenter); tips->setContentsMargins(0,0,0,0); tips->setStyleSheet(QString::fromUtf8("color:white"));//;font:14pt; //数据模型 m_inhibitDataModel = new QStandardItemModel(this); for (auto iter = list.begin(); iter != list.end(); ++iter) { QIcon icon; QString appName = iter->name; QString iconName = iter->icon; if (!iconName.isEmpty() && QIcon::hasThemeIcon(iconName)) { icon = QIcon::fromTheme(iconName); } else if (QIcon::hasThemeIcon("application-x-desktop")) { icon = QIcon::fromTheme("application-x-desktop"); } m_inhibitDataModel->appendRow(new QStandardItem(icon, appName)); } //列表视图 m_inhibitAppList = new MyListView(m_showWarningArea); m_inhibitAppList->setObjectName(QString::fromUtf8("applist")); if (isEnoughBig) { m_inhibitAppList->setFixedSize(520 * (isEnoughBig ? 1: m_screen.width()/1920), 320 * (isEnoughBig ? 1 : m_screen.height()/1080)); } else { m_inhibitAppList->setGeometry(0,0,520 * (isEnoughBig ? 1: m_screen.width()/1920), 320 * (isEnoughBig ? 1 : m_screen.height()/1080)); } m_inhibitAppList->verticalScrollMode(); m_inhibitAppList->setStyleSheet("QListView#applist{font:10pt;color:white;background-color: rgb(255,255,255,80);border-style: outset;border-width: 0px;border-radius: 6px;}\ QListView#applist::item{height:48px;margin-top:2px;border-radius: 6px;}\ QListView#applist::item::selected {background-color: rgb(255,255,255,80);border: 1px solid #296CD9;\ height:48px;margin-top:2px;border-radius: 6px;}\ QListView#applist::item::hover {background-color: rgb(255,255,255,80);height:48px;margin-top:2px;border-radius: 6px;}"); m_inhibitAppList->setEditTriggers(QAbstractItemView::NoEditTriggers); m_inhibitAppList->setIconSize(QSize(32,32)); m_inhibitAppList->setModel(m_inhibitDataModel); m_inhibitAppList->setMinimumHeight(40); m_inhibitAppList->verticalScrollBar()->setStyleSheet("QScrollBar{ background: transparent; margin-top:3px;margin-bottom:3px ; }"\ "QScrollBar:vertical{width: 6px;background: transparent;border-radius:3px;}"\ "QScrollBar::handle:vertical{width: 6px; background: rgba(255,255,255, 40); border-radius:3px;}"\ "QScrollBar::handle:vertical:hover{width: 6px; background: rgba(255,255,255, 60); border-radius:3px;}"\ "QScrollBar::add-line:vertical{width:0px;height:0px}"\ "QScrollBar::sub-line:vertical{width:0px;height:0px}"); //继续操作按钮 QHBoxLayout *hBoxLayout = new QHBoxLayout(); QString confirBTnText; if (inhibitSleep) { if (defaultnum == 1) { confirBTnText = (QObject::tr("Still Hibernate")); } else if (defaultnum == 2) { confirBTnText = (QObject::tr("Still Suspend")); } } if (inhibitShutdown) { if (defaultnum == 5) { confirBTnText = (QObject::tr("Still Reboot")); } else if (defaultnum == 6) { confirBTnText = (QObject::tr("Still Shutdown")); } } CommonPushButton *confirmBtn = new CommonPushButton(confirBTnText, QString::fromUtf8("confirmBtn"), 120, 48, 24, m_showWarningArea); connect(confirmBtn, &CommonPushButton::clicked, [this]() { gs->set("win-key-release", false); qDebug() << "Start do action" << defaultnum; if (closeGrab()) { qDebug() << "success to close Grab"; } else { qDebug() << "failure to close Grab"; } this->signalTostart(); }); //取消按钮 m_inhibitCancelBtn = new CommonPushButton(QObject::tr("Cancel"), QString::fromUtf8("cancelBtn"), 120, 48, 24, m_showWarningArea); // cancelBtn->setStyleSheet("QPushButton#cancelBtn {\ // background-color: rgb(255,255,255,80);\ // border-style: outset;\ // border-width: 0px;\ // border-radius: 24px;}"); connect(m_inhibitCancelBtn, &CommonPushButton::clicked, this, &MainWindow::exitt); qDebug() << "applist->width():" << m_inhibitAppList->width() << m_inhibitCancelBtn->width(); //hBoxLayout->setContentsMargins(0,0,0,0); //hBoxLayout->setSpacing(isEnoughBig ? 24 : 12); hBoxLayout->addStretch(); //hBoxLayout->addWidget(confirmBtn); hBoxLayout->addWidget(m_inhibitCancelBtn); hBoxLayout->addStretch(); vBoxLayout->addWidget(tips); vBoxLayout->addWidget(m_inhibitAppList, 0, Qt::AlignHCenter); vBoxLayout->addSpacing(isEnoughBig ? 32 : 0); vBoxLayout->addLayout(hBoxLayout, Qt::AlignHCenter); //移动整个区域到指定的相对位置 m_showWarningArea->move(xx + (m_screen.width() - m_showWarningArea->width()) / 2, (yy + 266 * m_screen.height()/1080) + (isEnoughBig ? 0 : 10)); // applist->move((area->width() - applist->width()) / 2, isEnoughBig ? 51 : 32); m_showWarningArea->setContentsMargins(0,0,0,0); m_showWarningArea->setLayout(vBoxLayout); m_showWarningArea->show(); m_showWarningMesg = true; m_inhibitList = list; } void MainWindow::judgeboxShow() { click_blank_space_need_to_exit = false; m_enterJudgeBox = true; for (int j = 0; j < 7; j++) { map[j]->hide(); } m_scrollArea->verticalScrollBar()->setVisible(false); setLayoutWidgetVisible(m_dateTimeLayout, false); setLayoutWidgetVisible(m_judgeWidgetVLayout, true); m_judgeWidget->setVisible(true); } bool MainWindow::nativeEventFilter(const QByteArray &eventType, void *message, long *result) { if (qstrcmp(eventType, "xcb_generic_event_t") != 0) { return false; } xcb_generic_event_t *event = reinterpret_cast(message); const uint8_t responseType = event->response_type & ~0x80; if (responseType == XCB_CONFIGURE_NOTIFY) { xcb_configure_notify_event_t *xc = reinterpret_cast(event); if (xc->event == QX11Info::appRootWindow()) { XRaiseWindow(QX11Info::display(), this->winId()); XFlush(QX11Info::display()); //raise(); } return false; } else if (responseType == XCB_PROPERTY_NOTIFY) { //raise(); XRaiseWindow(QX11Info::display(), this->winId()); XFlush(QX11Info::display()); } return false; } ukui-session-manager-4.0.0.1/tools/powerprovider.h0000644000175000017500000000361614553704753020603 0ustar fengfeng/* BEGIN_COMMON_COPYRIGHT_HEADER * * Copyright: 2023, KylinSoft Co., Ltd. * Copyright: 2012 Razor team * * Authors: * Christian Surlykke * * This program or library 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. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA * * END_COMMON_COPYRIGHT_HEADER */ #ifndef POWERPROVIDER_H #define POWERPROVIDER_H #include #include "ukuipower.h" class PowerProvider : public QObject { Q_OBJECT public: explicit PowerProvider(QObject *parent = nullptr); virtual ~PowerProvider(); virtual bool canAction(UkuiPower::Action action) const = 0; public slots: virtual bool doAction(UkuiPower::Action action) = 0; }; class SystemdProvider: public PowerProvider { Q_OBJECT public: SystemdProvider(QObject *parent = nullptr); ~SystemdProvider(); bool canAction(UkuiPower::Action action) const; public slots: bool doAction(UkuiPower::Action action); private: bool canSwitchUser() const; bool doSwitchUser(); }; class UKUIProvider: public PowerProvider { Q_OBJECT public: UKUIProvider(QObject *parent = nullptr); ~UKUIProvider(); bool canAction(UkuiPower::Action action) const; public slots: bool doAction(UkuiPower::Action action); }; #endif // POWERPROVIDER_H ukui-session-manager-4.0.0.1/tools/grab-x11.cpp0000644000175000017500000000444114553704753017546 0ustar fengfeng/* * Copyright (C) 2023, 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, 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 "grab-x11.h" #include #include #include class XServerGraber { public: XServerGraber() { xcb_grab_server(QX11Info::connection()); } ~XServerGraber() { xcb_ungrab_server(QX11Info::connection()); xcb_flush(QX11Info::connection()); } }; static bool grabKeyboard() { int rv = XGrabKeyboard(QX11Info::display(), QX11Info::appRootWindow(), True, GrabModeAsync, GrabModeAsync, CurrentTime); return (rv == GrabSuccess); } static bool grabMouse() { #define GRABEVENTS ButtonPressMask | ButtonReleaseMask | PointerMotionMask | \ EnterWindowMask | LeaveWindowMask | KeyPressMask | KeyReleaseMask int rv = XGrabPointer(QX11Info::display(), QX11Info::appRootWindow(), True, GRABEVENTS, GrabModeAsync, GrabModeAsync, None, None, CurrentTime); #undef GRABEVENTS return (rv == GrabSuccess); } bool establishGrab() { XSync(QX11Info::display(), False); XServerGraber xserverGraber; Q_UNUSED(xserverGraber); if (!grabKeyboard()) { return false; } //不抢占鼠标事件 // if(!grabMouse()) { // XUngrabKeyboard(QX11Info::display(), CurrentTime); // XFlush(QX11Info::display()); // return false; // } return true; } bool closeGrab() { XSync(QX11Info::display(), False); XServerGraber xserverGraber; Q_UNUSED(xserverGraber); XUngrabKeyboard(QX11Info::display(), CurrentTime); XUngrabPointer(QX11Info::display(), CurrentTime); XFlush(QX11Info::display()); return true; } ukui-session-manager-4.0.0.1/tools/loginedusers.cpp0000644000175000017500000000245714553704753020734 0ustar fengfeng/* * Copyright (C) 2023, 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 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 "loginedusers.h" QDBusArgument &operator<<(QDBusArgument &argument, const LoginedUsers &mystruct) { argument.beginStructure(); argument << mystruct.uid << mystruct.userName << mystruct.objpath;//< mystruct.usergroup; argument.endStructure(); return argument; } const QDBusArgument &operator>>(const QDBusArgument &argument, LoginedUsers &mystruct) { argument.beginStructure(); argument >> mystruct.uid >> mystruct.userName >> mystruct.objpath;// >> mystruct.usergroup; argument.endStructure(); return argument; } ukui-session-manager-4.0.0.1/tools/myiconlabel.h0000644000175000017500000000267114553704753020172 0ustar fengfeng/* * Copyright (C) 2023, 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 2, 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 MYICONLABEL_H #define MYICONLABEL_H #include #include #include #include #include #include #include #include #include #include #include "pushbutton.h" class MyIconLabel : public QLabel { Q_OBJECT public: MyIconLabel(int labelWidth, int iconWidth, QString path, QWidget *parent = nullptr); ~MyIconLabel(); bool containsPoint(QPoint p); protected: bool event(QEvent *); private: PushButton *m_btnIcon = nullptr; bool m_showBackColor = false; QPoint m_point; protected: public: signals: void mouseEventSignals(QEvent *event); }; #endif // MYICONLABEL_H ukui-session-manager-4.0.0.1/tools/pushbutton.cpp0000644000175000017500000000260414553704753020436 0ustar fengfeng/* * Copyright (C) 2023, 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 2, 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 "pushbutton.h" #include #include #include PushButton::PushButton(QWidget *parent) :QPushButton(parent) { } PushButton::~PushButton() { } void PushButton::keyPressEvent(QKeyEvent *keyEvent) { qDebug() << "keyPressEvent..." << keyEvent->key(); return; } bool PushButton::event(QEvent *e) { if (e->type() == QEvent::FocusIn) { e->accept(); //qDebug() << "keyrelease" << static_cast(e)->key(); return true; } if (e->type() == QEvent::KeyRelease || e->type() == QEvent::KeyPress) { e->accept(); return true; } return QPushButton::event(e); } ukui-session-manager-4.0.0.1/tools/plasma-shell-manager.h0000644000175000017500000000341614553704753021664 0ustar fengfeng/* * Copyright (C) 2023, 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 2, 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 PLASMASHELLMANAGER_H #define PLASMASHELLMANAGER_H #include #include #include #include #include class PlasmaShellManager : public QObject { Q_OBJECT public: static PlasmaShellManager *getInstance(); bool setAppWindowActive(); bool setAppWindowKeepAbove(bool keep); bool setMaximized(QWindow *window); bool setRole(QWindow *window, KWayland::Client::PlasmaShellSurface::Role role); bool setPos(QWindow *window, const QPoint &pos); bool supportPlasmaShell(); bool supportShell(); bool supportPlasmaWindowManagement(); private: explicit PlasmaShellManager(QObject *parent = nullptr); KWayland::Client::PlasmaShell *m_plasmaShell = nullptr; KWayland::Client::Shell *m_shell = nullptr; KWayland::Client::PlasmaWindowManagement *m_windowManager = nullptr; KWayland::Client::PlasmaWindow *m_appWindow = nullptr; bool isFirstCreate = true; }; #endif // PLASMASHELLMANAGER_H ukui-session-manager-4.0.0.1/tools/CMakeLists.txt0000644000175000017500000000330014553704175020247 0ustar fengfengfind_package(PkgConfig) find_package(KF5Screen) find_package(KF5Wayland) include_directories(${KF5Wayland_LIBRARIES}) pkg_check_modules(KScreen REQUIRED kscreen2) set(tools_SRCS main.cpp ukuipower.cpp powerprovider.cpp mainwindow.h mainwindow.cpp grab-x11.cpp grab-x11.h xeventmonitor.cpp xeventmonitor.h loginedusers.h lockchecker.h lockchecker.cpp loginedusers.cpp ../ukui-session/xdgdirs.cpp ../ukui-session/xdgdesktopfile.cpp myiconlabel.cpp myiconlabel.h mypushbutton.cpp mypushbutton.h pushbutton.cpp pushbutton.h commonpushbutton.cpp commonpushbutton.h plasma-shell-manager.cpp plasma-shell-manager.h mylistview.cpp mylistview.h ukuilockinfo.h ukuilockinfo.cpp ../ukui-session/xdgdesktopfile.h ../ukui-session/xdgdesktopfile.cpp ../ukui-session/xdgdirs.h ../ukui-session/xdgdirs.cpp ../ukui-session/xdgmacros.h ) find_package(PkgConfig) pkg_search_module(GSETTINGS_QT REQUIRED gsettings-qt) include_directories(${GSETTINGS_QT_INCLUDES}) pkg_check_modules(GLIB2 REQUIRED glib-2.0) pkg_check_modules(GIO2 REQUIRED gio-2.0) include_directories(${GLIB2_INCLUDE_DIRS}) include_directories(${GIO2_INCLUDE_DIRS}) add_executable(ukui-session-tools ${tools_SRCS}) target_link_libraries(ukui-session-tools Qt5::Widgets Qt5::Core Qt5::Gui Qt5::DBus Qt5::Multimedia Qt5::X11Extras ${GSETTINGS_QT_LIBRARIES} ${KF5Wayland_LIBRARIES} ${GLIB2_LIBRARIES} ${GIO2_LIBRARIES} -lxcb -lX11 -lXtst -lukui-log4qt KF5::WaylandServer KF5::WaylandClient KF5::Screen ) install(TARGETS ukui-session-tools DESTINATION bin) ukui-session-manager-4.0.0.1/tools/mypushbutton.cpp0000644000175000017500000001261314553704753021005 0ustar fengfeng/* * Copyright (C) 2023, 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 2, 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 "mypushbutton.h" #include #include #include #include MyPushButton::MyPushButton(QString iconPath, QString buttonLable, QString objName, QWidget *parent, int x, int y, int width, int height, int iconWidth, int labelWidth) : QWidget(parent) { this->setFixedSize(labelWidth, height); this->setObjectName(objName); QVBoxLayout *vBoxLayout = new QVBoxLayout(); vBoxLayout->setContentsMargins(0, 0, 0, 10); m_iconLabel = new MyIconLabel(labelWidth, iconWidth, iconPath); m_iconLabel->setObjectName(objName + "_button"); m_iconLabel->installEventFilter(this); m_buttonLabel = new QLabel(); m_buttonLabel->setText(buttonLable); m_buttonLabel->setStyleSheet("color: white"); m_buttonLabel->setAlignment(Qt::AlignCenter); QFontMetrics fontMetric(m_buttonLabel->font()); int fontSize = fontMetric.width(buttonLable); int fontHeight = fontMetric.height(); if (fontSize > labelWidth) { m_buttonLabel->setText(fontMetric.elidedText(buttonLable, Qt::ElideRight, m_iconLabel->width())); } int newHeight = fontHeight + labelWidth + 30; if (newHeight > height) { this->setFixedSize(labelWidth, newHeight + 20); } vBoxLayout->addWidget(m_iconLabel, 0, Qt::AlignCenter); vBoxLayout->addWidget(m_buttonLabel, 0, Qt::AlignCenter); this->setLayout(vBoxLayout); connect(m_iconLabel, &MyIconLabel::mouseEventSignals, this, &MyPushButton::iconLabelMouseEvent); } MyPushButton::~MyPushButton() { } void MyPushButton::changeIconBackColor(bool isChoose, bool isKeySelect) { if (m_isMouseSelect && m_isKeySelect) { if ((isChoose && isKeySelect) || (isChoose && !isKeySelect)) { m_isKeySelect = true; m_isMouseSelect = true; } else if (!isChoose && !isKeySelect) { m_isKeySelect = true; m_isMouseSelect = false; } else if (!isChoose && isKeySelect) { m_isKeySelect = false; m_isMouseSelect = true; } } else if (m_isMouseSelect && !m_isKeySelect) { if ((isChoose && isKeySelect) || (!isChoose && !isKeySelect)) { m_isKeySelect = isKeySelect; m_isMouseSelect = isChoose; } else if ((isChoose && !isKeySelect) || (!isChoose && isKeySelect)) { m_isKeySelect = false; m_isMouseSelect = true; } } else if (!m_isMouseSelect && !m_isKeySelect) { if (isChoose && isKeySelect) { m_isKeySelect = true; m_isMouseSelect = false; } else if (isChoose && !isKeySelect) { m_isKeySelect = false; m_isMouseSelect = true; } else if ((!isChoose && !isKeySelect) || (!isChoose && isKeySelect)) { m_isKeySelect = false; m_isMouseSelect = false; } } else if (!m_isMouseSelect && m_isKeySelect) { if ((isChoose && isKeySelect) || (!isChoose && !isKeySelect)) { m_isKeySelect = true; m_isMouseSelect = false; } else if ((isChoose && !isKeySelect) || (!isChoose && isKeySelect)) { m_isKeySelect = !isKeySelect; m_isMouseSelect = isChoose; } } // qDebug() << "isKeySelect..." << objectName() << m_isKeySelect << m_isMouseSelect; QString str; if ((m_isKeySelect && m_isMouseSelect) || (m_isKeySelect && !m_isMouseSelect)) { str = "QLabel#" + m_iconLabel->objectName() + "{background-color: rgb(255,255,255,80);border: 1px solid #296CD9; border-radius: " + QString::number(m_iconLabel->width()/2) + "px;}"; } else if(!m_isKeySelect && m_isMouseSelect){ str = "QLabel#" + m_iconLabel->objectName() + "{background-color: rgb(255,255,255,80);border-radius: " + QString::number(m_iconLabel->width()/2) + "px;}"; } else if(!m_isKeySelect && !m_isMouseSelect){ str = "QLabel#" + m_iconLabel->objectName() + "{background-color: rgb(255,255,255,40);border: 0px; border-radius: " + QString::number(m_iconLabel->width()/2) + "px;}"; } m_iconLabel->setStyleSheet(str); m_iconLabel->setAttribute(Qt::WA_StyledBackground); } MyIconLabel* MyPushButton::getIconLabel() { return m_iconLabel; } void MyPushButton::iconLabelMouseEvent(QEvent *event) { emit mouseRelase(event, m_iconLabel->objectName()); } bool MyPushButton::getIsKeySelect() { return m_isKeySelect; } bool MyPushButton::getIsMouseSelect() { return m_isMouseSelect; } void MyPushButton::setIsKeySelect(bool isKeySelect) { if(m_isKeySelect != isKeySelect) m_isKeySelect = isKeySelect; } void MyPushButton::setIsMouseSelect(bool isMouseSelect) { if(m_isMouseSelect != isMouseSelect) m_isMouseSelect = isMouseSelect; } ukui-session-manager-4.0.0.1/tools/mainwindow.h0000644000175000017500000002066314553704753020051 0ustar fengfeng/*Copyright (C) 2023, 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 2, 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 MAINWINDOW_H #define MAINWINDOW_H #include "powerprovider.h" #include "lockchecker.h" #include "mypushbutton.h" #include "commonpushbutton.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include class XEventMonitor; class QListView; class QStandardItemModel; class MyListView; QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow, public QAbstractNativeEventFilter { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); /** * @brief 响应按钮事件 * @param test2 按钮名字 * @param i 按钮序号 */ void doEvent(QString test2,int i); /** * @brief 响应鼠标事件后改变按钮状态 * @param widget 指定按钮对象 * @param widget 鼠标事件 */ void changePoint(QWidget *widget ,QEvent *event); /** * @brief 调整界面显示的控件 */ void judgeboxShow(); /** * @brief 消息过滤 */ virtual bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) override; /** * @brief 初始化系统监视器按钮 */ void initialSystemMonitor(); /** * @brief 初始化按钮 */ void initialBtn(); /** * @brief 初始化提示内容的控件 */ void initialJudgeWidget(); /** * @brief 初始化时间控件 */ void initialDateTimeWidget(); /** * @brief 更新时间控件 */ void updateDateTimerWidget(); /** * @brief 可以通过配置文件来控制按钮是否显示 */ void initialBtnCfg(); /** * @brief 设置layout是否显示 */ void setLayoutWidgetVisible(QLayout* layout, bool show); /** * @param btnName 指定按钮objectname * @param isEnterKey 是否通过键盘按键选中 * @brief 修改按钮样式 */ void changeBtnState(QString btnName, bool isEnterKey = false); /** * @brief 当有inhibitor存在时显示提醒界面 */ void showInhibitWarning(QVector &list, int action); private: /** * @brief 重新计算界面大小及控件位置布局 */ void ResizeEvent(); /** * @brief 画出提醒界面 */ void drawWarningWindow(QRect &rect, QVector &list, int action); /** * @brief 根据inhibitor的名称获取对应desktop文件中的应用名和icon路径 * @param allNum 共显示的按钮数 * @param lineMaxNum 一行最多显示的按钮数 * @param btn 指定按钮 */ void calculateBtnSpan(int allNum, int lineMaxNum, MyPushButton* btn, int& row, int& colum); /** * @brief 计算键盘左右键对应的按钮 * @param key 按键名字 */ void calculateKeyBtn(const QString &key); /** * @brief 按钮是否可用 * @param index 按钮序号 */ bool judgeBtnIsEnable(int index); /** * @brief 匹配上下按键按钮 * @param row 行号 * @param colum 列号 */ bool matchKeyBtn(int row, int colum); /** * @brief 打开监视器 */ void doSystemMonitor(); /** * @brief 显示常规的按钮界面 * @param hideBtnNum 隐藏的按钮数 */ void showNormalBtnWidget(int hideBtnNum); /** * @brief 显示需要换行的按钮界面 * @param hideBtnNum 隐藏的按钮数 */ void showHasScrollBarBtnWidget(int hideBtnNum); void doLockscreen();//锁屏操作 Q_SIGNALS: /** * @brief 按钮点击后的事件信号 */ void signalTostart(); /** * @brief 点击提示界面的确认按钮信号 */ void confirmButtonclicked(); private Q_SLOTS: /** * @brief 退出 */ bool exitt(); /** * @brief 按键按下事件 * @param key 键名 */ void onGlobalKeyPress(const QString &key); /** * @brief 按键弹起事件 * @param key 键名 */ void onGlobalkeyRelease(const QString &key); /** * @brief 屏幕变化 */ void screenCountChanged(); /** * @brief 点击按钮事件 */ void mouseReleaseSlots(QEvent *event, QString objName); protected: void paintEvent(QPaintEvent *e); bool eventFilter(QObject *, QEvent *); void mousePressEvent(QMouseEvent *event); public: int defaultnum = 0; private: QPixmap pix; QGSettings *gs; QWidget *lastWidget = nullptr; QHash map; QHash rowMap; QHash columMap; UkuiPower *m_power; XEventMonitor *xEventMonitor; int tableNum; bool flag = false; /// 各按钮是否隐藏 默认隐藏 bool isSwitchuserHide = true; bool isHibernateHide = true; bool isSuspendHide = true; bool isLockscreenHide = true; bool isLogoutHide = true; bool isRebootHide = true; bool isPowerOffHide = true; bool click_blank_space_need_to_exit = true; bool close_system_needed_to_confirm = false; bool inhibitSleep = false; bool inhibitShutdown = false; bool m_isSystemMonitorSelect = false; QHash m_btnHideMap; QString m_btnImagesPath = "/usr/share/ukui/ukui-session-manager/images"; MyPushButton *m_switchUserBtn = nullptr; MyPushButton *m_hibernateBtn = nullptr; MyPushButton *m_suspendBtn = nullptr; MyPushButton *m_lockScreenBtn = nullptr; MyPushButton *m_logoutBtn = nullptr; MyPushButton *m_rebootBtn = nullptr; MyPushButton *m_shutDownBtn = nullptr; QWidget *m_systemMonitorBtn = nullptr; QLabel *m_dateLabel = nullptr; QLabel *m_timeLabel = nullptr; QLabel *m_judgeLabel = nullptr; CommonPushButton *m_cancelBtn = nullptr; CommonPushButton *m_confirmBtn = nullptr; QLabel *m_messageLabel1 = nullptr; QLabel *m_messageLabel2 = nullptr; QWidget *m_toolWidget = nullptr; QWidget *m_btnWidget = nullptr; QGridLayout *m_buttonHLayout = nullptr; QVBoxLayout *m_dateTimeLayout = nullptr; QVBoxLayout *m_judgeWidgetVLayout = nullptr; QHBoxLayout *m_judgeBtnHLayout = nullptr; QVBoxLayout *m_vBoxLayout = nullptr; QScrollArea *m_scrollArea = nullptr; QHBoxLayout *m_systemMonitorHLayout = nullptr; QLabel *m_systemMonitorIconLabel = nullptr; QLabel *m_systemMonitorLabel = nullptr; QPixmap m_systemMonitorIcon; /// 计算各按钮的行列位置 int m_switchRow = 0, m_switchColumn = 0; int m_hibernateRow = 0, m_hibernateColumn = 0; int m_suspendRow = 0, m_suspendColumn = 0; int m_lockScreenRow = 0, m_lockScreenColumn = 0; int m_logoutRow = 0, m_logoutColumn = 0; int m_rebootRow = 0, m_rebootColumn = 0; int m_shutDownRow = 0, m_shutDownColumn = 0; int m_lineNum = 1; QSettings *m_btnCfgSetting = nullptr; QRect m_screen; QWidget *m_showWarningArea = nullptr; /// 阻止列表 MyListView *m_inhibitAppList = nullptr; QStandardItemModel *m_inhibitDataModel = nullptr; bool m_btnWidgetNeedScrollbar = false; /// 是否需要滑动条显示 bool m_showWarningMesg = false; /// 是否显示阻止列表 QVector m_inhibitList; QWidget *m_judgeWidget = nullptr; /// 提示 int m_currentListSelect = -1; bool m_isFirstEnter = true; CommonPushButton *m_inhibitCancelBtn = nullptr; bool m_isFirstEnterInhibitList = true; bool m_isCancelBtnSelect = false; bool m_enterInhibitList = false; bool m_enterJudgeBox = false; }; #endif // MAINWINDOW_H ukui-session-manager-4.0.0.1/tools/mypushbutton.h0000644000175000017500000000371214553704753020452 0ustar fengfeng/* * Copyright (C) 2023, 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 2, 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 MYPUSHBUTTON_H #define MYPUSHBUTTON_H #include #include #include #include #include #include #include #include #include #include "myiconlabel.h" class MyPushButton : public QWidget { Q_OBJECT public: MyPushButton(QString iconPath, QString buttonLable, QString objName, QWidget *parent = nullptr, int x = 0, int y = 0, int width = 140, int height = 180, int iconWidth = 48, int labelWidth = 128); ~MyPushButton(); void changeIconBackColor(bool isChoose = true, bool isKeySelect = false); MyIconLabel* getIconLabel(); bool getIsKeySelect(); bool getIsMouseSelect(); void setIsKeySelect(bool isKeySelect); void setIsMouseSelect(bool isMouseSelect); protected: private: QLabel *m_buttonLabel = nullptr; QFont m_buttonLabelFont; QPixmap m_pixMap; MyIconLabel *m_iconLabel = nullptr; bool m_isKeySelect = false;//是否通过键盘选中 bool m_isMouseSelect = false;//是否鼠标选中 protected: signals: void mouseRelase(QEvent *event, QString iconName); public slots: void iconLabelMouseEvent(QEvent *event); }; #endif // MYPUSHBUTTON_H ukui-session-manager-4.0.0.1/tools/lockchecker.cpp0000644000175000017500000001471414553704753020505 0ustar fengfeng/* * Copyright (C) 2023, 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, 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 "lockchecker.h" #include "loginedusers.h" #include #include #include #include #include #include #include #define SYSTEMD_SERVICE "org.freedesktop.login1" #define SYSTEMD_PATH "/org/freedesktop/login1" #define SYSTEMD_INTERFACE "org.freedesktop.login1.Manager" //QDBusArgument &InhibitInfo::operator<<(QDBusArgument &argument, const InhibitInfo::InhibitorInfo &mystruct) //{ // argument.beginStructure(); // argument << mystruct.name << mystruct.icon; // argument.endStructure(); // return argument; //} //const QDBusArgument &InhibitInfo::operator>>(const QDBusArgument &argument, InhibitInfo::InhibitorInfo &mystruct) //{ // argument.beginStructure(); // argument >> mystruct.name >> mystruct.icon ; // argument.endStructure(); // return argument; //} LockChecker::LockChecker() { } LockChecker::~LockChecker() { } QStringList LockChecker::getLoginedUsers() { QStringList loginedUser; qRegisterMetaType("LoginedUsers"); qDBusRegisterMetaType(); QDBusInterface loginInterface(SYSTEMD_SERVICE, SYSTEMD_PATH, SYSTEMD_INTERFACE, QDBusConnection::systemBus()); if (loginInterface.isValid()) { qDebug() << "create interface success"; } QDBusMessage result = loginInterface.call("ListUsers"); QList outArgs = result.arguments(); QVariant first = outArgs.at(0); const QDBusArgument &dbusArgs = first.value(); QVector loginedUsers; dbusArgs.beginArray(); while (!dbusArgs.atEnd()) { LoginedUsers user; dbusArgs >> user; if (user.userName == "lightdm") continue;//过滤登录界面的lightdm用户 loginedUsers.push_back(user); } dbusArgs.endArray(); for (LoginedUsers user : loginedUsers) { QDBusInterface userPertyInterface("org.freedesktop.login1", user.objpath.path(), "org.freedesktop.DBus.Properties", QDBusConnection::systemBus()); QDBusReply reply = userPertyInterface.call("Get", "org.freedesktop.login1.User", "State"); if (reply.isValid()) { QString status = reply.value().toString(); if ("closing" != status) { loginedUser.append(user.userName); } } } return loginedUser; } QVector LockChecker::listInhibitor(QString type) { qDBusRegisterMetaType(); QVector resVec; QDBusInterface loginInterface("org.gnome.SessionManager", "/org/gnome/SessionManager", "org.gnome.SessionManager", QDBusConnection::sessionBus()); if (loginInterface.isValid()) { qDebug() << "create interface success"; } QDBusMessage result = loginInterface.call("ListInhibitor", QVariant(type)); if (!result.errorMessage().isEmpty()) { if (getInhibitorLocal(resVec, type)) { qDebug() << "get inhibitor information from login1"; } return resVec; } QList outArgs = result.arguments(); QVariant first = outArgs.at(0); const QDBusArgument &dbusArgs = first.value(); dbusArgs.beginArray(); while (!dbusArgs.atEnd()) { InhibitInfo::InhibitorInfo inhibtor; dbusArgs >> inhibtor; resVec.push_back(inhibtor); } dbusArgs.endArray(); return resVec; } QString LockChecker::getName(QFile *a) { QString user = getenv("USER"); if (a->exists()) { a->open(QIODevice::ReadOnly|QIODevice::Text); QTextStream fileStream(a); int k = 0; while (!fileStream.atEnd()) { QString line = fileStream.readLine(); if (k == 0) { QString a = line; qDebug() << "uid="< outArgs = ret.arguments(); QVariant first = outArgs.at(0); const QDBusArgument &dbusArgs = first.value(); QDBusObjectPath path; dbusArgs.beginArray(); int userNum = 0; while (!dbusArgs.atEnd()) { dbusArgs >> path; userNum++; } dbusArgs.endArray(); qDebug() << userNum; return userNum; } ukui-session-manager-4.0.0.1/tools/pushbutton.h0000644000175000017500000000211714553704753020102 0ustar fengfeng/* * Copyright (C) 2023, 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 2, 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 PUSHBUTTON_H #define PUSHBUTTON_H #include #include class PushButton : public QPushButton { Q_OBJECT public: PushButton(QWidget *parent = nullptr); ~PushButton(); private: protected: void keyPressEvent(QKeyEvent *event); bool event(QEvent *e); public: signals: }; #endif // PUSHBUTTON_H ukui-session-manager-4.0.0.1/tools/mylistview.h0000644000175000017500000000171414553704753020105 0ustar fengfeng/* * Copyright (C) 2023, 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 2, 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 MYLISTVIEW_H #define MYLISTVIEW_H #include #include class MyListView : public QListView { Q_OBJECT public: MyListView(QWidget *parent = nullptr); ~MyListView(); }; #endif // MYLISTVIEW_H ukui-session-manager-4.0.0.1/tools/ukuilockinfo.h0000644000175000017500000000475614553704753020404 0ustar fengfeng/* * Copyright (C) 2023, 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 2, 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 UKUILOCKINFO_H #define UKUILOCKINFO_H #include #include #include namespace InhibitInfo { struct InhibitorInfo { QString name; QString icon; }; QDBusArgument &operator<<(QDBusArgument &argument, const InhibitInfo::InhibitorInfo &mystruct); const QDBusArgument &operator>>(const QDBusArgument &argument, InhibitInfo::InhibitorInfo &mystruct); } Q_DECLARE_METATYPE(InhibitInfo::InhibitorInfo) namespace SystemdInhibitor { struct Inhibitor { QString action; QString name; QString reason; QString mode; int uid; int pid; }; QDBusArgument &operator<<(QDBusArgument &argument, const SystemdInhibitor::Inhibitor &mystruct); const QDBusArgument &operator>>(const QDBusArgument &argument, SystemdInhibitor::Inhibitor &mystruct); } Q_DECLARE_METATYPE(SystemdInhibitor::Inhibitor) class Ukuilockinfo { public: Ukuilockinfo(); ~Ukuilockinfo(); public: enum InhibitorType { logout, suspend, shutdown }; enum buttonType { switchuserBtn, hibernateBtn, suspendBtn, lockscreenBtn, logoutBtn, rebootBtn, shutdownBtn }; static QVector listInhibitorInfo(Ukuilockinfo::InhibitorType type); static bool getCfgValue(Ukuilockinfo::buttonType button); private: static void getLogoutInhibitor(QVector &inhibitorVec); static void getSystemdInhibitor(QString type, QVector &inhibitorVec); static void findNameAndIcon(QString &inhibitorName, InhibitInfo::InhibitorInfo &inhibitor); static QString getAppLocalName(QString desktopfp); }; #endif // UKUILOCKINFO_H ukui-session-manager-4.0.0.1/tools/ukuipower.cpp0000644000175000017500000001047114553704753020256 0ustar fengfeng/* BEGIN_COMMON_COPYRIGHT_HEADER * * Copyright: 2023, KylinSoft Co., Ltd. * Copyright: 2012 Razor team * * Authors: * Christian Surlykke * * This program or library 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. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA * * END_COMMON_COPYRIGHT_HEADER */ #include "ukuipower.h" #include "powerprovider.h" #include #include #include UkuiPower::UkuiPower(QObject *parent) : QObject(parent) { m_systemdProvider = new SystemdProvider(this); } UkuiPower::~UkuiPower() { } bool UkuiPower::canAction(UkuiPower::Action action) const { //以下为代码结构调整 QString command; switch (action) { case PowerSwitchUser: command = QLatin1String("canSwitch"); break; case PowerHibernate: command = QLatin1String("canHibernate"); break; case PowerSuspend: command = QLatin1String("canSuspend"); break; case PowerMonitorOff: command = QLatin1String("canLockscreen"); break; case PowerLogout: command = QLatin1String("canLogout"); break; case PowerReboot: command = QLatin1String("canReboot"); break; case PowerShutdown: command = QLatin1String("canPowerOff"); break; default: break; } QDBusInterface *sessionInterface = new QDBusInterface("org.gnome.SessionManager", "/org/gnome/SessionManager", "org.gnome.SessionManager", QDBusConnection::sessionBus()); if (!sessionInterface->isValid()) { qWarning() << "dbusCall: Session QDBusInterface is invalid"; return false; } QDBusReply testReply = sessionInterface->call(QLatin1String("canLockscreen")); if (!testReply.isValid()) { //解决老版本升级到新版本接口不兼容的问题,在session接口不存在的情况下,调用systemd的接口 QDBusError error = testReply.error(); if (error.type() == QDBusError::UnknownMethod) { qDebug() <<"updating ! old ukui-session dose not have canAction method"; if (action == PowerLogout || action == PowerMonitorOff) { return true; } return m_systemdProvider->canAction(action); } qDebug() <<"dbus error"; return false; } QDBusReply reply = sessionInterface->call(command); return reply.value(); } bool UkuiPower::doAction(UkuiPower::Action action) { QString command; switch (action) { case PowerSwitchUser: command = QLatin1String("switchUser"); break; case PowerHibernate: command = QLatin1String("hibernate"); break; case PowerSuspend: command = QLatin1String("suspend"); break; case PowerLogout: command = QLatin1String("logout"); break; case PowerReboot: command = QLatin1String("reboot"); break; case PowerShutdown: command = QLatin1String("powerOff"); break; default: break; } QDBusInterface *sessionInterface = new QDBusInterface("org.gnome.SessionManager", "/org/gnome/SessionManager", "org.gnome.SessionManager", QDBusConnection::sessionBus()); if (!sessionInterface->isValid()) { qWarning() << "dbusCall: Session QDBusInterface is invalid"; return false; } QDBusMessage mes = sessionInterface->call(command); if (!(mes.errorName().isEmpty())) { //本来应该判断错误类别,考虑到运行效率,不做该判断 return m_systemdProvider->doAction(action); } return true; } ukui-session-manager-4.0.0.1/tools/xeventmonitor.cpp0000644000175000017500000001331314553704753021143 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2011 ~ 2017 Deepin, Inc. * Copyright (C) 2011 ~ 2017 Wang Yong. * Copyright (C) 2023, KylinSoft Co., Ltd. * * Author: Wang Yong * Maintainer: Wang Yong * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "xeventmonitor.h" #include #include #include #include #include // Virtual button codes that are not defined by X11. #define Button1 1 #define Button2 2 #define Button3 3 #define WheelUp 4 #define WheelDown 5 #define WheelLeft 6 #define WheelRight 7 #define XButton1 8 #define XButton2 9 class XEventMonitorPrivate { public: XEventMonitorPrivate(XEventMonitor *parent); virtual ~XEventMonitorPrivate(); void run(); protected: XEventMonitor *q_ptr; bool filterWheelEvent(int detail); static void callback(XPointer trash, XRecordInterceptData* data); void handleRecordEvent(XRecordInterceptData *); void emitButtonSignal(const char *member, xEvent *event); void emitKeySignal(const char *member, xEvent *event); private: Q_DECLARE_PUBLIC(XEventMonitor) }; XEventMonitorPrivate::XEventMonitorPrivate(XEventMonitor *parent) : q_ptr(parent) { } XEventMonitorPrivate::~XEventMonitorPrivate() { } void XEventMonitorPrivate::emitButtonSignal(const char *member, xEvent *event) { int x = event->u.keyButtonPointer.rootX; int y = event->u.keyButtonPointer.rootY; QMetaObject::invokeMethod(q_ptr, member, Qt::DirectConnection, Q_ARG(int, x), Q_ARG(int, y)); } void XEventMonitorPrivate::emitKeySignal(const char *member, xEvent *event) { Display *display = XOpenDisplay(NULL); int keyCode = event->u.u.detail; KeySym keySym = XkbKeycodeToKeysym(display, event->u.u.detail, 0, 0); char *keyStr = XKeysymToString(keySym); QMetaObject::invokeMethod(q_ptr, member, Qt::AutoConnection, Q_ARG(int, keyCode)); QMetaObject::invokeMethod(q_ptr, member, Qt::AutoConnection, Q_ARG(QString, keyStr)); XCloseDisplay(display); } void XEventMonitorPrivate::run() { Display* display = XOpenDisplay(0); if (display == 0) { fprintf(stderr, "unable to open display\n"); return; } // Receive from ALL clients, including future clients. XRecordClientSpec clients = XRecordAllClients; XRecordRange* range = XRecordAllocRange(); if (range == 0) { fprintf(stderr, "unable to allocate XRecordRange\n"); return; } // Receive KeyPress, KeyRelease, ButtonPress, ButtonRelease and MotionNotify events. memset(range, 0, sizeof(XRecordRange)); range->device_events.first = KeyPress; range->device_events.last = MotionNotify; // And create the XRECORD context. XRecordContext context = XRecordCreateContext(display, 0, &clients, 1, &range, 1); if (context == 0) { fprintf(stderr, "XRecordCreateContext failed\n"); return; } XFree(range); XSync(display, True); Display* display_datalink = XOpenDisplay(0); if (display_datalink == 0) { fprintf(stderr, "unable to open second display\n"); return; } if (!XRecordEnableContext(display_datalink, context, callback, (XPointer) this)) { fprintf(stderr, "XRecordEnableContext() failed\n"); return; } } void XEventMonitorPrivate::callback(XPointer ptr, XRecordInterceptData* data) { ((XEventMonitorPrivate*)ptr)->handleRecordEvent(data); } void XEventMonitorPrivate::handleRecordEvent(XRecordInterceptData* data) { if (data->category == XRecordFromServer) { xEvent * event = (xEvent *)data->data; switch (event->u.u.type) { case ButtonPress: if (filterWheelEvent(event->u.u.detail)) emitButtonSignal("buttonPress", event); break; case MotionNotify: emitButtonSignal("buttonDrag", event); break; case ButtonRelease: if (filterWheelEvent(event->u.u.detail)) emitButtonSignal("buttonRelease", event); break; case KeyPress: emitKeySignal("keyPress", event); break; case KeyRelease: emitKeySignal("keyRelease", event); break; default: break; } } fflush(stdout); XRecordFreeData(data); } bool XEventMonitorPrivate::filterWheelEvent(int detail) { return detail != WheelUp && detail != WheelDown && detail != WheelLeft && detail != WheelRight; } XEventMonitor::XEventMonitor(QObject *parent) : QThread(parent), d_ptr(new XEventMonitorPrivate(this)) { Q_D(XEventMonitor); } XEventMonitor::~XEventMonitor() { requestInterruption(); quit(); wait(); } void XEventMonitor::run() { if (!isInterruptionRequested()) { d_ptr->run(); } } ukui-session-manager-4.0.0.1/tools/commonpushbutton.cpp0000644000175000017500000000547014553704753021653 0ustar fengfeng/* * Copyright (C) 2023, 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 2, 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 "commonpushbutton.h" #include #include #include #include CommonPushButton::CommonPushButton(QString buttonText, QString objName, int width, int height, int radius, QWidget *parent) { m_radius = radius; m_label = new QLabel(this); m_label->setText(buttonText); this->setFixedSize(width, height); this->setObjectName(objName); m_label->setAlignment(Qt::AlignCenter); m_label->setGeometry(0, 0, width, height); m_label->setStyleSheet("color:white");//font:12pt; this->setStyleSheet("QWidget#" + this->objectName() + "{background-color: rgb(255,255,255,40);border-radius: " + QString::number(m_radius) + "px;}"); this->setAttribute(Qt::WA_StyledBackground); } CommonPushButton::~CommonPushButton() { } void CommonPushButton::setText(QString str) { m_label->setText(str); } bool CommonPushButton::event(QEvent *event) { if (event->type() == QEvent::MouseButtonPress) { QString str = "QWidget#" + this->objectName() +"{background-color: rgb(255,255,255,100);border-radius: " + QString::number(m_radius) + "px;}"; this->setStyleSheet(str); this->setAttribute(Qt::WA_StyledBackground); } else if (event->type() == QEvent::MouseButtonRelease) { QString str = "QWidget#" + this->objectName() + "{background-color: rgb(255,255,255,80);border-radius: " + QString::number(m_radius) + "px;}"; this->setStyleSheet(str); this->setAttribute(Qt::WA_StyledBackground); emit clicked(); } return QWidget::event(event); } void CommonPushButton::enterEvent(QEvent *event) { QString str = "QWidget#" + this->objectName() + "{background-color: rgb(255,255,255,80);border-radius: " + QString::number(m_radius) + "px;}"; this->setStyleSheet(str); this->setAttribute(Qt::WA_StyledBackground); } void CommonPushButton::leaveEvent(QEvent *event) { QString str = "QWidget#" + this->objectName() + "{background-color: rgb(255,255,255,40);border-radius: " + QString::number(m_radius) + "px;}"; this->setStyleSheet(str); this->setAttribute(Qt::WA_StyledBackground); } ukui-session-manager-4.0.0.1/tools/main.cpp0000644000175000017500000003335414553704753017155 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * Copyright (C) 2010-2016 LXQt team * * 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, 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 #include #include #include #include #include #include #include #include #include #include "ukuipower.h" #include "mainwindow.h" #include "loginedusers.h" #include "lockchecker.h" #include #include #include #include #include #include #include #include "plasma-shell-manager.h" #ifdef signals #undef signals #endif bool messageboxCheck() { QMessageBox msgBox; // msgBox.setWindowTitle(QObject::tr("conform")); msgBox.setIcon(QMessageBox::Warning); msgBox.setWindowFlags(Qt::WindowStaysOnTopHint); // msgBox.setModal(false); msgBox.setText(QObject::tr("Multiple users are logged in at the same time.Are you sure you want to close this system?")); QPushButton *cancelButton = msgBox.addButton(QObject::tr("cancel"), QMessageBox::ActionRole); QPushButton *confirmButton = msgBox.addButton(QObject::tr("confirm"), QMessageBox::RejectRole); msgBox.exec(); if (msgBox.clickedButton() == cancelButton) { qDebug() << "cancel!"; return false; } else if (msgBox.clickedButton() == confirmButton) { qDebug() << "confirm!"; return true; } else { return false; } } void messagecheck() { QMessageBox msgBox; msgBox.setIcon(QMessageBox::Warning); msgBox.setWindowFlags(Qt::WindowStaysOnTopHint); QString t1 = QObject::tr("System update or package installation in progress,this function is temporarily unavailable."); QString t2 = QObject::tr("System backup or restore in progress,this function is temporarily unavailable."); QFile file_update("/tmp/lock/kylin-update.lock"); QFile file_backup("/tmp/lock/kylin-backup.lock"); if (file_update.exists()) { msgBox.setText(t1); } if (file_backup.exists()) { msgBox.setText(t2); } QPushButton *cancelButton = msgBox.addButton(QObject::tr("OK"), QMessageBox::RejectRole); msgBox.exec(); if (msgBox.clickedButton() == cancelButton) { qDebug() << "OK!"; } } bool playShutdownMusic(UkuiPower &powermanager, int num, QTimer *up_to_time) { bool play_music = false; QGSettings *gs = new QGSettings("org.ukui.session", "/org/ukui/desktop/session/"); if (num == 4) { play_music = gs->get("logout-music").toBool(); } if (num == 5 || num == 6) { play_music = gs->get("poweroff-music").toBool(); } if (num == 0 || num == 1 || num == 2) { play_music = false; } gs->set("win-key-release", false); static int action = num; if (num == 4 || num == 0) { QDBusInterface dbus("org.gnome.SessionManager", "/org/gnome/SessionManager", "org.gnome.SessionManager", QDBusConnection::sessionBus()); if (!dbus.isValid()) { qWarning() << "dbusCall: QDBusInterface is invalid"; return false; } QDBusMessage msg; // if (num == 4) { // msg = dbus.call("emitStartLogout"); // } if (num == 0) { msg = dbus.call("emitPrepareForSwitchuser"); } if (!msg.errorName().isEmpty()) { qWarning() << "Dbus error: " << msg; } } if (play_music) { // up_to_time and soundplayer can not be define out of this if(). // otherwise run ukui-session-tools --suspend with segmente error. // because they will be delate at the end of the playShutdownMusic(). QObject::connect(up_to_time, &QTimer::timeout, [&]() { powermanager.doAction(UkuiPower::Action(action)); exit(0); }); QString xdg_session_type = qgetenv("XDG_SESSION_TYPE"); if (num == 5 || num == 6) { if (xdg_session_type == "wayland") { QProcess::startDetached("paplay /usr/share/ukui/ukui-session-manager/shutdown.wav"); } else { QProcess::startDetached("aplay /usr/share/ukui/ukui-session-manager/shutdown.wav"); } up_to_time->start(5000); } else if (num == 4) { if (xdg_session_type == "wayland") { QProcess::startDetached("paplay /usr/share/ukui/ukui-session-manager/logout.wav"); } else { QProcess::startDetached("aplay /usr/share/ukui/ukui-session-manager/logout.wav"); } up_to_time->start(2000); } else { qDebug() << "error num"; return false; } } else { powermanager.doAction(UkuiPower::Action(action)); exit(0); } return false; } /** * @brief 判断当前是否处于锁屏 */ bool screensaverIsActive() { QString SS_DBUS_SERVICE = "org.ukui.ScreenSaver"; QString SS_DBUS_PATH = "/"; QString SS_DBUS_INTERFACE = "org.ukui.ScreenSaver"; QString displayNum = QString(qgetenv("DISPLAY")).replace(":", "").replace(".", "_"); qDebug() << "dispaly = " << displayNum ; QString sessionDbus = QString("%1%2").arg(SS_DBUS_SERVICE).arg(displayNum); QDBusInterface *interface = new QDBusInterface(sessionDbus, SS_DBUS_PATH, SS_DBUS_INTERFACE); if (!interface->isValid()) { qDebug() << "interface not valid"; delete interface; interface = new QDBusInterface(SS_DBUS_SERVICE, SS_DBUS_PATH, SS_DBUS_INTERFACE); } QDBusMessage response = interface->call("GetLockState"); //判断method是否被正确返回 if (response.type() == QDBusMessage::ReplyMessage) { //从返回参数获取返回值 bool isActive = response.arguments()[0].toBool(); qDebug() << "screensaver isActive:" << isActive; return isActive; } else { qDebug() << "QDBusMessage failed!"; return false; } } int main(int argc, char* argv[]) { initUkuiLog4qt("ukui-session-tools"); #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 QStringList homePath = QStandardPaths::standardLocations(QStandardPaths::HomeLocation); int fd = open(QString(homePath.at(0) + "/.config/ukui-session-tools%1.lock").arg(getenv("DISPLAY")).toUtf8().data(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); if (fd < 0) { exit(1); } if (lockf(fd, F_TLOCK, 0)) { qDebug() << "Can't lock single file, ukui-session-tools is already running!"; exit(0); } qunsetenv("QT_IM_MODULE"); qunsetenv("SESSION_MANAGER"); QApplication a(argc, argv); if (QGSettings::isSchemaInstalled("org.ukui.style")) { QGSettings *settings = new QGSettings("org.ukui.style"); if (qApp->property("noChangeSystemFontSize").isValid() && qApp->property("noChangeSystemFontSize").toBool()) { return 0; } double fontSize = settings->get("system-font-size").toString().toDouble(); QString fontType = settings->get("systemFont").toString(); if (fontSize > 0) { QFont font = QApplication::font(); font.setPointSizeF(fontSize); font.setStyleName(fontType); QApplication::setFont(font); } } // Load ts files const QString locale = QLocale::system().name(); QTranslator translator; qDebug() << "local: " << locale; qDebug() << "path: " << QStringLiteral(UKUI_TRANSLATIONS_DIR) + QStringLiteral("/ukui-session-manager"); if (translator.load(locale, QStringLiteral(UKUI_TRANSLATIONS_DIR) + QStringLiteral("/ukui-session-manager"))) { a.installTranslator(&translator); } else { qDebug() << "Load translations file failed!"; } UkuiPower powermanager(&a); bool flag = true; // define in the main() avoid scope error. QTimer *up_to_time = new QTimer(); up_to_time->setSingleShot(true); QGSettings *gs = new QGSettings("org.ukui.session", "/org/ukui/desktop/session/"); gs->set("win-key-release", true); //初始化窗口 Load qss file MainWindow *w = new MainWindow(); QFile qss(":/powerwin.qss"); qss.open(QFile::ReadOnly); a.setStyleSheet(qss.readAll()); qss.close(); QCommandLineParser parser; parser.setApplicationDescription( QApplication::tr("UKUI session tools, show the shutdown dialog without any arguments.")); const QString VERINFO = QStringLiteral("2.0"); a.setApplicationVersion(VERINFO); parser.addHelpOption(); parser.addVersionOption(); QCommandLineOption switchuserOption(QStringLiteral("switchuser"), QApplication::tr("Switch the user of this computer.")); parser.addOption(switchuserOption); QCommandLineOption hibernateOption(QStringLiteral("hibernate"), QApplication::tr("Hibernate this computer.")); parser.addOption(hibernateOption); QCommandLineOption suspendOption(QStringLiteral("suspend"), QApplication::tr("Suspend this computer.")); parser.addOption(suspendOption); QCommandLineOption logoutOption(QStringLiteral("logout"), QApplication::tr("Logout this computer.")); parser.addOption(logoutOption); QCommandLineOption rebootOption(QStringLiteral("reboot"), QApplication::tr("Restart this computer.")); parser.addOption(rebootOption); QCommandLineOption shutdownOption(QStringLiteral("shutdown"), QApplication::tr("Shutdown this computer.")); parser.addOption(shutdownOption); parser.process(a); if (parser.isSet(switchuserOption)) { flag = playShutdownMusic(powermanager, 0, up_to_time); } if (parser.isSet(hibernateOption)) { QVector res = LockChecker::listInhibitor("sleep"); if (res.isEmpty()) { flag = playShutdownMusic(powermanager, 1, up_to_time); } else { w->showInhibitWarning(res, 1); w->show(); } } if (parser.isSet(suspendOption)) { QVector res = LockChecker::listInhibitor("sleep"); if (res.isEmpty()) { flag = playShutdownMusic(powermanager, 2, up_to_time); } else { w->showInhibitWarning(res, 2); w->show(); } } if (parser.isSet(logoutOption)) { QVector res = LockChecker::listInhibitor("shutdown"); if (res.isEmpty()) { flag = playShutdownMusic(powermanager, 4, up_to_time); } else { w->showInhibitWarning(res, 4); w->show(); } } if (parser.isSet(rebootOption)) { QVector res = LockChecker::listInhibitor("shutdown"); if (res.isEmpty()) { if (LockChecker::getLoginedUsers().count() > 1) {//提醒多个用户登录的情况 if (messageboxCheck()) { flag = playShutdownMusic(powermanager, 5, up_to_time); } else { gs->set("win-key-release", false); return 0; } } else { flag = playShutdownMusic(powermanager, 5, up_to_time); } } else { w->showInhibitWarning(res, 5); w->show(); } } if (parser.isSet(shutdownOption)) { QVector res = LockChecker::listInhibitor("shutdown"); if (res.isEmpty()) { if (LockChecker::getLoginedUsers().count() > 1) {//提醒多个用户登录的情况 if (messageboxCheck()) { flag = playShutdownMusic(powermanager, 6, up_to_time); } else { gs->set("win-key-release", false); return 0; } } else { flag = playShutdownMusic(powermanager, 6, up_to_time); } } else { w->showInhibitWarning(res, 6); w->show(); } } if (screensaverIsActive()) { gs->set("win-key-release", false); exit(0); } if (flag) { w->show(); QObject::connect(w, &MainWindow::signalTostart, [&]() { // 从界面点击 切换用户、睡眠和休眠 按钮,则等待界面隐藏再执行命令 if (w->defaultnum < 3) { w->hide(); QTimer::singleShot(500, [&]() { playShutdownMusic(powermanager, w->defaultnum, up_to_time); }); } else { playShutdownMusic(powermanager, w->defaultnum, up_to_time); } }); } return a.exec(); } ukui-session-manager-4.0.0.1/tools/powerprovider.cpp0000644000175000017500000002754714553704753021147 0ustar fengfeng/* BEGIN_COMMON_COPYRIGHT_HEADER * * Copyright: 2023, KylinSoft Co., Ltd. * Copyright: 2012 Razor team * * Authors: * Christian Surlykke * * This program or library 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. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA * * END_COMMON_COPYRIGHT_HEADER */ #include "powerprovider.h" #include #include #include #include #include #include #include #include #include #include //#include "loginedusers.h" #define LIGHTDM_SERVICE "org.freedesktop.DisplayManager" #define LIGTHDM_INTERFACE "org.freedesktop.DisplayManager.Seat" #define SYSTEMD_SERVICE "org.freedesktop.login1" #define SYSTEMD_PATH "/org/freedesktop/login1" #define SYSTEMD_INTERFACE "org.freedesktop.login1.Manager" #define UKUI_SERVICE "org.gnome.SessionManager" #define UKUI_PATH "/org/gnome/SessionManager" #define UKUI_INTERFACE "org.gnome.SessionManager" #define PROPERTIES_INTERFACE "org.freedesktop.DBus.Properties" bool messageBoxCheck() { QMessageBox msgBox; // msgBox.setWindowTitle(QObject::tr("conform")); msgBox.setIcon(QMessageBox::Warning); msgBox.setWindowFlags(Qt::WindowStaysOnTopHint); // msgBox.setModal(false); msgBox.setText(QObject::tr("some applications are running and they don't want you to do this.")); QPushButton *stillButton = msgBox.addButton(QObject::tr("Still to do!"), QMessageBox::ActionRole); QPushButton *giveupButton = msgBox.addButton(QObject::tr("give up"), QMessageBox::RejectRole); // QStringList usrlist = getLoginedUsers(); // QList::Iterator it = usrlist.begin(),itend = usrlist.end(); // for(;it != itend;it++){ // qDebug()<<*it; // } msgBox.exec(); if (msgBox.clickedButton() == stillButton) { qDebug() << "Still to do!"; return true; } else if (msgBox.clickedButton() == giveupButton) { qDebug() << "give up"; return false; } else { return false; } } static bool dbusCall(const QString &service, const QString &path, const QString &interface, const QDBusConnection &connection, const QString &method) { QDBusInterface dbus(service, path, interface, connection); if (!dbus.isValid()) { qWarning() << "dbusCall: QDBusInterface is invalid" << service << path << interface << method; return false; } QDBusMessage msg = dbus.call(method); if (!msg.errorName().isEmpty()) { qWarning() << "Dbus error: " << msg; } return msg.arguments().isEmpty() || msg.arguments().constFirst().isNull() || msg.arguments().constFirst().toBool(); } static bool dbusCallSystemd(const QString &service, const QString &path, const QString &interface, const QDBusConnection &connection, const QString &method, bool needBoolArg) { QDBusInterface dbus(service, path, interface, connection); if (!dbus.isValid()) { qWarning() << "dbusCall: QDBusInterface is invalid" << service << path << interface << method; return false; } // QDBusMessage msg = dbus.call(method, needBoolArg ? QVariant(true) : QVariant()); // 在华为相关的机器上,如果调用的method没有参数,但仍传入一个空值,会调用失败 QDBusMessage msg; if (needBoolArg) { msg = dbus.call(method, QVariant(true)); } else { msg = dbus.call(method); } if (!msg.errorName().isEmpty()) { qWarning() << "Debus error: " << msg; } if (msg.arguments().isEmpty() || msg.arguments().constFirst().isNull()) { return false; } QString response = msg.arguments().constFirst().toString(); qDebug() << "systemd:" << method << "=" << response; // this need to be resolved: // while canReboot and canPoweroff return challenge,users click Reboot or shutdown, // we call logout ,then Reboot(true) and Poweroff(true) is called, // if user click cancel in the polkit messagebox,then Reboot or Poweroff is canceled, // but logout has been executed. return response == QLatin1String("yes") || response == QLatin1String("challenge"); } bool dbusGetProperty(const QString &service, const QString &path, const QString &interface, const QDBusConnection &connection, const QString &property) { QDBusInterface dbus(service, path, interface, connection); if (!dbus.isValid()) { qWarning() << "dbusGetProperty: QDBusinterface is invalid" << service << path << interface << property; return false; } // QDBusMessage msg = dbus.call("SwitchToGreeter");//QLatin1String("Get"), dbus.interface(),property // if (!msg.errorName().isEmpty()) { // qWarning() << "Dbus error: " << msg; // } // return !msg.arguments().isEmpty() && // msg.arguments().constFirst().value().variant().toBool(); QVariant canswitch = dbus.property("CanSwitch"); qDebug() << property << "=" << canswitch.toString(); return canswitch.toBool(); } PowerProvider::PowerProvider(QObject *parent) : QObject(parent) { } PowerProvider::~PowerProvider() { } /************************************************ SystemdProvider http://www.freedesktop.org/wiki/Software/systemd/logind ************************************************/ SystemdProvider::SystemdProvider(QObject *parent): PowerProvider(parent) { } SystemdProvider::~SystemdProvider() { } bool SystemdProvider::canSwitchUser() const { QString property = "CanSwitch"; QString xdg_seat_path = qgetenv("XDG_SEAT_PATH"); if (xdg_seat_path.isEmpty()) { qWarning() << "XDG_SEAT_PATH dose not exist , make canSwitchUser return false"; return false; } return dbusGetProperty(QLatin1String(LIGHTDM_SERVICE), xdg_seat_path, QLatin1String(LIGTHDM_INTERFACE), QDBusConnection::systemBus(), property); } bool SystemdProvider::canAction(UkuiPower::Action action) const { QString command; switch (action) { case UkuiPower::PowerSwitchUser: return canSwitchUser(); case UkuiPower::PowerReboot: command = QLatin1String("CanReboot"); break; case UkuiPower::PowerShutdown: command = QLatin1String("CanPowerOff"); break; case UkuiPower::PowerSuspend: command = QLatin1String("CanSuspend"); break; case UkuiPower::PowerHibernate: command = QLatin1String("CanHibernate"); break; default: return false; } // canAction should be always silent because it can freeze // g_main_context_iteration Qt event loop in QMessageBox // on panel startup if there is no DBUS running. return dbusCallSystemd(QLatin1String(SYSTEMD_SERVICE), QLatin1String(SYSTEMD_PATH), QLatin1String(SYSTEMD_INTERFACE), QDBusConnection::systemBus(), command, false); } bool SystemdProvider::doSwitchUser() { bool isinhibited =false; QDBusInterface *interface = new QDBusInterface( "org.gnome.SessionManager", "/org/gnome/SessionManager", "org.gnome.SessionManager", QDBusConnection::sessionBus()); quint32 inhibit_switchuser = 2; QDBusReply reply = interface->call("IsInhibited", inhibit_switchuser); if (reply.isValid()) { // use the returned value qDebug() << "Is inhibit by someone: " << reply.value(); isinhibited = reply.value(); } else { qDebug() << reply.value(); } if (isinhibited == true) { isinhibited = !messageBoxCheck(); } if (isinhibited == false) { // QDBusInterface dbus("org.ukui.KWin", "/Compositor", "org.ukui.kwin.Compositing", QDBusConnection::sessionBus()); // if (!dbus.isValid()) { // qWarning() << "dbusCall: QDBusInterface is invalid"; // return false; // } // dbus.call("suspend"); QString command = "SwitchToGreeter"; QString xdg_seat_path = qgetenv("XDG_SEAT_PATH"); return dbusCall(QLatin1String(LIGHTDM_SERVICE), xdg_seat_path, QLatin1String(LIGTHDM_INTERFACE), QDBusConnection::systemBus(), command); } return false; } bool SystemdProvider::doAction(UkuiPower::Action action) { QString command; switch (action) { case UkuiPower::PowerSwitchUser: return doSwitchUser(); case UkuiPower::PowerReboot: command = QLatin1String("Reboot"); break; case UkuiPower::PowerShutdown: command = QLatin1String("PowerOff"); break; case UkuiPower::PowerSuspend: command = QLatin1String("Suspend"); break; case UkuiPower::PowerHibernate: command = QLatin1String("Hibernate"); break; default: return false; } return dbusCallSystemd(QLatin1String(SYSTEMD_SERVICE), QLatin1String(SYSTEMD_PATH), QLatin1String(SYSTEMD_INTERFACE), QDBusConnection::systemBus(), command, true); } UKUIProvider::UKUIProvider(QObject *parent): PowerProvider (parent) { } UKUIProvider::~UKUIProvider() { } bool UKUIProvider::canAction(UkuiPower::Action action) const { //这里是调用ukui-session注册的d-bus检测是否有inhibitor存在 bool isinhibited = false; QDBusInterface *interface = new QDBusInterface("org.gnome.SessionManager", "/org/gnome/SessionManager", "org.gnome.SessionManager", QDBusConnection::sessionBus()); quint32 inhibit_logout = 1; QDBusReply reply = interface->call("IsInhibited", inhibit_logout); if (reply.isValid()) { // use the returned value qDebug() << "Is inhibit by someone: " << reply.value(); isinhibited = reply.value(); } else { qDebug() << reply.value(); } if (isinhibited == true) { isinhibited = !messageBoxCheck(); } return !isinhibited; } bool UKUIProvider::doAction(UkuiPower::Action action) { QString command; switch (action) { case UkuiPower::PowerLogout: command = QLatin1String("logout"); break; case UkuiPower::PowerReboot: command = QLatin1String("reboot"); break; case UkuiPower::PowerShutdown: command = QLatin1String("powerOff"); break; default: return false; } qDebug() << "ukuipowerprovider call D-Bus session"; return dbusCall(QLatin1String(UKUI_SERVICE), QLatin1String(UKUI_PATH), QLatin1String(UKUI_INTERFACE), QDBusConnection::sessionBus(), command); } ukui-session-manager-4.0.0.1/tools/commonpushbutton.h0000644000175000017500000000273714553704753021323 0ustar fengfeng/* * Copyright (C) Copyright 2023 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 2, 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 COMMONPUSHBUTTON_H #define COMMONPUSHBUTTON_H #include #include #include #include #include #include #include #include #include class CommonPushButton : public QWidget { Q_OBJECT public: CommonPushButton(QString buttonText, QString objName, int width = 120, int height = 48, int radius = 24, QWidget *parent = nullptr); ~CommonPushButton(); void setText(QString str); protected: bool event(QEvent *); void enterEvent(QEvent *event); void leaveEvent(QEvent *event); signals: void clicked(); public slots: private: QLabel *m_label = nullptr; int m_radius; }; #endif // COMMONPUSHBUTTON_H ukui-session-manager-4.0.0.1/tools/loginedusers.h0000644000175000017500000000232714553704753020375 0ustar fengfeng/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2023, 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 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 LOGINEDUSER_H #define LOGINEDUSER_H #include struct LoginedUsers { int uid; QString userName; QDBusObjectPath objpath; }; QDBusArgument &operator<<(QDBusArgument &argument, const LoginedUsers &mystruct); const QDBusArgument &operator>>(const QDBusArgument &argument, LoginedUsers &mystruct); Q_DECLARE_METATYPE(LoginedUsers) #endif // LOGINEDUSER_H ukui-session-manager-4.0.0.1/tools/plasma-shell-manager.cpp0000644000175000017500000001226314553704753022217 0ustar fengfeng/* * Copyright (C) 2023, 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 2, 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 "plasma-shell-manager.h" #include #include #include #include #include #include static PlasmaShellManager* global_instance = nullptr; PlasmaShellManager *PlasmaShellManager::getInstance() { if (!global_instance) { global_instance = new PlasmaShellManager; qDebug() << "Here create instance..."; } qDebug() << "Return instance"; return global_instance; } bool PlasmaShellManager::setAppWindowActive() { if (!supportPlasmaWindowManagement()) return false; m_appWindow->requestActivate(); return true; } bool PlasmaShellManager::setAppWindowKeepAbove(bool keep) { if (!supportPlasmaWindowManagement()) { qDebug() << "false"; return false; } if(keep != m_appWindow->isKeepAbove()) { qDebug() << "to keep above"; m_appWindow->requestToggleKeepAbove(); } return true; } bool PlasmaShellManager::setMaximized(QWindow *window) { if (!supportShell()) return false; auto surface = KWayland::Client::Surface::fromWindow(window); if (!surface) return false; auto shellSurface = m_shell->createSurface(surface, window); if (!shellSurface) return false; shellSurface->setMaximized(); return true; } bool PlasmaShellManager::setRole(QWindow *window, KWayland::Client::PlasmaShellSurface::Role role) { if (!supportPlasmaShell()) return false; auto surface = KWayland::Client::Surface::fromWindow(window); if (!surface) return false; auto plasmaShellSurface = m_plasmaShell->createSurface(surface, window); if (!plasmaShellSurface) return false; plasmaShellSurface->setRole(role); return true; } bool PlasmaShellManager::setPos(QWindow *window, const QPoint &pos) { if (!supportPlasmaShell()) return false; auto surface = KWayland::Client::Surface::fromWindow(window); if (!surface) return false; auto plasmaShellSurface = m_plasmaShell->createSurface(surface, window); if (!plasmaShellSurface) return false; plasmaShellSurface->setPosition(pos); return true; } bool PlasmaShellManager::supportPlasmaShell() { return m_plasmaShell; } bool PlasmaShellManager::supportShell() { return m_shell; } bool PlasmaShellManager::supportPlasmaWindowManagement() { return m_windowManager && m_appWindow; } PlasmaShellManager::PlasmaShellManager(QObject *parent) : QObject(parent) { auto connection = KWayland::Client::ConnectionThread::fromApplication(qApp); auto registry = new KWayland::Client::Registry(this); registry->create(connection->display()); connect(registry, &KWayland::Client::Registry::plasmaShellAnnounced, this, [=](){ qDebug() << "plasmaShellAnnounced..."; const auto interface = registry->interface(KWayland::Client::Registry::Interface::PlasmaShell); if (interface.name != 0) { qDebug() << "createPlasmaShell..."; m_plasmaShell = registry->createPlasmaShell(interface.name, interface.version, this); } }); connect(registry, &KWayland::Client::Registry::plasmaWindowManagementAnnounced, this, [=](){ qDebug() << "plasmaWindowManagementAnnounced"; const auto interface = registry->interface(KWayland::Client::Registry::Interface::PlasmaWindowManagement); if (interface.name != 0) { qDebug() << "createPlasmaWindowManagement"; m_windowManager = registry->createPlasmaWindowManagement(interface.name, interface.version, this); } if(m_windowManager) { connect(m_windowManager, &KWayland::Client::PlasmaWindowManagement::windowCreated, [this](KWayland::Client::PlasmaWindow *window) { qDebug()<< "PlasmaWindow..."; if (window->pid() == getpid()) { if(isFirstCreate) { isFirstCreate = false; m_appWindow = window; } } }); } }); connect(registry, &KWayland::Client::Registry::shellAnnounced, this, [=](){ const auto interface = registry->interface(KWayland::Client::Registry::Interface::Shell); if (interface.name != 0) { m_shell = registry->createShell(interface.name, interface.version, this); } }); registry->setup(); connection->roundtrip(); } ukui-session-manager-4.0.0.1/tools/lockchecker.h0000644000175000017500000000331314553704753020143 0ustar fengfeng/* * Copyright (C) 2023, 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, 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 . * **/ #ifndef LOCKCHECKER_H #define LOCKCHECKER_H #include "ukuilockinfo.h" #include #include //为了兼容性,要在tools中加入ukuilockinfo.h,而在该头文件中已经定义了InhibitorInfo,为了避免重复定义,注销此处定义 //namespace InhibitInfo { // struct InhibitorInfo { // QString name; // QString icon; // }; // QDBusArgument &operator<<(QDBusArgument &argument, const InhibitInfo::InhibitorInfo &mystruct); // const QDBusArgument &operator>>(const QDBusArgument &argument, InhibitInfo::InhibitorInfo &mystruct); //} //Q_DECLARE_METATYPE(InhibitInfo::InhibitorInfo) class LockChecker { public: LockChecker(); ~LockChecker(); public: static QStringList getLoginedUsers(); static QVector listInhibitor(QString type); static int getCachedUsers(); private: static QString getName(QFile *a); static bool getInhibitorLocal(QVector &inhibitVec, QString type); }; #endif // LOCKCHECKER_H ukui-session-manager-4.0.0.1/tools/ukuilockinfo.cpp0000644000175000017500000002074714553704753020735 0ustar fengfeng/* * Copyright (C) 2023, 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 2, 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 "ukuilockinfo.h" #include "../ukui-session/xdgdesktopfile.h" #include #include #include #include #include #include #include QDBusArgument &InhibitInfo::operator<<(QDBusArgument &argument, const InhibitInfo::InhibitorInfo &mystruct) { argument.beginStructure(); argument << mystruct.name << mystruct.icon; argument.endStructure(); return argument; } const QDBusArgument &InhibitInfo::operator>>(const QDBusArgument &argument, InhibitInfo::InhibitorInfo &mystruct) { argument.beginStructure(); argument >> mystruct.name >> mystruct.icon ; argument.endStructure(); return argument; } Ukuilockinfo::Ukuilockinfo() { } QVector Ukuilockinfo::listInhibitorInfo(Ukuilockinfo::InhibitorType type) { QVector result; switch (type) { case InhibitorType::logout: { getLogoutInhibitor(result); break; } case InhibitorType::suspend: { getSystemdInhibitor(QString("sleep"), result); break; } case InhibitorType::shutdown: { getSystemdInhibitor(QString("shutdown"), result); break; } } return result; } bool Ukuilockinfo::getCfgValue(Ukuilockinfo::buttonType button) { bool buttonActive = false; bool newIniFile = false;//ini文件是否为新建文件 QString iniDir = "/usr/share/ukui/ukui-session-manager/config"; if (!QFile::exists(iniDir + "/btnconfig.ini")) { qDebug() << "btnconfig.ini file is not exists!!!"; QDir dir(iniDir); if (!dir.exists(iniDir)) { if (dir.mkdir(iniDir)) {//目前创建不成功 没有权限 QFile iniFile(iniDir + "/btnconfig.ini"); if (iniFile.open(QIODevice::WriteOnly)) { newIniFile = true; iniFile.close(); } qDebug() << "inifile open faile!"; } else { qDebug() << "create inidir faile!"; } } } QSettings *cfgSettings = new QSettings("/usr/share/ukui/ukui-session-manager/config/btnconfig.ini", QSettings::IniFormat); if (newIniFile) {//貌似路径下文件只可读不可写 cfgSettings->setValue("btn/SwitchUserBtnHide", false); cfgSettings->setValue("btn/HibernateBtnHide", false); cfgSettings->setValue("btn/LockScreenBtnHide", false); cfgSettings->setValue("btn/LogoutBtnHide", false); cfgSettings->setValue("btn/RebootBtnHide", false); cfgSettings->setValue("btn/ShutDownBtnHide", false); cfgSettings->setValue("btn/SuspendBtnHide", false); } switch (button) { case switchuserBtn: buttonActive = !(cfgSettings->value("btn/SwitchUserBtnHide").toBool()); break; case hibernateBtn: buttonActive = !(cfgSettings->value("btn/HibernateBtnHide").toBool()); break; case suspendBtn: buttonActive = !(cfgSettings->value("btn/SuspendBtnHide").toBool()); break; case lockscreenBtn: buttonActive = !(cfgSettings->value("btn/LockScreenBtnHide").toBool()); break; case logoutBtn: buttonActive = !(cfgSettings->value("btn/LogoutBtnHide").toBool()); break; case rebootBtn: buttonActive = !(cfgSettings->value("btn/RebootBtnHide").toBool()); break; case shutdownBtn: buttonActive = !(cfgSettings->value("btn/ShutDownBtnHide").toBool()); break; default: break; } return buttonActive; } void Ukuilockinfo::getSystemdInhibitor(QString type, QVector &inhibitorVec) { qDBusRegisterMetaType(); QDBusInterface loginInterface("org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", QDBusConnection::systemBus()); if (loginInterface.isValid()) { qDebug() << "create interface success"; } QDBusMessage result = loginInterface.call("ListInhibitors"); QList outArgs = result.arguments(); QVariant first = outArgs.at(0); const QDBusArgument &dbusArgs = first.value(); dbusArgs.beginArray(); while (!dbusArgs.atEnd()) { SystemdInhibitor::Inhibitor inhibitor; dbusArgs >> inhibitor; if (inhibitor.mode == QString("block") && inhibitor.action.contains(type)) { InhibitInfo::InhibitorInfo inhibitInfo; findNameAndIcon(inhibitor.name, inhibitInfo); if (inhibitInfo.name.isEmpty()) { inhibitInfo.name = inhibitor.name; } inhibitorVec.push_back(inhibitInfo); } } dbusArgs.endArray(); } void Ukuilockinfo::findNameAndIcon(QString &inhibitorName, InhibitInfo::InhibitorInfo &inhibitor) { QString icon; QString name; QStringList desktop_paths = { "/usr/share/applications", "/etc/xdg/autostart" }; for (const QString &dirName : const_cast(desktop_paths)) { QDir dir(dirName); if (!dir.exists()) { continue; } const QFileInfoList files = dir.entryInfoList(QStringList(QLatin1String("*.desktop")), QDir::Files | QDir::Readable); for (const QFileInfo &fi : files) { QString base = fi.baseName(); if (base == inhibitorName) { XdgDesktopFile desktopFile; desktopFile.load(fi.absoluteFilePath()); icon = desktopFile.value("Icon").toString(); name = getAppLocalName(fi.absoluteFilePath());//根据系统的本地语言设置获取对应的名称 inhibitor.name = name; inhibitor.icon = icon; } } } } QString Ukuilockinfo::getAppLocalName(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; } void Ukuilockinfo::getLogoutInhibitor(QVector &inhibitorVec) { QDBusInterface sessionInterface("org.gnome.SessionManager", "/org/gnome/SessionManager", "org.gnome.SessionManager", QDBusConnection::sessionBus()); if (!sessionInterface.isValid()) { qDebug() << "interface not usable"; return; } QDBusMessage result = sessionInterface.call("GetInhibitors"); auto res = result.arguments().toVector(); for (auto it = res.begin(); it != res.end(); ++it) { QString info = it->toString(); if (info.isEmpty()) { continue; } QStringList infolist = info.split("/"); InhibitInfo::InhibitorInfo inhibitorInfo; inhibitorInfo.name = infolist[0]; inhibitorInfo.icon = infolist[2]; inhibitorVec.push_back(inhibitorInfo); } } QDBusArgument &SystemdInhibitor::operator<<(QDBusArgument &argument, const SystemdInhibitor::Inhibitor &mystruct) { argument.beginStructure(); argument << mystruct.action << mystruct.name << mystruct.reason << mystruct.mode << mystruct.uid << mystruct.pid; argument.endStructure(); return argument; } const QDBusArgument &SystemdInhibitor::operator>>(const QDBusArgument &argument, SystemdInhibitor::Inhibitor &mystruct) { argument.beginStructure(); argument >> mystruct.action >> mystruct.name >> mystruct.reason >> mystruct.mode >> mystruct.uid >> mystruct.pid; argument.endStructure(); return argument; } ukui-session-manager-4.0.0.1/tools/mylistview.cpp0000644000175000017500000000160414553704753020436 0ustar fengfeng/* * Copyright (C) 2023, 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 2, 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 "mylistview.h" MyListView::MyListView(QWidget *parent) : QListView(parent) { setViewportMargins(10, 8, 10, 10); } MyListView::~MyListView() { } ukui-session-manager-4.0.0.1/README.md0000644000175000017500000000200214277116241015616 0ustar fengfeng# ukui-session-manager ![build](https://github.com/ukui/ukui-session-manager/workflows/Check%20build/badge.svg?branch=master) * ukui-session: Launched at the beginning of UKUI sessions and responsible for launching and monitoring all other components constituing the sessions. * ukui-session-tool: Deals with 'logout', 'shutdown', 'reboot', 'hibernate' and so on. ## Build & Install ### cmake ``` cd ukui-session-manager mkdir build cd build cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_SYSCONFDIR=/etc -DCMAKE_INSTALL_LOCALSTATEDIR=/var -DCMAKE_INSTALL_RUNSTATEDIR=/run "-GUnix Makefiles" -DCMAKE_INSTALL_LIBDIR=lib/x86_64-linux-gnu -DCMAKE_BUILD_TYPE=RelWithDebInfo .. make sudo make install ``` ### debuild ``` cd ukui-session-manager uscan --download-current-version # or you can change the word in debian/source/format from 'quilt' to 'native' debuild cd .. sudo dpkg -i ukui-session-manager_{version}_{arch}.deb ``` ## Issues [ukui-session-manager issues](https://github.com/ukui/ukui-session-manager/issues) ukui-session-manager-4.0.0.1/data/0000755000175000017500000000000014567236327015270 5ustar fengfengukui-session-manager-4.0.0.1/data/clean_all_jobs.py0000755000175000017500000000137214553704753020575 0ustar fengfeng#!/usr/bin/python3 import os import cups # print(os.environ.get('USER')) jobStrList = os.popen('lpstat -W all -u %s | awk \'{print $1}\'' % os.environ.get('USER')) jobs = [] for jobStr in jobStrList: jobStr = jobStr.rstrip() jobStr = jobStr[jobStr.rindex('-') + 1 : len(jobStr)] # print(jobStr) jobs.append(int(jobStr)) c = cups.Connection() for job in jobs: try: c.cancelJob(job, False) except cups.IPPError as e: (e, s) = e.args # print(e,s) for job in jobs: try: c.cancelJob(job, True) except cups.IPPError as e: (e, s) = e.args # print(e,s) for job in jobs: try: c.cancelJob(job, True) except cups.IPPError as e: (e, s) = e.args # print(e,s) ukui-session-manager-4.0.0.1/data/com.ubuntu.enable-hibernate.pkla0000644000175000017500000000063114277116241023412 0ustar fengfeng[Re-enable hibernate by default in upower] Identity=unix-user:* Action=org.freedesktop.upower.hibernate ResultActive=yes [Re-enable hibernate by default in logind] Identity=unix-user:* Action=org.freedesktop.login1.hibernate;org.freedesktop.login1.handle-hibernate-key;org.freedesktop.login1;org.freedesktop.login1.hibernate-multiple-sessions;org.freedesktop.login1.hibernate-ignore-inhibit ResultActive=yes ukui-session-manager-4.0.0.1/data/99ukui-environment0000644000175000017500000000107314473070526020706 0ustar fengfeng# This file is sourced by Xsession(5), not executed. if [ "x$DESKTOP_SESSION" = "xukui" ] || [ "x$XDG_SESSION_DESKTOP" == "xukui" ]; then if [ -z "$GTK_MODULES" ] ; then GTK_MODULES="canberra-gtk-module" else GTK_MODULES="$GTK_MODULES:canberra-gtk-module" fi export GTK_MODULES # Disable GTK3 overlay scrollbars export GTK_OVERLAY_SCROLLING=0 # QT apps to use GTK styling # export QT_QPA_PLATFORMTHEME=ukui # Change to GDK_SCALE=2 for experimental HiDPI support. # Requires reboot. # export GDK_SCALE=1 fi ukui-session-manager-4.0.0.1/data/weakup.wav0000644000175000017500000244056014277116241017304 0ustar fengfengRIFFhA WAVEfmt LIST<INFOICRD2021-10-26T10:48:10+08:00ISFTLavf58.29.100dataA c%Q^rTbxm}|sgT@2h4y3?oFS~wPvYQo5LV"}E:8U:,:EYixLrgjf?P?7Ij :Srgggc pF 0 & U% :v= J D s="2n;<1 : {tMd\.mU T 5[*~Y8o+Iat  8 _>E)"]4aj'p  #KWsG? Os.,n 4+ڛ!`ۍ3܏=ݕMz~! O ] ޹ { N $  /U lH*'TZumfvqWDxCVwwr `Y`e NP  A   , |x   1 (92-d  \Y[M2} < ':u]WWy]2fv/h; E gWl;k\F8F "U$&" '6 )O )d Z*G [* *r)( 'e a& $ #5 " c C  &1flF #yV'Y-w*1TfgT ;8q ?7)8 Dn   Sm k1 n ] " tn }!Cr~gaO+Wa[{18x~/3U (/ze6!Q_ otq$]`|  & FZ Rp5mEg e U  EPO ! 6 } ]s O P{ Q*JbL:THTo=-BD Y  '& g StQ$G a!!_8",8"!!,! k %% }BzbgKxk< ro "!:'K"@atV~E 3' %&t#,M : f WL<`F $'N|3q&HX$X!1U a8O܍ݚ]j}g1=v(b6}m,5 z^ Z1*] 5"R.]o}!?6g\: lI } ~S{b_r+N&Ewj6V\ + G K Q 7 w KM =o 2 =# T|2WJ(]H2xHL l"#G $ $ $#*6" }+M,7 { O B D *O3|6_dbFUfnVUsVH, YDu>$C{3br+[,h ~ Q7Gq > f _Fg=0   ES/,W a*,q@VGgDM,"Iz!g(jLl2j3  K  cB% g" # G qwH5 E(2NSkJD 4 "r%.l  a~ > *  ' z %Sm@*M / W k @ : ]: X Uk~S^ 2k -  mUK9f-yp\qk!H:KO0-bV M: TJ*u%9n-` j|g#}|g98jh 'Y 2o Y[4g:JWSe`K( . 7OS';1zHz|_Ip $ "  !Jto ": s Npt~Y@FR  R[!#%78'8r()n)+* */*Ti*)l(&I$c"NT+zQS B kJZ4bue8uCA!G)bvhrG'BF)x$+#j2C3!jc&Q|5b2a1r  93I    |:ZTnI\1G_ {^Pewb}g`Df` !tUr8 L  Or;E]7 E 1 ZFQIXt!#p&()l+M,---,h+) '%$d B" o Y ~fo+F s oRjrfk`#7/5Zh`~'~5= Jk >XA)bQ*BqX8j  ( SCM6~ -D ) " "t v0MSx  o @ n]Qcoze;s R~|Qs>BIuGCY|b +yU&qv(ya'jKy l2~{6IeIM|k8jx Y I7 g   } '  Q V   t  )E  of!>.ac   1 '    0    \weC\`   /w r 6  G x TF E S T $J7u$J_et\~-+V 7+`IjzP#-s/oKzXotyK)  uDe"RR  } * 5   %K5E|)5mSq>y\icMrP@Q$cPv*< [ONbHo509xe` T  0AUtANk9 >K{g.r)arT}   U41". 9 D>  > {bFA Oq)oOPQ S|KE}+\s0"4 %P{R c!?V^VK^&UhlfYm#-sPKwb4&73P;~Z&AI$iܢ۱{s<KӔӽ6Ԕ׀5<~X ޜbtqz?YHsw5l5\ B a{  <YG!\!"$4$e%z%&&%d$"#"!}} :Vx)t 0  & cps i4 FV"i4v0>*>S} /l L Z m hnI/*mNt U *s zNwC| <0*pc $+ *CH[|8tS/? SY.-S$sM/]5 X ePq `SNy)LR`q3VJ ]  ( i @`Z=AbX j"S5^L$?`TWd!-Q]PFB5 +[:<=8+0I(icT~nSR1+6 R | ~w X J: C~ +Bz)$ ^m3. p D  +N R |0 OH 0  ! 5* ZF  o -  5F 7   lc &)Q z, hioHS$ XLT kO@6t`a_f%Vb5iw/(7>B["Q6 Rn0jdHra / N '  0{  GdgQQ!AxV,_ V|\R(;0Ng7 IXtKGL5Q kH!$'N)!)$ )&((C'z*#++(7'}&($))N'I^%"K1"v b ; 2y x[D=  d e+ { iq % S |  #" { f" w%1mS?Z^%ILp X' 7 : Wr9zU_/`~5*o < P , )T ""#%(+r./.n,)'%#&^!9V !-ks@ΞʐV'yH $ռ330"#\"$t e6bP I% ' Eu{ko w1 ӉW2ӯ'Ԓtԏ֩5ٵ8$hX{R Q2N7fFnWz(1g6Nq{ S~.F1E@|T%@[@ Ot " wX$ 0 n Y}"tkGm~<u  s%8 2E1nY]a 0$.^']))H)'3%0 !n"0$p Hj|s = T xC% $&`(Fg*z-/9.,)&":x,G[ y dxT Kdܓ[ѕьVղڀ6 @]l@G?Lduh}q;xIyAz]r7ge&mC]\o9GPr z! %L swc ! I *x  M \  r & |C  ]W ֒mxҜ`Θ ϳҒh>R;x2I = !vP6/#L!#W$!|&n()6(?&?G$W!4B0R ;Q&l~O hOD(x~Q #X y3%~ t, xpd%H_|[-[0z (Dh2\8t'J) T8Nit |aߙSSXw- &8|dQR.v % n9 ~H>HDz^Xxvt6L:5\T#t $// niH  3 j8p>2(' b 1f$>P3 .[ OGbjӹZ' Y56eڬ|vU7ԸUgF?79x RpVAG ^}7 I`Wt>e!%"#>$C$#k!,_ ^i:6@   6G G #Bjh< Yo  t?+   5 & (/QsGi/O~XY2#I? ! S#c6 U /t;s  ]6nLL+4& C@ g z "k (2t9)>lp{Z 1J6.nYp:|Z-_ٕa`ʫ.T ˎL; .7%Jޏx BP1k_")')**I+,,---U,.+q)%^r!Nv?  m/dqysd `3/ n(|x=MNw cA 9u#xjHd!"D"qL 3hr1 | U,]NL ֛|խԷaݗ  jxh}5YN <,G "_$ q'@*pO,y,+o#*(7'& %I e% $Gv"iRs p"%1'n/)**@)-))[(%' $^ =o !RGo, I&x AAR d c k^  Yo 76YhXS(8 Btf , pI xy-gpWyY5-"IA^ S , pmNR[j G D / +OwV  =D5j+ߦ K߸; y =QP.V oe( 6#=[&/V/u lG ,m[n~Bpq5RU&׍iJ fطMwS=J 'l_DKz8U o$cn[{P(:WL ~DK2P>lU=`B*@&0  H$ ck/1dn<S j5@F'{=\- + b,   * &,EX t {q+ . d.{,Rs ( ! 1 b- ,QdV h   Y -)\)vSVC׍Х oc˞p͵c  C k9* ? J S % dAd`\UH!- T xD  n , q  [J\+pQ?+8/]FO $ 0?F.J#5Q6߯z݂Q:ޥ߫I 9 okV!_FA avcc|&1H"qQ3I5uAZif_#hkт>Er.9nQ_G R  y ;nv V^h !(V"t","!u:]g?2} / Q `ntv Q #~\Y%C g$_w(L+;N, k+ %)#&q"Ha0S 5 8S#P 3k} ?(@5[6bM\MH:8a8XPqES )QU mk G .J [8  v W T@kbr/(! vU lV J < OZf@;.^b?jئ֕GftXMK tCQ-!5z\ Y Mz :z&<j85A2ex ! !FI vWr2-}8 , E>r z ` fyAkxO}w47NWH(qQph ,  >~9v r%5tqa })Hmuqe?gyR{D|-3*AQ; ? R.wK  A i "hpFk<W8}Ro62/e!V$ %%%1P&Q&sR% # ! k ?-#  Z > tj R3Bh7aUI^uk,C i_Ni8~cݧ_,YHy& : *Sz:6Xnh$Xe%^>  HRwm[C36 B(]o&|kH2De_R6B,@;.*hF 6qwd7b2+Wi/  p E  x (  F ] t 8D  #NZ/ {%!J ) 6!" y !B# e$$$-#WG!?We   *  k sK    0 ~ TI&hF8خ$؀ڎݑ  " V 8 q P 7O \  S USd+n=qzM _  v & |V|4v m@B*]1nm5[dT  h6  F n t 7  #/ 8 mXkK6?iwT }hەyא ,ڳJ\kޠ7A K:LE  P47cRYZ+_Rfn!bK ;7/DtvwWZy  r ^ jIRnKGwBx|  1  g } :V  v6)8N . f[GGG~(b5y5 \ '  |`0\ 0k^5UNFM  i 5 H  o l RO w GCd$8$ފ+=h ds.pڿ^pE:hrqVN/zZ >v  ~ L  Y V Yx <0qaw;7|>?q->U&E  B$C 0J./St n )9bC|> _  z     ^ p R;12Xw@B0{9 ^"*ZDi,AJVDE 6 4" \M   + - = %? N{\~Vi@.# WSxHL,a"8gޒ܇EfLJH8>PQ? H S N {  % 2 ?K^#\Zyjb2H^{'.H_eg\Jshz)}sFטM?{eVqJ 7 n $7Mwd, o @2 y{cg!3>o HE 0K@{Aan<81"n,Uܞ[܌v+JKY,Pf#XJxn3v>2[vF:qݼܾ )j,sq#3rx/ViHV Fr`Md p!VxO >B N bA)u K .M 5  F'g a (  c5   h <> x h8es6 ^ 1{G3;C"gI LAEX:]01  > K  j<qM'r>4'k\pVh ^G/U`i{--Hw["A% 2 , y[-Vr8bbY;I~V5 R [ .5vg}^B#Bf 4dnEtXk". |~ ;  {]NYd@\uI!C""e"""3!Uw!*2! [e?xK #dv GJ rV1]moe } f/z$ yki-w_u3'q g i| *& dtI3! /@:? ;A |r,2$X3( P Muwl llx>n` j L n H" e.^  n ~ RpG F. k o Hj}xb^ l>L? 2?3`S}5]W0z`:?Dw4=0-o݇mE`ۆmݔD-b{FGu6,lO |9k?!z#>,c |V/2|wy R3[3.0CJ ) {} -MAT ew  0 `Xb_s*dn`J|egzM/<A4; ] V^s   f`QOH& 6dq9h|&/.S^@' 6 j H*;P_R744P]rRS4w`1q *#i߸F S#Lڦە?ܺv!߶#xE@HO%U 6:"1#$]$"f }WKzu ;s{Wt]~:@j"nKj[;7Dg\Di_ cddC: u p y r T3 n wT:2B@0$Zޭ=e* A6o09g?xb; SS (  . (iW>h6/qD8UvKN Q pT9T c P 5 \U  N %lb"pFPg o  n 0 G 9  c5rha67~)f,ys[]"2= f ::6[U(&IwU nd 0 bS Pu]c, .   uY:6:fux;:+_~R gr8U~ `18] =z=h#:uj.j. m-_]mw B :g*c ="S##Rq#~ #U"p"&,"7!j GH{ S  <i < 9 =9 cfKBSwON a / v8Q8J e Z u 1 b g tF(iCEs6P49 O*NCa/Up q!cWTh8 .Uy/s  E]aOQTq2P R  R T FQ@  @!wrWS~]3;VT!|r;{EHI Q&|!%{e2rJz #MxtZ`"0BW>=QWK P-<w)&]{,Zqk/6%Zjoq h  b 1~ E @l)qK 9F$}3= g4_#i a> K|  j A+hh &p @]x%Z!#=%q&9&!''I-(( )]2)S((B&.# 0 K  C  #  / cXt3z Gby  DYif  5 oQ 0   k  L )  i rbsGch+U n{u*3BbG1kj_oM n *`#cm\Z]JRaNe`\ 0hp4XTM'.lUwgxC'O.R_}z$x|-= g9  Z y' K U Fnv$_cy`nE WAp Rr]}gjPt*' g E J qyq,0   ,   3 H 3 Xou"yseajsb -  ; [ H  TA]  k # `I !! /!m V  AJY  |4\o^i v[ EDE   7!r m yn7 *QA x VHC-=Rq(z_DyFݱq X۵ާd4^!]A;1]r/moZSC9 U8#vA5~-Dw , ! Mq-s0 |-"IspAn>&X  F2 % Va  D I a %^SN%)j)q iN XH Djq@&  4   zP B  G " ] M  *P 0Q  gX)zrM3$T QAOߺJJ' }-V6+g7a]!%Y 4aueU"]iP/  # rma% ^ ( ? V p9Y< !MR8Kw*2z26D(g@=-ac5 %V^6'/vtܬ?i1Q.+ڄ''ڃޙjzV2?#@iky+sDB./ud@    |  / i  :lb@p\ {  PNGV *Lkit CPvg9CPT>3LTv0 : [ t c nJ  a  f  ^ =^\  c 4  M aR : a v$K~^.ByuNFMf3kk\Rw.IIai\~_m4u/N3w3<rUJ 9q_BxC]1sqJZdqY4~=cE,>H7Muz * G sO>lLw~ ^v Z`: & X 2 N) 2 '  | & 1Kd1 U t bTJ 7 2 F  Cgq-Xd  91w B_ uJc0p5&%?``@_)=f'g|%]  Vj.'o nE   F n  i dD/tMHr T c Y J  CIU#1jw\Ovch  20E[v@k_g|P[U/J 1> a 0Q  x ) '1b [o=GY/%r;< 3 | Z  q 2 + vr'xVsQ73uP!w%.z|j% + d fl . $ (p?   P )  d i Xi   *9   W ! M(  cL +  P b  [   X s Q`I5e">$ %J k% 8$ " &t  # Q P m j 1/oYBPZ06x A g < d 6O{_uv~Dzm}J8'&ON]g_ &KcALXp&<$LcO RQ #q%z}_rlQ]Qfe1L5YkETd/ k 7  b H x<aXZLY7 ^9sIsn]h9.LLPn:")MVH0!|29/o!lv \  h  ]x  T zQbj.q;\RB:kA![ }? w 1q Fsy1^%a+@b  4=zwGj x    * 9 q<PEWu  p3/ W{%fA mF mu ) < {hA1a" , {z^l >Lj: SE0pM@ވ[%:YgIfAkU~_j^D#j4t.^ m0{A-[bRv)/tP>k,"&cSUr;D `=:\V i5  J7/{<xXes8vS+HeFAO$nZM D S yxcd& v3 X 6kll  +W , .ql!LLA-v  UB+nU< 9\ @  3 m aq H ;  - <Z,|A@rBC ?bqF` znj+xr '3,!%3~b@Yroz`ri3@!-8/01PAel0j1.&|0S}CwU_7*;Pg n9~Q{y_P@:7}05#v^"O%TU(1nEI,~i }=  g$VdQBU!t~X > - &v}c'H4sG*3Da, #. 9 =lW9-x%M,zX}>W8%?Mcw@ZH0p,qc`BCb 6C[+wcjNs(=oVx06yA[bjV4 & 8P&"fh7uGX ' O }1 `t h lMe7= 5 l % h s' 8 u U  |V,c]V_.t4f$i L  f>   - q    ` E f   LX b ? 2: x u4 U I L| x ?& g' zob & D $ 8 oh5%@pk-\0n:'6Z*VtOJ!_ss4(4J] Dff y,diBp  y C tr!{KjP> lO h > :[ /VmRgW;3sq"u3YQ6*b\ a   N 8 & 9K m  6 n t A ^ # m : \b-exH?tm7r H *pl<!_$t&(}?*s5++zx++**C)x<(g&6I%^#5! W e  P`CoJ Ed qB T+ 45]H$rrO=l)g<68 eomUKvaxy/$L,2`Zhz"TI.J MP#ThCi$&?WM %  l}UB m F %   'D =\ @ s  r>P qT,BrFU~W-{~^6^JfdfbR.XWwc&E G  9=XDXVBZO<2 i x:2> #b /M'0 *]|Sk?   :y  R py S n  'h}'Eo   Mrl^n\+c - 6 M Z2 .7 T 1  f   ka  Fl | F  b "  0erDe-O%t|&-Ln/4~CkgKV}Ts"h(|,vr( ^no:ZbQ=Ji~ەxۘݮFmB)MdEnt:nT Dm{9 N '[E%DoI   g  6t >$ I((  f&+ F ^E jcrZDV?Fr}^t^\,s <Q  78C P !I x^g! #mJ'  : +D?J  d 4 < 3 14 [+AJh/9]N{s:)&JL/ _}7.`4>  KQ7?yrs,O=R FNR(*`  W}HGM |mV  .3 vqY[g[*nI!n{f=SK^EM[+;1X / LWmA~  #  UasA37Lm^GC :pJi(k \ #Q{! (W3]Ms0#g}V ifY#i8ky85?U, aE]y h  U+KrJC/0߽K߹ R1NF&tDy{!7K7^,XPS<4_a(~ ahK{DG>#75 5W.hJ\Q V O fPvcfk"u"= ? .g/6,'w& ;& pBuqNBLAP r  V &   iL"Pp   $tE&=NT8   |A  XA^(rf) * zm;(l2Y; o@Y  {RJJS= "r$& z(5Q*,k]->J.. .B- ,+)&r#*=s# miy,\*?Th%q" f+ Y 6wTg#].c4RmdP6KIښ1ڊKܠMl 26 I?Z]{a=B|[,c)MY-*3n8+ *I'DO@gTvMQUt *U6gWP=w`j /1YW{{ VuFS/tg  3 p ^x_*  ^n5*F Y ; !nG 5eBBf M' <G=9gikd-( J U p: d # } P ]l M} 6tFQ . Q??D3: e > eXJ.riqb$ax9 N 2w MfxQu )V O=rQ.Y@~Nh6w1uL@o*augOp"' H&k<1Kڤ2ۆܟݒސG޾ܕ3مz3.sDٖڅ!ܮ,mkls`NU<t x` y  w K% !@$&:(**+ z+ @+[ _*(%&5L$*f!$_ j _WkQ7ZMHV  * 2K b@>x~%prN J>   w =jH:LeQ(  Q &>iH*f58 j d Z3 Z~   '& ^NCe6R{|z:>>en4!x<5)cgd>w:gi~x1L{N=njVU A 0-BeVm#u ! T3RQ:MP]Jj U^bKCf ??6j O s: h$ 9 %]Y{k w 0 4G2BR F ri!ViDMx2Numm T, d Q F ' A ,  ! x9 q Cm =aO ,e  F .>VCt"rIXJ YB._xezT: JJkh%&{( [ " 17F~>}}d 1*0M3z/*kX{5 \ c F  9 5 V) M   ^$ L q*%3BpY\m CmjtbCk4&:[N3 C0Gv e""k$%&v1'''A & & '%$m"6!1PJ*~(R r jMCY?bOtT"J)4[):mrkNS[  { D2E& m|* & as _*+""` w; E  Q  h.h;y~,Pw i sO]pQeC=*v:_.4LBN Uf_&]QbUwEZ&E*.`b{S5Ycy0   MVhHY5l*b  I Nze34Tq~c k:4yW x u  yO  A N  A _;  8m|y'  $ n  _  c   p E [P } .h f *8|? C~WG xc J\ M`dlPo# \k k CR-3 f g!y"c###4k# n"Y $!El!u9! j[z 6w  P- 2=?A}0B M<_!4OJ}} 9 (t ة؟I٤3OeC\ދ[[$882beF?!}s = "Ve<q 9cu= j-&52AX3}-14i1= 9TgDYl-TiL~e= "R - -?eT~9 HiRIh "*T1g\V{w)tHsH1K/Az$  ^^Ju6/qC~', n %6_ ve b u ] H+ [ .\  # !Xs C M'F6[Hv  H Et9dx& D + LK B+:GA,_%9j  B yh-6Vb 9 W a }K a_ :G  3V$^ l/O7EhIۯ+aafާDحXxvگטBK-5Y5R_1/eK7~yN^  ' HTfOE6  K  3 }\s.  x6LeH9 I O h D <~ I, y  BL G[H}T   d  &*rh8?Ai_ \l,I=b # N R 5 ?e2vNhh0z+zr2O;X5j~)xrNe={4V>'Y|g >#)d@&[V!uFzYt X  X n P 4 " s tv".5|v.#'QD3X[%=IM,_&A#`hJ 6 & Zb64$gj*"/n \ 1=MIr,PD%ZO  ]=:|Pz3dO A %*  >  :9  0W <)O}R B^ F    S |!  x2 V   = 6A  v  `B+\%FEz -eY2 1 "l&{!Wo0ajHA{d8@@C nZ2xeiVT`d^QUo+c k:TY1Dik7si R xq{EPCp0~zH5.-߻cO5 ]N@K3hMg)M8%J+ MZ uF?oWh{#wLMT+    +]P&X" N {l)Oy6gS<K}p]Ls3, < L L x C J=![F8H18JL^F t  !qr |HpQ !mt  escKc@/p{: `oCUS&!os{$%\t+`'sB[{wje$n kOus5pEue@qq&(ZwmS_V8r{"}(k1EdRv@B1#mO04w(_|K  C <G5s!B"[$ >% |% %[!$" we} 348H{ q  ~} < V 0v ? ]k>c FY O B0 X-%}v$ g T Rl  5 _:vmEJ    zM`L!8k[!1#Bj$`%^&&&''T'&&% $ T# ! " 0x5!W > y #~Sbz(yK ~l%a-.އݧۑNTU {s6e]"5<8 *%iFG 5   ` _  *vfv = GqLuOqHMO%=B! 4 4 ==fr i , @ >   E 1 ; I? M o+(/Fb  / p F  Fk ^( 8 6r&  WG  -a?P.^auJlMJ -#Z:#e.Y3g+GbO?thh,^r#۹8@߭V3'#}G?YF_QV  h l *?"s.>umY^q,LbLp hff=q.:ppi-5 \ qYm WmHH%j    C* zf+$ m7wq;rma (~)!t2/uSrn_'{6L3j~v+ a>},  f 5   -~ezZs,  6   'yGg!$&t'N(a9)('h%e"w V  jb()=cbElN#3bm\ i"tiFgps+iDNl~&v2--N6J \W V 3%h9cFAk q ) Uk~mnj>#]T+bbޜ .,3re 3}$?<`.MO !gB:_`XqV4[$s;qC z f/   9g5Hi p'=UMca XQ $ L ( e @ d   \w x m !!BzHR      ] 6 n - 4n_\f# v*  V : J D F#AbYkgc]1;y;  (e[v$*߱N0,{eHdw ? $]Mz k21-: 6 Z t  b2v2>8*_`{t1_d >f* e(74+t ^P \hJ )"Z#,$uA$ #) " ! }i]/ W  jn[A&bMpa_,z  : !f 8l M': D G V h R V$ i  t; d  9M t > z m o = ( 2 w,J*qe o!x ! ! !!!n 8"~ _=]okn1   HN  Nx M)3H?!>qz0F<dS%k=BAMZb4x*jmK)2>HYaIax0 ; ! n'uN0a&Qe5Iv1ܧIm!2v/O4mtc ^g b.Xeb7b ; IzapYCa   H  9 + 7k  52QwC*] &m h } ]#5yA > 1Zr$a%QG9g > !!!!U! :  J yW {  lM0-`) - B :6 'E,Mi@g3oA7lO6F<܉jE<޾7u-B[Zi0KKLa/jn?oH#  j7 af U P,&mfL3NcC KE it N\I9-S'NZs#$(%(j]Fu]J:~{5D}TO@ Z y ).  T  5  N I A Y V \ h$ &?{Ay6rw ^ ` uu  ( * 4 ? 2:   w<W#Kb ]8  v -nFYD}$@u w4  _ v  0 4 S(   d K  [ &  D S   v * 0Ek{=TMOzuN<-:mܹZٿX3&$kL{;i,}<4Pg.8v}K. k B ) B,  t 9 ..w=i cluP>sT!Q}$^*Hh%[pE$ ^ x9`oXt._Q5d <  R x ?Ly L)_xhNwFk5>o|&<frE^{3" Q iu+ 6  j?b Jn w +Z np;FT!n #$n&g'% Y( @(m g'= %% =$ 4" Ye 5 GD E R  CW//  i86SHCcJ\,'s.1w6d8`>]!1VF}V&^a_D0_ Y(c~QZ>sxQhxA.$nay6A+v;^9&;"5NUUJV ?1 C> q0 v `t 0 FP=S{en2|,]e s t Of.dT<   _ k R E Od X j i\ - x   rA"\(u0W~p?[VojV&k+o,H8N%s    P   C v  L](\kE+i} }Li[k6czf%5/V*6E"l|jp;Rbޅ)Q/Su3fvQIQN- * \  >_h Z  } L  Q] t X U:g]0 !QڜjKkB܃+_M!#'4]k4b F r c&:# " $&' r' '%$c!C   76 kJ t h61 b XCG mB/;n}-\6G1gX OA-   1  ,1l 3I i % VhSs)v#H}p    w U 7 " 2 ;*mg<8I~Ck  Pqb Ec/b( ,B 36{/3dG*7SGq^_fCOB20.mGqGm JSY TXr!9//8#Ib'Y Y,#L;kd 7ٵ];'8 ߰HpGUN6*7 15etG]!$VLk, \j(q&OoJrH F   /s s<   ^V-a  + '95TE+2;g/  P S&'m^n1,IQ 5  \{f:  ip-v o !q_1`)s!/k N?]Mc-h6w t wiQhQ U }T9:N51EUc[k2 #v0r00![UZ x`>k*-NS<!o@|8 zU@gE a VTD_UDq] =' "JPF.H9 JG t : *v ` m@%?e f zQ   A B  Gz # { !x    C  , ! ;K B$>G')9+++1+k7*($'6%?#S M! U O =      4 n  6 a qmF.ma)dZ_[AjRP H}W V*q{x!3s|2oPZQi* :7!34UN,hx@olQF_XT1?{ Xz*?;rK }*8_O$Sq=k % ! mp wc    n = %Z#;yMW 7 6CnJ|>,g[r #-"UZ4@u)M^c0#,"aa  l.b?   nZ  7) D e- K Y! @"J " " 5"4 k! s h S3 1k)`< N f :7 2 HazSf;@>I?1I>v>3CXj5Oc6#wLL]_ALQxD@kaU!fkH?_C:[:m&< )-D Y?z/f>OC+' >cf A GlR?Q3t3|HaoOrN20#d3Xtcxz!i= W$&rWvlbbty+ & m i + 4 yg/:  ,E(IX=:J} 780HCK?UOM4d^a ZvEl^eaO q 82MAH.5E  g_3p| Z   C'uy0-n\_=b U }vY   q Q RWK0C!^F94)C *  Xq 5NF K FCJ*,1Y sP% ^    " \y  x c '  q~ ' a! B 7 R]>(%v"kFw7'`$8m>4w, v n *A+lD 'N 1*i$e6ڲ؃.j@wFM*r8h *C^Wa.>k''eI##{.LG1<;#GdPIo[TJegvfzi8l|'I&["d!  q  Mmvr'3%K E) L  ^ j :Bui/ m ,  b " j 9 \ kS + = y ' l/  x!(le8Gqh>D5P#%]VvX0Ek- _0% z} jI][Q8+ 0/sݩۇ؃L؉%ؙnڭHۄܓSB:7H d [,I-xP*y.s] c X! fN=^9ECu\b;:8M)>[x*M5cCPtnߗY߾E'.iFR. #>'uv2i    D (  S<   |    + 7  f1 _ c*k"   k s  * > l 'RU''V@>9!"#g$A$a$#A#H" ," ! ! >! Y dxdTn@ J3 _  X $193*R%X,T /PXB/zT[5okXi\C9BYb[yE@@ l8y4FU9 GklCS%1$` 5sj ZtVn]CI,OToP <\sR/9n )   *; C4 ;  4 ys b ]C|    sR0T~k   v  dSeo 9 #q - p] k4g2~c\0 _H>*ewYw2 O +^9Hw G  K_!l'jy N R / 9  /   S B3&_ys!I)x`:,1K4,-L`/^RI R k J   , Q n  j"OUi,:m!s Yk2YA>G~]&n &HZov 1H4"FL?0sy"&u9!.g}p z]Tl@6)IvuEO9\b<)C GMr/ -~w*&!% 1\  b`~*p~`'O? 7D ! TY l Q  M & (42) |4h$5` 7y@ H knos`07 >oBzJ72f+(uh43N"G%L=%^oJqWN:*mT*4et jggs$&!kHuN{#;>4ot$e:rg ~?_tE*VwdJSMzhj/ 7 w  )~&}[5|ecbB^ k z,!h^!,W! !a f(;N=Cv  G  jiO*a% 5 VXvsJi$@sz|4( Y<_dz 7  }._BL`(G   OA p A h L dJ UO/::[KT"D#A{9} bU{-g`b  ?V}{?: & yujKSz|Jr!L_P&4hBd:uM}{w&ut~'Fd!AnR4(?Mt#@;>9sStA 'v_:Qe.f@ |  DH UU AK !a  | y V $k \%^+e  LN ^ 7FfG5`6F@V! E  !   N " 7 } D)]+t!C#^$%%%$#"!a < | KgI 3 kF/ &avt^~&"Y(,PdX ,;|{UWk( Im W1y z   = j  F;j#UrCZ[ (Yb r7~Bm%P.@6UJr Cm G[  A E  T { C^F}^SX ^Cn>v7 2KJ  P k | > f   p  r x1 e ? yCAw6B5k` U - _h5=YIfDb47C|X}\U!!t"":"" `" "N w! Y  (  I < 4F   F  F& vZ* 2&~nubj(-8  e ]  < |Fts@ (f  y e  h o;R1 x </+}E]Ti70I9= F PFn:NN 4 M B  dX  ~Y&6 ! bX j<;- @ 9:#u  U   j @ j/h}xkt<+eCUEv^>}%`leR01vtN0m'r'~Gi A ({ (N?[ n%[< Q 28   1 U   =og#L|Dv$S<d3 *p  _ FY   ; aYMRGz cs ? ZvmH$#aW3m.r$>',Y@R)3tsW;h!8 |ql%HmB`Kv{" 9d+c|=7xxJ-D"i<m*_B<diEX:(/8Y'v%#6 *_,^\$JW*v{4MR'Hr&W  p V% 8D:Mn]=7\CodQb $ tl: | L . S l X  K  Z 6> L_ 2  f &?A|  J  Ka m b |U>O" E{K j  -H~J("9tN6*;+Ug1oR65_ %1 H .$b\Ue no 4k X=rvPmL*,( {f&#')W:uqD7)~&9 {k4P<t%|O0"cL.R*dgdR7K=BI"/qv0WGCBiEIU|!|_,W y n P  AT o U 0    m }MUC09MNg6[DdT m e \  3  G ` D - R _  "$B$T%9%{%2Q%$V$. # ". ! ; w ` 7CX u.FZ 0< L K9%' R&H%D!]nHWBf8V"gXd%dQ[\j5}lY{gn%t![( Cj8VwF  jP 1w4/{3k(HIn~u#XBF:.,:RBoVW* GMhz}js_ojnE3@,kWJtg+;1.GlA~e.y0): * ! ! >a ] Kd  <  u ? &> bU .k+Ju'  V |   9B !  . ~ oHu/Zo EY C)jg.:MIH9pfy$E) ,!:G6hO7 f  Fk  J8 h ? p 1s_1A s '>p^~ `|])U8* O.mcJ;dQ~ $A\`J )&~A}rE7nBeN  M  n (yJz Bl0t~"H lsiz ([o1^ l .Z!9 V  7 8 2 & Qno+/ohQbpMQfZHMn-&_nH<Eny}}?io#M` 3 w 0 q Fz`ft' "t  g  P &< !   h  ri  { c ~C    ;29)eI|L~"FM 8 '0* 4:#[X;>#.5t?;-k~xM(BD6VRlyu= =A}!es!W0mla|DDC4! $ F }4V7 [a  &SOiF"  Kl2MOIJ(  L. Y  $B!T3,4-f09v!\ FfxOn<In"}SA{;sc6d oSNq^6j X/T% }!  7 q}A _  OhCpv 9 5 y t%)Z)TxW2Cexv~ M  ,[bH4?L | f( [|B7IpVW J  F{Q1}Sb~ L~ C }0   H j8 =  J  n ?T  K J _ Q   -5 ~ %/\9U-C?m+ FM^(jX C2 _ B S ~k c 8i#jpu!-U  r y *NmP>5 WywBWLKG8<}?U b]($u#\y;:7d]|=~?6n'| \ByQF('F0vWyiLa:~* -bvv(:AB'`a;q_; i {  2 a W X  ! s @P  F'o"p  Awiw    y  ` !  E  S )\  0  PDtRvZhVnj|&f>h!a)c .qM[[vwf%eL@g DiPlcL/UlPgoV|HT@rB,d(` 'eZIQ ~aU%G S^XrPPaJ]GrxN|i$*` icF5""L j7:&ENRC k & $H `r P  % `H.7/ Y _ u  rY Y 4 $^B'P]~%|AVel 5   {6#\a"QHXz)@{.*5 uxzPPtlu%.e8BT2aDtx@ouZzq4UU]DMdm APRt;j/bvCa.$kQVM?:T!-?dM/8Dn]KX*LiB:[Q6p4 14G)%  -bp?`ld p~Mof+6[g\Q~5= 0 c5gl#q$  P< +0\E<C'0ObWRu`QXxqty+9[:"p %  }= : Wf  b< @gv/ xc=>vK ` A T(3^#Q49  G )LH0^%  & z~h+.a j @ h ~*@0nTyQJ(,"DP.S{2WS|nEX5Uh<'fwa"]f@*ja0{ $VNili~S ,+510IM~$f5(HGm6cSYZx91NkbN ye c  ; .4A0m!!: 9,q E 0 L \ | ` _   I    ,1vbiyRJk#@iFw-Mqk%glBM)\"`V{&_YLO.W5nY'Z)qTQ1DU&+h$EHZnJ -b e (bkO L zv  r ^1 *Y :d;o0Y/<># z O z{ R%>4At(f}f]  ) Ts @%#%J=Tj@l)K~<(  n  9S' L +J *U   ,   % @;RQX`x$    _  <  !  I   y P  pt  >(H}`\{$W;%I/*D{{I4h$ p  )^ o }  q Q ~?-rn M f  g:F]- hX'c < $LQ5>}3ejIOJ]A6"hPCnPX~1]X(imlQTf_]B3kbV&~ 6  C H1k * t.5!-S4p";j  '  D+H"9Jc z9 C r,'Q?}/"X*."%qXm w EE $s ( d Q % [4 p)6>y@A 0a U]  l 5 ; ^  3 V~  5 8 / ,_k  &  ) 42k|wK=UrCNUY''qX>f*N.IKI/K0b!&JMH }mD~e?5%iyAT*YH+~Q]!QUcbThVhid]<T\_DSH\Ld\?!]LiHy~G:{MQQ<y9 NS r } ! n_ B W M ~ w } 2   Tq  Z ! x ?L uS u[%y2 f n 8 CD+ P 58J$B=K'DKM?D  ` vnR}> |}~0\%wSN17A6=l3#(^O>Vv~Y| g7L ,OWB_^r$;{yAv8sh_-yElOKDg[|GPI81=: *pn OX= ; 5_ W_^ -    Ld sr=Mu  { l   n    j 7 D  ge  N y V 4|$35[P!]2CZ2IJ`27FShvyu,;oe 3 * Z V uEKms6 v 9rV8nq0  (mlc9[b Z h  Y b  v E8,:cCH2/ tV e ]Lhz[Oa(lE@ & Sc&;`/7z.VB@&9Tnq%U@ Eanh/WEV@RUX d 3p2$>[]E Bti% x L ; @ >   dS6N|'>JCd u/A<:@ZpD^'  T n 2ev1SC  y %n  ~ lg c s i  &r C ]  9 ,  , 8 9 -  j ?fa:zA0o~M+0k)lR .:l3E_`do+(^nNAdq3[&[ j]sTUA812~|F  jXQ2F<U M   ` % ~  }4*"P)KMHC)j3^sj &v r <#YUYWB. # df :E@8% N  i d /%V F C orQ?L  R & ` j  w8,Uup}g'/  X q `E]W/]7a.c + >4z)X"c.\GceN Xm%R$bRMe5kp\*y(   5 d S( i a 3 C z HD ^ iE L)xx"c0-<H 7 wB =J;t5OhEL2nUٽ؃Y$dBۘڜڃ^zݞP 9de"$p&T'D''b%g#L" $t&U'?(' Q'W& H% I$ s#""\"tS"!R ^G )- R*q..L+u 5t(<"ԬLҁѩ Ҁ\ח.ZjV\)*OM2/}\|MO d\  HL   / M 07R  ~qi[ 9v XyZR7?p<7,eQYBw+9XrO2ov. >>yIOru5C" P / Wq ]6ht N e j:7% y (  9     O>i = ]  SG &mR~_E?[i2 xU 9 Y @ 7 % >  /,8XycJt wVgxR$T4j  *$uHU Z L8 fP.M[ " # #B ###p#T# #"!$!\" 06? 6GNf Vdv4"sT6Gw6O\% څկp]Iӎ(h' ^yBo;b X2:~! wNp#  wK</E! 6 ]\<LTT 6 Y A  gNuv߯Wxa;1AG<@bKR5bb_MFH@q&x,` MQ3BqAYkj .~I_ A-u87OACeJlz7<M[EdKs$   n: ph E  !q~X|Ca@/ G Yy $YM kX& 5.1Ooq([UwD#]x/L,n*"J = 6I "XkY m!#%A^&s'_(D) ) * e* * **w*)r(&$A"@9TmA(Hh;Wi % l BC . K uIz%$w2O~63'j%H{Tpl ) K   cI^A nF D } +   >f ( T  g^\Vn;sgE[+5 8   E   ,Z&d7^E e\=5Rm~%_} &a 'O /, zbR't # r { ] o J  .# 6!m]^wS3B2Tbp(jw Wmo;_7? N ~ T @ O CAP5myJ|V<*=Zv#4k_-= ij(v L|u);^v*iY6l}SNo:}DVv D g Qd 0 j F+ 2Q>^#9ou1NRx` o2nA r   h n  A nfvyO(Q<'= !M"0#"b" wK: vE NG STE :;\tLNN23_/'K0SOaFZ2;) _]) Py9"+SUNX^.(ecZkFp {N4zcV7Gr M'8dDZ7? ]  T A ; w kpxdu"o)Fk~ U  I v rU EJ `W E -N2@jpp_8 & x / Q8 -  !  7 ^/(B . C +0 _ 1K"o"d"3@!  Y ( m =< F mN8;0 w3 EW9 !߹nvC3BGN?htGX>*sUe{&9P0qM|  "\><9]u0nv|pGq[qIzXeW >U$gQJpY 5S J\8&2cj? Z  j ^  6V y\w A!!">&##@$$$N$#af", 1-6k"rj(   K6 E xf>$FZ  ?}N{01j+`/ZSw2  t L ?-K4{) /r_v*r@jߣBg߀<߳޵L޶6DFI hKhך@٨LCzBJކ xd'I$AX   ! "rZ"{"t"'"\!? M6 ~( l Z  R{2ELz q3e(E8QLNj3P հK&{S7nV eGhLkVv9.f1 \S _jmQ b Q th_  U:5 5   --c ^ cWY2J 3 xB< {  v;^E@*4Z|Q^HHr݉ۺ,ڂ%CjjX 37چQ (r܊CPކ&ex[uVB{{D9|!}V;f&x} yh O z zi=.wI(w#DV80J LB#%' ) ) `) (w 2'@%#g!3:nQ4>m I ? kS kA)dJ6(nOd']Nf%LUI : K.H"Z ;2`rx  ]  "$U%:%"%q%%'%) $+"+ 0,,+{*)M'&a%${h$\$$ /%` %\ %+>%A$"| &(*e ZzQDdtesq}<]?e߭.Cޕ2P1܉Ecݫ[H1+4(iIO. w*C#,7]{Z } WSg g>/ ];'`!LyJFrڂiLf^iY ҷ ׺,wހ{N_-lDq p%&B!D G"iSL=u5/{AQr5{]m7zyUo+HD.?12^uo41D$r n , LO E  + $ _ E{+friQ[ O r-Rzh/.a}C6V ^Z B |]5V A! 2$ b&i ( )")(o'G"&$6#!A `IJ;r>F^" "0*Yt v 0#'R]_\}-E3a "_N1mkM5HI*xB)G$j5$s)6NiZR ( @J)*Pv.yFR8hN6<?+ /;:QQ!%s)+,x %," "+*/)^(^'%#f!nkz=/Y&u ?{C=moKGN|{@x8!\yL/$7$  | W $W{(06 3G=R:$ Q!?"=""6!# i!".#" " Ob ]H* NgJ(F;;?O- C 4t'E(U;l0C10x=ho%7NDG_}_7rj]wYTXqFfx]w6<.p] MLG\>Ru7J f^P&9FLO=9TA &w}*M N-FLOlincr,[/GiwT CPJt{Dy>kߙp7]sX*Mު iݽ(5_-e #2qN)GK\h Q%t Tpo?{Og P_ *  W u`[/?(z::OVVe޸ڤS6֓ O p: N8=R!~ yaliTC(H :k\ tR bf9&7N5_6MCi6\xCCn C x!  #$ y ' 3|  x G2  Gu Ov{ (Q5'Bߠ3uܧڷ3.8d*GL#O \X$ "t j.fyEGSjq P V\I" $:JP/fc"Cx{XP=.ge 1P1`r^;p^sFyK_ [qӼZ.YgE5 ON.1zX*DD:v+pW k m 7ill8'({#t (5SX }Y;!:""S""!N!M 4a^;x xRm)OP'0 &( mK k -{{U  %   }W T!x  z!!I"cb#]# # " ."! !  [ Y! "{"6#v%~ .'! ) , .//E{/ Q.$ , f*C (=%# ]!*2GtA rS  oq 6 0! @! !N )*  -t8!/7<<~ )B@YF9 s!"e##}#b"<n!. w#sb< tEj xl(/% +-HEz  Yq l  O*lreAxX6.HG]B ց՘W;;[O/;+C} h x\bx > A    0 , w P ]8 2  / sR^K $ _ ;  i^O\VgEsAVL*8uec[!ܺ׬%H17wн!lЗO;2ʏ~ˌЛ:ؑޑL8 'd"UzP@M Ty^?>S&Dt:0  # _Y   9@ n _ , J  0 4  D 0 ?Y sA7;K1%}F?s6F"5ܫ;ܻS`Q۰ii6 &Sڔ<ծxnZfs=c1 ߆+&8qRcu]w[4{4 64F8R     Dc !d " #`$k$$#"c("!;O!! N 9Uq  ygHP ^V{D[gJSeB ޑ qܼ ٍ +׿ ? z( <` <#ALPzE5̘ΈurQ7:>ޔ{2={5 B/ti & 1>Y<^x!3|J#*G n~٤y"z>rHݧ 6N``$m;BF I PL CV_aJ2jU4z+QHcI q 7ix & cZ (/ B>^b_TESܠ @ ݯ  ?L  *N H Oc0 pO/" 9 e  Wo!N#9m$$$t$o#]k##"""="!T AJ9aUBC#A ` _Rp"^6AF/q3?d݆VڔׅA՛ 9? SzA|إx[܎031_z .\?eTI:PlQ- @7 `J_#03+lO:>1\d , E eXh+Dݙ jfeӿ bKˀʉv hdoЄ~ն= xr;;c3u'W2C {"@ c h E >   S Do7I w g lu  j @ CZ " M ]I!; 8 I8 T  k ! T<|Q-g+?szH ~ = C2.?}+sX55+^1!{hj &$6}AAqr#Yk9e C4R Q k:\X~v : [@bvRH0^.Bpc^`? + 9 7,u+~ Z* ( &$\"(J:/wh]5x P u J:  pCrITYsNFO`)H Z V r0 !  ]NC]3"pl    M8YF5> o^ .TOXa .ۏ f] + s WnZ{$] Kdqwn q UT  \"{ES`CD#^w6r A\ b q 9D1K o^~D u' f~  r eY>PZ$ u e,C  }t(k;M 7  Agtb}yB [!3M! ' AB>_jR; V c<@d)M ?A GXe~( H {Qڴ;չJmmԂf{r4ۧh?dߧI4?jhjUMBJ!*z/Zp3P# x 6 7?IC\(DW'S~]OT$}. j A ^ 1-wo~#V~k ]F d U GTM(_SW< oFR\6 IX$i^lji=^Q > 7  8 1 f K+ ei   ~   4 vm 9 n x J;YOWAvgn  =8 R HqHM~DTCmyby$cZiuJCQ[ c<fS L Y  ' 82l;GUn} Wz# F $l t 8tm>7 W 8{'T~':Et{_Ha/hEm4,I00,   | UL y: _-H1*W@werS1b^zr, `a v#  P uv 9a_ %c8],jIS & Nt{;H'CB L4 8- \ Z1K  L ^ G : T" vb \KU.-`/ . C9]5<~p ` 8+ Xg\ Qa0(2![V?;o/t5L.'C@b{J c ) _PKlxF$ &  6 `z|j~CS } N0!!!! t .,3 lltG'z2_:=;*\#/JxtU~C D?K:CI=5bS\   4IX*,>} } zT 1}#I3sUW-lYrH  -Fk :2 ^G 8` eCVyakmvK> <5Dr`NmF7Q5.NU|[?P`[Jy5 h ;vm  \ EX     ( $~ov-  8z9fC8OCklJk'L|>\E܀WcP./S[lK 2@ / Z ]I   ] NV R T S! [`a ?3 [ B &d/liv!7W  aL1[h=d(VFtr3rEFH'5 H , J b t E t} ߰ EX 1A hI ' l  g@ 1mg(@s  Xu b h a Z V޽ Z | W 3 ܆# =8pGw#SK' 4 P9 + duR'MOK !w#%@'(6 * + +x ?,,wL+*('7%S#!/Nh${4  } F J v 6kwAa!UTnu  ht\h"T 7{_! u k DK  W :   [    ' Q ni!Y.44 fX d K ,  ? : . |L n x E a&9SH8 ?0Y!}n_#-`iޙ'sR֢?׷` ڻugrKsUMxzC;RuL46~K*] xX { zQ ;fD s5uBQhZlWgn  Q 3O)4 Im%Zir $Zo={uw@5{vi<&;'E=|AB::BGSQYD.-w 9@z2g w  ?d57  e   R $B0Qm= u VP.~%e lo9i2bUw'ܱ=܎ۆ۲ ܿCܧqܺܬI3*DS1[" dPk~=We6kLhvFm m e  E (  N  /B  z' 8 A{oNKT ;E tFpyu]k:N jV=aC~_q^w$ZJ'=e*R$FLoY~~ fO  iP}yiy  'wkuYG,pH0s80f VJ;B  \|}{( y k&~t%W_ig Y5BV&O}&p~ @3(<!]$&U(CD(&E$(D!f uQP.O 9I <qD u#5*3h)iY u  \n{Tp٭9٪zoe'U>PH$_|9 i g-A$;OR-{W@ C m}/5>tTV=  gx x}p4)2Pz^4jӾщOёsӦ_HJڗ*ݯ jBW.P"{ w ^J !5#x&<$)*P+,03/24?5O5v5"5$5C4=1P.X+)&c"l_jw1Xܠ B$1Wpݦ_ޛh_4S~A/ " !s7 Z ./#U  Q8cwG`6 %!+'7+]/v1122\y1zA/*,(($ '~& Eb4*I#Ko v- B + o ܋ ܰb~ zT4DHamwFd< >dJX  +na.q  i| !vJ  ~ (EhS*qf0q9VO"!#U{#w"!mR9h h GD+ft Ok+!n@"Z!Д Gq}T2ڭN߻is F + YdrZ ܘצKk҇#y(Yҕ,)ҫ/N2;c455 5P5W2P/+4':"mzD;; `v [<"I&F)$+L*)'8&$X"a` y]rD7% ^yC m/2"\p'DY,W=0)233uY3 $2 0,G(dH#iA7 3 M&,  (BFPۛ&XG3* q vIR/vlt  ny7;c!V!k7!? ,{ -~^   !&++902B\3W20Dr.L,2)&'#TAxwH qS7 !Y>K;W   g!mA?arCj90$BpZ4 y4*i n f]]!)&!#*ڞ-S0J2ե3{|4_44244v54t2d.|)"c 6 {]d!^MDCY-:!T"ە$%VR&<&׀%$!f'0޽a߻6߷ dZݞܓqޛeLuyRQۤ $׍y@- HU`O'F$E@+߉sZT) _N.-0h^ *,C"V'$+A.-M1m4\4d444Z442.*W$R J"fut rygJMi *ԳЬΌ ͙ e i2OUX,2^/Ecڷ_݉cWJ6?) 5dqS_ + [K '`PQ G"jd#<$?*%&+&@&%D/#q Zi6W W3I7<;[m 0o&܀ؚS] Q"$%L'b!(K'?%p"Z{) gJ:]mpyߎ l+a@̻vˮ K7,˚5ˆ 9!2˕"1˨"I!˙lI <r}8 G`" g0{1 ݮ7P>#$(xҾ,6]14\4ֺ44ܲ4c44 42,'~"@r ? bLr >Oy!`b%?(I+--!.ڧ-ۀ,k*i'޼# 4to{:@ FQMr`#@"7 =\'#f'(*++-*:'=K$>V G5߭,  o5L8X ,` [ T ZYr"ZmD4 F~=ej"2$Z&'(M(g&"u <~ BM]D X z T V3!;"$$iٚ$&-$ُ"ڛ|qߺ  .L  1K!M1g۲ֳ ҇ 36"ڶ%@''&%M$" & o5B/? N g@3"^'j-245 555055'5543M/Ej+G'8$/ _C< w , 2/a9Kfv!L"w\"k!?E  Y Kى {! B<ޠ=V~Zm $biRGbٮ}] ߂ >mY m ( ^6.*po' 1 ~k$m(9E+,N-=- -G,, ,2A+)'M$g <ZMr P E w[ީޱ޺ !~ tj Zܟ ܾ ZBߌVGoe[p*imq\ Cv4QP_jf_$2** .G^2 $ ,)Y<-.0C34aE54]N3y1/-/,h*(<)'j%Q"3 yw| ZAb`a?H]% u$qMEJ<CCׯ WGwt{&x׳p$ ) @e 5[[6L> @TR 2%)+C-L->,9*P(}&1$u!:ܞI6]o iK _uqa޷ ܁ۑOG4^*چer۹cn݁ l 4t%{hcL z#?i sFl1"%3)a+"--V....,-h,EA*';$O s'Z  e S ` HvsK<,G*[mHXA % *,c= &a/VGgp6~i4] \ v  ` d]"&(T*x++@*w(e%C"DwuY$>AgvG# vAH?P2M.:q ۸t; oxLztvaK f ju $.(H}+J-إ/u0t՟12iԐ4-5i$52$5(55ݴ4 ߙ1-( ",ngL & EW5XJe K:7EQO W OՁ } r #q @.%|+} tU n ; ~'3 g"$0%7&L%/#q=!h ) E` xI&F . {  D G !A $&d))D,-_.0'.-KI+(%!6.bm s&?/= F T 8:+@  36u~THSJ~| ?vcl?,!d#q$%%3%#" !9  q:~ RRTyTPy ۸ڙ>܎Bblc P R }{h+Jt@ ~ W: G "$`$h9$:" k0 ; Whn#Lzr.8)#ZCk]߉ FK :sjoO7Dl-[Dpy]  }X$ )w|/'4&5c#5#55%5525 5\5K4Q0--l(8#mw~b  x:/;RZ5aOvMݍ٩ָՅkW6;;I֞ nךm0PrWN+7DQ\Td |nB Zc;2pR IS _:rspS'M*kKC@+\@q@c H(TA /"j###X#R" ^5T=#_)Da c UCp:+T,v& R . ! &q9iJ!*ziqw$ IEP~Ox e v'zfvnW|? = < )i?v! f%z(*0-8/0U1X10>-/6-\*@( %XY!~3 ^ u.=M v 4k r Bn~3#E>  dw i+ "[,<5z -ب]LU!!"n! - y w \ I G!H%)(3m*m(,-g-%..@-+t(%rH!iBwG @ <=wa ] /=@ GLs 1 .AaD%o\LVm/ a O, # L&)(w(5)(p6'%"M]mY62e k j^  p^3" !+'#'#[#yG"uR d1<.KI|q y 1ib ?5($ }#=/M(( 2TG>c9 p`/x&!5#Z#~#"!_ aj7 hm^0KCd(;qGj eO ܝ b]VRu K 1dk8  @eU o#%x(*9,-o-5$+G'x"X]u_D2   ?F$[{V>Jad (['ރ2fڽ[)xHּI.1 ׉Hu3(MM { I.'318 hH'2TS*X#F  J5Hd u#$P%$#P":@l0  f UOvD  S 8_eO7 ܾٴ Uzէw4Աmԛԧմ9lUݿ0Zl=[*pKA_rT]Sݒ &x%bsb8wKZ(RAJ N a  'B KS"fD%1 (v*=, -1,cN+}s($" 0{Gg 2 hrIlM*Vw/n9q n܏Tۚ!tLׄq !- թ{Bܖ/5;t_ y)ݣTݩV)W-ޑv{ fHTJ./U}("  *IG$ o(]+,!-`,*]j(Fn%"1?.Y- Z %J;a nh ޡۊ ڜ]F^ؼ O pڷmfR  p@7S~I ]\y{ )FTb o Pyl xkMB @m \|B(R$L?#+Cp $V,PJ / _ zy3XI6[  ]3#BR=Oz  *' ? Z L O]@U` U 3,9 Nx"]#s#K" !b ?WX}_9\ rWW m; ^0%(j+Z-[.]..:.-d!,b*s'$0!n&*b {iSRz u`r ]!H!= p^ ==%ޡC zo [6i~e& 4@4-% _7 NzG)&} V|"1%B~(C48L :);A ^VIQzQGNd?zpOOG+/zB ? ^. M  qhU f ui{)WHiMt3>S}AS2 & i'U:Up~^IkXWBM<tW S (0A{ %:*4Z/ >3P 35m 45UA5&>5<5<5[H5N3=2ty0.?C+H'/n$  ' 1* X) 7zIoK*uu /, S \ &ޯ  = b<2,}#3p}&Y y.c!c :x%pU<c,*ݯ1+ l pA L2+Vv`H4߄ U ger)Y!!tDoAmc'gN9\HWyPB1A j+ko\S#)R@ R?(UT k!L"|"""O"Q!~ != Rn|jM,9R]  /p7%x\<6[ c=0I8  m YP p GMk2D!xTH&*L~] v q qAD L4x &qX5t{I)Ll (! ,& wD}q 0~ ' h U )i! - :Qax!o A[z2FtF&6 ߰ D W,mߥ1IM ߤE|^v 1*X 0p; Sz݄nږniC $ jdS$(ېWSakt Wxf X]kibct1R3B$s9F&. ! '&hWr {  b6gOu O" ;e3;v !  ! 6;  ( t | *1DJL>-Bn2XuG65sU$jL6 y8 S a R S  8   A y2 /   p }!{!#(%@#&lQ&W%/$i3"t  t v,T'im6R K '_;Z9['GL֮ٺޑeaw7$z2Q%ۼ Uن ؏jSH~ְZ֯HbMf~SHܘ Aj z. f TC/!V!i"|W#6#n$y$=$8$:#8"!wo5/pN e  f H   ) 4 X  X )f > vp 5pFxq+ (qay.E>~j 7 4;f(1   ; I lcnK  CT<&/o{_ j=C$JA 6f + Na 4x/B&@?.-{, >~e|nH1)%,I$} @ZViOm z c :hjowaD(;OZUM;+ B )o|oj 7 @ QNdrE%j D!;!!K!] me 4 :T9t  7\ " b * Y|Wz0 EHY i5 RI %B.ahc6G l l' !Q KPK8 5) % h5U %x 9mXC m o!]zK4M}cv v X C D,^c hHtk\/:+pZU9=Gc7Prs{dE0 E? n Uf@X9?, e a] nmoR 0%(.,.d:1q 2Z 30 Y4 @4L3310.,"**l&"5n c q>KhLW! Nc=ԿьJx؇ۊ6߼1p"OWGi@)Jd e N(^RbPPߛ)=߶w#E,)   V\=kS11 n"#$vA%Y%~$d"Y5 VCXkj #G {h=h*6<awi`.q3O\ڡqۀ|݉p$t1hMf W6sC.T5pt 3 j P :Y1,FC\]7 I +!\ ! !! Vd1*Jg n  `<7ek1 , - !  a<A;LITxx$Vo * 9oKzhzG{o$>NbO3 _ zML}4Nb,RW?VLN}s '\q+Kn*\ ar U}t8 D2Ei=jgY&Gٲ>#*?m dl  {9'';^` K Bp]@,ga''5*] q)T#YHNi: 3k }k1B1eIC^<   0 nJ "  46v;$dnI @  t23zQzCKUE0ofFg  ^  :   h Mm zS ";gW89Hf' n6f@ o J ($a>(W_S   %  /  HE !R$V%6&O&%$#"yq [Y'I(% TG+r9;ܴdL=*n Ԥ Ԁ :گfU0tXV,9&  {Lx2eߗ ޅw3YoM,tc8 <  -cW> "EelRPJr6x`P\O ds'w)vp3 z=6_~(Xd_-6^s+7 ?  Y f] r Hx / i& M  r `   yj E7X>TOa\ iv!deysS){B:~xU%ۦ$6 *ج mٝ R JݝiKU/. /  *B6t_(g @& R Oj i 4Q*NBC T T7#sdC|Xd>%; D <_8 M >Pd0p'Y:m;4h=O Cn . Q 5 < + ] djy % 2j-e{ - HX4(G(0h%ko)%} K8 uc[G*KZ } =,^a{QsG"afoYU]4 6: < ) Z O Ag\# ~ XI)\;S Z=fjdXRWm *1 <2nvg.78mIX 9j'1!) f"b$ &H' ) -* *1 +*B)S'!$HP"jpLII-A@9 { (r_O  7 L o ~X t2 K٬9xހ+7QS|<) % q) <u}T݌W`ڤrV'Z'l 5>? RI7t%F =YgzY1(U5f&S_ ;Rp| R?fQdn jb1Af< -^?#U`}gC]\2 7 (: pIxr*!#aO% W&&&&%#k!3@ 9  !8!C!| !o l vj$Qo &Q}h;  B Biy5KK"Ie'H3RF  * | hb\OVOQq__<r t-%lduE.+eBEJ)Ns}9  /|\0 4 Z~< MnW{ " GA 0D  9ZfA y#JD"zkj#B{V\C1*50f}*C Q yh GMk ; C d, _}g_gu B8]C     3C5!7W > w I : m ;{ g< ^ (S  T& : 9 }0 > /w UYnC;9J!_W* o A D T w ` ,B^<vFU6iv$?%VX@ r ' fR Q  ' ` c N K G N # &| gn ; S P    -JfbQ$cm#OEc_ w a )8ZY2\B`F8}4Tٗؔ 4{ښxtR t "1fP|B;,3gG۪ C ڄ ڀ\:Q'#NguxQ2GY%OZ ; ? $"/tF1(QKr Dum   So'!N9U`&l; -ދi]I/K_]Z6p8>#8 "O6KyP]N , Aw8{S#Lt &2  B g_  2sG! iHX ; ,I1`VA*,5ev܉z | @ 9 (T Uo ` s MN M $( -;$Y!k`c 1 E Zs S r mjvVbsLRp L 6  Z  FAXjZV;/v u, z N*uNA;Xo*`Ww5?#kwSZ+C84L@5r4/<%/Nu0A"v(( ]  !  -  a v   c  <,>N_#oM8E|+2 ` h2;a:!:;$.& 'q ( ))))6S)(k'%@$Y"%i EN^ 6 -^gE'YK1*K3|.--W`s<,#q/iL<t u::jF'q. _ . r|$].vYT{e0g(+( , @+ L B p yupj(mu e8v~sQ`1T\?hg UaL?iY2 I (dk[G.M \s!>"# $@ #"}!}?  mD zi?7nN 6^)\ = -1oW='G]QF |/UU{x~1s,8 { \3dkPBffH,EC>  k\+ A9 4 W ; n I  ~Y z: O J  O f  w< o)?%tjyzsK|{:6,  Z  U    t\  i$9%TS<cS( @rzmaTIH@  3 f  :"LAJJ]65{y^ E $ 51G+jnNevofTj9@߇=~+ 67 g`*UE|U$nw)*{O' TS OH/Mxf[qH4G ~.iJ-%<#|T|GLB!?Gmgo]X Y 2 P   ' R  ( J ~ `m&- > N 9E C h|  wk-c`S') wX o  m j ' np1CZ>q#>p [ r-  (/HE6$=@/CX #} Y } `b KS 3S!BDYbv)@ {6u_~W*mrCe[tmt~^>f` * [ *  0 e E    6 >W9] s pZ}SX4: YKQ}A=qqFۧ# ڏGdFccߧ6=9WzZ\ytqEzt@  y c2,T+it E3!E!x! gk%#QO ^  P L_!w{e!"m"~""("8P!s! RmzU 9OsZV~g^y޴ܰ}OfuݚG q  > Li.m2Np8MF =m"qB\eTB? 1Z  CGeg8.3r G N %d +^hG r s BIj,  'XOIPt8rG-NDYB_)YR%9Y~ 1Lv u |: q6?=mD5PcxX-G J 2;  $3%! |` f^BfC5~U 8o  *} :@\}.kr 4 Fs-\+H91j y hJ m# 2+80VyB IXKS~d'UhsLcRG/J `fTak  : R < 8Ga-@ &  q2;h8w A; ' L 9 c  : - H9  C gkGw&wMUq'N>4xV b7 m ; " =oAny)>P9f/X`p%H  "}2cFrh(=]  ' 2A 7T\2SK1V cm$wH*J#p':O M#8[DTR Q)_mK2u Qp0fn=Csi,C7S<[ O.8?{P1 7 ' LV >4 zo WC`j9_mq4@mWRKZ[ |< }%0y.k8m0*"zd =b > % Z+ۜ\r g ]i S -g@}q U*{YIi  SPle   yQ 86= 0 [q=O2!(D jUI27b)x(  9 fFSN;5T9edxpEXݱS{Eޏm.<.}7)&Y's  S( 3` p ;  , % r vw Zo^ {'[h~bT@O\.{ 8 w\ t /bg ; O  d `2S= '[3k%VpjQ; ! 1qf=3\`.N[b_$}u#'@{*zC D_s joeQpZb>zazMt46C<-TXzl]K'%& ,>p3   = O* % Y  - `~A Q U } 4 } H '  .l [ Q &  1I8.y6>dKMH80PeDؚnqN;#EYSVN`85a!] vaXxyH   w z M  vM@2P#0fdn F I V &  G 0_2efE qNۚQ ۢ6M޿M^)\ L.h'f#f:L" `4 , & S \ D0( .8 S  2}`   B  $*+?%;?;& mX'|y*WE!=r+ *ݘ)1 7@n p . ! E : vW5 Z mcxX9vWGw N  I  J d3 ~] f;G(Cd (\(jNzK+  }K >b 9 B"h      9rh}<!duZ4_}  < :E r(q.3'Yly#sf|FNg7?874"&$0_ a dY [ d ~mIP[J(y8ll k {  ? $ h,Bz"|vwk(Ai 7 jANH9&EH* '05.M }o  $@%<Pb}mg e F S  n [.  & ) \ l ( nD % b   Y%YRKqwYB S s  7> ( b  u _ Hb O[@;08']Z|v8!!&)2T18ch01\U2ZoI Z; :` :Bh(o VS@*\$ .R@Kw dd`h C _  * 9t2$ gTowveGu&oUB)t*iDp && vy<O ^ t'BI-?FT*vMiri^nw(-yx k| $ e 7D @[  p  d  ( I  Mi  r`TSb5?r  Oiu2<k91!}S< hO m;7Wt_\^BtL[1H_gdNMa]Wra_Y  zK zINdLN'm <1@=Bpd1H1= ` T ? \ 4e Z4  a  _ ; #V =0c0ul*Ek[[Q4( >c=;Tynh">:frQG$s)Y@gMaRB ^ R s',oD!(6,)zR! ] - D  x R7w,|2O\5H:(gTSOZ0g w_/tbXVkiTj zHh=YEhN|`x)8 8sCktW,g)ݤ 'gv^݈D l0ًW٬?6W ڭecߋbd!{M9m g gJ X k z 3 =Q) H2It[?l?# ' 0C+3cE+iyCc w 9 MN2md'x lY4ud`t)>.1xRkPL+~b/1E\_?T y  f 5 x : )$GR -R d >C 9#?Q Yl^\ Q$, i#+<[vA>nsPw ~Q]X8pK;,wP\l K W}i?4K ? R f S^%slI'Z\xs!# $| %5 B&d&%J%P$#s#j #T"U<"c!  5T&{~ kI va1 Y  #Y{Xrlpwk@ L  CH@urA$$.TCS _7  3iMH;lvo;L |_ ? $jMwE? y: I@B D~[<- !` `  W{2EfT8yA_OWWt <vN + T Kc_D: [ Uz>.;Fg,aP>6=enO2.jA}M ] e  L 0 R% `~\&kxoLW ^  R    0 T J p ` <0Z X)q / X1YF5H VZm~_xm[W&!f:IxY  , ' q \ ::>y \   j  iEitv}^ @ :s > 9QJp7y?F- )=!E:r34P;@qm ` M01_N| L 2 hP    r =5  %  !Ze /J>z4e5pUSyLs (2|Cv='T5C9UUkqc=3w YqJ&lh 66Qy1N߹VO,ێ~aIڥݡ/!k5Gzj,2ywe > yHv~OmUa{|m|x : ,uQ|b&QVf?4E%9qX\b s/i@b|/T GrJe5H=KSc< IP&a8[$fE{C0  %69iPwyyO6spuot-S P 8!ew7vsQA@[ TBKJ h 0U;lfE[w v>sS-{o5.CK @!w[ZaRS, a  3D  s &  S% J P sH9z`+l.-X/dS)~i@dB^30Qmx :l tT-'Smti~Yfwy6Y.>u#|D0X;jP2`n=>_uXC[ d7hn{s/CZXFw> 1 Q* x . ]|]|!Rp_{` T  uA  3( a = u G    X g W 3h  _ 0 ?#~  W  e L6AF]f|+qsdn1 A ; , G i da  D D=  9g"v F  H xE |  #QvHQ%km<+k>YuIB&!yARltZ[?^P7BD[w>*#R72?%mX ]V fs Ve ! f G2/ v^.2y4CUL kC1V K ! a " N _ J >i x} H `i>M$lg| 5  I ! }nNc<) yk;h!w 2  I    C ] K  cG  E  i   %   X >P  q2S {(  ; me_AEb;I,s1 * kqtfzihX@2'd@\ClH!4y}Y/6lW*bE- gZd  [>jap0W tCo k) ^ b'e (Y|l_Qql}'8 ?bQ!MGoI%s02c>+_yqP!#I;L0 8 $56e2pF%M)#Wr^81j0T(`]Or &  T?># s(MDP/Z}_ 0 % '%Ce m8 ;B&7#C  c * T m+5U8 t#{|qaUIjJ.?\;ZF,&  ^ @9 := +4 S2  -    ; ! " #o |$3 $R$x!$$#y#eg#":!d?/*  + {  Q  qSQ.l: :GL:) Nn 0sx"0 Q\ @ l1Sq   ,7 8b*WxfXP =&WedQ\&[ _ ^ a y +yEl=q_k iy{ A  p k  e N7#42Fl6&{<+1 vG8:2_4I,XnF7( D &I 0 S-| Ae$P?YmP   , O Usp09K?J50@0&-E X d z uSQ;o24^GZ kj-E8E GY'F-o?\R7zKu[e9 u2K o@[Az :T=e^!j|+G[w ewaPLtC]! - L S[lLF>2^!}l[ 4' 82qJiJAcP6$ ln/M  c W :8  ; RK~(  :   X3P* e ` < Hy d + pM , r tk L 0)@Q Op7d&JUl% S,k,e !N %g%$fs_m\D, 5~oeu3*z|#w_N!T qnEP*& + S; pWiTq\B 'W&ky3 :uS9IyS};)[KgRPuDfWe+!A|QanH[A  T -!mm+/mia)?!gI /&sI q e, - ] qB0hR1![UW- C  %o Vg^0t{P\,3199XX3o/7Dp  '#CF\"#+4_0{_R % V {so6="#]!5o p [L :-L:3_+lNz A).% "bbn,'jA?gR&RiO(oPev%}f!K&*' /df+2{Y@sn?xf8Hy/Rp@7k,P\oN7}d8h(=Fi[C<  - Z$|q6QHgXxU!9a0Dj 14Q/I<@3)e/hBKBh-X;2R`85fB7g 8od X_,N KskN]'ep.t@'[IYzIpfox4hO5=I>=mBu <z. &9 WI x $ Naz0X '\R _L)A2'~ k+jP-~eL3DX.?[~*L? .5 - o a{bZ+3lA%5OediUk&}W._Xu o F  - kh/O / 6 \b$|@x>cEKj nE)$7n#7)?5.j)Z P U G # , F 2 m/ u# k 83&x%=%sYqS# |~g9i 5  / 5%2Z?CXYVM8%<- q NX'tT^N%LGf]YY   \ ka.V5x1!q"_##L$V$%$l##s!B""2!$2&'()'s**/~*)2)R(t'&% $u# L"j  xAa( { 8 cRlENXXZcW ' +K CH d . j Y> = R =agNC n>U#0X*R?&V]M:qKBPs!CxWk 0 ,, U*  p D N5Kvu03^,>N j-kY}gTL^T T6*pSlHyb p/ e ? P iEyza g  ~  f   a $S f \9SwVxJ;-6E 2 + tE  0C89/ $ ( 9v .1'( m /  sAyfb^ t_ ! \: /#N'X1x)dOX> m? uT ] [ = X%_sc%8TjS 5cNJ#Li-u%MUO}H9wDCZa~|Q@ n {c $4  6 c k 6v *_c O3 JXZG; z $ 'p_T\nx+$>9A>XS _ r x U0 a* ,D7am9Pr6'U~`Z:~i65|o pC;y v )b . .@ J :  !  Y  n ;_ 1S : 3 q @(FLtj1M-N )Z F"  a q s 7M.q3Ob S    5{f~YC[+9kq7A*n, SSmxlBc9"<^.T;ru2cH$X{$9n)( 6 ~FGcL G099v{~4 G~ vt" 4 T4Kfp17P=:lH3Q|-7T@W R  Cgzfm</K  { mt y  aU)u'  [ . T (  N d s m   CI 8 Z \ W   nhfdT<{5Q0JM]Xd{P}P>a_-NB?8k8/aA8"L7c0k}S , R E ^  ) E g @ ` ' v w < r   ? V u  pZ}#Wy`*_h7 # N  laa z  @ M P2 4 "z LC [m3=D?vOXq X0 v ;(t[sje^qn< : m_ !Rz/ i$W+KWzYnMs]D DS>P=q1H/8|.l Y 7 ] ? m o I t z  ~Iclo8?Y qcq keN3@~T:S^e Ykay@34!i5r7hZwpPxL?m/P s r h) 7 = , S^' )fU`49uDQ2JYs{pa4GtcF[ShjdUkLKAMssP_;?@oQ95v; _ - ^  q ( y +w]ppgjK 6]dVm d  m ) Sf   % J2 % o`[;d'bR[9Z|"q*%{ [L = Toc$)T@(+xSUp|RW;zE]l*-nuY}0 Q^LZi`B}2"XS`F2%q06iS  Gr=g=nM iz}c<OThY]RD|JO\d~2|x||f: a  6 ` 6  ?R  6 m2#6l [O7^D<tCY! s"KxJ`OA/a^puAAs;R+X []? RT ~ \ p  3/^ : k G m T  #  zl  h K Ro lG #   q d d z   q "? fp}\u Vu  a =  ax X  K o r! S *   ( :P 9  3 {X7!2^hdLxq\T){;lKXw3E % !4 =xrN*nA'YTIm|l$kq r R ( z 6  0@NSL 'n6I>)m1qHRiG0A>yQ47lG,EbjBtE3 ~ `y u"}A6 YbHR6 M Jw"9iDM 24g&,l*L:bb  g {j{"3t  *11=k{ xGnrvF Pd n2>|)z +  hS    n   ^ Me(iJSnh+Q[B2kJa(%geh-{ߒKx f;R{zx* +X| #y )k1i q0I@" r F7 ~ ag6 _ q>tc4cu1KhPx_O 2 m m  W -  x 5`]2:*.H5HW\UD-|H r PAim?"t , y6P UM0!5  ?  (D)e    ]  w;  ,F, Ec pIz(]nJ!^~ { i^  z []xPQfM0#(O0j_mN8s ; K =20A!8XL'a0 uz<iD!??QgnVC]\_;! mJP'xu> f> f KtA'zt  H "p \t XO  ?N!pa(tM+%y U.NGm0V[)1p:[h~`zO5/coB !r[nJMWp] |F ! tVQ+x k|wpLB p J& )@)?<CFGxN # [6 : 16  <N[ B~v + n | m1n:YWR2=OC#lC))4  n*/^x# f { W ?VekB<]>sIyq6)OTb4MI@G-B'V{3<} lSޝw b3{:% wH92<|[)D D- sXfA+.zyc{Ro?h,O$<*-K 2f=b,2]Ub' j 5  )s aNV&Q6|}C^q">kS "`j3#1M]g`g54SH:kVF z &2 B p z5MvlAh ;YdVk 6> f3) L/\W< ,5gPO n  rU {/El#q    q d1  y  QRl~Ap  Z @ { :PUQEdB( y 2Dt3;CQ%r(lo%Ln7o"L-`$ m eu6*hP icjC8sn|,q}7P[*ZM=T&  N2Fg 64m,qa#n[=@,Qu(kj#`+'GOI Wf8_=8tL>S x"$y_T3 JK  o \ W /SnL.XNO p!"f" d" '" ! % 0l=KRs4ngL>ye1O n&n # C m   M,rb, M ,so0}ui _W]1bpEp  b :"Ov 5N0OY0zd `UTl"Gmh"M#?B~G}v{qO"Ce* x<!:z*JA U z A  E7 7 SkkPMVkX&>  , @ )  a"   *; g\ .(oe>h/j cAXw@nv5L;%mKFgjcHY"Q ] ,Hs|3gw(C>8D1ذr|}Ԥҵ'`Nҕ6Ի+ֈ8Mܭ0 Gq>]"'Ocgd>XrVbh#0q1 wIL `Uf@BE/8>k # /  r  ecULR&A  a7 wnW,;Cm;}E R-<)%lA|kB&# ScD `   a  0Q.E Q=HM6(c ^  EP<t XElan]l. _@ LV ,V 7 2lxz5goyxtx3,_p2 \ DK N E 7 _   x| : k e G 0 . O    c6 < ( d  )! ,!  # 0   % } 8  D~ A : c+PC7H +iO4z+4Rmk!3jJ !^p K g P   /2    X 4qqv:f>FvMsc}uA;MI ` 2 . 457* * , ] . I M3 9vO)Fc4,/=-, s nv.!  & $,pa1 w_H7a ., [h~D^&   d f t %; |lBd[l<lbh;#\6 g6B %  .h '  8.|i6n;mWb+$lb + P\2%@4JpZq&۩=۝J2ߛxda|NuGO&JYkvDFzQVl)Yt4wN0{a,^zL2# $ S "G0VT& rcaiet5g`fN Q v,Rf]C] MaO/}~!<:IV[i(&6 ^ s F/j#   3`"Ke q kd O $Q4:+cB3 8- f  p 4BZ$6D9:9NVUe:Ud `[?31'b% 6s  " { Q { C >| F K ev?\%`oetymj$ w 084=Euoii !i  }3VG|_^8 S^G\Sq7Y5f~b gNBf _3GaO"2&.y6UwBWoP*CBHLG;&e(Pjtk1Y1(qz{:$?"VR[ eA  8t>  ^C   w o!+"+y#j$$$%z$Xe$##]6"S!f }c@h K t Q\U}9KdjG3'N | $ m| n f0<'8KI#0l'<.) \z:8NFeq -@r7lsZJ6o|cD@%0> m U 1 wpYd-{V[$ )8   E V ||'-]Mp$pT5[G@lm@z77~V x*$iSgb#Hc%jm  ` t+ h L :$ Ih z <cs$}75 we y  $7^,jLQh>iIWM2q5WcQ g*Jewtnx_T@\   TR  8c   n   wV  | ' D f B . h 7k c nBg7V#U";~T/!"KR$%&'-(/ ) >) #)| ( 6( '1 &* % $ I#v ! G  !q sp'c1yx   &D9!FtUy!bYY7CwON^m}v*su{I[  [[ 3 } ^J ( ' O+ _ 7+ b Ea [&  l} 3 V % x0 w-}IOlBO\}Cdn9uv i49dxl   } + #ZAOYQbG." [>a! ^6 F?z\f$ R A X } o Yz $ ,rGD]eNt|3[c  =Rznc2Ty*= ^{''t11'ވܳXj܁ H@g.)+ o`J7 \ E 0 [T\\4| i o_ rtgDE 6U"x'm o-Q /V  (J}U@'T>uZnfnm l  & GYB*(Ue2o6Q( @L[V?C  "w9tL a -(z'_ 8 y,p;#,i"  Y & oCaHU*  ' }N(8_Bq>j>1fZ)0*x!bD>L 1yD+M/sjL S8(&LV <|j N HA<jM. * QHV}BW4kxF G 1%Yi.),Nz|4<;1`0/vQt>q8_' z7 st/V)7b1[undl  /Ae.u= &z f q T KTw6!,c VeA8Hur<3bHy   % RT Y   P O ('|?JYgWr"}pdhegmt"fCP,sjN<, i T X q,      # 0 < 1 o 6 K 2N"~Rldf*{16n-%/لxկ*~fmGѐиb=iУhң$՚#/"];B~QV.m%U#va2i([ YP-sctck6^AUx*  y H?Q`]N#"7 |=Zz?0 U ,ED#H]rw~ySyZnLg z0(E?Yks$ x] ;Evqi<5(GBQN  w & i ~ c& 7C C  }i C  ? Fb!;^RPe5 5DvThke vT*+B[39 E x 8H} g_oNju+dV^|7 5  cz X[(+: >]"#&%(&M&*`'0'K'j5'&%$#g"{! S)2 M j   = Q ~  i   T= h0 n,  qr0H A|THx BY 1x?Lg ?j  uJ&B9F>(  W  z  ItzZ,AP34,s>@zK%qz$ VB%G `ssU- n"#$TT%%%#g%p$#"!HvbPA8J+#8    h  > q q %   H $  (o;& # YS ~Rl'~#l/YQG = g4 z Lp yP`S[&g>GeliGkhJXfY? 1 |3 T [N gZ k w r_zj%`I,6bamlKAF~A  v K >`J?$jna/3ftr"W +  H L_mTdB T D&UXNz|nP?S ZW^%S F6.3wJ$Z   q    .! `]G Qz8jw7/  S N|WRS*&~VP4j "J{    P - =7ki50LHf`FK<,XJ}f   x$ Bh 4JM[ Zwc%S&bwR-5 cMtx f M 'J%lCB|i;k mBwVduGL :R])<9 U:m5V.gPO 1 !d 0 Z c * _  ] 0 )UveA3<oyT ;1}6$r4XgGb9CINxSBh;6O<~Ex`1aC!c[  pbBia q!x#$$*%ku%%aV%$a_$#"'! wV`M3X h3; e +H  N5?oT {G:yf3#>H^7k PU=z|>B M *^`~ڑJuTԫ|Ytӓ0ׄ0٦tb ߛ's+?Q*?)e~4i.rU|4cR*U1%ElU' D- QoRu H ix 7X &?cGhW/Y E  Q9 ^I S 0  4 H *tX\{DN `E f" 4 L q,Q:4`H\lZ"=: "1,z,Qve>L\R [VKnTvMOBXD [l z4 X T t' o Ci & n ^ w zi _ ); dj`o_QE8 ~(n  c v 7J "   c"Q # _%8 & K' '% ' <' &%$1#m"&!vj@: !C_!@c"~#g$$$#K# ! 3 h gG 9 f8  y - 8 gG  (i ($BXieC !!dq""K#d#:#"%"Q!`U+oy= { M e#I3h=0tA'rH9 lM+oKR'd+Z%ެ Lk fwݴ5URI_C[M.0"T 3jaw*LglG]ancn0B6#F#_G5 A  a    d 8\   @ [ [b :\=|@q p"<#$4%1%=$#"@! 871lH\-  8z, Z]QoDp )) Nm   g  h  ' ?K g O\rMnKi^1,e|dcOiLKJH|Tv*|,`4uW<ZhnY 72   G :` :D] 3c {    wJ(X n._|!?"->r@x-:x~M>:'? F @ L  7 |W  9L" B    Y, E sM Sj]W3o `H8$=_ n j LeU.73\,[<'SS,k6]~ f)J}v nW0 w zp NF'bO!eZ:N  v X-4C_1Es  Oz w }6 [_ n niyinXp\2w.xDu#rJa;n[U-*,k+E]8 Wio['&w6WQJp`C u xq ]4 Q Y- pN ( |# -D % uBZ. p HInw=9-F^|*R1d'X5jMz= VC anO3% $ w  7 o  o F  @r <!rJ""j" !< Ta)tIgFe) l :-}CxdzY +)v{qwrO6C#$[E7  ^b.Z8 " vLe<+AytEPqe ~isO]FOP}Rp/K}4:%6Ivv9s/k{GI[]jI39QiLHkVO(^ EWH)NhxE'|`F6& ( (( '!;vm z.oJ !   9 ?  ?S=_HKe_xqRX,w$0=%ty8U7]l[nQY;Vcr?,7Ul bvj?8M%)_2fE{zWBB` o>A;\_+~mR*zh x9L>?- 5 6==mM26{ /   <.Yb    / { Rz$ | { '!!!!n~![ t ,We  d;!"""S"5" !Q q&m _ _ wwU\D`f}  +*8cq~d@ ^ JK \,npcZ<7y . 1t [[ $ >tPb93 yq)qyH@TaMb2K"|j@HO @DsYu!&ehm!# '$WI#Rt+4!gV~7C8>YY>!y\ a ' Y n . u ) ` V n<Jt| H   i" NL)V!"b#U#]$$$%6D%[%,\%C%w$1k$#V5"{ v48 | 42Z]EPxncgq}  X Kc II %    W >jsHJ w<lvhmc&cHi$yR( M)uh?hIkW]|rPNB8C@o0<(  =:  f*j\  / 2   ,/ m  +>$Qv5 \ [ O/Pg"h99]P+1R~v Q * g h < hJ a?D6/ s O /   n  -Hvo rwlg"A7%Iz2crRBbgLc2PN%h  +, TCu9b08d fv >   e k|1<4hj.EB\lX_.ne>Cm:^W_L H > BI00 6) D4 L 3 + @S 3@<  *  c  Ow $ 9 hC]Mzw HR 8EX!|g^.= @vu/8{4~Oy B  ~ S!BX Nx{Xha!CGdlRpa9`3 ^u8SDDHj$x0 ( 1  'g 8'cyq !r{r8& %kdk;wmKJj?0KzI^8CTCSk0b(]fmayAl vpFxW*Uk7 J. 8%1L52/84-6 6-0HWE2H9ޯ6GiSw!`mn!ib4wN4zC { O 2 JP K ?3-m-+<e3V u NR2z6g&T\~QI!! m 5 % +  | (      oy((cQV~gJ7>rG/+2h- ߹uވDz[Oe&u\ }HIP#7:SSei;4! *cuiO/3/TB'C k @ `޲ݲo!)i%opSRl#^o}  76a msVv' <5fnsF]m+8 L}gygQ* h o ?  { ] RF J 5  s O !U " $ n%H & ' 5); s*'+Y,-9---~\-D,+x*L(v&Z#1!QC G[=c=^  0n;"{T VV5`q]iQN/o U l z cm3sYS^1- > a 3|  _4  ^t_MK p oiO06NNi0^k } 84%N;RkH7%G}o IK` "  ]  3 e8  p T A {t4#W@[% >\=|EI@DU|f:54o$f%Lfq{6>JsQ$Vf+ s U3q?P-Ss`6[~*[{Y  k)^oV7$n1Z.YI_hX G R N H Ya B 3 3U NT I6$d?ZLWU8l`F_c~H]kG # !2 [ - 6 ?3 e y u Xc <  e M I qtJ1m81p+G][@$#BqJ\9AgTLeFO_oB{Ki>LHQ?YDzANg,JI4   :gy_{} ,  /-j#6 K(*3/d45% 6F.dK$-6D'O m9 z/ | | 6~ Hm(Y G pzUe{={ S A $  k z}HS*Md<4fZ/OnL8V b~%*=atcwv<)`  2 v B{ a_~(7B߱fh?ܮ=,U :{ C81 |AXB|PX.n{ As A D;U-"lKw5$ #C@J3QN8 o&S F H ~3+2fw ey go5JcF_YZAv ds ^ %  ` 7M ~d<>} R 4 R # . }4Jk!5Y[?w`G%d۩KyL,3k۶iޏ]DLkuEC6_]b*= # +5   @ z߶\W/4O byS"CG'jg or]   Yt!i7"$h;'e$eMy"i{dD)Vi,Sް:f^89YٍS 76۰5 Y| L .z* q  u,S s_E }O71<al877jz tu+qFi!?#+%*'')*,-V.//x/.-z,8+e)d'A%t#d B  oH: ^ ^RR(*^$yVjxVMyF x 7 ) W  / \ c < L !  ( hSx*jo@ ~ @ L g  J aE cz 2P 1Qqk r6$PZg2JhK=rF_z"zg*pJ1A ,  | (xl&f7 8 N B i?  8 w zde _H%7Eb_@}+|.  <?HBw3! duRR}eg!"]!v}c$g-<c0 r io9 "&lZX=gZN, l?'. F L 1 a eO l N  P @ c u k{0_U[[,"f.8B6Yk9zVygs^#VN}7(J"/; o D+b2rea6}Y^F$o pmh  9vhDeTX=-' n%_=2=fe eHcte^<pr E WikWv6e1L' w ?&AwHUBila3'=y[<_ d d  ( (W6?   : q T cM%Ss3 #  r? \N M; (  w l3p;8ZG" 9Bb7=E+F  (qeU]w@t}bGKik=OBS8iLmj=5Bk#'bR,/l? 3MTe( % TfJRx k ?%0X C6~P ; 5 6k7y).I"=&O }L P J `m'?= uyNsJ,w</ ;^r_kKBP!t " (i }c"PU=*~A:g|4S1^^A$n;t)OepX+[M<[75pj8# 1nJLEA eF`GZp@OC %\>UzUzhb5ng 8Mz`B}&  LZBsEt!H#\%&8((%@)U)xA)p )(P(^'M'H&I&d%$#t"Y!9Mg9 V   a 2 o N  3 F  hJ i vc >  j  ; R G { ^ X F q  ,F I   PQ    . i`z{NuxUx*`J=>Po{  _ 0 n|9Okee  G " 3X \U  [ : 8?=k]&xCmPY0Ig==mlX Jl5&ftX<؎+Z"ӥӅ ԍ!յ)#_%߆~EyPBd\+gQ GCMmE !**"T##$5$^$$$\$$r$$qs#F"2!H r*Jzgvm8?)D' Sn@ZPBj Q.MhMbt#w(8 j ! igy\< ?n&t= 1z8Bc>myo 7`z:q  5H olR-==V 7G:U   SR-9|yQ.h83#SF!0,9Se3 * `  D , (\ n  ? < { _Y# Ct ! < q  8 Z  N ^ 8 BxglZJdM@t!qntI^x}_QUi]O[NF^)/MKZC"7BU%I^\6Gik}g\. 9ZC >/Czpmn*f|v(74)Y4g{"g oO   {^  7 x     [ N    vs % { $ `"y;l"  W   Z q Yg/` $cl28P90l  "Zq\pcg~dKkC/]<gU;bWS#:u3#-(soG'gD^mA~ u*  4 ,  bjP3J{2s =~!:"]tXKB}8x) :S A I y 0ZYnu|1|dP p 7 q 5   G0  [ C g `H$*s@Ga6&ovOAW.qd2un[iZ}H"B&461-0rCO?*&efz(L :RW\)< YEw\>:b2?-18Z`X*N V D x `  x 4 sG i YUJx_ I4NqN@)&AAk Y DN bS]0\.J81xxXw7H/o/b|y{gY)k1xbO)] {o!8R&[MUTJr+JޗߕDwCr gICuaIDaRF8}d"KY;) ;p^ =OdC !P#-Y!5Hc:   m)\!!]#$%Dv&''.'t((Sa))I** * +*[*s)A%(e&_$2|"A B\Ab>J)l6b E e ^ KR aH 7   c3M7 & $F W B I  W  i X  +    & 3   Q  pj#AAs[wyM 1 ~ } V] n o @g h;  xP/&\NZ3g 7:*zH;R[#,q#va c_HhsTljfQ>fHUhj߈ޤR@?ۊF)(?e!b]% R Hd U ( :H x Y(g%"| ^ n"$?&7(k)* +K,,}e,,+>*)Z(m'&$[# "W aM# !^ @" j h  3  =+     FIUo+1(;m)g F> @ C A = 7/1C?GA4)%N(!4$CMWte*k@egcUVOaE.&F} {.PX  qqXK[|{\M6 T cF H f0JJQ?k66S! o2F'<;}yk;M - )E 9?     D :k;   W  v b % / f O v x E | [J   ~2 "/`N_zc'"AQw%>6g9x j,>YJt>4Xt , yH,^\G(i.>jy@?M ` r  1 6 Q8EcA;%]H8SQ2IPu`f7mj  g. : _]T{oc    V OH8w" t 1 ]IKlXQcY  N \ t:?LeL0X3O) 8m=wF ~e0'P~r7Z`PBCA@ZPq.*]s WNj:Nkw8uT?A R wDQZ:0{x##l;0C"ob  g7B=qyZpx|X9 : Cgra1 T& K[.ZRE>x9 H$ xj m7pKmlQ*cCF8!IT+S x*(scd1xv*~YlYp PY  ` - Tk#I6L!JT0jpg\6 VIkQ]&oYh } ;><<}+UmIZ~ j_xf+&$  7 %" "~| W)znA7)K#^[Rb{Ge!R#eAU5z>Vm;$:o$NhW?.Z_=@?.mawf+kjn]Bq/TkqT~CD c22" w8? # rB(d6$Z&~E*g]'Wu#ppa[wF1S5tD%FLN; L l   Uj  ;u zM   N , r  @a!6"##p$/S%%P&&&Jh&%T%n$"#r"P.!1Mi|%PdnC 9 I " ] t %fzvePN QPJU=?m` ^   t$  U ]2 / M   K ( Y /W d+ R ; w 5 : k ; N q z Q3   88ikzeo+ `}B<wYyCxPMUtL_ ):Kwe{ NhjN,xs<&7#Z|CR aCt(cEJ:n S c>"..$&'(4)l)O)(6L(w'{&*l%Y$D#U&" W!|v  # 4  !6CQc  _  33    J  h d y * 6 "Q{ ]H *~MoIM#s ' 9,F+3NwHA0N$TOpgmJ92E9yy,#zrZUEb[ VI   ] P i?iQdB`^l!#W%&'s(((e(z(f+(9' {'=' '*&%'$#!VXcbB< E v  uR G . m>V5.</ ; i 6 gY9MIFz?sZ 4 Z Q '   03 9 (yf[IoLp= n  ] Rn;[k8e%c.gxzWO&l|uW1UD#.T}sw  / ' (Y F6 ` e 7 n  A  :]}D<&_kK!F"""p"R!w Y'9J\_L *  A x9T f SNs[SDph,Iv*dDg  c   V # R }z  + 5 , ]  O (e A.sH >CL;2Za.QS2x@;x2{*c3j+IJg) / f # /Q x^ H  D   C /tOw# 6_ R w} %7"Xe U4d!V=$ \I   Dz R : ]  pZ7AX[E C lF8s*Ot||1^80#dugvPk~nQIEPxs g0) vlH5%t~lAxmBN6wA;VrK0{D+&Xxg"aJUctA4wXK3oMqIt{J:np:t& 9a=fb39(B$];:-=U]r iJm p;||8_ b| ީ'fܻj݆ ރ .POW5x&IIv{+!/Yoa\#m : & 84 hNnt4N7 wCe{ B y% 0 ? } k " ARf'X~b :b=W    ,x-(, )a{ :5[aEf!E-z#-;B 7 _  ~ Y  ! D    b -  W 2"T-aMW0ytf*:g_"3| ; %N   =  (~Q\k& ;,\IgWl Qi D^ 7@ ! 9`%~a7T;R\EA)l%,qp>y X[IO^XO\b}Y Qd F 2j  Q ^ *? W c . % , = _   7 { p E H  i q   D f A z -xq/43)\&Fu^_ S IIQXmR~? N'2B ZW[U4w)T("cFAL v1e,;^  +HJx zX% h31U3NnRE>J^rnR /h|7]UBY  k F~) (K]fuy:<YR.   kz vQ=);Tl"i&F;>0-xOLEpD }hPz]   Q u$ji7m  yX@l7)]Qc^@?VI21 C|9F+BG  Dp8<@ v .-8]*x3h}Iezav@hUP9|_k_sKge+<,W ` =  # u  8 D , ^  j %  K   * rH   >]>=iW\D=#]n\rma>Q9 !!g""" " B!M  ?%zD>i  %Q\3 AzhMeA_LCnwhm!L_Jbp.)phdZqbzw%XzPmEaA/6Z~+U;VC~j!>p !O"J^x/  d] J G p 6 > S E H P S 0M   `?x:UdG_E U)sr.)zF ,8yizJpB /  l E _  W n  H   LyZ2o.?l_tl[s@R  B  +kh@^wHYK'x/+RR-'xua1]==mXQB :w'VN= yg_S/2RD|n O  oA/_#Y#RcMwP|iP|O`B=Q =MgY +X| [O3:VQ8vM%z/UN@JG3%:5K2!@ f+ 16\Jp|D"9|NN30xM*4}0jY >FE[ Y A v a  / +9 LJ x':&[qC"~ _( r % 0 Of B { =-[kq.WB1rRMu e  l   s  P   { gI P /P ]n@!K  6  J  P ~ D~ L l X + f  *ehY_Ly4 VZS=| - k V 7 8u(stF2v_86jd*ARc&Ga  f  j9OucDYtf6PL) J)Wo?=qR>?$k Vy fu MV   R>& J   |O4}) @[?Gth.pd  M  ~T{)M F/ Q p_o4sYH5tyv Q/[7uxX~m :QM3%TDAlK0EyW}%<|IkLW4T8,].C%Og[rLaY ,x ^ U XVM})KfoQ< v[.?kg='+L`mJWz7  : d h  | A{$2oF%][Y /  : }  ~kV5 LkHd{86  F\ ; W PY%N/@ JYN|9GH *R1>gA%"-+[L"f9A9i}O/z|6$8 ~s%e`7`Mt 3w55SYO=EBm :OH |;tm2,t  8 X   f f#n t{y^[& =6 X   r!t"m~"g"G###/"&"Uj! | ?C"=Oq=J 6   iP95[ >p:mlUn|JLZ Kmc5e7~2&ZCMFW/`rT2U|z"[d+8Yl_N~`]M-Q{w(<*6 d[Uu`Qe,1 z`XiQ>,UyOo Z')E IB 7V{H.V.mQ t1 5 :h  He   ]`   $  ) j  g =={-Vd+nIKi' A Q  ? k ! E fn C   ~  w9k6~(/]C~K*`  W %$<iSkmyl5!gy}fxB};pD*m3f_Sg5 !c xR1E `!d;HK~`?T  T  ( S" V ` wm 9@,OL)*Z+*_ VZ,v$\I6 I*fb-H #5{}fY ^ d -  $1   N   o      6 Z 2  = -cCI #R  8 L ; 'Z m s ?b ;e mz v OZ 9   0c / e nte3jpcE}[ *M@R47ny 9Rv~0 +}{%GO( _%_1$5w NA /l  e d >);i  ]c P   5e  R',HO 2 c&   ?Wk8?*Z`>h&tF  *  J- s  ,M#t'99w*%_>V^A 4t6l7r ? '+ *  g `?ur:n77V 5 { >G }+ E%iEx d )0 V]'4VgQzޥ(#^߳9>1ku4G.} /9[S*+h* xv fKg\2zf9Ync5`Gf =X v   o   %  N Q .  B J 86   RuOT a] 5  S 7  9   g !  `  yVrUT v  !3 ! "L ! ! /  aDl]O BO ) y _ *r5)_b[1g|qS>+EM~e( ^="5+Wu#k)2\ ,_3F%vG3 `*=g<=xFUy}tutnl)RarB644F^dc,9'CUQ TS  9 )   r 5 7 0 .MXg_6 \]1=}` D4cMq!HlS3GGo6j)J>g&R]G   ;c     Vt '   &$L4 q S < i   YhG > :+7,BLZazZgLABjRpzboDD8]@.ERb5p*f$(6rl([^C=TH\4%H)xBe5m652g'U`JB?5R 1 O :, zo+Uot *+s_Gknf P  y 7 ," ^R;  )R eDF%(jS(sbI3"  3 y f A  DTUe~) Z) }| f `l  2u9c $VTm  | mK u  28 ~)+Hz $DH E y  g e{ , u ImYF8k#KnA,A$)^fG' ޻9ܟlLڐDY^3T^E)  uM*`,cpKKdsKXjO 3j119.q tG@'t N]#EK U P7 3 E 2?,~a4!_"W#;#$$!y%&&I'b''(<(('C' &_ % %* #$c.#^("!!ThvZ]G  ,! ! D" " "V " " "W u" !d <  S *   -F'-aOGCI2O-FF .} \ PjPrV~&JIe.4 z } t  E {.  'w s+sdim_>@7/\[S[6 <  W0^z  D" t H_B? ! !=!P#"@"5# #$[%&&VU'' '^''}'%';R&2g%;$"I4!lU6 c?z@J  1 K Y YWP;PUEd*{3 NX3'F9r;]%3pGg?7|u52&o۟dفIزؔ=د0Sظj!jڜJ;$h[ްPNPeA>fQP3!fqH_3K] 1Yb<S?gvr8x:M   c . C ~   F $ K Y N R h  I   |^CY# ,VU  ` r   l   *  5    w 'rN}`y E"#@$%5&c8'R ' ' c'. &%IJ$"!lR*WP%U Aq **l>/2pzBuVDeK7oAilP8ey~(#{޿j+ܭ_2e,܏\ܭN Zߴ8kd}{qyK~oZ.uH:6]\5XB$UjXurEu^I ,.},m{\k`qnxtrV_9ay@12 ;/y.nZE%M:s. t *c*k Wi]wB+[[kV({y<#w  X 2  y W O * t v  W t 7k C).Kfp0<Y`{A!1v]@VRr|H{+f W_0r3"TA6Dy~?Z@,kN-W6y$YOIFv>r M)o k=-9(;*fKc WP FQ    6_1,W`JH ) q2~  + F+ b) Fb^7}<TKdKFPXQ < * >  -zF_-\88CfN4s %   8Nt ) z v?$o-m E  ] $_+}* x; : 6  bI(% YdN{w9av3 t4VE W 9/R\nE]\x ;07f[p.=E33PLX!)HTz)2t ( \  z# V & < dzy'}/P=fO 5    Z"&,=F[JO]f e jp\ $!G!O"$"_##R$p$$4 $ $ 3$ #"9"im!y X+ ck `;fUHnVpC :!U!}!o 9  ^ + < J E[L VX t >N1v4a(bYt"`B,}2l ,JeKg6iw|B E^M muOgBW9)hgd %g32C !UOziW/'C^QG,{ Rd)   eg  8  FiwP!:L7y!"J$N%= &&'6(2(w)E)g9) (d(['&n%q$.%#! Q~|YFb)vJ  ^  ,   >e.T=u"^+t?!l#*\5C޵Gޑ\dBO!ڍ8Oy\Ш+S3"g:s yOV 1#q+&87)Bj+b6 n q .i    GSLv.^-nZFJ)A.{ ~  \ O r   O %cY|37 Z  P 6 Z 3 G   T~Aw`=a&7I9h 2B]"f'D()`:p#iD~|_wb K!"^##d##2#4#"! J   b \EMX s< 6t+<G6Y!n:(D C;G^wv' ދZO5ݟ0R(eޢ[ut- t`_4j&ߟ+jz> A^-"_ty~A 9yk`o?,c&fU5^FxgZN"1-i! d A B   z  G l # w +  a 4 W]v x4s~g{OW;x2;xIM-"!%G | #  Z A 0 4 Y  L g g H  ~ mO B =T^vFL0s#`S;O. @t1LQ2>OtX;t_v`&v?8 IwaE7<i K vR2W u(}/Q{ux: 7Qn?w&\d 1KZ]Q<$- :x2;[tm}8h+H] 1 F & D 1djX36 1&.s xI:. ` & ! s 5  k  # T p w D0}eIo'O]f2mml>|5?wCt"  -  ({`N c6     $ -< K %Q N Q g + 5 ` )[  c Q j U,cR_!Q"[Ayner5?(lF޻M7^O8c+"0"rhGPT(R%vISWI]![ \yla7jhrheh ] - o 2) c r FU  X(  St %~ztvD"Ev*  |1 \t i u.lu ;/4?`'cI !j"#$*%%%%%P%%>K%%w$ $ # "* +}$@x'}M3?u *0  ($ P E 2 c ,   * &3HT|@v=\I  w ,jm;X8(zWhYUxCk.B6uB5c|f<$e~]T@z0v ; wek^J. *8PRi-!g ;%<89  O  Z-qQ !s#%&') *++f G, , ,s ,b,V,+{+"+'*@*)S('0x&-%#ci" y%J6'?a O 2z^D,I^l#5QEW3c1ޗ23w?6thAk҇T*ҘW%ҵ-TӆqM7^LNHN/W4^8MZ cVC7]9DF=b7XT (J70u g C Z K Hz ( 1 m&  k$" 5f&^d2$ \   A4  j - r g  "| YPeY%c9EmNVH?.j _*6d'M?@o Z>?L,!2e}_3p&:]&C A5= o ~  y x \ # sbN)8JMH ' sI-EN0/[8NOMgf<}ݣPb$ZߨMJubhQ|:zVG5?Kl>o)8^je;~Qb ch>#D'{/_eKG9i `wQ-wU-yjR?.( Xpa^"  m 97ddq/' N  W>  D , e X    ( B = 4 < \  H  ~ W   E R " ; , PwuM]0wI`-P~ ,t:{FN)<߬GHߟroz l:-pG05Jl0A`,+` $ J3 w:  H- |(>#8.  (   e F m ` , *L~j'({V+f!dOq66@(sC?w(k;kql_B8 kxU e  < R E  u q $ u   %8>;ik3 8c g @   >v  i +wVk4o~9  !  K 0v7XNjjeP C a  X . N IifrM()X U K &( M p m N W  : b 0 Y '  o5 K }J ( 2 |^ h3{bwF"QIs mP|KߙEN 8߳,$kcFVt i);]btDt*hRD!"Br I h;  RcC)/]0#W, ];/L7jVJ q < $zti#h\ ha,."/? m! "St#9$$[%%]%:%$#"Y!N   5. z0 * "  4 C x    !+m#~;+v*H"-]wE b!)4I$k2R`jE BV Q G2tO~PiT+&Y;< nVtFj{PU88kKy=]X2wjszZw]<;CJGM,y Ubq>SJh2x K#D]U=5+&4wNCm-0RK h \ f7D] ? v U /!"[$&&()c+-/00712o23=J3J3 %3u2F2,t1[Y0].J-+ )'a%#M vl Rs  [e2L  \\ m=*:IN-;K,.~r&d|ߋVDK;NLe+Vی>ؐhփԠ;ԡBUԀW!5(LY2W}Af% KCb*B")qZd# B  z Xt 7  A  tDZZY~cJQi<PV  '  K:Q qayIU 'X e^/fI3/Id}/Wكq}ۉKݣ޷bcU&r5dj))wJ^(;  l] LKrn[A|io/CQ b[d*B;3u,(Ii  & Lx nr'%7 F fd {  w j  7  Br?qH8 U @ +eKbppq ՆKD~&*]ړ}uoxSoq!bD'lF56?:; D 8G+geSr)P c X   9 i ` a  \# e p +  f  ) mo e3r9]J]E[ H!WP_L %~T-  "F$'m5&xqlPk H x Yg f b O 9 4 E gb  s  ` % q Q H!N|=-zVowTAYme&*   t p H'O*YT:{FjEHEo Q:9__!D:>g $ c Q k1uO,D ?U 6 P QZ R6yK:L޲q+% ;-iܷ 5X0p)CC JBZ-T*Pt*t ].e G Z`/JJP~S4 Ew +#N0 #r 4A e< a a $ *s;y\qZy  <'N4&_;%:~ c D t ?8 "P/~Cm+@O3 } "  / q Q } h   (R|0L J<eCYR6xwR7,RXmf_3Bw7 Z8 ! c"S=~ H@g<8EB 1}swG.`r$T~] ,/;R{^R\"[01,sOVlx?rOa6?_|Tum<kWSpAGlqJ/'F 4  55k G A  $Zl "Gy$@&'Ne)*->,-./GW0^0l#1/100/.K-+)h(!&6$3R"X CAE<~p 2 4  l3>$MM$!Nb%;o,(gZq~3LDRc cA,7T"L@c:hpژ;OإH׋ օ$=@4&1}i)j"0:ޚGn,?NR~yAkEE;(%+x~*/ _~4e3 D  n B  }P +fU6-Fi4  vC J 2 ~>!!r!8!""L""!r!0! W:|5 `]3nH$7   !] paM+ln"EL : b  H 2 C_Sy9o R  V 6XY?_RK - Gp-:! 2"kDzBs@k"yxx U? &?eNT_mpwjR54fA&n7>ݪ,_$XKףq_װ]mtRX|l5_@ܒ)%`?!;{E@WFf  jRU+}?Oa(A?Hge+, br  %9 ]sa Mq>?G [ I F6 DdORWm$> iZ"m&j U߻Sݰ۬\ڷ2FhY8L9Z0ד-uۯr܉ݨ"B@;.8a$pw 6:x]*P T =   jA p < { ?L   = Eo @ 7S +  u2 C  &P : R   p ! D 8 aQw$D )@o|V]lA*)v4u 7m ;_ CE : s=Zb|A1g1bX40xu<1D  6] KYEi rQ<y'G0CS/Q Gq.-@SWv@ *ySJke~W*Hz<F m q Z    [( "$&kU2.2Q86RP@ dj    9   0 k Y w , ] * bc 5"?k ;kKCXF+(3 H.NSTkV|) |u *%^krkh21bfQMG6*D4(a:buWyXiodZ0{(6)QP$TGjQ)rI k) \ H   { S := 1 6" O ~ a 6E6x#N)ELDq665>i(}{/l ~0W<Qwu1Y+< C   N\,b\4}+]ys%?7zw\r1=!q].{a c \bB5{}!N&^E0Rst]MUT:LF< Q@jkgE>'FL =f   R  j3 " R!D"###y####w"("[+! -3l=bpx#,  s  _  A oqyA=:1v 9]   R XP&BT6SN1U!@ qXߵXސ@ںS{3mpWok05M ϦЧ@[//YF@zyh'7[%H %  e M ,LXLQf;I) 7!.;"Y#e#5$$V#i#V" ! !U 8  Q[C=%[W=*)}R K RU nt;5  _    V9M8S,R0   ET s;ts <-%|RCffL;7O'jW'_\/<aOfGߚl;Q۴x,QE AF%eדX B֣֙P!؅Cm]ނKEn4'1ZYOi %13Q@#A,' <y'&>%dUWx(+:[&s8qV  H $7 9DxI42AHjVk)y" "Ix]nR8O]]e{߬C ߷%ߦx)&?bzH5 |zX&5Y d+#W +L | Dn 3 o 9gTR  } e &   % 6= (N D u  1| Z l= aw 8s14gP"$ e41*}yP{> ~uBNBOWROU>cV s  ; r 33ojFq\i tb$}w4zVZ% iP  + TSE3;25M H f 'F   1W : ;(   f J  # $ n.wR6qLni;ZQ5F rRd.&@$4ZfAM p k   6  &+%(u4A?;e.t5 j Y P ]l D<R_xVqYT4mId>d%XjZ1 /C c . .udqfK~ B&g)cDNCk!5>l\6?JTfe>#Z,EOHV,^q;Tplb,E( &tV,~ MSZ;QOxIhz_"z+J +  a  + e>]_&u.vnI?x=nl#Aj$I;<j9x+ <X{e#  g  ;   3 \ 2}  y.)  !^R&L =b jm߭۶x&Ӿѵ:%Bp?MkH˨nT71y!g$؅(u߂`T6 [%%tKYnB7 Zi z =%QiGv!G`,>=28083o~&$m[gt:lq ? :IYY "V$N%^R&'','w''' L&r 1%J #) {". !boGD 'WX%z [ ax } h  z I  = U b  RO  g| d>^8h('"+?E3 Q \ 4 u   k[m5H xzG:s.EV4n Kv*pmhަb\J|ڌ,t}aoNC؂EFoEQ cNؓV۲܊}XH*Q4}VUFH<nPg    ^  W q A e  L  xwdp`ja`Y^<qo|y(f6AlG(NaA_]BjA11,0Yuh|:a8`z{^4dL}n]2P0Z/ZA>a%T(\L >z, L+-zs<UW) U[4{ m hK ^ @ I   (  A  qtp |}{J>:5NA' eA   ycE3SuVRV [Q/F"DP]:DU[ND{ $Z; 20 Rl 3h}q5jtJewLQ1Q{W"3fe8 G  4 t @ ~   C  p T .# - iOH,`T:xXg8Hj3Hq%b?%iw|0Cv \ 4a  4bJIzBM jgDG3GzBJz  LZo:3\"k17:[-d  U( Y/1C p=AHL0oy70rX c 6 8 5 1Q  T# ]p  Y  n{7XR<SW&{MSh  i H wR R y %3 w 'B*@[W, .G WP5L@zY MK(A|*,erl vNq) ({le1Y z  u @L"0U,daTTMphtj,Kbqh3pTce7_u$o -UY}E1;u'6 <ID$vgP,CS|)yQC`oZWthXsvTNjT(/IrQVJoS #U <  M do; NuWb> ,-!:!B"H@"o@"%"p!+!![!7! ' AZX!-45SY<=J3E55'_T i  # ; F Q c rDy#T{D26Oe1j6mو~kftМ]Tdz!ʀ0*LA>̕nJҙ1SaݪUY&aWP{II$ MM:Q F QC VPU3Yw Drf^IgK! r Z'bRz  D%Gc ?i c!]"x#~##*#>#"N!  R(   { . [ : 3 5 ,;   pd  n ij    1zno[UI  +n ~ F tq |  s    y  p   5% R) f  ] 3 y    O N   gu5(g~nR4-s*_eUA$?u<`8QHߏg~ fݔ+0-\ݛTDޝPߚޡ ޡ!ݐBsyU@4Hph٫5:[~w"q~[g5!)+W^?@uni!PX\+dC+0:4oQ pVmITB.k'@0ne"=FA.y,BYVD~2*m G&oeFmq%wrq 2M^VT[ys\ iA H+%73T  =p @ .`  y + B 5 m  u y #Y ~4   +# M v3DgwKSR:NY*2.NmJ,Mv k F9  ?b [ynS`Jx*߯_ݩzWFޔD+!acY*(8 p l b R F :] r s K , J ; E (> ]^nI#X .A}J P1&8p]L<qfKY:j|4`    e +  G   9 r  1 S   K )hqlyd(YP2E " 7 ezx,t@r:NXn%K9u\ihj_< }m N 8 d 5 h  `"3I/Ad1cQz;V Oyg_RKWq@LD8<k+ ER|!On|r'Ia # 0  V k E AYwT&Hdw"Sm }(,.W>{5-V,*~V;tY-G I b S+W%q C <}Dw K 1k5iE> :uh( @^dc\TDc%$6be'Y%#(>O/]d<`U2PXjjGDr\R^6kF HnF?cA R q #  t& o 9Z &d&DAH Z  R!8v!W! 9 y }2-sve~ 6M?0*P$:1DU [d n o `c  M A T xm*>q'EaT10]V&/\'ۛbӼ|C `в4΋ʹ9̷˛e#Zp !ԯ]֭JOj%xJ{$_1 &S#] L`>F k  puf6{$ zs&W:usk  % OfyEd}lxgS`.J3L:TB , sP   N  ~ V  )t *\ j    D> x b.L7thT6 FW M 6   [   | ^ G 4 $< l   Z   \d  l 7 8   T +  | ) P)anUaEi]Gs8s?TZZht.߈i:zhQ.~Jx=~L 7ߌ(,ޮm) iߍ*u:&hWnaA@?n F?b a d D r p( F Z6 PziYt--#2E{[j`. 7gIZR IR31"R$R1)Y'e_ED:[] ,Qp-M s 2lS Lxa'E{m/hm(6`V3FD4>*'O/" fJ}{GB   ! -Q$Ir9J0Z5b<ZE(q9;  M %!V E! N! I!, 5!@ !H O I 2  . P  "T -*0@{5oE^e%1EBv=dHP`yF6 Y c 0.qT!MHJ2vCvR& APp,rޅ{ܾ2lۮiOݡ+2?g0Pz(`6T=^D/\(/b^%& 3W8y f i[PHNp(97%an1GL>! yk  f  A   r /   <@ ^ z< o;:| mcypJqcx`z&y NDNx~8gmO!A{uU6GE$^~}7 ` V 2 e wJ/]s4F}"2u}@ r8g2'Tx,(E!;AD0 twUTTp    t ?     z\2^u3 IAoo@aV&6V&eu:wD5\\S-!q 0, R S8hZ6 X-ZeS9U[\WWn\dlM%EjQXJF9!LN|+A\i #V :uw>r ߔ<>>N;rqFL4eg XCF^T>&knF : :k DRSC':G J+ Z k % z Hp  qj%T }$x>WX\G HLp' +{ ( -  4 Q % S > i M    U  / x   V  z 6OE-5*    o s . L qs0m|go [  ' # [q  ;  # >I R]{Y@3iK2tUhw{m_Z7:!Z.vg oJIg)}{3t'o=N%D`dR:TG&1im ^VM/{sV??-K()-& GQbPQ|We|)~Mf:?; k M`_pFNaq94!]wv L U|P1@? }+&0dV^!|4*=BOoV\X[W= !I(x;UV<xp- s> [/ %   7 pC B L a h Y 9    x    [)   Z R /   (M'z q  ;  ` *H  a  vgfbP+l6_qS/zLdsbAt; Y z=?iPQ?& ^d^\s4? ~z4v9\_ܖAݪ!ޠ6q85 Ua O 'Y%(OrMYA3( l]P8g [hYyQ0!;@  Kv  g  O  I   4 : [' r+p3*vT#|fB)  tnbk=R?!B*YqFtA|%F0'kp {hk-> e CL 1o ;Z I\|0u{xOc')"we/Ayimg_i'1\8gKOic~GM*m{8b13TaP[ {xI8wd .mKi|` ):c;39Po5|-   4    _qVG^K_t"|u_=:^Q}{Qf>9,ma jO(+ ( U  k   2 I , ~'0"  ',|:?Wuw;9|#5CZs~ Lގ"d דճ!8Xϫͅ@r{ʰ. ;Ul̎ͪ{ _ T<ڹۢ݌z jWE{vEtbdx{jE%uYxu$9AF-UC0}:j4>y)\d1VXm@}wT^(f&X9rRhnl}(Jl(QT~}L_M=|N7u)JmAWR6O g  e&+*iDcCx,L -E  y S ? E  m [ D`LnhlAO]  n '  : 9   u f j  Z H  d^  Z ! T!d ! ," " +#> # # #e R# "X " !g=.d ~R ] ;S )=/p&SM rHK/6o mWk"p71?ߗU0NL۳n=ۻܘh'@Cb9lGf;g H=F{(iu5oHc\vFckO;I #>Y{<   O ?  BDgqiK 2n` "n}3WA3gj~[SM?y>?3[ F 3    Tv&rB , f   LS  O $ s k /  0 3X Mv Do B   M  <  R*  J MG~uHb@:Itv/>+l 2 l )/ = +g\zh%CF3wF  ap 'M   RP ),y8<(D\aB   ; w / jxde pDp\ " J gR I ? ASvj}<vb>c!q6w 549Y>_~l~'Nc/R@+;4^gbO[%)K{H+;U^\[`@-RZQ\P7y2 w%?dr4Ty17 ^^>)"l.Tg8 FP^ARux2{p4pSa/    ,A i~It & C %n nyB:%hW<F,P wDH)'BLz   H = 4    S~   B | ] 8  p 6kc'%) l)cH6[OS rI 3v!R;Eb6Gr_5 Kct;&ڳBҡc Ј hϐ t#&{JARf|k4O o ,_nԃ՛׶/>?.AۅQݑ9-}H>D} <9vIY=fqTjTqh*N_nG *pO$}zeK9z0B"Cr "` +LTxY*R4y%b~ uNfrA\""7Vjr{߄ނ{c%: |]ێj9ܺ}3;y޲1iN^GKe[Pb 8Qr:`{cWNg?0.o{Zc/,[sj6 2)Rm`n3{,]}20uG?]&*6J-DCj_hyehJC5$y_uK%@;-!%BWs5..r. / d P  \r+U2,n!=b|UV;05ME\2(DA.  !Uz!! " " "M " !X ! !  ) 3 ,I '`P-5> _ S 6Aa ]2L4_ovsmS|PjUM8 <_#{|>W-U=}VMKx(gBx[!Iw%+7/{kmUd 0Sv@.*s&oz!(ecAgrLLH'd|sjfJs&(}j-/tnBO3`Rw{si_"TI1Q 4-*7AEsG@HFFVOr{kN;SQ  k  h 3 , i C - b *2     "7D.,"&zA  X L :/o&^.Xq=' 3NFBL  w 6zw%=E&,bK*Kq=\ Z?} I {   X /0 ]c5v! u` O 2nuwt urm G9 D h )XW(1[DaCY7Yނ޾߯|' % $z9@-41W_5yr;j C U"TM]UQ79U/Y `a@^7Xqw~:W5qDgN #s/A=l'H>_nD(FesC7'Gio& W    f 9& G o  /L U4}9L$vngk9rW < <  ' [ w  X  : d-OfVO/   !h  | ( 1I7st9cJ+P(  W \gQE+q<*9+K{u$e3Fܦ^[l֕gT{ӬUFCѥ p6 2ѐp|хҧUJӔMJզB T׮وzTmܿ2]ݚ0tT@E`[122[U=G Zyfs.1]2R T #  J ( ~ UU ' +srHGjxQ  '!!!"Z!!NS! J 3  n  7  f- <r  m  e ){!RFYb)y_)t92NORSWo  Y g  e M   v9   @  L :g)(`hy)z 3   = aY}kK%hX1S0":$2Dj^J-   X   '.y1 \<r%3C$~O:n66L}y'LVE)Lޔj[ܠ'ڸڋI9صXٌ[?AYNt=, m 3=@7=lR"RqT `b- IGkI IvJK[6^(8LADH\6,`sTUk>.28uK,^t~ZJM?{6J\MSCpRJMk2e/AP68Q1\K 9   ;N  LOnW  !A!z;!3! {iFk9q  D Kx   T   FW :"  e)1x+, e .[&ZI[el<RDm2, W b )}Q>^o'aN.jX`<"S8 W\E%\p;L[}Nz|a?*@\pjceAY (H*dsw݂ݘ ޮld'Th(:PlZ" 3yPU;|1Ex#v1?Qgk16VQ4u* e~ ! w < p  ( l k _ f u #O ) X _ >3  J  nh"/I 9  6   S    NX  Z - R R  <j6-Y. =JhtnJ9E6 * n % %#:L,q Mc? ?!!"".#C#<#6#4#6#;#8##b " " !D $! 2   ]  !9"  x b L X )  c 19Gi;t[U]eRI`"G |eHHr,5_JPf_ -EqCS*E4 5*6u6#.I] sZs>^xgr1;7$:@  y>B.EUi\2637S{k2LvDAsD@ X^ X)=B@   W  #   l  $% n]Z35  > L  16 o $ sQH=Fx    . ? T C2B(K/m[aH\  n!!Q"b"8"##"t"!:!*= W.t(b1% p 30do?G)43|{c7n5A6HڔQoAר< o 2ҙX{KXBͫ+v}!bˈnnQ~͸DWa1җԝ/4׽NKn ޻T@r?!IL:JNejFk  |  D c 2 ]Ld (. ka!""2"# #E " g" !x z! e  hg ^ r j % u " l  no*_#(Nn%NXx S^  Y > " } B q D : O U  }I  ^ W ( P~d!] M P S d  ( ~ G  [ O h \  L n _^ m YgNlYqh9_S d X 3-'5mrSY_OJN. IQ(oL LG[gU E~>VC.k<}=%-(BSa~^gq1#(lh_hijj { 6 a Z u z  Qd*T /ii4S'Sny[jy 9_f5)\l"pC4/_1af'&8n,ܙRZX<t < G A Y" ,~Z =z(a@`=/>X]>;>6CD TfG2-i}@fY5bj1 %.#9afSa   2# E ]5 y ^.Yi4 s & | q 'R  n   a !G   .% Xo R ^ 2E^~)$ -A5}V<#a'Z _o"7  B +  J 1E  1N b l\L/WEa e   vS[E[T/dU  XdXWIgC=^KIZh*nA\M;*#oE#$:MZi = 8h6A:S~h\NC-1'6 g_0ya5iv!IlwDMzB?)IK4__Fp7YB% Ynx`ui!ES E7K T ` I  c     ) (  ] q >  T 7 u 8  ' r D 9   Y, r { ,g  } }[VbFN8>-^%/# !!"k#)$$l%% && &%%%cb$#Yq"3!UVWom,F' [c>nt8ep?D\ kܬ^z@؎BlҏaѩG/NͰN..j0#Tʷ?3˨)o`bfά VE6ԭժiJ<?+A"#}LM2>"h~%Cq!w8SbXZ =  u   e I  t .m<bJ  _  E7  r Z :w  & x S 6  S   n <    j g W .  m ! b  T t 8 Z   t U   * %  z P  //  J U p" Bz * # 9l=7  1{ C  f 7 ^u r  g  CK"W/JK^]VF &LAY#E j L  d IY o>`A2j#)qF23hK (Ew`D[(lmSP&[{H>w?C  # k 6 + f  f ~ i! FL y } W  rZ  w \  \S W0*rOy'bX7-.GzXjW BI^yCxp@okj~aewe&7Fm[{ 'q%wAf?1;HyD+g o % EB  % v     [ B 2 - s* . A T B_ f q  c (  $ 8 @ I 8 '   R   D     < f D:UtoZXU]fO(]e !1#$,%&'$((H)))Z) )B(*5'R%dl$X"3!Dw;xr8S M "dD'Ayyd K{wXE78w5B>aB$Zkz08l6s1Mݕ c;`ܺ2[ ۟OQܻܛ܇gvQJt * ] g L \ f2  Ag H YV8?*ZyG^Hr,hb8!8dR _Q'^)uM0 Z m .^ -      X r x & w/ 9 =dw>Hmpf'RxI/hd>-/zlY|fKB<2$K  , M  +6bb-0 GCz 2YZ*7OT7\ cp1K6Df~ | U< m 1$n/Vs]ci\+ck/8~ (|( E9%:r@IdiSW t_LHH8g~  ov(wv5G@^g]Ci)S%I p,k[xU|Wg_hlbZr]aZ\PkFEX OH zf0z(a/@\v4phQY%k@'Q ; e -] 8 M Qgc}!(>Av5]abtY+E F   C] [2Wi-n3R $!!t"HG##H$+$$-%9%T%ka%g%k%l%^R% %$#4#P"B! YKCHh)  D( RHfc?MV6dJ|`݉ وT֏3ӢgB&ѮЃYPz|1Ί?`̛n`w̢CBh , ѫ*=Jajnviڗۀ&txH{I8 z n   D |    P  - | .D?VMOB.3}U>u/  X~hP8{I i j    *    l   -  L      ' )W  > X CE< T]_prM(>  q }D X  4  k V GD d`oW@ KO]TVBBRw2l^-\ g8kL" ~ 9 q e PZgP> 6;NU][a?W GBc@83Est\W /!BE9R N @  a ~ ~ # | ` # 6 *.ZM#VeYG  j ^ >C 1wR+dJ<& EX@!%$3^v<58E&^%wus;krs#bd;L{.Mej."%Dkg1ns"d| ar  9 H W S E5 /m ( 5 ]  $  Y  T Y D kN 8@    r M `0   \ D3 EL^<PT.gyv8$'29%!0"Z#o$,j%D&&''[''?'4&%<%#"B ZZXp#S7Vp 3p L yv0O2v }3cT_#}w2MA"4U#^P~{ 8kvH; >ݝ4bۭ/eWݧ (ތw@O T] : !  & 0c !J ` :   r@^Wu+z5$~`;MQ6| g' pGn5M   r 'uxS @x%Z&cEGzj\!#aC )_BYiUeO73y3-hk=Y r  U K | N F  P > sY 8 /  75KO[Y (T }jY9pvENDeM; M  1{LKmy[Vbn{ v - ^ N '+ HC iH 8  T EM c z | I. L , h8Kh_kG*_P]pq2@Jb 9M+J &9]L59_/hlI+!_N 3gSZY" 3;oW v* |JT)wzPtW|ztKl-و9cشC^' w7)% 9]ѫ-A>( `л2;ѸѩүӾr@ :cw|}'݀ޓof k%Lw: - -]/y2=! qw .  r#rd X<\bL$P[BeLvAEK Wmd+ J 1  &  51 K 0h  3y\F ^m<cd _95Npw *7Ab]s#dm6gk \ ZJ H6 O e|u@ 2O0n<mL%w,nA-3xJp{}Li/j u*=h. u  % P6 kc j ^ 3  f )  R  L1h W+qwcg[;M_G$T> ea?(:$jFJ@{S_3P>OwM; z L B n {(G~V;H/ib`Me=]<\TL7]Hx$r=icU:yh#5YI3idv`k:  P*3&#eHs~"LGp d< z2l^R M P P H 3 oX};`Vw   tt CY ?5_;>3. C~DM?dy< q{9d#l u!8<" "#$N$XM$#z#m"6!";!/ ?^{  tqNZ`8yP )%(-,2@ߎ*݌c^=IY:޾:ߠw DT H YV x I ?<x:ga0ZkL:<_ ( Y G |Z  E | 1o-O_w[zNbhB]6]0mB}) g 35 r )r##l,7x]`I2 (F[eu-`BvqE2{ > R 9 Q Bx B'c5W"@M  *Y v^ y N># +?WTh({}j8 5%!\!!!!0! Nm1`REI  @x&Q    Y AEV!mN4{r&R).RZ 0 | vE r= 3stb?2%1 s?HrU i>@|rdCt'@Wc`1W1F&*si{t*,%TZc4xPz/dwnIFXl,*yA<"bGuVx5'6XC%FHt>d_j @f+a!^X = d  E g  r z 3 I 1r QZ[+[=>#!: FP $u | (w 1 9 A N o  2![!t!!`u!T!N!  ]-{l4Vbh 5Y e N09CA]w;3pO @Q5J@aQ\^W)maܐ5ܽ&Jir؟Q;ֳHG>qT;;"T6CUt.ԟuMAڙCz/ނ0Sla t B T`Op)i7>31` h 6! !! !X!![!!  88l(zdQ{r+V-Z*K  F.  g$.c%F$9U pxS/ i C m< 'T )=!`m+8kr1Q\:[{m; ?  p(sjec 1~ 3ymVG;03KFbTg$TxZ 7 9k'[LSTz%V ~\ C 4+x}Mv,SGv  S j }'pp$z`5q~ha-suT q! K6 4H #a }5?b$ no@@>f"CrV_Un FjW/-QHfy|S w-~hF }G;5+M=-[\a39$&3b70()5JTvepjD>uDXLv{iY.+[voSm%  4 6j   /q  E I!Q#$j    x 5xB2wF- v|Lj5t^$(CD|} vLzu I>JUpxyW0ioh- &0/ n  s vg#y)GSpcd86q K 6$bI;_p,MV"݀2m^W,Gl/ݵ0]ݎ5 L8 le ݐ ݮ +޼s޷ޒ;WSD"hLm.6r3=v6obP } ^ f -!Z>0eot9 B  { t @ n % ?% "`   %e^T=&W-K[GJ   ~ `Q ^ }    !$ E! X M 1  O!2L  ]N Jw q > # t  z Zk &  e / v A6Z3n '[:+Cn/je9(Rp9{Q2p:',A$j ` % M a a Q -v!8'\w}hRXTHQu$)e | q| :x m [|VVt!yt'Tߵ[ZU?YTKܮsLޘsZe&b\V\tT!R?} Rmy)K`b\~D=CfuXshG)4Di%T#F93CT64DOvzl=dk~:':vdGwkw|?.zf?ih ? mg,   d & (  ] a  Ep X +7  z &YIU%_SePGGTI - m| )y I y {U? #^h.k/1uzp _gG9&  Gu}!PKy+5=?:r4J,5&!.^.Ltc'k3`޴O,U۱DQع'7fj7u0D%X֑%ږ<$aܼI޸[pa fs n kk uVE7LLex :#n, u ! Y Q oi^&\"G`Tq9 :6hst&   y J*^1+4,3].!S$A}?Qx  z_"i(N?RRC # c \ 8 o l(k?oRvL0U$`/O#WGgLpFY^sueLH<[ ! 8 [ G|) u)+G}6^Vf*l[iym ~ ` AB:_]|qb/- = /" ] xh}&cZ'A`l]7 X{(UdhE  L   1 O^ )xFA cHNk>^ & q@!!"ig""Rf"""p!>! f Cq?8%}3y1w-wM7^,$L qH=R5T"ynv 0 & |   \ }%H p26}"oZ8N/ dhXr?Iz ߑT"0&"$nKc>S=Z]أ H * \u <|rO߁][*E2)}m.Kg`j N 7  V DX{ZHfJhh '$}%S; d0= , 2 o ? A |VN!yp<nx<M g p   e  _ x  + (v I \ W 5l  V 6 $ + g   - : X  < f  ryjQD*6\cREoi|S]-5* P_lJ$(( jdKg++P*A- 8U ` /  ' 0 - t *E:`(aNOx2#ytb R  X4 P AbdV6&@B G,^EܞG9?ؠ}ּ֊z֊f֪sFy_ٶ a^ݵ8!N:!;C4WQ2s'2u)/1O*K n4R65~ `+IHN5>'y%uj:c \@%^5~h;"[pUjB$W`3b8`x_ 8 qg 1    s Z X 6   7 x Y  H EU H ^+ [Uk<&QSf}%,LB> w S} Q  C?6k{ydu   P{#Wrxa VkacX}h7"~OM0b-AyO>DC7K0#^߸1qr:0# h>1FPH26P]jw}Tܨ܎ ?ދ  : zL?=u7F }bCos.X+ u  0 ljHs'SjldJ44MSw 7it w     7 # 7*jb{liuR8]rT 0":x76|aWD[3 h[ P  = ` E GU]Q.4 !R$= 3{l#j?}ww}>jVbthR ] u    6!l%%1oH[Z@aTaއf!ݭr k8ݥ. ߬,{n6 jMtFQ8Q \N  tCb?vod+|Yr+K { 8 h<_'4!&,&^.4`y(l)#GP|E #f7(* k o dO 4 )p"2#7[    # hn w = %  > F7 sG$\Owj _ O 8!`!!0"E"A="%""!!!q Nl<J1w BFGC  /I T    7 L ( >  8Owq^Ar`;Ob!R=qc%@tK!@u+|Y^N>304_E?߳~Qڼ|ٲغXYAךn ! R b [> hڽ+]܇i']O@K  W$ & %  `3I0`*I c"SLVyFhC_H:cJujq{uznJ._o}_5'b f ,  $ q  6ks+ 0 < Hn  @ $F {mPwpbqi~li%M}8!ݘhlqK) 9ds׎ ׆ ؍dfڸt=wp"%%5!"?UTp   FDhj{1IZr42UrprLB@Yo~<&@3/*H|h_9kvUXg|R 5 R > P NvE ( M    -vBCc|_Y@=Cz   q~ Ou-P:LBy<'i;ViRy^.JQ6M2)Gv J' t9HqcP(v5X%/]'FFo4a>VoWrC5'uU1L54rCQ`Pwa-n4yI 164m3ߠTEޯDEܛ' B n ޗ ~%-?>Cxg^G5@da5X' H  T\!YE)FhpJ WX   { ( * $  2 P e vA       h j  C 4{?5XAKX]Xi%t6o 9XPS!)w  O|hn7FW߷uX_dFޔ06ݧ%ݞݙ)9ް(ކ0_!xc=jGnDc  [ Pf6/& v2ilZEo3<cv= 3 5nHJA5YgC:7;N b7 i} [ @ - )# ?g}j\Qk6TD2Nތ\+BSۢۂ!ۊ/ܖ/i:ީYtg'_J; 8t  ae<Hcqc b`YB"nh N`  f s G3d7,b934eXv fu!$Dnj{u$*P\#!O@o.?4 6 2 F i7(b6 H  y  |  6^ N e   x%\t. 0&_(O=bc\9I{GH i^KSo4Q=&kH XL}IaF^~r &< g n s0 [o G  F,Gqy@r\K0hqS3XwFQK&Y=s<8'ު,$KܶpT6aه0ٺ:7v?׬ x +  VL֙־DرٌWa `  ݩ M  Y B+*n*!Px['GuH]5%6f<4t|Kz  w H :p!&#!u]p>*   =!3 S!} ;! * p  5 ~ w ?o2S:N^ D% z ( 4 M  k , 6 yL  {  6 rn C <V \ i/x;:wMWb+(W<L E| m&]l~`-Dc 5 l 8 ' Z *T i c \J % RO(=ky m^>"-[JdA,rS67%5l $ %Y=(roo3'[Dv9iz<I duMxS+9.bdf dc % k ZW|n/2)AquDN3+m8Vz&$ݨ~xFyפ,ԙWgq'P΃9QΜv{pe^Β]Ωχ3Lկ2ٽ6Krj85fSbN|f|Te+j$6g=lu/LjG&U   k  /f\_pK*YAs[4^# E M M S b] T M) ^J#    L~ (  wV^rW!ee:Zzae8TRBMT;f!y@S     ," B, KS$3h8v v5.f$6VZE[W? v R @o 8 [ Pw^7ph0hcd{hTFU]]ihTChB$@]9,AA="zf+Y(P(R ~masc8/]Ti6 e\S=&M}3~@9S4Os|7Pn:O xz#`n#::'}(9R9$ty1 W9H "dF& K  VmGq'!""w#-$##I4#X"C."!!H!P!!o  p % o /* hI  @bB(W.]z = Do.{'CF 1VI7Rj O8fk\cxaWAtBZ2 >foH=|w/vZFy~I! 47%12E_>n,9cx " f7Wkw`Ce Z 4 9 v XwUh'v0 hM-$XUxs ?   >n  7!=!! Sf hKilLbtXYEGLN~; 4 gT  w =  s [ }uD(9l u @ WUDS,7߿-qFdLT|޾SBsgWI f, ~uoibER:52+ޯ+w8LUޖFuߋ9 N k ha 3 {?l$$JH! Y  X )  0Zz^sU> r M )#l.I\>IzR+ t 2 fM9U2I+:z0eVX@Z=" @ p mCOU<:'v")n7%q jJ=G?|k"gIU e~ &; L DH 6]@&hA!]$ &n3|I-8}R+C(l+@C  } K X/$j#ly'~r LwDAyzOU3jpFxf9v U"Cjbk9e=JPt -.RUiy"Qpo_V\Ul0{vyEIU:  8~1Vi U)@*vgh"' #{g0e +JDRT@NTsx*%/,]͆w͗̄͜KpZ9̟̇#͈ͫljӳeGڊKf"4n,keSpy\J\EX-Wi IUEZ o}}- ; # iHmTq3?=B$,fi.; fJ=!n7 'w J    opLA  d*n-p\GB9+rRuCZ i  a 6V4Cuyc +.Bn7"  ~&5!N{[+SdV a x n>Hi-:h==BAaD,|/e'[^nlymA@"(W-XnhdK  t-eA1IMhc /;zh5!o'w<WiCiNo!"j*, )R%0QW b(LgO3#:x _;h2CJV% 8 ^_XK:z  S=  !"@h#$y$$$<w$o$_d$`$%t$V$nu$}Y$ ${#"! !^ L2XcrNbXU *C f  o }  T }n,!62bP>;^t,u9> H]jx@; ߹m }nB}i@Vz5 @h[ 4lUXZ q930~1f -9,KQQ}_c % K >gla*ahtc=UOewJby_b@<,9G} x J  c=  !X#$%U&M&'('&Y&%X$#"!A ' YS GV Pp]CI\ $ U   o%zU4v9 w f" ) t W  jL h X.xmF{"XDcAst~'#@!` SmY< V@_LBtߒ7 ޏ"Fܓ<܀czڒڜ ۈ5Vi ۼ N O  Q u us 3 ޹ H$ .} IRW4Oo\j7,Bm! #O8vG N :  5dI*E!|WGU&_ c O } m $  4 $ ` d `  L 4I!-1zK#Y;rVei4 O[-FH1@860!vnn/~  P V ! ZT [0}]bv8')p,(7CYy{=^j oq57" E; &.sp|Y&JlO?9C NhQC)8:/) 6/?m:teaqb#Jrse"^5XNHf [b}r'pHkgOeJ| ZLfL)E 1jNQS'NoHd3c{s$_{G1   aDxN;N)}k<-(`}4Sڰف>֣jH> LwҼy?XчV[#NH5T'Z/fLtуяA҅] Qw-n Lօrxٔ%v߂[@9 /cX"T#  mj+7=o+L} y f  _hpZg^]|v9 O!0$"""4"V"W"G!A Z> \TTh   h  [.s9 .!a!"#L$ %!s&"&#<'~$L'%5'~%&%r&%%U%$$$#""!/! pr1wX4x6Ek  "' '+ o:UQu>   V!Y.%$O:I;t4 `9/a.U_{yx8\i.Zk| @DJ$Qv$8"8dxfP7EC5]<cW=G__l]YWmb 'P!a  QuH[r9+N&|9^g@M7fdOFX ^  j BD';FmBknD  ! !O"c""""""^"~" !i!W!  A UJ 2?cZG5ba_^NE'C  [+   ?4[;9'PM=Jr@gNa/v0ދf^q ڌ٨=feڒ ۹] >)0}>o*Y 87H1H;avN&9X `:${VAxY , 6  & ` K Sxz5Qw2)yvcD&/(pbSM 'Vzz6 Y'ARPt.%.B)    $ .- +yW.{~AJ 8B"zZbG^)_QmfbJJ/VDwA&2^-e!t\v0[U8-b ~!!!i"""*#m###;4$`$q$m$J$3$[#%#"T! ! ? } u 2 l    ) ,j  $   & ! 0 {  $ fZ[2Yo%d#Ep;#7՞E d5^wz.sҤҺdҤ5`ы!їdғ ԏYנsPZ/ە~w@l]MCEEmRz((g%m='iM a!jAtlO.|1TXiJ _BKb> ^ ajiH9#O:!f|"#R$;%%%%x%$$ E#3"!!`  K  +  !"#$%e&P.''( [)v!)"@*"*##+#`+#+5$+H$^+#$*#_*u#)")\"_(!' &&L>%'$O"P!#rX  y\)  ib J5-P=iXmN+KmdB:'k3bwX:MPnGGG2u7|b{'(H9qxqpYv%z!wKHL@~S9$.UsT{}1t+yu0?**%2fC.fbIE*=7:Ki,9' HWg:F / _ ^  hNyX[)hG6nyL2Ja3ވl'#ڠيwRzոգԼXb9գ ׏!ٴ'C*/ߢ48 ,  > mM 9 ْ I զBqѮn3/c 6 ς%{n[ ^ {ݧZ> J9%cE)(~>d k e  f       Iq LT  U m >! ! ! ! .! w  j]]ue4S ~g<`w:oz --ImxQtc-:_hAimJZ4v'ms7,EO[ l 8 3Y Fc @K2)p 5-S{Zb\rT4TuUagK @35JG B' rQ G { ' w f  B>dD5+|;qY)fP 6O@U).lj']4"@* v!:"##$D=%u% &d&&~&F'H'z','(_(, (x ) 1) %) (r j(( ''c:&T%p$i#X"H" ! R  m  V-   b  7  z   j  ~ , c 0 M(b13RySlK9Mhܗbr pף+wӹbGѤdGSO̿\3ˋyM̛^FΪb0)SRզ؆} dj_#FXY;W40-~I`m&k z'7`~~WU\9)PH  %:2Mv1oO;mb! ""d##W $ I% %; & 2& '&? & %h%T$$$$###l#f#t##6$~$$  %3e%G%\w&] '=!'!C("(>#g)#)$X*$*$*$*#*D#j*~"+*!)W ))D(6'%%v$"1!i   )exG+? | O( 54pDAK%{\C:aU +Ob 4h0!w"A*ml VVK]R'L1j`6<:?'.T j1e8E#<]ji-S`PU Fx /    d R P  ^i!x\kK^Y@$v7/w\\;L{ @UOqEM fM  d N >I w S~ o [h s - U =  D M! 9 \ J  +B  ]  F (egM~&9<1 qaE# i h F 8  @Z!*;z*LadaC# /-WR 3o7GݹNdxmGLZOKղ},ֻhI]ـ6ۛܯ ޴ߚIDp /0H i"hg,n3IWsQr 0@twD   b%%E\]EvX)e[14!( "S $ _% &'T)*/+8, --O.'./E:/2/..J&.-,t+H+/*K)_(k Y' '& $h6#i!$n\%.|uC e5 % ? !6RT%'(-U1;yEI*;r'2QZpL%g1.}`BMV6ub=1Bv?O߽ ߊޝߖK߈߸(}[[P @f@{mL@*+.&FAc  "X X{ y \ B > O \ Y I 4 Q "ֹ9tև\׌7پڞ<ܒݑޓ_US?i #N 4   J0LY1GU E I_ >!j#&<'S)x*6>+B+++%X+v* *L('e &N$"2 x O f`ls3)f b\XjLGO ` p m QO&zQO (O^0N ` 6 d/ C xM3ev.xT\5[ P? ~= u A\:" lߞ0s88߈$E?z8?mY][lWV-bd%3^;|)&RQ ^ ] y b 6P *,yhHeobp /(>;2'TkXv-h9  'e!C!pK"""->#o##U#$Ld$$[L%%^&&j'a'(i(tu)l*@**h*****))N('&%$#""tR! qt r$  ni  @ Zg ] $ c + ] ' Z e E$ s + , dm o`Yr*|^5EHXO1 ޺T=<رJ0/t`Dџ/3%Fbh͜/-[ʥ& ˍgyΝ@м^6Ֆ0.su 1,-$Id eCWG{?&j>s~SY]E%X-L Q;O3*\ 1g 8 H_N@ Er2xI & #! !' !' <"# "'"4I#J#i$$%u%%%t%%o}%%G$$A##y""f"""Y##$X%d&&&&@t&q%%r$="! cT +  D9Pih$ wd I F "fKJ'-ch<)eg |sO1 Py|"zZC28*V&)r @ <M'p>H?y4R-L1Z-&3x:jDHzj4r?Ud9@#/f$IKF/$p    +"10.H{b= X, f M: S   )~K 9m!t3Mdz"QQucGP``9K Q;  r9nnLT%nrY8X_r`bu>!3tMw   ' ,  6$1q#'4   :  b+ 0 ! *LvC1/IRgQj#eYzJ'W35[Nٴذزا؉dHH^[sp[PLLsEI4=6J[rߣRX!+Se/mck,G *U`    Y   #l / H i6 u ` 1 ? fx  FDw; 'iDmcHg3n7 nP '!L "3 ^$%3'()"+d,-./pJ00_C1{u1Pm1190P//{.-\+4)" ' {%:#$ ]_GldNJ 0;#qAKBMOb@y#bMKB0L 4sOq-H rh EU`QH?S@W2TpB@ V7-r,3.P0M9KU (Oq]S; ( 4޴ Pm boޫ7ޢ-lߧB5F l [  ` T6zr#xO j ^ X R x4*C6 "$> &T!(4")"*#c+2#+#X,"t,"C,!++T7*8)#(q&B%$y#M" ! 1 Vmy T@ x 5 cR DoN D)|%^ L ," i h^ + x v ,v$}!qz'1߬=R߸ޝFFC+DP|jgVW nW@\EBgr2 /y(MPjN| 5s G Jc+/1;/D c/FTa YGI, g n N S A!o\xM f:9fI!zqyG9i/E;.*@,cDt Az9\H8)S>N=8cJ8 X4$ 7`} P@  |  F P 0&mzY12W p G!  cW f P ! %5qvsp ; Xxi7S}Y#"Y yq7mlo3<74 h   ` '2 g  ^  ^ [    (    CH 4   cnC:~O5#|6=5E.1Dla'<0)oq܅1Fۑb݄ܰޓ.@hslT&J3JXifH, >cb x: 1 { G T i Z ; k^%\'vE)K 9J U {BfC      -j !9#{$1%/'2x(Z)\*F+,-T.l.f.(.>Y--,, *M ( &$w",/ ~mw}G $W.:i}aM\.>E@`[s};{MC`xtleheg/sja9L 3p7i<[y :lt)qqUo ~$i(NNWp&AR }] 2 / ߟ  # w P tm@7E8f8o }  \^ 7 L . Gk2 < ]PO8osK t"H!#!%!&!&X!' (\(g(kL((x''E'N&]%7$" i!  :+Bdo F  fI]0@&8%N&~F ܐKܧOGQxJߐ &}Y#b>j6c{&BTL-\ `AxQ QA90]FJ!m@SfSgM3S$xZB i < @5rrI&T$ [nS  !{!"Gn"""Y"")p"G"Q"!}!"!!!!!%!!!"-f""#T#p#k$%%s&'7'c(((q)4)kZ**+"!+X +*,*i)^( '~%x#5!ZjL_*?  j 0>_vd+BW\1h#(R&b}M)QaJt^ ;yU:3xsSފ)NvٳT:C=2&4Ӑ Н=3s ̔ˏʢAZ˲'̗KͥWF+Ԃ`.mhڤ 7ߥqTiBHd&k< t W [2A}}PEv$9<  Zf  6  / 6X^R  0 N  T + J [ ;  N8?I3O^j  @~ :   -FSDqT=V=N^O8kyVTaA& D   Z]vH\Zq ; M lc B$DjGtlX\,ym%&__8IJ9}rrjC3!,Z"yk2e:ub}3\F|l24a Xx Ep{xkZPb\|dN*ua2W^7#Q| i U.  X 4oj KI:SMZak2jZM9T!~Aw4F   T XMV p4Z<6'22wH cZ JzZU8Wm9{g!aM'," 2= k Dx5=`"Hm ^9nq3ifC%;M=LBS N t  Q IYOP'/ + <!u @  O 2!V]!l!(Q!y! !"5##0$~$U$$$}$ _$e#"!  + + F ZD^%]*P. & { b8?"UCK߉>:۬&\ٺ^uL2Y! #S ֹF4~֥v#Gv׳wzb+zbۀmg='M@]L-b&V,M)z]_]SU;0rowW#n E ?\ Y 3F  # ,.]f-Q} D!p!R~| !(#e$%&'t(tF)p)ec*P*1**E*)`)(\(''L'0'"''~'/*'<'^]''s''\''?}'C'h&&&%a$-7$#"]2"\!D U7pQ7: NfJ!6D(|$#!A}<(=IF1J_ivS Iޙ[;۲ 'RלWՑe҅o {ϱ{WY=͋VͦͅξhDt3Ў(rX҆cՄ2l/ٕBYݚodJC/QP>ZM:k { Y ;: k x`j&_-?Q=dMA "K; }  w)   / Br5 . N r  }B  J   v J   8W  3  [   *{  <  u [ 57m0&ka_}cGz (  P P$2 T ? |5 +<_xtN3">.zBG5 ?mTU%V=w%c]Tgn?V lo/B4*>]. ); _{)ccVWVe "J\&D@eV);>Rj RBXP`3=m  Rha/ A2|Ve3j`<Qe3NT*ShDUN.~  2 Z`)uK T|F1-*(cO"!cs,K>;R/0M>}}0G.=phopPmpt@mQIU_J*ULrJ8p{ / t/  +pCtR&d6PfcjGk@ do>JIkf!'/bh9޳ݖ݁Cz݁ܞWs pL2TZ%ld!k2)q Pd}ou A  M & a a p<Ezh91-H{!at7O , R =  d q    8K IT<^=h }!!_""#$$%|%q%#&Q]&f&7& P&E %p !%T $"b!80 WuJ*Eb c @X i2)I_j$AzڔD>k+[5ݒ8WަvPl5Pu7 r;t.1y{o[H>u`aJ+ ^iTpW%Y 1` t 0Q"ijaald#P3T 0DW mQ" ME  M8 2 V  z 6  -h>35u#   v El9cu5Bag)D<v^ \ r ^ `L1 Oep3AQ۹7aJ1ֵղq)2O{iվ`ro(zd 4 T _EC4U"4hJX&3 t:w-Z*$6I&PE766E w\ q s(QH\fe'b9^L*qZ*:P;XV=}XA3j&O -C9E6[T ,n  !> ! " #a $ % '& &) &W .' ^' |' 'd ' w' H' ' & & &B & &S & & 7' ''''E'N'&%x$Z#]+"z 2@4.WgK[8 SM!RaAv$5;h_r5\;n-9E|X`"dx0k}9gWLi6mcKԯcӑ{euaR΀X͹2Buh ߡvj;e ߜ!Kѩ8)O9ߟmr ^rt\dD#DO 4HX9<Hw-k ]+  X  K Xk       @ k Y K : ' S( ]  a!  \\;&^26"p'H9N4RaQ@ (    t &' _-FXh x!  ~ ^ ( i  E V  ) $ U  o  E  vH0y:[`AnI  S  D X $] Ff lk ^ E & S z f &2 t1z%N~8WA*U:".*Ky(Y'j=/2?_^VNaiKg>$3 \K]m`;r#^$wK2T'&_+(4_O-E KG!r-Pb6 &nZ3   J Y{ Bc Y^j=qmkY>?$PLO/ B At =<a!~Iq^8  1 xCefZ*Enxu VU Ԩ Ԇ3[qZGMտ'{Gִ?<U܍f3vߠc":`#KHPj>FDe&uEy.By^DLFAO.L F  p I   H  N    H o  gK   ^ 4 ) ; b = f v C    _ s f 5![ W![ n!s ! ! ! !^ ! !( ! "4 !" !"7 "!!67!  ,O "  G 0 0 !c!!0d""#U]${ %~ %c -&0 t&y&k@&%X)%B$V#! 2s^8z   YfPn ,[0*\8tu#].-M?; S{HsX4M߻ g mާ+9Aݮ}5ݕ {1 TTvf(MA/~PDkYjћޙјݕZdZ_ԓݙ"חk tݮ!}-]<[mFfC^ %  Hq  m Yk`Z%M_GeoLLMCnJSLy%R*)0 'w*T\(E%r$vUV<OSraVP}c 6E   QC  G s 5ax`,`#Q(R#Sp,>n [$N)r'8{!Cuz7LD#os[d;<_sfcXMavM291-HE[I6p(yDs9?_"T+,L~3Y#B;,zRZ6 ky<SMoud#(!hDgf.hM>:V+/h$(1|"-0\Jg$m  T v z _v/&M!BnW=t +   u w 8_   5M  + O  A9   W"4_IJ_Xk[5GikcWo M  eX oF( 2Vri/2; w ; y u= z?$n36L}\GK/ZOߞ"h]4N9>RJFT<Y^2x ' Szw@x =r h^]x]:T.)5F9NI%j9c#5K4hLyQ  ?^ 5  1  c r b  aD W eQ mg `N @ \Z"#@A-~$  |z PFKfHR-و?U}2RՔP5ո -(խyG}K2Dٖx~}݋{޽CXc`[;@R 1iFyFc(~dF x#4Pn.Ko `R 4   !QaAgPWW}oZRMj=Oi=Epu'rdsfw Z.>{{A33Fvtz.fLWe?k[3]    P  0 1 # & I32e=3~K  AT2qߐ`A068wG9w a \҆g{gh}јѲYkijLּ7/gK&sBݞ޳1a93 n.Tq#T:& q1k ciU/y   U  XF   9l4 WpkZfc  f   . r U !E "6 ". g#? 2$e $ % G&< & ' 7' 0''&r& &2%${D$#"J"K!n !pa `P3;+{0YKUj{,0c ? 4h!!R" "_"o #O"" #"0 z! 7\p @f! /U d $_H4p~)c#2$Kd*@܁w@&>)|\P  '  E? j  X3 iNGeS,lwt."JN iC2fx-LmN3V]7(gV\[Go " !>9:B#Z g'h/eZB^P-+Fgqt~w_U  |  ^t1 ]@R8.}TJv@Sm9PZ[U @"(`j<\q0@g0. R] ; K hk?Eh3?<>s}'{oMhX <` # ' d { i A:   ! Q sk=`DmCeS<k$H  y F w$&69;Lrk18oxzzR0!='vF@LH.w=u9Y38[q"2[Q/ E 9   bjd_T3acTDKq " qv n*$j|5q / %?j 9JzG9U+?X z  A|n r_ t> 2.. u1NE(!rrJ<A%~QK6<NM R / V Ir"j0T8  -4%b>HVJ= Fz 6 #k4'wo~sO .bؘPh $Av $Jt?]RY7 wb>&)KO$7@^"|N;5&4(T(DpvOjw+#w  fMMwBM /[mYz  a   RP~T > n t g U =&,&fOKf3% _ A R9 ? X z 6 w " n 4 $M 6 4 %  @  TD $ct!  i    V ( s+t{A߳#lܝٟW!׬9ӝ҉d҈ Ғѕ{=G҆-QӐbJ}֏t؀+TUW"1*h XLG*=fk W"1"/Z{  k ? ~$4mx]k%tIAlFO%L F[!W",##?r$$r%%K&?& q& &g &` ' B' F's '&%$G%$(# #w"Z"!u!z7! ( 8 H>cLm; \[*sJX:  P Y5g]o2 h  # ^ZPMRRCP0Pߪ!Nݢ@p،݋- WU '֋Axrٛ?.ctޔޓ]߁mWJ[l KRzg j߇L ޼Jޭ"g):)<wwDBH~"8p MW(>O||z=j;t}x,  0  n l 6o ib$7!i3EX 6 Hh!P"\"|#i###D$a$i#Y#4~#!#"2"L!+!  L%a j|g:-Br"2:$@ A PCfw{ y   ith|r>fwJW 8:n0> .|WW"DWSK'DE]&oNnO/x30t*Y15A1pu'!3nh#=XPgX.f i p < P Em r < n  [e m? P /: y [Q"`J m_QKAZj=M3DLo%E9\A* Fs   M  ,[ Y ~w < (5nOg[ m N  oy   =   1   | 3 [pk<EXiz.RK';FW6W uB o7 Qyf.{bV@T[ehYdndQQ._y[sda7lu / (c<>a&/1Xp { K tP0%-P;+qNE,~CAtaiwF  k~ >(sS(x%i@:"&M[mDx-7`" d s R#%9P m  8*D0, ]Z0> [ '!(4!-! Y :!P_ G V%0I9zAgW _ w YX t t4 1 2X a 3 W :l z | Mm O V ]| G VQ|9 W 6^(tKbB3).1x'K'YKJn$s nV; * X$X8_,@I&1 fl)sp'?SH[`^Q?k@>E&& J ^: oUg=Z:D  FX!mu- | =P  qy Kp.L  p & 7 a   0] C Pc)3T\6 H]0Z8s%Kz ?h\Oi*Z?)%I Iۺll, سMFՑAQӳTp,҅*Rӳ/Ԓ9F.`ܬb*= x>]H>6So0$^ES^HVbRwt Vu^b T$bO    n h  { Q ztwF[<#,8?B>ELbpt) !gP#>$%&y'''v'A'o'O'K9''&H& P& % k% $ d$ #u#W #"C"0!!>]!! @ pbN4= 4;l&/ L\y([P   ZA w t Up(Y1 ^,ch,=",Z6Om۟H}DF֩~iײg=;چ+la߉1S] ot i0 Lz[{[ucON*+@~M\=Q{ ;/^vߞP _ߐ%gvG1 a]u/56"{H\ 59 ,d:ZLsv~O'I0RVRtg2Gz3STK[5Zcp d1C,-C'2p] r{^|_cD } qT vx  z  z t -h R &, K, yzH{ymsgIJ./), u_ @  f )/_ F  k}  z z M 9R +) % J   c  J  Bu $  M:cg[#}H8q1FmjlL=E?H$ kd[QK*OrX g X  Tm 0i 7c>1d%@fzN%KUd HGOEz+X v'?UOKBq8B7UzG}g6{ < 5V N 5 ;,  me7^vrvp DLs i J \;<<LrK.l~dv?&6^!:~|Z j j=s ;RL"@ u F  H$^F'Qc_^]I=ls #[ 't%N^FKm ~] 2-   . N o ~ w e* IA #Z { LMxi e 6 d % ^.0&j6BF Wvz6iqBd?uWgGl&H=G5F-P9aE3%Z  {$ -67+~ |I1+yjh6E73@qii7|c)  "(!q  %- 5 % = 1E4@3<   j5 J \ Us; }   6 a{VOq~J#`_h A jm 4NMq`TTB3w;_a![-lBVNckFfKYyJ ' v<'o_eXo#u7Qr 7ja$zUJSb}:,g :il\M4  A K  E   P N  e  ) y  l `ia7Ski   ) <   9 b ; $6RDWb+ @  G y4   bq ) }I:}@*!~1Q13#T/ N    !|-0Y'~vEcI^upp? m ^  - $ s 2  h F I 0 l   =@8Pgn2=6>zX1?QDJ1CAs0ާ6%ߪ>`^QD[Z: fio2P@pZCh 1TChSpKm/X)u*_ c 5<|{lyA4\D`74G&e[d1[840d?%   ,w?h^Z).Ydh|tut{  !G!<t!!w!TD!   ooCfgCBI<"} = j  (fl G f u8sOTf#'^I|\y+v]s6`>cr~ [ x`"BEnW(nl5L7kQA/*C-;E " ( 5(A a  P 4 3 u & s q N Xi/nM .   .l : +p Vj-LMh A / _Jz3j Uz(i`]/ 25 r C - 8 I|\X+iY>:`#-Y^E {K3^ R i    &s%:pj(L 06?M)2 !{ZKx6SmNHGg zzkp6+1cz&3 6#lUH> h (G 7    u\'{7%MJ4ZFKs 1 ]s )R>/~,>^P9mgB  6 8 RN?_!-8 .  2 SD jrMa *}v   d`Y fAhh^H \ / s(U$ ;bX\pO!1kHL35MvnN8<'v}vS.Hx-KXucSKLS\i)tO3F[z~ cHc;;DkZ[Dp@ I$I5Z//$VxGt6rzC}z7zIF%B,Rv>   5 L>  x e   p * !  |  O  (   U ru N{ u p iH gm}> P3csx3"N];qyWQhf o!VV+dg<Cpa]&L8k& -XgCe[GAz~Oz|" rv\'wT{-;)B4b~D|re6A Bp!2x@Z#\ & l     #v ?( _    |  ~  y  W B G b  bs L Z X .  5  p    Ud?(58$4 '+ . A R G-K9p,@6mYPh Qo GilD K  H ] } E D])%=9^8}pI   F @ O D w_G-HH05FP>)T^PA>@CS|B^wH]bc^I#.1=$ :lp`USE+o  I P } v 0 s ) [= 8#O&Z99(]M?miI'Ra}AJ|"7^k^'P ,k5^wC>zUf ^ b E " + 4 *G p u KM   h+ &.  q wY : !  N 3 ya U X  ( A< K aZ o D+ m  N   2 < ' < r   D>rU SIhaC4uk6=P] i r ?Q P#"k@STz~_*QFe]J.[e'  ?n  h"  m IodIEIc aQ F A Qp n* MB;a6%0JO*;:f $'FEF2FoVJYX"n(:+ ,-..'&O#!& #x I;hqM [ (  q$'QVL'UYlkDU.HGuCNr$ n I (  )Z2{qKM7:[ D\WG = . [ &O"t: QA v 2  FgrfB]s~Qn64 . D Tk\-\S?%gE>f]f?n7 G z Y  ~ E Jr O 4 i!  9q w d=sWa_P.7qROC8&Fup}1cJ<5BbW{Te[gUJR)i(9:_2*(//FpPZHKh/HgM)WUqI^j&z]+ )  C }rZTDRRKC\( 'Ah|Z-<,nUx\M`2;HVQ:zB>(2s%4}{ h h&ImN !   6 e  V   c , ![ J W :   .v   g* ` ; VO T 2X k@S#yj9]lE B]@v~ 7A9.9 [j 3  s g3 c ~  u J  Q  M (   @/ H T ;A   ,4 {  gU(NafY1^CruA.3:JMdLz4 He*hUG.!0;'T* 2eD6/hNU<"]1nSkY(%R^))3#G`W-dTQ/UH2yxR-)6D X{rR* \_  + c 7yH ic`w)BfH=Cf"]:i54 } :  t $ [|s=Ldy J ']  2`A]@r O 57 !f   )w/Z2/18CwT,gz.Nw ;{I+Hl8G#/N~1WA^&<`b]x6cxC C o ; `  R~L(M)BGzIc U L>) Xt 7 / R j  C  f nV .V %q L < } Xc < &t !uOuB     9vC] M ! < rg><h\5PVB$$^CU!KBu 2 | 4 [  > .:,hXM>*D!w- 6 <9 < 9T 6 1> + $ l  o I   4 e ]  K dM jk ff[SV>f2AT 8e/v 8'!IIvEMJ\_ %!,%CGg;W,+osp7%6[l5jb P l  P \  = 7\?..MOu ` 7\!ioD V t 8 F ;@ $ u ), T     % L |t Z - - z c y  ( T  #B C J: = - # )DuPPDN7 n]3Z NS ' A g DOk\~.YkZnvl2+yFB\D#y)Kcb:c8 x]b= )?_^2 d w  ' J ;W"t.j@^QyP/R"ZE{e? ?3^"RR( _ :/nH v4Z# "Hx+k  )T- * u   9 h $ f!Sj`M=UbNm&??a@$/9W{I_QGb8*.&,.bDp_r ewG;I_l*#q@*vP  ^ c( +Y l o j \ E  C]  oy? c+ T_')t/B[s~eP GP;r^h'83_s%D;  Ai  - R }N A =  T^  m>  vA   p   3   'OFwp   X    _~3!l"Y,BMGOg/[K@S 3 S~    XpEN]Uoh&[]X R  M   8J +7s~.Y='I`{L`@^7< a.na c[:! \H?FO Q'p0Q',U3+6 kxa.F=t M}7)dJ:>WZEIJK]7%~a9@kS67"f'$&7G<%  J > 9 As !,  #F,*@vQdXBR2H(L.8o"^   r ?  /|wGc\H'u ! ] ( 3 5 .CRz<H Lmy|~2! l.@;V|_DI!oh@(z (n7 o ~ $Z 7)6& #cbb-O S~N(W P%G /^T(hN S b (Q b Z %  &-X[}X)..20#{i j 8  h ^  P  2jE0Zi Z0$N zx Q .  O Y 6Qk+ u|].IAPn}a  R e  O  o $ g o<   r & ; Q s j 0 FJ,5GZXy`cjv 6|]rjnR.*rbSC:. ;Jn#5]eqP| `=TH[Bi3t\%r)dv[at{L LcPO&,MkWGI"+ ( U3 / R i `txRAW!.rcq/jyFi & a[ /      } _ = Z  N  O  A p  m O  \  , v1 %  Q[/Q 5 lz ^  !  e : !%  H,Iu5ZQ>[g0%)@Z;h ].FasV=i? a*D/lYvN  z Z i   6D j | ms K  * F \ A TR []$_g|ALNi.@zFg\\Nme&BBPD'/i7h)t s"1hh^ #C}C4HA_k; x1 x %!>t@U+| zxnZ23ix;P,R vcX3WUgmaiCG< B}O_Rt?b\iV0X  R S' (   *; >A V! v w _ o  e\W IdeNJ+ar X u&7mPk qDol4YB=Y@UJ}2 ] 2~ d  ~ c 3 ? \  m 8 Ot   @  u JF #     G l j a ~   / Eo I 7 : j    x  r  L   Z  I  6 " U ML=!mJki@ " }, C V c ` D 3 ~ | E" ,CL"Nz1t*Z>n0Y/W"?^^I,DoCkzy6 me 7 ]"4qM?i_/d}#aAc=s9rC#8%SUap)I s 1fq\3`BA^u6P3uSfvVRGG5+  > 2 { - j  U   ] }I?s{S-JI^f [c?{Tu >x  H# +  tV:5Yhomjh^=Fa! # uEag=O}JL mED]Pc ^wGh N \ l/<j%6>]??;,Ze9})C(:f^e$R= K D Z M : m  j |L 3> E \ |z V :    | ^A   9 A   \J  Q   B &m 4 Ez _^ . 4 Q {  ( h3 ? ;)AaAl0@1"K B6cUxZ`M:t/}!(ZT T <  e $ ] l! a(6b.oi F{%-w &QY +.;t7!4{69 y{ $_hd.~g`Ps$TW F0<7K\YenxG ,/k<6&d N,qh; nIK9p( t  #8 ? S9 & y    ; t0 b    @ n         . H Y \ %K *  b  9  h#>@T`dq/mcdSpr /U/? imdlr474: }XJ=~g7>}S?f7E;GU\l\(XMA972"`6+B=w0DDhs`x+5  t  m ,P  L (  C $ C z G x} W RY>/r[YMJN&NVOT_ra#J/ a)UuwI >],cN+}R9N fPz'pQM%;;hl:8v?Jwn (CM@ ;t(>).aQ:7XHm=Ej~H?7%aP9he*fFs pPPM l*jLaul ":;Vv5d HDzK\BFz,Mc{R8yPN~{{8v@/s` r$ u Y  %0 |  :  C  v ^  # 9 B a F 3 K L E U -  x H  #| b}     -x Ok [ C 5'  B  q{$Oo_wP)vmw?}O r  m  P Ia WhyP{kDE|@K)Ef6]l(I7*V!&X01= JJ2">T]]Oq-XY`'T2cQ2O;e7p=}R+wE2;r$_z2ZF0 R j F ~J  J  H;R3K%  e 9 \ < t   C    S  W J  . =  a  N U/_b 3$q|;<c a 2E <iCZW D|< TJaFJ8k'UU Sm<^_VuYR#?KUi*{/vKZC z *A 1 Y 3 b 0 q8 dj b g d) Nk *  q @ K )k  ? I 5 wwbQ. ^&;YyAY#=d4fRv+ f  ( 3 IP _ =] ?  \ n 0}{89p_c3dJ  zjmx g,{y"NIC8wE"W^1{4>,N]lN n   j WyxIl0B%R?E<(<u (&;BE`D8!Fi20fsAi_XWdBJ2Bmfod;Z<6r!C3N8h8SwgO&5D=anfGQ7"Bp$H_S6+1{C%I7<X4l  v  c  _ %S   a &  %   { lX g c: ^w X L6Du%`~>i&"a#F64e<#uQq`%A"SX:M8!~<]rE-),0-5;34 xe#!=er C.^7$0ott#V!]D~f9#6UglJ 5 J  Gp o/ } Lt|il}\2=0 fmqd\_Sf!ca 9ydI9l$Nw67 6Q\sgsf\UD76+#!%c,6,@}HNQIZfv;Uj~?Rs5?n~mpqp=7a]4kW)iwvzmyPS@Z9r%<5`9U`hZ4E 6Uw&4'_7:]cK l g '  zD e O 7S  L{'B>prfc!,R'!qiyCs4^g}+|M2')A]v o W >%2eH| zlti"Xu< Ks"Q|[Y|h~=vGS:s*$jkZJ>4% (-4i>NGBH>E:?31&w[;ih >i/~Vk*L- m'193Z$f l})V0Qp-k-S;k  u'', ?`1}X 8G0(bP{A-BDOB]UsUVHZC)O 1!ec0GKF{M * d g &( ^A F > f* n  a z 1 + |  W De'f]'|hj ?6Yp${M`[< d@#:tU6$b';jZ-BxX-7izH /! x = 1JVSMD(n86Raio3kbE^`wh|"\ZU%@gi Y 7 e   (| M  S:sEZXJ_8 Cf +    ' >\ V ktjr ]:RhJy2;i <&xb GY'I? +)BKQF2E @gOa}q>DV~JF8~.+iM#h8z58xIOoSTZVf'~sgG*K 7r0n" H )f b V > m m8 ^-A8?Ce$ZvYg5_w)a9] HB<\ X0Bhy>& IuFS!xdX4_{aB Ijh~(aO$#Uow1e=** 7TR|%/xt iUrf~4Cm1M}HzV   A c { C  t J O V  ?   Fs  w e v  >    @ pp L ( Huu|e`sbedVYA)zU,-<HNVf{iCLo.TV9x    f i&;fL}w2$&K8O U V L 6 j  y f )H ^ 'xiI=a|^C>,m>),a_%v"oys5`4k7Z*e0%H)Ih]apZGP$_k%o!<O~^+cd[fr= i=PTO+pKPUy+[H=?Q{ssoo$rlwoMPf88o2z-R$ptECB`_ytI*xdXQNGTiq5FAy4Yp>gn l 2 % > K! = {&Yc<Cy\9u mR*Tov/g=Nd Q3v= $$ x1 7 (< Ik$gWUWO[O.*9NF/QCSt3"y0E~x++#LjK::/U*+k/4>Oi&&m`"xA,*)r  jR%1z|s0mlr|Jbl+QI_N#-9/8(\S;3amz7e#4{KKK=-.   q+{v-@2PW.-dzFL)I^l:<3WoygP[J9F [7l/d Re " B d CJy\P 'u G x  'f { 7E%Z\[}|c36+G87WRy@\Y:n7e4vS0v&];!j x 9c#KQ ^G^xq;x#w=.7>3&e/Nw<1Cdfy.MXTK;"+?LTUVPE?@MextQ/Zx".J* l]2>j< K}a;|0 ]*2@!L'PCJw6'&SN.vb:t8y)Au(*Tc)"+ij~>0YB HJpT".Oz m{SP.Wfh&_yzplCb`l2^ CwX  p4DE*+L  @R0@^EVIjxP&@wGR& Hl$fK2B!{*Ebc8 +]8%8EWcPP D`bC6`1/d0:F@HFIVijPFSw[|'"4}A!$WZZ5|RD#zx/ 2I_v   6 T l ( v s  ] X 6   B l (8 r   Z %  + 9 N j { q i [ E ( S  y  OW YTB1Ln~'>9cL9l-,=AU%h%y?t?&p:8 (  h :  8 9t-6Y  H  N4  [S  F d N   J   f N WI UeNBy'[nD_1 jJi| tiQ3;{ZB]&!b9a%/J6gN65v+RE( ,zyT}a/I)eYd!_?*I6u`jAVtBu?Iie$:z[hXcfie4ZK3Q#c7B 3H Vj-cXY8*5@>b1:[$dD#" K5b$8bo-dspc!;|44 | COr PW `:S^FU5VcLR|Ti)Y*YA3]1d#U)Z*]`~C%A\/&-68TD\/Giu:\q3,c &Iyp0Ix6 VsDm"fw]  P K U E .   u % a M  SJ  $ Wx9X:U9C8v^f bY4/u+SbLB=,l t.BR'xKS\(SzhaDho/ V{%Sp9NpaQt. lA "<Y d#(8+t2AN W(b<iIiVefgvrzyuuw}lYB$&V{C &/2;Nim4lb xl-4[ H{> B 4P6_bclfl1{zTWlWyH?;Q659D:9/B"AZl6T=u)Byz,EHyuebkl&+h%oB!(5vS ?oR.y<D&5lO. =Ga:>8Sr7/S5/2#)u5",.)@%y ?r`'IYEd$\7pA K*| ZzXO4|h_\J'+yw{0+WY4<9P/ & q 2;y-JbH> } /Q  |   6  )! & , ,   w * _F_XSVfZr R+{OCE|GJ]B? R_ w z $- mb x w zo c YE  S Y ) U  ^! {E"f v7 Y ;t   | r  Rv " 1seW7"}I.mVP 6$!;a !'5WJc=&[zsN:r\1L<#stul E  1 * , V # rj <;3f-EssVet{&Po* 8{U%`Zj\ p,qVUT}|62?A:1{-v*z)}4|Py}y{gO i@|8 Re:] T)Z}kB,GDn7<miH0r,w< ;e a ; " +9xE-[-ebBd+3f-ACW  f   $A d X   < z   U Y  uZtxUI$|Ca`P]S~})`n*X&mT+;Iu bK_|djVPTdw~N0#"7X\ =tC34~_-1"V60tBOE'@0Mmv$FX*V&H. }scrOM?*> BFSj> a<ELX ^Y_fkSdL$#PyYHlkHjET*_"=kC=Zo je 8Jt"h[,|?\0v; q < t * D VR Q 4C # '  5 fc* _ $2b%A-lC9*]4A5a1{.w 5Ib%e@E39j4!.Jg1zr|t`ID0h|Wd5?" %$zcG&x>O{|J~ $;Qo~o jUuO$r{H5u&S0 j;bd1c1lP&s(z A=]7 W< s     < Y mi 3h ^ J w" @  ^ i R=Xc<$+MC|v!|c2'zP:3=cZzk\4 '7'"_VTlH!ASg&vmf >A Y V :  uZiF!:4?3Xus;Sbc)g^.yQ$/SB3BOFF=m-# p/^{[:J|l/i:?qq-v_Y]R`cc\J1zo56v7uBfbk`-['33; `c  oNgrC*m'+bh%yy(tf9tm%HpPhr(3]n,;}.9q@x5v`+%We":  \5>bo) p a T c  . I`sL Kbuz  L   L# L d k Z /6  2 m $l A  qd F~kC}<h `~QW2Y4g.lk 9lJKIvp` M 7 M ^   ~ % w 0k  _t  v` "iUj]05clRB=@(IVZyiG}|@{iX %zp10Y*7|Xfg([wI3A Zw(BfYv&+HsBCKo6  * _0c"bFy5d+l.znR  u { 7 & v k  e b '  [T\})o.S    C &  : d <  j " (> E : ."   t  r P s c n x w 6T}T^OHD<;CZL)NJA&_ y _7aic7PHFECFJA.mP<,!p;   T(8Qlf"v"l"6}T%sH!m$~GH~O/!+2,5M2u49:7 0"'<Wt =rk< 5\~ob#QZ@3(5tMjC"F~7hwN4#@Z{Hd4Y.di5GyvBp s~X!B@JdCM&EfxQ)4) nuD%u)! Ci&I:q\  ; \ v v G x R  Vpgq-JG{v$6< VfF$m=#msF/J+6MV@{EG!:Kiv*slgp!Xh3 P X J +  o:hwMJydN6K v BX5(W%8 I$\8nAu?p1cP7@Ll6L UhCn4Fe"Acr:0b#3at{V3Fu;#yjB"Lm5d._SN<'q@Hy _'5D^kDfaIW1q@xLpW6n5]MIcZ'|o1_mc\};Q &rd]H,b|1K1#@ om8d rO;  N  m  9 Xw " X  { 0x P  n G 0A - = \ ( 13 zUC=APihN5m0oO  M w  U ` ( p. ]K  k W F O - k$W}fE #7pBWRBc0oy@iE+:nveFfmlaKVV]rbP O~.U&O8GUe| (]dF0zt?v.9^ ;6VdD_Q>"QN^@6-?{St(t.Y},Is<+bm0[3u64 $ Ng   N) G U U >N 9  X   J F > p X  }+>uy;wbU(  2 F {   *Q   j Y > t s DK Fo kNJ5) U)fsOsBBFKWi$dR!5K\"$de%@xe9dr-O-f=b6uKbK5Z65;-ZH. TzmS"+gE3] o{X?Za J@EXRanXvz|\yiNE, "V -R'w]I|<^HlQwd5||k= :jRKq"$1F[qrp lo>IRa@3B;xQt%;1HNJWCS;E+-sVFz AM W_q {Y-j GG*VfZ 04VGu <Xu  i:9; < g3 8 2 !  | Na 2 ZN!~Xb0 yj^[B"y n; 9M<l-Kblj'&8b=Yo/9i{P2";&RBaw_UJn@=*::A"QVbuqr\C53>"P-g+~gL+ nW81LVuD Z+v='6z}5T1e(Gi7]u[ jy2,G>)xb F#w@$Ksv3)d47 Um   # f     j I & %RvBSumMxa%(?U \>/A'b$W zb=G.c?%Y)lAe9r[ (K$uv v.Qp VR_ w1N^j?cjK)) ,F_{8k#$b8HWq9qP(buNhKy1~Xy-fUB1\C4'!\K ^)O ZwjfDo{tP+$&$$ 4!,><ND e 8   : j       V $  N i 6 { ' ; > 8wcs[bx@  ) H g  6 Y _   = e`  d g t) T 4 D  - p f ^dVQQI29H_s~vr{rijNW04*x?Va@:DX\tb 0d70da'Hri}E~9UfCnsqoiLfl-)#9b3 9bn1/b|+1nZ,*]/ D\w %/B^ f%8a`P]1 cFi {W { k Uo ; E     E  Yn^0TdJ:JB me=yU 'q  i 7 v  {  4. J<m jsO9?75;oI?^ }S6 uS!2eq\Cy(OZ+Z-+jAVn>K}J~psN~:R3:Kjf((p5@OqS6&^ 5xgGFy6]BZ,&O 2dZ+iIAd',!  e@B%('K6| VOv,-  gzgQ_bye`R)>#ip]N*5%$.8|=n>b4V"PNS`wv@ !RY4P;'~75|q02o+6LX4=KTPUPiE3R}U5 ;c)=Z)eVP+*P "sNve9 0 \ I b b I "  ( {9 6G S \ ic &g ^ F b ( B Us 9W4?)&deVrXy"(\^O,z:g?P&QjJB0\;nc&a<q_L9Z&(xBN_:uS dFL23AP[!>e5H"[ %7k?\epUG#~S$sO6f1(I]YO4-i\ 8 } @ s      M    N H  7  E+<[Af  piozS:+Qmy~tUe$^Y3 :]x<w <%),+%f< vgdiw3Ru N6QuP~ 5bE U2}1+Y]H2n+*>F3B;;GI `v%glYSA8-.Z~]|#Dl%M~mR_,R A'CmK* wBz,kV%Dz89I<isbP+cq7~N~A  ?   1 V c a Y N kI (F B 9 6- "  / ~ % s sV 7  V l r 9 t d Z Z f 2 | "  ] z  9 Q p_ 7c h {h f b ^ ] W =  ]F / o ' >}  l7533|]:D,KX_4tcXP5 miVRwXg0}m/ I^t9-a-vfm%K&1200|K't6Xp2 BdJv[PVe&{PK^4N5jh 3E[s'?Ragm6{ *C|esmjfc]UG3-vz=8p|E|Fo n{vvSG *R=?6d(n u.(^XQM g>FHT{^~]@n R@z &<$P;eD{?2~cB-Zap'`C4r4QHCnHeiP|xS Iwlf  KD    G m    p; /  $ xc 3 C i & E t  d ;`   axM*#^}l$gOt64{!E)QbRD~J'&D\"l?zjY*!/V42+3Lct||~H|vrpjY`3E'G8W beE -Q[=)#~*Y=2c.'}LHCkB\K~5-3v`AkYc5Q3?^p"}^ # _ C    T    + ;H Y g ?q q sj X 6 8  ] 5 m 8 Y(>osb(TJ?P6/8/98FTO\^p_cjNwN!GuuM"%-159uBmQm_vknaBzD"B_\u5 v`D'PuP**=MYf{ AkK#=n {IT@zr*vn3VAIICRC`LoVxbvyl_N 3L yc63DjptX8m1.Lzqklxz? (_rZ@))l=e|9~p3x-l'Z>]I:9Z.v-B   - c   z Y ; #  N   `  * B8 C wD : %  Kr 9 ' 9 d = n I 9 ; L g% 4 : G X .N)qQ)  Q_2k nwZR$Xrf}K >AqnNx  E   <w .J@\qeaP32 W~x eGZPKF`:)E+ _ m K !    b$ %/ 8 D RP [ _ 0e j ml f T _9  GA dmL= _m: v6oWL-M^xW"A { YY:V&J)Z}G390v)3xM{%Va#whb7dlkx}z,qgo`YR|H73NhC$ H-dv# 8RcZ-P~H1|7?Wx@9UOa\j`m^cZJUTU]ek|*5 9#->Sj2|b x^8bF5-y1JD bU: mu.)f%[]c1&KCVuXTL?3$ r5|b8S72CnojtIp<DgrL/7# *i &  i   a   d   $ |7 r3 r y   9  t V" ,  | e35~ 3jvK" @m&d<7kdb>&!s4ZTF6*m IA A!;H]}; aDqT^N?-m7A wI-[ `8CEl UXuF H#3H\zzI[%ZHabRt7aH  y* O % M   e  @ ma ~  N 2 Gf , {; g fs y t TCx%7Miv2xlQ3*qv!nod} a)xK(p>!3PmCgiM:$d B(/BK*I8CC4NZfsbHyVmN$|=% Go^ /WS33\.) ho ] a " cB W ^ [ /K a* g ` %! aqMzimtkGRvzOO0Lt  c>&&?b",8PI[tA%@s%^yBSi;~!n5 &_Ye1(aNCdUE( \ S1Op!MJ4 -|-!K *X|[?)@k`3 L5 ^ p  w / oY | j y  ` [  a o Q8>"g bE+dJkFAfy.U} O&2AP!lJo @v)V~LOh?Cqw K&H =}"w,bk49 Rrlv 4]9U'1Qw/z#mcq/Z"qA\f.g^faWJ:"*\M5<S v&"<GiD2:`&D0{:dUGC4X8P5yh-/tsdQ0 lUY'1|(Ui8$   '5$?EHuHA6<+2YpiRBu8h8a@\G[M\SdTmLxA6) ~uqsy $EnBwX3Lq_n'{6 = V !p C o  "H ku   C z   L    RIL  gl U<HP|\b`%ZiY` ll~?6{5L'Ad5] fF:/#j +Bg\ H D]o)yH[}gppXx5L!E |iJ>6c_+G?FR[(w6~,_+]>L9w*zN"[uioQ#$P!{` _w_] ,  \  W &  ,  #+ R ,} A _ 4 9 n, =  a  {  ! G< u+a)gX M@ ~$Q/xM c/q E!z3ETx'H\[L5]5%"5KBvMVUJ69 `]:cqIc9Nwh`f{G:%^1}%] ) 6 CA tH J J M W 8d aq z | q ,S k# X8pij9R1guQ*_tcDP< $ ?rCe/(Y02lGW+^_M2OO[Alw']jH& jg z y lM Y @ C   w 0X 2 3 W X , x , z UL  k U  a [ Zq2!5.nW4{jqse$sj BS O=eFY 3LCJG.:&P B Ni7t*2D6vuv"EAkD~"aH. 0mWI#RpXBg,J%M7WJ7^z"HLhxW}wDneRXH_4[WgqF_\\=) -P~x<n`O#BmAK]Nv,n"S;nN&` J9r2HUQSB' SdH$XeNco , 3F Y d :i c N t* 2EzI:24pR pIt5IX0`efKk#v ?u )&4'APb{a4zgUxE<5'v3z`^B: ma?7qL)hN>:DUi{ 1T_I$Qe~).F` d:u 0X~1J_reB uXc65]vXCr<@Lb`~4b.hi!f7Mb*M1HqraO"C>"=BO[d|@v_)*Mg{|B f9 wgWEa2. o7z`D)@lZ7W7r-!3_2:~2?]^xtFx:ZppcK+\#nH!Y$b3|dMVPQWU^m;)'647,[|/}A{^9lwn?XR\rb: L/ZxbM=67>IWi|bH - $' qL* f1R(B[ouSIPe7wL!Jv^3<,f/RuX{KyU~@O9Znm c h ]  ZT O G ) ?Q ~ B ^ . M I_ d Z : , ' X) % .jh68uIQx`SMp> /Rn|~taH'#09;2g#An<b2nAo\0Lb5:uE w0o\h.0 vBG6b2AK6S\cMkwj6c    ~X6M 0q (3=NW_shXg*IsqhSl4. 7Iky}3ak70q+%|L]J>Z;=@>5,'"!,< Pi @$_@x\x7,qn*rb8 GIc$R~.vqwy *AYq*Eb>tJ[!L<}P >t9& r(7M o+ > B 8 R(  Zvs8"rS%0=A;0 %jzD)  euuL^#Y\_ix_?#PM{O .YaATsY[WES+SW[YG'%-t;/JZryS8& !'z.A7EXmUxMz,bC"xeN:$ '3>HS_lw>a+smu$1>\|bi (59731I%]vX.GQc~t^O=' ~6 --U~jS4PJhbE`}6Pu1G 0BXj   #!e &8uDHL\{CKgM. KMpf$/W0 L|:9 RfL-Oj ~3PcpvurroeWB$n+j+q`T NKCRA vd4OX14J j?f>vO*~ U6'")6;UUxxRx'*M$kagSA<0[p{}{ulrda]VXPUUXcZx`lx5Oh}4TqpcSC9#tWKSm/cJfIo:u%]SO \%(6=H?7&2nc,IF Y; _ @ P~ N 5 xs  v 26 I K ; q \ T Wc c y 3 +  2eQADz#w"R?9bYM,ULzuI2A*]-p6yB|Nv\hkVu?v$kV5iJ+rj^HG!1#v&/>h3#s(5>M tWX,ECE?loM &   \   c  @+~]Ch:wW"upu']K(]O% $'C{b"e A<&[ 2-QbEPU`-%@ Uakeqp;qs {x6XD gd}jo UO6  M  z 4OY]v': j'vMz!M&m[V^ttN$l@FdCC'JXb?z;BX itx~rUh4a[X TOLG<)}MT G-eez+\)B3])f;Ec$ ")3>PL_xj}.jL /GEXa]M19Y)$+P,z*& wcTrJ\E=DJXm8dIVB8 &_`"y M L) LE vo  @   r tR - # w iF'wj$=c/$.w[jD5<+&'&$5$'z(6,-' y|=7=xZqvqm2ZA+&:Pjy^JR:* ;[tvJ#Q!yfRE7p wG5 }TK W{mH#kP4@hnL 'Iw4N=3Xx7[ywfP<, t[>=cu4F_g~s#~)63T_xD%ZctqoQryTOJslTL5lwq\8 r42SGo / >EFG@4j(6  *Czgqmu*_ H](Y8-h:v? Ax_uQ8g|eEX&R_-{K qEO@kQj(|v1rf&fYq(}7T)u^E+=nZMOD? 87M8f@|Qg~ /DZCsW2=j ]=/u76_zz,w *5Sir?Kr W8Ap] / n 7c u e I       u ; p c ]S \ a l. { H E%1^ARhpvJ.7gfB^$yU5yohd_VnG\;O-I? 3W!b9qaI`qK: m!qs[N FNABDJV@kdi.<lWj;tvnr`Ii,tB-ti2N'" %tz6Ij%;LOSUG0&F fg ?n]#Y%P:$'qPmPa 6)ZG|f 4_Et <,f88-wW2i*i1]QG'dec0{E~E-gJGn%Rw]5H4!uE2j\3 JwmM+(=Vzu`M=,^,qrX7cvSR$/to:b_fvOg*s N4*A^}  8X~).-K,1>"Tqw8k!'"2Wy #2@L V_gki~c`[IQ # O[$Ll8m8O)8MI3n;] (oyG/zqL9#PjE zQ6w)**:yX$sJfz\XZdu,Fb~:Wx(BW)eYikjeb_ bee]L:*%/IvsS)@DLr&:mY 1M]c`O4'xPU{8 I ?c!*}/S08Kagx4 ,U{Z@0|$TSz\'}LX%xD ,U~!Fl3q{jZKGW&q-A8Qd'ul 7 R 2q B T f {       d D &\ 5  nC IsgA'bL9djKSz>z/ugg+_[[ZV}L=,#j_R+ R}~X)-1 pA < 0    $h &&   T  h   ^2RQ-eC<GypZmA'fD/$"&'%":/WAwLNI<-K~J}vMP;k4Pm)S<ywL03"h#0Jl2=0>a7-%c#$0E`j|CgPyB^?5[S"H`kpvlhaTRBA.2+,5G^W"dms<trpInoq/rrFwzRNXT &5^8c  u!?1fL(o0liq1b RV %`  $(.00c'=kPz8aBr9yJoL3C'!"g$$'+T6KmN =ggAK|P9$z7vuc7Pr:8HOJ{6nid^U\N#HDCF_L1S^jsHxsgQD0 [H2 Eo)sA,@WoFv27\`} 9M^fc\ VSV^k[U".Fd.C1VPmi0nDl/d !,.R47;=VBH%R`*qE`g#8=Xn + G W ] X !H [.  [g{'.;CAFIMTy[^]JZ<M12,# 7WY \_s)G$"Og|cPD?DP`u)Z +GbB{_8jFcv`}viNV@"Dc(T~/|"<k\nXSY:]"enx&?V\w6at(]FDb) Q$'2;>v8g%j~]7V auX;O1::y4s(j^ J2v_I:2/ *% 8BGKNQSTUSK<%k~:r e`_et\25hm3S  2CP\dnv~;` E/apz+l\~({~ :oy;%\7f 3_ X   C  ! + - $4 7 )   M d  n 9 fC),SN QdQA!'Dx&M B~re5SD90/0r6qCwWn,Dayl\I9B0c+-/17=CC8&zaIb@EKFVao|,:?8{)gE<q,`M@N83225a5(431E331X1/2<hP8l sZD~_; u @CPU2*TKpk3hIM[0c_S8yjisAwg> R$!)3;C{JZQ9X^]SEC1 >B!`3-EZui <2`h/5e':DC:' 8ffK1<S8l5|5;EzMgPJQ)ME8'NYj@>r|9l]ODqf1m&z ~oljCr|M($`G|zS- ud{W\P9S_rmZ&KHCjCOb|-IIes~7TvZ1s+|a]<.Y$Q1]-0HtgQMG|%i9W oO ~      q K ` " y:/hY3Al#^hT6XO{+MrZ~1~=W:&+`-:D_ ~7Y||}~Iz% 9 EIIMVflH# Mw Y?=k%%>p_YIDMIb>%-jIdT.m"SB21TuE hQv(Kz80wu 98 d 9 v5 "j  8 H !M `G 5  9  7P g   7 t '7DRfWL0RTS|QR"Co+1&KZ?0,U0/<Oj:oF/?a xZ>X(84 0Vxy)q>Z$(v%/fg2X2N c ;Ckj5Hd1&+>]N*'aKnRND$F*d@,Nev<~a|ywtodWE/c0U }rm/^VW3]ixb 4le #bl/s2~cLT=3/{24.5z5576G7h:DXn7RAiqw~|q^>{|zA k#MT5  O ;LyVeWPRAG5:,'$[X,y/!LppP (&)})8!1 g&E_+sp}0e<h]#0069=u;G7- k4DoYH=A512;`K0`vZ=(!)>^sdS;R  NvSd2 &``>;s"<~Nk[Uc9hg`O6T(bB#o5ZSDv*G?h 4&aI Or0 `N9%N$GgfG,c?! 1tF]VD_#^SC2p!;p:~Ij q9 S4 zO>lsr^PCn6 /*(w'U+A6:FAUYexV@oWV6K'_mzN|sHcS>'J~%W8)B=-\/E<w[MW'[c\ZY\Kcxny_:hDyQW(m,xlg1O0 wX?W1..=^Td#iY )Ji)ZCQTO]SZZReBr+z}}zrg] Q)GR>2,(<$w# 8Xy!,>X}/BLKUB2"(Nt,YYwi]Q`B2 9 x:^_}>8Yoea]._qjy#T|)CP`|?[ii\jF30`e jM]/ _ _z7hx7?TjqFx! ,dSh)~j|[lP`E^:f1t" ;tZkA*=u$R 6XnzztIpunp}:$l 14WY| ILRJvZwCrN:81:X';  sU Q 6 !  4 Q 'd 6n Js dq h \ H &  0O ; C If M KF@B:y1,'T#$$')2'&J#!ZT<   /&x1,<IWagvluqw3e":Rn9c)j &#=]HG:$&Nob!TSZ~i%C!bBj;BYhC_>`EkY}tCEit,{3RZTBk>K0Xx:> &4NF [qr#C g1E]EyN& 'r.B:Nlf)&j`Xb'\IX@7AEZr,ef2?Ljo M;0n$/:FS_lzkFrU4 Hq= `"HD(MsET- q,k6p=}FR]i{1J&^dt>:f  *6rHf\Sp<S~0?ubq<v2XkJ# 1QoOe%&6EXS8ar "nL@y `,*nBP\4x'Wp\J<.1i& !,*n8OnGn"T =>U}huz^xr kef_YKQG?88I9r==83+%!  !?$d%).2320*"lIhL1iCxIf9 Kf]D (j$i5yZ>( '2C=zJVb/lgs|%Lq-g2}ivfBT@*(StszTW7'C1w;`B#c)rmu(&5W=BLY h8{\ &C_~9].ES_db$Z K3 UY.k J#a4vkaUG6[e24Qenoonot 8i$Krz~"^8 ;!d9Nb{ +2;fDHI.JHFB:56>O!fT[7.HpwERpL.9 o C'{6CO7Z|gtF N{obP!<I(n p' @C R Q E ( t_RH 0Z)]S+|?Pb0Dbwfm^NBO:66;NERe|oE"6Us~`@8"g 3^'7H3[Us}Z,5AjXuH3d@hR8AUgm0f9.~oajSOOIPTY cY n w ( C \ m s m [ B ; \ O i+38`>DKYUdwV*86/" B6f1 3^ 7e' IuHw |;ZU6l~|\A)hCiv"Q.u V`[9 opF<(  #0%MiUZ]R^`b gSn{v}6QdlhYA!ZPDR`4EI\ >(M V "-9jFJV0bn~!|RcgI|/zKt S7" )Cb&wDxYenrv}J")6TAFIK+M`QSUWX\c m}?ixs>smux~+[7Ugsx+rDkYbhSsBw*x vpmkPigcYI/~ upj_IO@4&[ S(ivKP&1vi_Y Z^g t0HePV&WL\&3o 2|gKf7Il(gK9l/U-@1.<Rq Ih3\~hS9# ] 5.`Kn y"d7OJ>[)fnk\E#s\K?I<BGkNSTQ:G7% aC+eA ~{zvobN7 @t(>0VuqJANg8t-Okv`QHFGIIHGB<5'yiYD.d+KyC?s~!eDQp<*"YWV|z}|Kw5.p%1=ElDY@B9+,[~M<U`BwO[/`\]yXe6z =}%N|z.&IJbjxJ~|tjV?&2Lf~HqOG HLw7\Eo2EVer;d*f%-)-q&!< s.Lj9*\Pp " =7 [W o ~ z c -= @ Pau7(m5OJkota@4D{weTIBD;5437:>EP_r,D]r %)';]>XleXaDU.B&GmI'~Q!w;~eDF }Hi<1H_z!O2QHr6sCnQ<eQ%*V2',oHRZ:}E#Pvz`D'(Kuw\AQ$ S_0$MptYMPaLubN9A&{ #"1:XqlUHDFH KI5BM9i56;C P;afu .0cY$pDrn(/)|-%:dVY!RVd.r_uJc3VJ@<9|4H148CUNWeta3!_taERD5=#V9XpviV>"c=-l@XPHb6r&*=X-w=C?4#Eq)01.*' $"-8BN\ m  "4Sy#6Lb:{^- Z#:SlyFjyL_4B#|HmZlE3'f@`"1 {dUJGO`{-AQa!n>{aP=xog/]xSIBB<~72.$*\#!b'9-671# gP?2y+L/":Ohh6rF# $Chk?"QtWD89QBP]iwLS;Ga@zpf\K4eB~y{ 9Un9X}/M&mU U7VlyGo|qa%P\A3%V 3\xfP:$d5#a9!I\rRL-VgTJHGB32XhFE%8'{!pW-Mr4Zx1f#)%kM)W"h*v@`sQ`zO(0>Wg9 -Kr Q3`G5'# !3#b(07;9@uBEF->l27} |Okdjz:iO6P#hQ{ .QuhC8[} @&hCb:n*8DChJMNLD;D5i11:Me  9Tr;Xs!8PcnqiV7 #?C\y8tB l *Z45C ]:]9H-Oepgp\hX`YX_PiNxNQV^hr}49Rcq$<Tl~xgYO1:$m:`!+0<??h6A%d*y=l7qI?!b\, @usX>R'IsyW6  &&%Bj r e1^?ZQXeV~W[ZXP9G]?|5,% lJapHl|~3M a}v&[Fa6//'17b> BGKJR ^is~OCm PRGE{?Z_$$bNoy1]B<RauWH*BP~U9QD3^JtVs;I## vR+f(6fR &rP"ofC , Yb37QqnR87Obwyi_ZYbk qndWD!4$"*14783,"tcWRXf}?[q~uldZO%>])U 'Db,Mm?p+03567"6*4105/5+1)'++++(&e"4}N vM"\.VZq7eV I?5-%% 9$Q$m$!$,7QG[uB 9BgZ=c%>MUUOC8 -" yQ x;lYLDHFGK_P.V`gik`hBb)]VMFB@>9580a+# 0^W*M @y3 pM1 x^LC>>A8EXKzS`vHz7Ss1YBt$Iq>n ()J"y :g!O|yi[I9 ' ! ~tvRl)dXNDY90.4WCYr4LQ sME1s Z )*At^ ] ]8}'h;sA b2yYxyl]M:(h<6uAN Aa0v1F^JG Sk8i6]j:<G}scT;D6* m@%~:VvIw!SI 5m%+,,*)W!)<~Nb[Fg+px<g@q GT|)wl{8Uht qfUA)$ 1D]{xR+g*b (=2Z%d*:@A;=5f1/1I<J^MyPl 1nb_eytlgm%BFolA.dPm#Oy->O]Skx67tF%j40Noo&<0Xz`g7~yw*zU~8r@r& k*15M4,: 5vBADc<E d` )Ym.JL0`pxytk%`1U[2f&py2QtnS8Ep6sR^gIy6#{rcN9uU/wuG`H,DBn_YW=]fq]zz1{K ~reW{I`4XHu%Q=~K G 9^WGm$DhkV?&"#_!rPEoeW%>"eJ:w1U,21<Nf_7 ._mL4A#l(:IO~fw2xqdE@` LtU{Z4:LauiL,lL0 w d L /='d<Qg@d 1pY\J:* "*18<A)I9VMcet0`=a  w_F1 mI"]h@A"kPx8j'ZOC';76M5i7=HUm8W,y_5Ki4@c^x! zJd$e3QFG'QerC-=WH$TY^`bdb_ZUM?'*PvfC!(.7^EXp<\*d3uYn=A&n5vW?0$2S{!)7GN^w PP4V8QTqWX%2<DfF>07vC_Vp*y~[>" dB!)AW`m3=o,uB (?TmrS7'XCm K/U| ;h +Id-8;7,ncF"MIHc(l9h n?F"r6j\StRSY0cuups $Dm F)>UjFr;2yHV\D]VH6?'s &R{a;8ThxqbTF8) 6f~qMjgd[cRH<^0$ _  5k'5EUdnqko^NO-< * f0 *o9%GTa5o~)qGAi_l!T;!1AbXf4 sd ZUX`l|"8Pl-5>G+QO]qoBrGmsW=#'3=DFiEB? 3 m<:Xiu6oO3 Cj%/: E/NNWl`lu} ?y{m]BI".xG7mP Eu *GT%X}c?!;[weQ)>Q-v   4gGPX;j-|$ $.>Ret||{v`iDQ'3 ~F`=s@ucXUP!KB;m0&#@ a T+w j `3_I__axejp{@s$Bci2wBr9u=uT"W2KfIpJ|l D,EV\EYJ36ur~%dK3^+a,a9&9K`w 8'b,*"#Z3cc=e?g: `6 g* (q47BJPMEo7^$TNHC@<73/-1:!H4ZFu\tGn%.A6a=GSaq2HZj|| x-uPvuxz|$Ed!8'L6YDaNcPbM`E^6`"e nv}hL5# fG%tG!3*TAy[{,Z$Il/J^p~%28:8,ugYOKPE?917+%"(!"h) /6U<>AqB4CB@9/!tjdchr #6G`Z>m~uGG O )09K>@EI{HJA4v|nKrwRR4M+f I4_L-{c&dHi1s>o es1Jk~C c- $V4!ET_gbo>tsmcUF33NqjAo<9ShzteUC5' TlB+^\9;g .Pv: ]0Su^s.K rG`/S\$5 X,U"gR@|0n"bZTQU \4fKtk 6a"Gm*Z+b1I#[fitz)~i{o\H41_6ia=8ej0Mb.yV/5Nt:w5r%^*DH_qw 0<>91v$eN4o8nr/SE^}z.L m2G]5xy yJ+zN,nF/_xsaYY2YTayjt$+28=@?:7/" 7WN +/10-)#%3ANY`fovtjwbyZrRjK^DLA:D#K Wm&Oq*5=@?d8+1cY#TY&/p Z[.=[_@( -Rrwlc \WTRT^|mY92Os^7+I_p{|{yxz}{iWsB\+>]s/M&W-rIoX^!OA1"Kd*]- -Wyk4`wXRRKT_pcB 3\<Y:r$\:VsIq+-=]KSY^cehmt~NMbq[ED( m]L9z#b E'`B%Ab=^{uW6S!Pb#b$s=@ZytW8 {wy,<KXbj s!~6Mg"A6[rlz-i{OpcUC.,R t 'Fg%Jw  I {    "$029?@KBR@U;U1P$GB;4/,+z(i"]QD 6 } tokfb^ZUMC8-  6Qp$1BYr #(9,Q,e(t&~%(-v2b;GD'N[l|m9p8 #`+./27:d;16.'h ;gH.uU0zFr-0'w@Yu|:v6Z]<"[|` HE5y,*-7.HT^v}/c1W{Dn Gg=wCu)[tA%0hpB(Oss=n#=6 I^qvCygx^i]VdCp/wP&);UzuhU=#KwT$,bsMP,,\$=Un}~|tgU?.'9 ?<5+~`A#x^D* p^I4|"S,|[|;v rmg^R>* "Ju,=O!fKv?2lOk+\>ga<ChyR*+Wi/'YC `&,1=M_ruK%%=Xw $=RAbeq}%8L ^p~ pY=y~Ds ib^G\ [^edo.~PTa (EWeb:tZByQ .VdM!9G(kwmnuwt r>v^~?b|hXQT[l&7BGgXiw{qh_ZZoZM]*e sjX2GO=g8{3/( %LuwY;%Jrf1Me4D0qd`SD5(Hu   "&257F2X%k~zbN9&%,/158>FRap{upg[MB8{+jW I>83.'mR9 hA~iXGZ3%|D _1s`YUY'`qrG.X^G;U89M(DgM6k#<PBasp{Dc{pY=lM+yGzpcPUE5)#c#3)1:EMzQkV`W[QUFJ5;-q_K: (-?Rg| "&k*A*+-.m1-6=AxF8IHHyC8=95d8 =CMXYk~i97X} y;ePS`Al5q-q+m0g:bJa`fxo|M'7=tOZb3esb[R4Fr7%=g ;Yz&7MUqs6Qlxpf+\IRdG|>5.&ukc\]W5X[bjtf5{\F5,*.:GUi} /@Pcq ~"$$# ]4 oL+! <Wq]2\*!!SsBkS:"  \3  U#t"1$(0W: EUkNj'JD4_'x'1AZy!?yah[NB:O513:RESds1[)B,XIimz6{grg\MC;%1fU#%AnZ@wP>Zxh? yM(#-,"jXKC1?N=i:73*.[2}eU*)]_+<ioH% 5J`vyqjb[TL@-  q"M'*+ --* tZ@' ygXI;2,&z&s(m*i0h;fCeMeYkcsqy|~|wocZQ*LFLeQ]r8'i?Vq/`'S|kJ+ 9RizO (KSlS }97Zz}K $8tLXa>?~DnJXUAb(reB &}3]FB^(} "TfA:rA}tjVA1-#X '6I]u 0NkoV='rY>!}Yr4a N:$w F\~5bF0hT?% uL *XzW84lM=~;d<}N ;$40|>Pc%wLp.RwzbH!/&%!lQ7 mO/xY=|rg[gOJC/7*!   y_C) rO)".67u2A+ R3?'4`C!Q`o]72F\p!M> %>SliFH 1f/!^"&1< F-PHZ_fvs2GZfnm.h=aNW^NoC~8+ }.h=TK>\&k zp[Jp@U6;/+),19p?FD DCGKQ\jx>^{iQ3pB '9Ji^Cxf:/EYiCwo1}urnHcWM?3&_B +S #%m!. j ( d"&8OmoQ< ,:!f!'D/w5BRb*vG_oy{"{Pzz{v!nYdZQI(DRA|EQaq,Z+9I]yz];Gv[#)Lyv L8 cS!3RE$UgzhWKDB> <6/+(*2B}WcrL14N`lvraL5>jbC8$p1KbzzhVB, nU 6mG$y[<wbS KJ2JMOjQVVTQKG HM%V4bJqg+Mp+?Ul)AYr'N}(iv^'DY&rQ00DWgwTW1B]L#SVXy]]r~uiZJ29Q&t)kCXZHq<2*# ~`B wR- }W/rVr@Z1@,).9OnY//St]7"Grm[EJ<.%8 s!)J2{<Lb|"EiFo!Lv; X+uDZm|%AtYZn7 r@x~ahGW-G:0("vV6~dxOij Awn\@, I(-4eEZs!Fh*R!X*4@PTgiz~ &_;2Ql\(#Mw\.+AdZAs\/&:L`huL7(""(2@P i #NwqQ-+9BIQiYE^"afltqR4 %k?DY"p&y,j1Y7D9-;<<AEItNOQ,Q OMKHGJMwSg]VgBu/#A[r5Tw5Me|,a*[5K f)BXhpuBtrmaTB4*bs<RZ/v O/?Nx@ sB|'].C5/;!@GN X gy .?Qcv  (08BIPV[]ZTzJe=L/1# gL6%g M 2 "0CYcpF*q0IC#XnyW35RzrV5"Fjb>$Jqa8 <Uk~%Ed"Bd %4FLi`q|}9wcmZE,  #1:@C|EoF`CN?<:*1'} b(F0):HSZ_cca\WQF8+y"m`O8!,=Pe{hUG>76H7w=EKNLPQLD!8S(4FPV}U{N>& s!P>.[vylbZQMHC<3%\+a4 vS78Od{ &w;IUnk(I](F{]AT%%fW0Wy/G`z&DemEf#@3!ANUZ]\VI7~y_f?M!0{k_XY]gs(2>jHQS3_kvU$hz?bI-wT3kM*Vj[`c49 Rpx[ F86i.-0:EL{d~)O s4^:eKJ U(4 DGSbw(\(1WSw &!=8RLg^zm}~kXC.~z{u[C(~~~p^E(|rfiZGL,;1& !#&*/;:NG_Ru]fo{%08<<5*5Pjj4r,ft'onkjk,jgdqa2ZSLtF3@7.j#' q;q]I7( )058=GsSabIs.Ku^I%7^)7n)>T=ESf#EwgcN:%1DwQQ_-l wycP=(sKNpI$?i6j.BS`n~kU?- '8L#`0vC\x 2Y#A^{/W{,\ B*j146751'(? Vmi?5L\hhp)zX o}r6eWHc7#&yGt^K6#5*N=gQd{  }hN1xQw+l`VE2 `:c7(7FjSLb5t"W wHkeca+_b\TI:^(L~fB   !r_%Q*G/A6?=@NIcW}k<W?ng&Y +IpbZt@( sT3 ]/vL n^J9)  ",7@IPWa%o4|F\t:\} 6JTSG1wlebTbcebbVa4^__]\[ZYXUuSfJZ>O.D<2' |^A% %9St}hGRz7I|(JgCitZ09V}U BqO!Sz<Ww8T B>j^~/CS ]_#^+Q/B.,*! { O  $^2-@Qdw~_H5%   %q+S/711*!unjfghgecdhnt~\6 qJ"wL"X1 v]?pH$p@o;wI Bhd7 7kd=PsC;97K8:=CKLW}dx&Lr;v>4X| PD[l Ez2Rq-Pm{]= {pdZMz@^5>+!*Jfo_PB81+m(['K'?(3*(0 8CP _ n z.Cy\nz^L5 #>YuiR8T$f.|;h!jIO7  JzbRGBCJWfw4NjvcS5Eb9/'%'R*09ESJcts)He$08;92&  s1XF;_~Y(3Gfd ;.Ru|K1?oJ]PQSJTGQENEJEBF<G5G.I+J,N1W7e;x?@?7+# ?Zv^3@`z|M${ofybrbkjcx^WQLD9*xR(xmdZ^9^bo>^"T1cytr#s5vH~\u&5AMWc t-C[r#Ei0G3aQ{nAa{ u3ZC>Ranv{a)n)|Rm ]N@N. o3Lqha^^3[YZ[bho}0@GIF</"|]?"ui[NA1zu|qFonotrI( 0Rs .U$e8} Q[w7i^XI</% )A ['s0688;AL]ppT*8?SgzoW:m9  L [' /?O^iqtvx|)@Vm &4CU!d4oMyfxm]L7ux\MH":0'!uM&c;upS]2K;0,,5mDL[-yHzy[srooLsy'Ge@e9 e %Sb 88 fyH7s%b\C'":M[eov} /?Q`ny~pZC(tEn8ydSIDFOx_mu_P@. tU8d>jD wV2w ^B)e:uncZY@U%XaqqJ$CvmD>mvJcuVMG @F91%:u K ~/D_'c(mEuK'T\1%\B[s $9ITWUMD=50(" rZC)qK$];p]J5!u]E.*D^p4G\sy_G, %;M`o}oW>& )}=hQSd:wvA mRd{}gQ9 $-Rv!Df |7teohb\YFWvX[^gr,8?CB@>A8FXOu[gu*AWn   !3EYr#6GXkzT)m? lDi7b/V%vQ/th]TKC<4,$$-7>)B3@A9L,SWURMG@:6p1_,L$:% kL*xU7$4BScx $Y a6zKO/Mm8[zl]M?5-*,3:hGFZ#o |$h6QE7T_hovxU4lDeO:+"-BX j{ $().+?*L,U1W9THP\Ht=3'0X{!-8DL`Q=PG:&bD+yN$lHw$aM8&lH'oC1Jfy_K9W-!L ! 4-NPom(]2Q.o^9` D}|fJ,4 nv*TL3lp_O@2$  #+01/(u]?jW~Fx9p0e-Y+F*4'!" #)2>JTY]`ckuzmaUKD?=?CHMTW\` behk%j,d4Y<KD;M(T\cfkmdn3mbTEo1B`<yk_]Q=H@81' |ssz_> 7f}\; !0 Z :huld:\uRG<40z%A.1FkdRFw"RJz?e%4EU"f.v49<<>=<;71&lO|0bI1uP)|vamFb-XM @5# *3l9Q;6< @ EINV_iutW~8AmF|ojT<":Zy{_C( }n_ O"=:(Peu~eJ-|sme[kNUBF5:(1,)()()(&"( /6?EHIA4!@ihM2  6] #=-f5;@GQ_,nE}[pt`I2%5FWi|y_C$ b/7DVevV,nI ( 6Le4Je| 0Hd}Nu?$ ;RluAyL#}rkes^_UNH?75!/./01t3N2+0 0.-,)$*IlqAfk\RKD?!:E4l-$$Y>wsi>bh``gp|1?JRTRH5>X5-&  (#F)`/v9ESbr/=IOPOMJGDxGpNjXeha{_\[Z[[VMB3   (3?JR{Y_]B_%diov}|_?X*  V b:|nf`}]qYlUgPdGbB_:^0\&XU TWY\]ZTH6 }skgvcj`\]OZ?W.QH@6.$nJ# 0Jg)8DR\4bida]UJKB7.1)j))-2B5t9AI T>dqu,X(Pxy[>#  sU8}Z5(<R`lkv@{wP*xeUJA= ? C;IXPuW_js~ #)-.01.(#( 7CO\iz#BbzyhQ: |Wk.O3T)}hyVOB&0jAtI =p\\{KBBKY/nXF?zj>lL|21U[t 0Qre&DG#h{`N=/& !+:HmWUg?v-sia`ycefMj8o(vz{|zz{yz~s]F-zpe\OC{9k/Y'F"0!&+.//x.s/r2u4~6:@DMV`it~  "$2^<3FNSY[`Z8TJ?5) c< i?}fO;% 'Bx]fzXL>1 !4_KnZ)Ff2Z E{mb4`gdm{$Q~/V$Iq6c8\~ =Vm5GWfsxiS6sSl/P7  Z,yO)w eQ<$ Y)Y(nVA." :Ww*` @unU@:\uyW9! {iZL@70+&%('%$>Tj ~ (<RmpR3)9IWl@`}tle^R$H@;[(vm H$2EZne<i?eG. &6Lc|$7GR Y*]M\qVI7! ,9DLjVJb&qf,w;sfX8>"a?#_8eN;,! +?Rj!'-6B0Qgar\ a Z D}(a#Dav-Ie}~eG+  )29DLU\chm$s3u<yA}G~EB@?>?AAEGIMS[`gjm}pynyl|f\O?2% tL!~<U[+vY4d9 a@"~kT=& wdM7#kR ;%*5BNUYXWTRQN}PrSeRWRGN8E)8(v\E1! %5 I`)w5;?@?==;n8546Z=ER aCtzIx/I2d\{ /Tv-uTczP<* }\;|O \/a? oS;&  ;Xq)<Tp(Fby!1<HUbqzgTA+xofZL:'lU< `6 ~`DZ*-gD!oe^[a^Be%s <e6Ss)`Gz` 8Z}50kZ}'+,)  4K`qvO)nO1yhXK;+ ueV,H>:J.RVUTSVZbkr{f@tT7{l]PA4*!yqnqv~z5oL^dH~, }Y*97@CC>5^(=t_N%?.75/;,9)3('-5BSlq\+EQ-|Z3~}m\ LN8# J g2LT4r!$2DW*k@~Uo /Q9sb-V2}Kbz/;EJGA9/$c>[*q_qGH, Gt_PsG(FIRF_o{p0{^I6*"*Fc=[y{gYLB=9#7F6k3/)!+;FMPNMG>5(~umf_ ]Y&X3Z@]Pa`fpmt|>_!!;3UHmYm n$X(D-61)7=A EHLRXdpkQ7<[vX/(n7I@"GPX_eflKv5"~_A qa-U>NMJWK`NeRkVoZo`mll}mooop o/kShqd^UMA4&q9wpj(\L6Ra.wU7\7Y1 tdUvNhJfLfQoYfv@f2HJb|C'Rg}C ?tf:@|\u)X &.14768f<H=->=:73/*%!}~ &|5hFXWPgLwLS]htsR+sEoC%,27l=E@ BCGLQs[Me'nx|}zqybjPY:J<)[,]s@d&WOJNQXblwsjcw^k]`]X_NcHlCw=<71,$ 1G[n$,0?7b;=>:%8[4.(#0^!D(t1:H U:^kehkpp(rGsatwstst{ !(,-)#  $%|&W1  {Y7hQ8"v R. ~`A"ufWJ@:6719S=uAEKS[h6vNdz$'17:HAWIfOrR|ROG:,~wog`ZUNC6u$a K3jG&oZtHR60& n]I6" }}{z&T18lp#_I{I?bP -@P&]/d7iAkKjYej^zSE5! sZ~?m%_ TKFB@o@J?%=;72+&teY SRXdtlS;& +@UjydM8# yeP=*hG%}hSD2hC /zKfiN9$5Tsq\F3#+ BXp &6Ja~a>Ft;}qtfWJL;*Erx\A$*@Wkz,BXm ":Wv+Rv   (30E=WKlYhy $2sC_VCl%kF |Y-x]C:/ V`n"Pb/rF*:L`s~\0p5;@HCQv]l{#RG/|m/4r{He%Rk,@bh.@Q`lu|~ywanGc-WMB6)|m\J6% |lZF2  &;ySrpkgddfks|!.574,vW1 oF{W1 {U/kqP]6G$-~Z6ukc[VSSU[en}uf|TF;658>CJMOPSXY\,\F[cYXUQMI D#?@9]7x79AL\rFs.[v jC^hUQKH G5G^KNPQPM M$N:MQOgRzX_gpv}wbM2c@pV;!t\B%lO3_># v8mSipils} !4DTdr$~8GWfs+5?D GKJE</ u[<}q}aZM>8)!  }j[J=3,**.05=GYnFr4[$S1K,cRuv )Fd~ tS1sbRE;1(fP?0&!(1n>^LL^9p'v^I5 "7RnsZC0"mG!yeP_=>)!{gTDo8\1E..2<HYnw s!q9qRtkvxz|{!|N}~Dt #5"A4IFM]KuGA5&1OofK21Uz Eue[}C+wg[TQR TYa'j0v<EP^l|   7Og *Ga} 1Tw*=nMZ[Fe3ijd\N<& XxqjRi kjjjljqgfca@_\Z{Y2&Y) u^G5# #*09BKWfv %3?M[jztg~ZlQWH?A$;52*"M i/a5 kQ4qQ3xnhgmxwo3h[ffiq{Bb;Fe};1kK Z>u,\ -Lf:z`vi\<NV?o.lR9 xdN5hD!|xsnf\tQ\IC@*<<@IWj "71MDdSy^ehfd]SH6!"!lH$rO)[0zkWbB=*tO*iYK?:7 69;!@#F"MWcn| (8Law6Md{ 'Bb=e"sGeiYPIGGFEE&E<IOObTu[epv}~}zw u p3mBmMoUrUwQ{H7%eL4nT<&|iT>#{ejRK@.2' uU8%.8CP]jv!1CVj 8Pg2Mh .@MW\`cceyedcL_3YPG<. w`I8'^B) !.;HQYakt1Kb}!2BQ?]`jw5X{9Tmzofa\XWVTQQOLF=2j#UA1%nV=("|/m?^OI_1p~uhZ!O1B>5J)Ygwp\G3 {[9ucP9#jO3jO5 |pf\SMIHJOYfw~,~Kk )Kn6 ^3\Bq0DT^=``_[TLC8.")4 =GQ^itdA\4 rT<,"!#/@To.+=2O3b/u'3Kgp^PD;4.5,U+s*,/243/-)D Ym {vaF$~xlt8sqqqDsv||6e] o2GfJ1)9J^v"/;HW h.{Sw-$G:cO{i# Eb(4AJT_gk&p:oOkbet_[SJE@84-'+9*E+O0X6\?_J`Zam_XOF=3(0Ia~0EXfq}uS/n6R}jVq>?"|]Dv/dVKD?<=@DKQW^elv $.7.ADIVRc[kcpkttt}rohc\TPIA;0$}rdTAi.F$ jCv]aC@' |skaWJ<,#A` Ad(6EUg~#FmC8foL {0X-Nn=_*<)EKFl=./H]mx|fWJ?1% r^N8! wngazZ]U>OIB?@CeE?IIJNQX`iu  4GYkypT6lK* y_jGG,%joCJ$mL2 %,)3:8M=bAtDFIKKIFFC0CKBgCHMQW\!_@badefhkos)w:{IU]`]WK<)9Z{|*sKkjd`YUSQTX_gp{ 1CTgu{m`VLA;50.)%rdWI8&u_I6# x`I0zfUF:y2W+7'''(*-/0220/,**,3;HWev*Hi#8:IYZ}ly.H]n{}l^Mw>k3]'L9$ti|dddkv %/9CP[h x)<Odx6Y|vc3RCDR5`*l$u~~oaUI?p7W/?$) *=}PceKz3  $0;HTawllwbRB/~a|Cu ng`YN{A^0C& {mbOK36#zj[OGB>9868ALZn &Da6(UAt_~  +1KVl}%L>p_{'?Wq|nbRC3%7H[m\.rH ueR>){z0Mhzt'l8dJ]^WrNIA:84/#*8%O"cv iM,v=EH RS){j\xS\JCC/B!CH PZdq~1.G<^Pth0;O]o!%E?lYr1Kf*4=zClEbI`NdOqKID?7, ,49>ADFHuKmOfXacZmP}I@7- %<Una@pFk`NA/#  k_WsPdKWGKB@A6B*EJQ Ycls} $:Uo}kS;|!unbR@)|aJ3ry\pFe1XOF@=>DMZjm\L>1(!$C a$~*6EVk%Hj7ZA~g +:Jfh &)C3b;CINS W.ZQZrWTQNLJI FC@#:-34+="JU bpzt|jwco]cZWXGV3VWYZ]^i`Cbbcbbawa`_Ja8f)kqs uwy{|{xtpmh^SE2 ").|1m5^7P:B=0A?;7,\8pWC1x$iY I=1)!o WA. %3@MZeox'=Up 1Tv ) C_{!(.220/8,M)_*p+|/8AM[ujhxWD4%  #.9ER_jx#&)-/1579;95/%udUC1 ~ fM4`?xV9 xi^URNMQTY_foy1Iay->O(]Gjgv"-7?FI{KqMkMgOeReXh^miuu|2F}[rkezXI=/#| n`P@/    (?Um{aE+ )8GVet~~zsmaQ?(~xqj`pTZFB6)! xW7yk]OB3"}pg`ZTPPPQ}QsQkScR`QbQgQqUXY]__aad!i9lUqrx)Qx (#GGhk 2 L4gEXl 1EUepzS6 +LjgH(kUB5) pZE2 |tqu y!7J_rzj!V.C;1J Zjyz]9RNY.u|h`YHJ0@5/-,04;BGQ_n{y}*C]v/Mg ,Pw6V*tW#Ed   &4DS`lxzrjd]~XtRjK_BS9H/:$/'"#(.8GUcriR8 hN 4 }l]PC5*      {pe]YVWY[_bcvdmfihglhnlsoxq}qoh_QA}-xqkhfa]WkPPH5A:2(xtpmkjf#d%a(]-V1Q9L@FJ?W8e/x% 'Fh%;7XJu^v&/<MQjcr~2Vy=a~xutw ,:DOV[bhpz{qg]SG<4+&t#h$V&D*-08CLRZakiQq8v y|}sZAy,tmcZPF:."{vsclNe9['RKB=6.( uaM9' ~hP 8 % %:Sl#*/2K9f>CHKQV Y%[>\U^laegjmopq1sGt]upwzzsomipRs5u| (2:DQ^l|    y`E){pg_YS|NYH6@5* iQ;) v k c Z SKGDCEKT`q !!(70M9eE}R]jy, BWl!&%# 'A \ w!)06$<1<<=G<R9Z6_1b-h'i jieb`^_diooTu8~! wmbSD4#xoh]VMD@< :=?*A2G;LCQLUVZbamjwv~zsh\N:' !"$(,x1k5[9O=<?*@@>>=<;6/j%U>+eK1}wqi`VI;,&5FVfy "/:E,QV[~cjpv9~h +H `%u=Vo#t2dBOR8asuZ>$   !!#$#$"xncVI:,# {u-m?cMYWN`Ae5i+l!rx}   %.48<:4,z Z6mA{qi|b[\?W'SPPPNHA81+$n#]#P'@,13$@M Zi{ 0,EB\Zov"F1iTx *O#w,2689.<L<e?{DJPTXY[]^_bzgsljqdrZsRuIr<n/i!d]WSOHC=3(   $)1y7i?XGCQ2\ ess`~L{:{'{uaM:) xmdZqR^GL>81$$ zl\J:+ "/<}JzZzf~s~.CXo6Ql -9IZo $D c&,28?ELRX+`CfXikmqrrsoida/Z@WPS]OhJqCy=4+  |mZG3kR;" ubQB6+! }doM]9J&7(xaL9+|wuu y~  +8GUgyzusqomigghjox|k^RIC:(5>.U)m$$0"<5MJ[_juw &>Rfxt\D+ypic\UME4)(;Qj"&(D-[3r9@JWdp|{svasNn<h'bYPH;,d E$wakLQ:5( !6Kbz 0Sv+7CQ#`6rDQ]ipt|6Ne *Iezsmf'`A]WZmZ\\]\ZSKB7, dH.|vnbS~Dl1] QF<3(yvx| !($6&D'Q%[#fnt{ {jXF2 y^F1vcTD1#yrlf`XPIB?=,A3H5P7[9h:v999:?FMZgv>`/[  '"88MIcYxgpvzyundVE3 $2?JV`jvvdRA1!|wutspnmnosvwywxvls`lUbIYCM@;?(@BEIPV^`dBj(pw #-6 >&D+I.K2N8R:U:U;T8P1G%:*xX8~uqnhfgaR\@X/T OHA82*" "2Ga 2Uv*D`| 9Qm4FVc#n=uVzl||{{||}{{zocWI9w'lcWLA80+*+.29<CJQZ_dhijifc]XNC6"b E+(5=EMT[`emwsk|^RJA:2$ |tj]P@/yslbVF3qW<#yz~}sjd_[X YY(Z8^HeZmly| &07=A%B-C3F:HBJGHOIXKdErB<74100 3%5>9S@hDxIMNQPOOQV[bju~(!;-P6b=uCGLOSVYXU%R;NSIlE@=>?@C C@(?4;?6F0P(W\begjjifa\QC2th`VMkIKE,B>;9652|3g8S@BH2Q Y _eiihe^}VfKO?8/ywtlrapXnQkJhDf@c:b3]-X(S$N%L'J-H9HGFXEjC|@<740/17AM^ p!+1444/)!#2CR`pjS>. %A_ ~ ,:&IE[`lxw~{yxzxdK4q`qOb@U0K#C<3,$,=Pez5GWeovz{${'y*y)z'}%~ }seVD4!k V?.t\C+m[J7%:Xw -Mq ++Woq\E/paTEv8g+ZK:&qT5|vponoms\vP~GA@EKS]iv  -?QbvxgUB)zwp_gI^3XS QQQSSTSPPLF?83126>FNU[zZoVfO\GW=U4R,R%T"V!X!Y'[1^=cLjYqe{nv|}zuqono u~  'Fg%3C0SNahm~{  '9H[kzlW>% {vrpnkid_~]oZ`VMV9T#SQOONNyR_WD`,ioy #/9DMV\]\ZUND7* qU7|^B(vbO<,  +>Tj!-9CP"^Dmd~<^3K`u0>JSY\\\YURPPSzUr[m^gdch^jWlRkMjGjAi=e8b2[/Q)G!<3' )7ES^ht~ym^ L;&#+5@ITZr`Yd@i)klnoswz||{xv|rhmSh=c+^[YWUSQMIC;2' uiiI\&QE;3,~#bH2   -<M at *7GYm#7IYdkp"s5uGuXxf}t '2;BFGGFDCCCEJNW|_xgwpx{y -?#N*`/p6<ENXdq 'A[w !%''p(Y(=) (#"|fR?- jT=) lU<# bD)zpe\QF;, +5?IVdu$.(93B>JIRST]TeQoKzC:/# 8Qj|r1kIh^epdeipxxdN:&pv]jH\8N'B4 &  !-:I\o&6AGLNPONIDA91(tcQ>, w^D(vcyPu?v1y%!!<&[/y8BKT^$jFtg&Gj0 ?@P`b}r &>Tks"X2@E'Zlzk[M>0  ~cE~*f N7! hR<&}yz{}plhfhhhhjkjjhe dcc#g(m1w7:>>><:71+ $):Naq|{rgbQQ:@ /  bC* $*/16;BIS^ht~ %5G^+v:HT`my5Kas &5B J%P)S-U-T-N0E293-58;?EMV`fmtvwvsp}kmgYfCe.fks{ueXNA8/%lVA,wk_VJ?4)!    "&*)1-5.7-3.,.#/.00,*'#udS C4%   gQ;%#*19BKU]d kp-tBvYys|#>Yv 16HZ]~t':IValv}o`rPc@R3A$2%    ,;ITbnz"2CUfw|rfZOB4$kV|Br+h_WOHC@>|<o<d<[=S?J@BB:E/I"JMPOH@6'tV8qbUKCt>g>Y>N?CE6I,P!Yco|)9HYm +8HWco+y@Ues "' ) ))'%#$&+2:FQZbipv{!1CUj3Lf1G\$q7GT_dfbYN@-   rT5aC%o ^PC4$ti`VzNcFL@6< 9 764359>yEfNUVF`9i,r"x n^PD;65568Um#2>+J3V7`9m6y/%rV:k{Nl0_M:% wjv_YR;F<4,&xk^SI?5+!"* 6CTfy '.4;CKS\dmsx !%(*)% @^}  3BMTVVRLE: .# '%     ucQB1"{ gT?.  '/7>HS^gnv}uj`WNF ?!7"/!(  o^K=/$  +:K`x$Mt#@ \"s;Rk 9Rj%9K_ps\C,'0;HTu`Wm7wjTB/zpe~_pWbKTAF68,'"     wncVJ9&{bM9&  |wttw~*=P`p%.7=ELPUY]$b3g@kKpSt[wa{e}iikmnqu} /@Qbq~tg\UQRY_n~&|3|?}IQY__`_]\\]_ekpou]|J8" zpkc]WQKF=5* qX?*|fO6)7GUd~sm]OA5+!  s h&\.Q8F@8I*U`m{#3CSbp}*7F[r1Oiytommqty~~zvoje]U{MwDu;t1q'omllmrx ~{!}$(,-...//--*# &.5?HqP^YM`>g1m"pruvwvroia\TMF@;81*"zqg[OC6) }n_Q D7* !$ %" "1@O^ju!*19CN\jz $')*('"   #!,(6.A3J8S:\?dDnGyKRZcp~5Me{ #-: DNW_fjmp|q`oDn*jifa_ZSvL\AA5'(wfpX[LFC1<8543345z5e5S6D75:(;? DJQYbiqz  + =L[l|  *6CTbp#*19@EJLK&F.C4?5;4926,4&2/,'$ s_H3 zjZJ:/$    ! ) 1 8 @ JSZckpvyz{z{||~xqj`WND:3, '" ~kXH7) "-7BMYgw7Uu  "+)I-c4<DP]iu$0>HTbo z-=L\oteS>'(7D|Pg^TkDv4&vbM7#vk`UK|Bi8S-=&-    sg\N@0"mYB.  '7GXhw+>Obr(1;CIP SY ` gpy!&*14;@GOV^emwykZD/0H_s{qida^[YVQJA4( udSE7'xocS?)%/8AnJ\RM[Bd9m3u.~+'# } sg.YDM[?u3& $<Uj~ %0>N`w  ,9FQ^itzspljjijjihfeb_\w[iY^USPIJC?<47(20*""+7GYk'6DNW][XPC5$vdP>+ '6CP[`gkkifc^XURNK~HwFoCg?b;^6X/U'SS TWZaiq|lS<(~o`N=-  *9FWhx "&*/38?GOYdq|! +2(;8FHQW]eiwu*D\p "-:CJQUWuWVU7OG>2#}gN9! ~n\G{6m(]Q F;2+&!q$a.V9HF@>;6/*%! }wqkd_ZXURPKHD|>s8l4g0a)\%V!OHA :61+' "*39@HOV[`abcba`^^`cgmqw~~ulbZPF 9.$  wjZNC;867;@EINTY]bfkqv~!0?(K;WOafh{ovz<`*:GOTWVTPI?5+!  #3CTeyp\G2 "-8BKSr\_bNf=l.l"gd^UMF@:40,)&#xlaXOE>5. '$ (.5= DMT\cioqqmhbWN~D|9~.~! ~lYF3s[@&&/9CKU]grz%1>L[l| )6ES_lxxj]RH>7/)&#(-4;CKT^gmq-w:}GS^hpw~wiXF2#0;GTa{oj|YK>2& {pdYPHA<6/(! zm]K;( (06?H|NtWo`hganXuQyJ~B<5/*"  &1?M^r|qh`ZVU/U?XM\[_gerj|pv|"0<GPX^c|iulpoknfjch_f[cXbUcRaP`N`L_H]CZ=T5O-J%C;5/ '" !.>N_q !"&'&&&&%%#m\J9)}odYRLEA =:9$<&>'D$KR[ bmyyiZQHB?<:743/.025;AJT]gr|{vspppruz ! 7Lat%1>N`p %&-24?8J7T6^3g/n,t(w&w"unf_ SE6&qY<y!dN7" |dN9&|uokihggfggh+l@nUojnnlfaXOE=#54-C)Q#\!b iquy#}(~,z3x9uBnKeS[^PiCs7- !3DVes (,06:>BFIKLNOOPNMLJIGIJKMOSRQSNJD;3)x!qic\TNGBAABDGIIIH|GwFtBq>n8m4k.k)j%i"i giige`]VRKE A#<'9)5*5,4+5,8+:'?$D!LT\huvlbXPIB;#6%0$, *(((*+/02577640*"~ +8CN["e8oO{i*C`|  $(** )!$!    } q*b2U?EI5V#fq{m_QF;*02'9?FHIJJJGFDAABEJQZeqwgWH9* y{rqmckUiBk0nsx}w`I3ycM8& $3DUfw .>M\ky$.8ES_kwq`QC5)~~ )7FS_kv #/;DLV`hpx,8CMV\afi|iuhje[bM^=Z+YYZ_djt}u]E/ zrkd_YTOKE?80)o$[J7%% *4@HR\dkpv|vi_SJD<8521/.-,+)*+,.1 7;CMU_is} ,:FOZ`gou} }|z#z'z+{/|2}5}:|=|>zByDyEyEyBz>y9x3y.y(u$o!h]QF;/&   &09AHMTXZ ^a"b)b3e>gHjRp\sexm}svy}|riz[pMdAY3L%=0${xutsqnkjfeggk osy{~{ywtpljij klqtuy|  "4 D*W3j=}FOXbny(5ETfwtbO:% xiZqJX6?$( iXFs9i-a"ZRMI C ? < ;=?E"J*P3Z=bHhWqfwv{"0>LWajouwwurpmhgd_\VSOLGB@==<;=@BC CB%@1>=>E<Q;\7e4o4{0.--,,--./1469<AEGIJzIrFnBj>d5`-\%ZVQ MJHJMPX^dimppqrpo~p}rzsxwvwpyl~e_XOI?6," xpf]VK A+67(DR brvdOA 5( %)06:<"=(</9357/;)?"@AA??>;83/,*(%!!&0:BJRY]bf+i;nLpYudxn{x &4BQ^lx"'*-++'#  zrhc[URKE?:2-' !.9EP]jvzmaUKA91(! 0=I VdqtaO<+xtqoakPi?f/fgilpuwz~vaK5 p \I8)*2: @ELT]enw )6FP [h p({18>FMRW\__^]]^_addeijjjigfdb\UPGA<6u2j-a'WND90%  &2&?2KAVPbanvz$5C P]!i/s;{GS^gmqtsojbVH8%p]J9( ~|vvpqhnej`g\b[^WXVQSLPGJDFEAI9P1[&fr zrg_VLE@=988779;;=AEKPW_cgmrwz } ~   #%()'&y"l_P A/yrnljiiikmkhf`ZUPKFDCBFHKOSY\`*d5g@lHpNuUyY~]]]^]YWSLE;2) {vqkfb^][ZZY[[\`dimsy  $ + 4=FPZhv !%*05 ;A&G3MATN\[dijurw{|}}{zwvrr}otljk_gVcJ\<X/OE ;0# ~hRA0"yk_UNFA>>>AEJNRV[^abcddbba`_^$^._9aDdNhSkZocthwmzr}uz}}ul`UH=1" zrkd_\XUTUWX|[p\d]X\N\D_;`5d)g"lrw{|tlaUF5",:GS`l|zjZL?5-%+:I Uaku}}qgYJ;-!   (1x>=;6.& $-4:@ HPV\$c+h1o6u:{>|@}A|Bw?p;g8]4S/F+7(*&& #$#"#!o^K8%pe\WS|TmX]_PeGh?n9p7p9s:t>tAvGxN|SY^bfinrtw| %(.1599:962-'#  $)-/269<?BFLRX^e jos(y2{:|D~MWajsyywsmh`XND7+ ~naRB5% tlaXPHB;73/-***,/1221/.+(" $2>LYeq~{j[K<0&-CVj{ y#o.g:\ETOLZEd>m7u0{*'"  %*/6=CKT[doz #.:EPzYpafi]oTsKtCu>s8q4m2i/i.g.g-g*i+m,p+u,y.}/~1~1110.,++)'#"(*,q/_1L4:8)9:;73.' nZE. ,y=pNi^anZPF<1%#+2 8>A!D+G6HBJMIWGbCm@x>9520-,)(*('&%#" !$').359;=@A{AnBcBZANAF>?;7913,-*%()- 059>EJRZcmw !(04'67;H>UFcNoWwclw|wqnljgec`^[ZWSQyNiMYJJH9G)EB=82+$ }zxxwvx|}yvrpkf a\WR!P#N"N#M$N%R'T'W([)_*e,k.q/v-z,+&  !-8AGLNQRQPOLHGFFHIMRTWZZZY!V.R;MJFX>f5u*{tojc^XRKE@91+$}qh]TJ>4)  "%*/369=@ACCAA><:840,*%! "$$%%$! " $#&')/*7/>3G7P=UA\GdLiOrQzRPNIEA:51,**$)-'5)=,E0L4R8]<h>q>}?;74-% +:GS_gpvz}|woeZM?2$~n\J8%{ph`XRKE@952.---,--+-.15;%>.B4H9I<O=V=[;b8g4o2u/|,+*&#!  ")18AHOX^dio#q,t2v=vDvLvWt_rgoonvl}he^ZRKE;2& yl`TJB:4-&!{tme\ULB:0')8EVdrwmcZSKB<3-'!)7DQ ^ kx #,6AKT_hptyi_SG<2( "*5>HQYbks{ $3CQaq}|tld\TMFA:5/)'%"""#%%%)+-023310,'# yn`TH:0&zj\M>1  (4>KXdq}p`QB7, # 2?LW`inrvw vs!o.i;aIYWQeHrA:0)#      !*37:?CEIMQ)W1]6b=k?q@yFJMPSVY_emv~yqjbYxOpEg?\9Q5C25/'.. +('# xmf^TOIB =9!5+434<5F7N;T?YD]J`P`TbZc`chgohth}gdb\TLC:0'#'-/269<BGMT\bhjknoqsuwy zz#x0s>mMd_YnM?2"{u p j fb^] ]] ^ ^` bdeeghjlmpsuy}|tme^VOIB=730/,)&  sfXMC ;)62/=+E(O$V![`bccb_ ] \[YYYYXUQMFA<61,) % !! $&)),/158>@CG JOR"X,^4a<dEgMkSmXr^wb~flou{ ~/yBwSvcqro~mifb^YVOG@8." )4@JRZ]a``^[WSNE?7,$  o\I6"rf[RI@80'"*4;AGJOTVWX#Z.X9WEVMVTS^PhOqK|IDA=61-*$  "'*, -/0"/*0.2457:8>9C<HAOEXI_PgSoWwYYXUSOJHFDDCA@><6/'zpf[QH?71)! zpdYOF=2( '3?O^umeVG:0(  !/=IValv"+4:DLS]hpw~tdUJ=3,'$!!##$'**-.---137<AIOTY`eiosy| "/=L[hv}umf^VPJD?><86679<@ABCA>:6.( ~sh\QI?84,(%" !$$(-/345y4j5Z7L8;;,=?DLNT\bjv~xme ]TM%D-A6<;7B7F6L7P8R8U:W>YAYGWMSRPZMaHiDr?z;730+'" !!   %4BNYahnpr s ssss$v*w.w3y7y:|<=???ACGJMQW^emt|v}jw^nTeM\FPAE;99/6&3!0,(#     wmid``_`a cfg#j(h0i5j9k=n@mCnEqHrJtMwP|QQPOLKKKLQRSVWWWTQLFB:2+#  %)/5=CGLLMOOONOOOLIGB>-:?3Q-c$u ~tle^!X"S$L%D"=#4"+$   #+6CQ^kw|qf_VOHA:2,% #)-.-,***./258;=ACFJQVY|_rbge^gWgLgCg=h4i-m&rvz~   } zuolie`[UPHB8 ."&(.3 :CMU^emw  *4>FMU\dkrx} )7FS`kt||sg\RG<2) !#%%#!~}   '+.w-m,d'X!J:'ui]RI?60+%!#,5;DN T[`dff ca^YV&T/R9QENVLeKvIFC?<96699:=?ABBCA<61(   #%')%-,/22478=;D=LAUC_FjHvHIGB@:2,$   {vpib[SNF?=8521001247u9c;O>>@.ABA@A?BEHMRZ^guqhyYL=/! #.;HUbn{%-7@HPV`iov~|sj`XPJEA?<<:9;;=<<>=>>?BEIMRZ^chlruuuuvuvxx'y6|E}Q`o{{qh^UNHC>:653.*'$! |rg]VOJEB?><<:741.-./2t5f;XCIK:R*Zd lv~vk cXLD9/&  #* 2 <GS\gqy~ $),,+,($ "-9GR^iq{}zvttvvvz|#'+*++('''&(*/4<ENV`lqxs~eZMA5+! }wsnjec`\XURNIGEDyFlG^ISKJL?N6P/R(S$T VWVU"T&R)P1M7J>GHFQDVA]Bb?i=l:o7t3w,x*z%} !$&**)'&# ~x t qppnoqrv{ +>Rfywme_ ZXXZ\^\[YSMC6+!-r9cETSF_:l-{" $0:BHLOSVX\_dinrw{yqibZOF:." ~ytqnlhf d_[UNH$@(7,-4#8>CJS\fox $*1$7/;9@BEOKXQcWm^wejosvxxwtohaWNE:1%ujbZUQONNPPOOPPPSUX\`gov}ytoh`WME%<-/3%6640+! }qcWNC;4-% "&*,/112459 :;<+=9>H=[=l99::=@BFIIJH EB?;40+&# "#%'(() * '$"!$'*.147=@HR^ky !  yph^UJC:1(!${+j4Y}5~.%~~ zwuuvuvuvttsqpop~ptognZmLm?k2j'ihhggeda^$['X.T4P:NAKGINGSGZH_FcGfHhHkHlHmHoEpAr=t7x3}/)$     }yxz{|"4zFu[qmnkkiiihhec_[WSNKGDA=94.*'!   {$q,h2`:Y@PGGN@T7[-_$emx '3=HR\emw~xmaUJ>2& ~xqic\U NF=7"-*"2: AINU[^fmt| " ( -39?&D/G6H<JCJJINHSFWE]CaAdAkArByEGKNMMKD>6+ |l\N@~3y'qkfa_[XWTPNKH GDB B*B7AE>T;d5s2-'" %('*+*++)%"~wme\VLE>6.'      $-7 B M Z es$)/49>ADE FGEDB>950"+'',#0!5<?@EIJ KMKKJFEA?>;;<>EMWcp      ~ukc[SMD@:52-w+h'X&I$;#+"$$'),/3:?GLS\cgxmkt\zM>0" $4BO\h s~!'*/47;?CHPZer~}tmfa\VRPLGB;71*%   ! &+)-90H2Z2i1z//,+.../0///--,++'&#wqhb^YUQNKIIGEGEFHJNPRVX[_adhknoqrstsssrmic[SH>4)  {uo iea]\\]_dgkqu| $(+---+' $ '1;DM T[bflpw|~}}{|~}~|yuqlic^ VN)G5>A7M1X+f'n$y#"  yog]YTQPQRUWZ]`cfkntx~~|xwtpnic^XQKD?;&8.675?5D4F4G3G.E)A%:50(  } wtr)n4m?nKnVn`piqqqyqqrpnkhheccb`_[WSPKHDBA@AA@AA@}AwAp@k?fAcC`EZHWKSPMTHZ@c9j3s*{" +8EO[gtwj_SJD>:632z0l1_3R5E98=.D&LU\ fou}!+5@JS\ ekr!x)~18@HOV\adcfgfgeecfeil}pvunyf^ULE:0%~sh^SI{As9k1b+W)O%G"> 7 . %$). 8@JWdq*4<EJOSWYXVUTSSQPONMLKIHDB@<955~5y4r5k3e2_2Z/X,T(Q%P OQSX\bjpy&.7@HRZ`g lotx z(~,}5}=|DzNxYrdnnkwgb`[VRMHB<5.)#        }l\N@0#|vpigdbdfhkqw}~reXLC:2+"    - ;KZjz &.7?IRZcmx}wrle^WOIB;6/*'#  #!/%@(S,b.s/369;=AEIJJJHDA;61,(" ysolhb_\XURNJGD@=:630,,)'''(),/27 >BF$K*N2T9YA\I_M`T`Z`_]dZhTlMnGl?l6k,i$f`]ZTOJFB?>;9862/*% "&*//3454541, & *2 ;DLV\djmrwxwxwxwy}~z~w}r|nxlujpjjleo`s[xWRLGA:4-("  # 1 ? O \ hx }zxu rqp)n3m;lAmDmHlIlEmDmAl<i:e6_1X-Q*G%>"4*"  {vrqmj!j"h$g%h'h(h*g)f+f-c-a1_4^6]9\<[@YDZFZJZMZN\Q]V`[cadfgkioksnwqyrys{u}x}z|}{{zxwusrokie`]YSNIA80( #.6?HMTZbjs|  }}m}^}P~C6)$3B Q(_/k8y?ELSX^dfjnpruw|th[OA3&~xsmgda|_s]k]d`\dVgOiJkJkHjIjMfPaTYYP^Dc7h+nry!)08>DHKNONNNLJGEDDCDCBDDEFECDA<962,z&t"pkea ZVSQMLMMOPSV[`ekotz $/7?FJO RV#W1Z@[NY]WkUxQMJD@93/(#zncWK>3'|m`QD9/%  ##$&$%2$@#O!_ly "*3:BLU_jvzqf_VOF>7/&  - ;HWer} "%#$%$$#" yqibXNA4+   ")26<AD*F5HAJMJXIbHkGsF{C?<73/,))(~${"wrle _[TNKD=70+! rld_^\^^adjpw   #+3;AGMQUZ][[\[X}VwTpPhN`I[FWAS<R9S5R0T+V'W"XZ[_ciqx!1BQ_n} "&*+-.-+(! {tokfb^]YWWVW Z]`&a,c3d:f<g@gCjGiJhKeObQ\SUVNVCV8V+U TS SQNJD@:2+$ wrkfaZVSQQQRVWY[[][]^]][[\]`dglnqrrrrpnjhgc `%`,^2]7^;^?_CbDeGhHkHpIuFxF|DBCAABABCDFFHHFD@<4/% '.5>FNWakv}yl^O@1$ ~xurrssvz}%,3$;9?KE[JkMxPTX]eku~}sjcYPG<3)yrib]XTNrJfFYAN=F8>39/6*6&9"<AHR \hu|sic\X S'O0M6L<KAJENGPHRHUGYE^?c:h4l0p(s"tuwx {||~}wtpljhec`_^\\ZYXZ\`ejqw #5FUeu  '/69<=;<;<;752-)# ~uog_XPID=7.' znaVJ>4+" {{| !,5AJU `gow|%,1:CJT[cnu}|voh_XNF?71,'"   $,4>EOW_fnt{  sf[PB8.! !*19BHNSX^`cca` \ZUPJC<6 /!( #  ~unhd`^^^``djow}   '.47:=>ACDGHHHGFDB>|:x8u2q/n+j&f"c`^]^aeks|-=JVap|'/7>CFEEA=81) |{ywvtpkhc`[XWUVUVUUTOKHD>;83.+(% # &-4<CJRX`g nw}~xqj`UKA7-$ {wrnliec`]YVROMLLMQVY^bgmqw   "#%)**,,,+(%! #'+/36;AEFINPRUX\aeimptwxz{{{}}~|yytniaWK@3$ |zwrnlijjkpw} %,5 >E N*W6_AfIjRpXv[za~eedfgghiihffea^\YQIB7*{tkcZOE;1&~ yu"p+m6i@eIbT^[\bYjXpVvTzT~TUUTWY[y_rckgalXqNwD{9~/$ }ywvrponmmnpsw{ )9JZk{%+/2100.,'$  {umhe_ZXUROLIGD@?=852,(%    yne[SJDA;741/,)%" "-6 ? FPY_&e/k6p%>"@@AA@ @@>=<;;;;::9850,'      ###$! }x$v-t:rBqMpWo`pirqryuy{  +3:ABCB?<82,%! ~}}~{ywuqnmhec`ZTOIA; 5.*$#,4;C LS^gr|ypg]ULC=4,"     " &,06:>BEIIJMMNOQUWZ^acgmrw{}yuoia[VOJE<6.% "%')))*+*(((&#! "&*/4:?FLSX] fkr x&~-39?CGINOOPRQPPPPOOOMKGDB?:6y4r0k*c&Z!PD8 *  yph `([/X6U=TCVGYM^SbZh`nhqosvvwxyxyx{}~{ri^PE8.#  $(,../.,'$$0< IWdr   x rlfb\WRMIEB>72/*'"   $(,,-,+)&" ~xrokhea ^ZTO"I)B.=3::7@5F7K6P9R@WEZK]R`Zbcemhwjnqtx{~}}~||zyvtoh`XRIC<5/(!  %.5>C I LPUV[]achlpw| }vng`YRI>4)  &/7@GLRW\_ acdddb_]W!S$M'I*E0?6=<8B5H2L.N+P'R%Q!PPMIJGF G ILOU[bgkqv{}wqjc\UNG@7/(   ~vslfeb)_4[?[J[SZ]Zf\m`qexk{n~w %1;BHKNMIF@<83~0|.y,w+u*w)u(t#t ttutt u r p mk c `\VQKF?"8$5*00,8%@ KVak u~{ume]TMD:0$   !"##$%%#!     $)-38<@BFJKMPR U TRRPLH F @:3,$ ' +.38;<@BBCCBA@>=<:8532-+*%" "&+069>CFILPTTXZ\`beikmpp rsrssr#q&r*r,r0s1s2s4u4u6s6s6n7j6e7a6[5S5K5D3;212(/.*&$ !$&+|-r0k5c9]=X=UASCPEOGQHRJUKXN\NbNfPjRqSvU{XZ]`bceeba[SME<2)    $0;HT_lx{tme]WQLEA=:720+($   {"v$t)q,o0p3p5s6v7z8:>>ABDHHKMPRVX[_behknpqppnie`\UNHA8.% )/5=BGKOTWY [ ^acdggh i iigfc_\WRJC=61-&#   &,28>CHKMQRP NLH%E,C3A:>@<E<J8N4R2U1Y-_+b)b%d#ffda`^ [YUQOOOPRUY^djqy~}wrke^VQKC>83.)&" ~yrnga^YWTTTSS(R3R=SDTKURYU]Wa[g^m`razcdddeeegjkmopsst%t/r9qBoHlLkQjSgTdSbQ_O[NZMYJVHTHTEUETFRBSBU@W@Y@\<_:c:c9d9e8c7`8^7Z9U<P=I?CA>E8G2I,O%RV] bhnt|xpi`ZSJE=5-$~~|{zxuuttuvxz{|}~~~ !"$%'((('$"   $)-13454420-,) &%#"    !"##"" !! ! %(),-/110/..*))'')*+-/011/0/-+*)&%#%$"! #$&)+0458;>BHLNPQRQPOMKJFC@<:842$0)/-/2-5,6,8,9);&>%?"@ABBC CA??=;;::;<;;==<<=<=?~>|=z=x<x:v7u4t3q0p+p&o"nmnprtx {  #-7@LXdnw {tm f _ZSKC>94/,*&$$#!   %+.28:?EIMRXYY[[XTQKG@:4*$    $% % %$$"!!!!   "(.49=ADFGEFDA?<975210..//036<?CFJNOQSSRQQNKIEB?9740/,**()(('%$##!! |xvrppomligeccbcbddddb`_\ZXUTNN MLOR%V(\(e+l-u.-0//112466678866766#6(7,706295:7;:===@?A@AAAB?E@G@F=J>K>N?Q?R@TCTCTFTJTMSPPULVHZD\?^;_7_3`/_,^(_#_aadi knqtvxz{|}}}{||{zywwurmjf_[UOHA<5.'!{zwttuvvx{|||zyxwxwxz"{')+,,+*'#!  !%&)+,,+,*))&%#"!  !!!!"#!% &)*)++)&$!       #%$%''((&%#" "$%)-048;<@DDEGGGGDB@<73 . * % % *158=BFJLORSUVUVSQQMKKIFEEDHHJOORTVWVWSPMIC>81+# "(/7?HR[clt} ~~!}"y%v%q%l%g$c"^!X!SNKGEFEBA? <72+'    "    $(+.2458987731/,+(%    %(*-/...+ (&%" " "%&&()***)(%$"   "$#'&)(*,,.-1/406/709/;-:*<';":::; ;=?ADFKNQSTWWWWXUUSQLIE@><9866421000,-,*+(('&'(**++*)&%" |yvroliebb`bbdh l sz #',.158> BIMT%[*^.b2h7m<p@rEsJsOtRvUtZu]t`tbubsbscqgoilkhodsax_z[~YXUTRP~N|K{GxCtAr<n7j4g.e*b$aaab cdfiloqvxy|}~}|{ywspje`ZUNG@8/(    !!   ~~#{'{*{-|///.,,,)'%"  $),./11-.-,***)((%%$"#$%'() +-/0%0+/2/8.>,B,E*G(G(G%G#E CA?;84/,' $&)-.124656665210/-,++,--,,+)(&%$"#  !#$%&' *,-!/*/0/7/<.B+I)L)Q&U%X#[ ` dflqvy~ ~||yspmigfeceddddddc``[VTNFB<3+   !+4=FOW_gox %)-27=@FIKNMNOOOMMLJJIGF~DzAw@t=o;j8e6^4W/P.I+D'<#7!1,'"       !"!$!  !%* / 3 79>BADEDEFFIIKLIGEEB=:72-)&#    ! ( , 28:==>?><<;;< ;#<%<(9)6+5,3--0,2)7%:#@ EJPV[] bffggeca][ZWU S RQNMI#E&B)<,7-4...+.',%)$)") '#'$''(+(-'3'6&;'>%C%J%N$S%Y'])`*c+f,i-k-m/p1q2r3s5s6r6r8p8m7l8g6e4b2_2\0X.V,R)O'M#J!GC@<62. ( ##$%&'*++ + , ++)(&#"       ""#%%%$$! '/8?#E)N-T2\5d8j=qByHOV]fntz{tmhb]WRLGA;3~-y$sqmjhfeddggijlnmoppmifa[WSOKGB>;51/*&"  #&)+-/..//.,*(&%!!$(-04;@DFFGDA>:52/+)($!   # -39?CG K M PRTVY \ ] _ a a ` ^][YYWVVVUU VUTPN#K(G*D0B4>5<:8=5@4@4?3@4>495785:2</=.A+C(E%G#F EDDEE FHIIJHGEB?:8311//0101200.*(&"  &/8?G NU\dls y"$%'))+,,/0000-'$}{yvsrollhe`\WRMHE@<83-*%     #,4<DIPW\`glp!t+v4}>GMUZ^cfhjkjkjffda^\X|TwStOnLiHbC\>V:Q5K.E)>"71*%  !% *".%2$5$9&=$?!???= ;62/+)$!   &,5<@GKNSTVXVVUROKE@:/(!    &+16=BGMQSVZ[^_abbcba`__[ZXTONJ F C @ ? < : 9 8886543200/.//01 1 257;>!@%D)G-K0N4R9W<[?]BaDeEgEhChBj?i:f7c3`/\+X'U&N$H#E#>"6"/!( "  !""$%%%#" #')))''%! |{xvromjigecbbbceiknptx} #''/0:7D=MEWK`SjXr`zekpt{}ypgaXQJ?~5|+x!tpkeb^XURNLIFBA@?<:852.-)&%"    #(+/0121122235789:;;999986766566530 0 - * ($#     !*19AGNV\afkorvwyz{|{zywvutqnlkgd`]WPKD=6.)#    #*08<CHINPQS STU&V.X7Y;Z@]FaKcNgQjTkTmVoWpXpXrXqYoWnVjSfQaN\KVIMEGA>=48-3%.&     !#$#"## !""!!#$$$#!    $*17<BFJKJIFC>93+$    $*/5;@EIKOQQSUUTVUUVVTUUTQPOKKJHDBA><:9752//,+*''%%&%&('(*,,/23668:;;; ; <;::863/,'$  !"!"!    ysmfa\}XwVrTnRjPeOcObP_O_O]R]R]V\Y\Z^`^d^i_n_s`z`~bcbfhjosx}",3:AFL"O,R5V;W@YH[O\V\][b]i_pauc{cfgfhhikkzlulpikhcg]dUaN]HY?V6Q.M%HDB@ <8521.,++))*()'(&$$""##!!      !%)/49;>ACCCEBBCCCCCEEDEFFGFECB>;82.*&     $)-28;@DF HKLO"R%R*T/U2V6V9V=WAWCWFTGPHNIIICH>I8G1G(E@>95.&     !$$&&$""  #(-3:?FLQUY_accccb`^[YWVSPOLIGEC><;521.+*'" #$')*/1368<?ACFGGEDA?<83 / + ( %   !##%$#$!!!#(*-2589;;<;::98630-*&!}ztqoljggeeegjlns}wzzywuvtrrrrooppstvy~ %+4:A HMS(Y2[:^EbMfVh^kfnmosqyv}y|~}|zyxuuprkmfhaa\YWTRLMEI>F7B/=':"620 -../113457:;;=?<<;98420.-,)'$"          !$) / 37<AEIKORRRPNLIEA>:61,'"  !&*+.147;> BDEKLNPPRSSUUTTTSR Q OMONNMKJHD?:5/*&!      $)/7>DK SZ_e!l%p,r1w4y9z<}?E~G}H|MyPvSrXn[h^da`d]eZbWdSaP]LYHSCM<G7B0<+6#1,'"      !$'*-!0%5)7*7-9294888<7?5A1F.G+H'K#LLKKJJ KHGEB?:71+% !%*-/3789<=======;:9640+($         !%*17<BHOVZaimsx| zvpid^WOJB;5/+'#!    #(+,1 335677"9$9&8'8'7%7"7531/-*& "#!'+02 6:=>ACBDB@A>=<998755555557;=?CEHKLMOORSRSTTSSRRRPONKGC?83-%  "&*/4$9*=/B7I=ODSI[Q`Vf]naqftlxnzr{s{u}w}v~xyxzz{{{}zzyvvrronhhec`]ZVWNSEP?N4J*F#EB? =;;:9987630-)(#        # # # #%''(%#  "###$ # !"$%'*,/34467642.-+)&$ # ! !    !& ,17=BFJLNNOOMKIGEC?<85421/..+)'$"  !'*-3 7=AEHLPQSTTTSSRQPLL K I IIGDCC@>;622-)&## ##&('+/123421/,*'%"   "#$$$$"!   #(.58=@ DGHKM#R'U*W.[/^3`4b5e6g7g6g7f7e5e5c6a5`4_5]2_1\.Z+Y)V&S#P"L HD@=951/+(% # # !    #'*".'2,31466:6@6D8I8M8P9U9V8W7[5\3]3]/],]+](^(]%[#[$Y"X!UQLIC>:3 ,$  !!!     !#&)-17=CJPV\chmrvz |#w$t$p%k%f&b&]$W!U OKIB?: 3 . +&"  "$&(,./122355788;;= < ===>>?@?= :%8)4,./+3%58;; ;:98541.-+*((&&'$%'(*-125:<?BFIJNPQTUVWXWXVSROJF?92+& $*.48 ;@EH K&P(S,X/\2^9c;g?iCnFrIuMyQ|TWXY[[ZXVTROJF}Bz>w9u7r5n/k-j+g&e"ddc` _]YXTPMIB=71,&#    "##$##"    "" $ %%'''()''&$$                $ (+/2578:;=@ABAAAA@??>>=>><<;953/,($     ! " " $&&'& & & &&&&%$$#$#! !&&(+-...-++,+*)(*)(''&"!     #(.169<?CGILPQTVVXYXWVURQNJHFC?=:7642211100.-,)(%#!  "( . 6 = BFKNOPP!Q!O"L JH G!D B!A!> >!<:;:99641,'!     !%+17=BJRYbipv||zwvspnmida[XQNI E C > :7541/.-/113356679::;<;;999887876653 1 /-+($ #',049;=>>?@@ABCCFECC@=862,(#   %+17>CHMQUX\_bbdgh j"l"n#q"s"t"u"w w v vtsqmljhfb`] Z ZVVUTQQPMJGEB?:63-*&!            #$#%%$$#!   !%),.3559<=@ACEGFGIHIGFEB??=:83/+&"    !!"#%'),,,-./02346889;::863 1 - )&! !$# $ "!   #+18<@FIMPRVWXYYZZXVTQNLIGFECBA>=;875431///.-+*('&$!   #' * / 047:;:; :":$9%8'9&9'6)7*:+:+9*;);):)<'<%:$7#3 -'  '-8@GQX`hotz~~|xvtsqqromkhc_YTOJFA>;8 5 4 211/.---,.//12344442110.--,*(%$#!!""     $&'('&%"! !"##&')+-.14589878754433323210/,)&#!  #(,038<@EJOTY]`dfgikjkkk j j j h hheb`_[XXTPM IFA?;61-*&#        $'((*+*,++--..12477889998887530,)&#       !&+.36:>@AACBBB?AA??>>>=974/+&    !& (+/258;<=>>?<<;::;:;<>AACEHJKMMMMNOONMMMMKL JFD@?=:9420- *&#  %)04:>?CFHJLLMNNOONJJIFDCA??><;8420/,**((''&%#   "&+.24689:: : <==@BBFFEGFGFFEDDA?<;851-+'"       %.4;CJQW^dkpuy}{wtqmkjhf c`^ZUQKE@<61.*'$"!"!! !!!"%')-./0/..,)('%%##$###"#$$&'' ( (%#" " #&(*-.1357:;;;;:97530/0../0002234358:<?@@CBA@?=9841---****)'#   "#%)*+.146;>CHLORWZ[]__^][[ZXWVUQPNKIH E BA><972.*&"         $'*-379=@ACFHIHHGFFDDDEDCDDDDDFFEEFEDCB@@>;:8510,'#!  !"%%'()+,--/02246452/-)%!      % ) .257<?@AEFGHGGHJILLNMNQONONNMJJHHHGIIHHGFGFDDB@A? > ?==<<88630,*'#    "#%((),,/235877:;<<<>>=??@ABB?>>==<;;::98640-+(&#     "&+0378=?>A@???>>>==><;<;97 6 4 1 / , * ( $ #!       #'*.27:>?BFILQVZ^cimorsuuuutrqoolifda_^[YVUS P N JFB=84/*%"!    ##$&%%%$$#"  #&'&&%"    "$'+,-235999::98442.,)(%##!  "%())++,-,,-./0211334457999::<>?@@ABBEEEFHHIJJJKJIKKNNLMLLJIIDCB?=950+(#   !(,046;>@BCFIKNPQRQQRQPONLMLKKJKJHIGFEC?;963 1 , )&"     $'&((&%$"!     # ' , 2 7: >BEEIJIJJJHFEEEDDDEGGHIIIIGFFEDCCAAA??>=;87542///./-./....-+'&$!   !$&&*--0/144677889999;:;==>??ACCBBA@><:7311.,* )(&$!$ ).39>CHJL N O POPNMMJGC?>;86311110011//,+*% $        $)/59=CHMQW]`e hk o q ruwxzz{{z z y w wwtts q q ppnjigeb^\ZUROLI FCA><:6420/- - +---./1345543222/-+)&$!     "#%%&'(++,0124789;;<>?>?@ACCB@@?=;:987775653443344321.,(&#       ! $ (,137:=>>@??>>=====<<=>??BCDHHIKLNOPQPQQQQPPSTUVUVUUWWVUUVWXXXWTRRQ NLJIFB?=830+&#     #(,29?DKRX]`bdggghhgijjjkllmnooonlljj i ddca] Y$U$Q'M)H'D)A(<(9)5)1+-,,*)+%($)") (&$#                         ""%&'**-01243432110-,++''&#     #*/59?BFJLNO!Q!R#T$U%V%W&U'V%U'T&S%Q&Q$O$M#M#L#L$K#L$M#N!O!QSTUUVWTUUTRQ Q O O M L K H G G F D D DDFFFGGGHGFDBB@?><:851,)&#  $'+./13679<>??DDFHKNOQQTUWXXZZ\\]]^a`_^`^^][Z WVTQN$L)H-E3D6@9=<=?;C;C9G9H9H7K8L6M2M0M/N-N*L(L&K#I!GEDA>= = 9 8 9 7 78 77 7 8 8 7 76787510,*'%#"    #$%&&%$%##"!    "(,06<BINUZ`ekoswx|~ ~}|yxvtspmljfb_[WSNLIECAA?>=<<<<;::;;;:9:87420-+*& $    !!"#$$%('')))*++,-,---//./1111222433410.+*'$#!   "&),1358::<>?>==:86310.+++*)+,+-//0035566:;<???@BBDFFIJJMMNNNNNNMKIJIGFEB<972.+(%       $*/38>ADFHLMNPRSRTUTTSRRSTTUUTSQOMIFB?=8542 0 /-+*)&$#      #$'))*)(%#"   !$''))**)(''%$$ $ $ $ % % & % $ % $ %$#"# " " % & &' &&&&$$$"""! !"#&(**+---..,,*''$!    $(,148=?DJLPSVXXYYYXXXUTROMJHCA=99532 / .-++*'')'%$#"      !$*-158<@DHKPSW\\acfhjllmmmmnmmnnpoonnmlligec_^][WRQLFD?;874320/0---,,)**(&$"     "#""$"#"!   !$%&(*++-./.,**)'&#" !#$&&)*,//2469<?@@?><851,("  #'**-../0011133455546543330/.,+'%#                 "',159=ACEFGHHGEEB@?;9731.,)(%## !    "'+15:?AEHLOQSUXYYYZZXWVTSRRQPMLLJHHFFEDDC@?=<98731/.,('&$""     !$$')***,,++*(('&%%#!   !!$%%%%&%%&&#$%"  !$)-/489<>@ABDEDDECBA?=;;:9::<=>@AABB@?=:8 7 6 2 1 /,)&"     !!!"##%&&&())***+ ( ' % $ $!!  "',/37 : ; @ CEH J K L N MMLJIIEC@<;743/,*(&#!!  #(*/5:?DHLORVX[]\\^_^]\\[XXWTSSQNMLKIFEDBA@?><<9763 1 0 . .+*($!!$(+- . 11245779:<=<====?@@==><<97620,)'$    !#%&''' ((( )''+&0$4#:#? DGKQU[^bgkopsuvxxxyxyx u sqomkhgedca^_\[YWWSPMKGEDB@A@>@@@ABBBBAAAB@??>= : 942.+)#$&(+-0259<<???@>?>=::9985410,+)'%$#!!   $&(-1268<BFILRUY_bcefgghgfgeeda`]ZVTO K HEB<952-)%!!!"$%%% '''))**,-+--///002223221///-/,-/-//12558;;>?>??@B@ACCA@A>=:852/,'#   !%(+,/13478:=>ADFHMORTUYZ\_`aabcbaba_][XVSQOMLLKLKLMKKJJ H DC@><9976634310+!*!)!$"$""!!     !"$$$'()*+--0113 46555 4"3#3%2(1(/+.---,1+2*3)6&8$9#9!;!<== ==;<!;!8 8 6!3!1- *&"         !#' , / 1 69>BFKNSVY]^_`a__^ \ [XVTSOKGC@<963/+)%"  "&*.249=AGIMPSWYZ\^``abcccdaaa^\[ZYXXXXXYWWVUTSSRONLKJEC@ = 9540+("%'#( ,047;> @ CFFHJLMOOONNMMLJJGEB@;87420,)&%    &*/6;?DINRUY\_begjnoquux{{| }~~}|zxvtpmkhdba^\\ZWVWVTSSRSRSRQQPQRQRRSTSSSQ P M JIDA=71!,#'&")*-/ 1567:;===>????@><;:97631.+($!  "%*039>BFINRVZ^behlnqruttvssol j hc_\XSNIGA ;"7#0&+&&)+-1 357:<>>>?@@?>=;:::6764434334554566779:9;<=>?BBDEFGGHGHGFDA?<73/,)&!      " $ & ) * . 1 268:=>C E G IIMNNOOORRSTTSSTRSRNMMKK I G F!D!C A? = < ;974320-+)'&###""###$#%%&%$&&% $ & $ ! !             !#$&(+,-./1346788 8 :;;<<;;":&8&5(2*2+.,-,,.)-)-(-(,(,%*$*%*$'$&$$$!%%%&%$$$ # !   #',138<@DEIMPRSTWXUVUTROMIFC?963.,'$"  !$%()-0288<?BFGIIJKLOPPSTTTVVUXVXXWWUTSOMLIHDCB@=;;:9752/-*'$  $(, 0 469;<>>?@@???=<<;96542/,)&$      "&+-/368:<>@BEGIJLOOQRRSSRQPONLJHGE@@?==;;:;=;;<<<;<:;;9888886753330.-)%"      $(,036;>AFIMOSWY\\]\[ZXTQMFB=62-'!         "%&'**+,-. . / 0 0 02 10 0 /2 001 0 0 . + + ) ( % " !          !$&'*+,+,,*+))* & % % "         %'(+,.--///./.,+('#!   "&'*.12479:;<?@ACCCDDFFFIIKLLMLNNNNMMLIIGECB?=<:86430-,*&#      "##$%##!!    !    "$(*+-01124545778:::9:;9:;==<==<<=<=<=;::99876531/-+*)&%"    !%)-169;>@BFFFGHEFFDC@=<841-+%"      !# &!'!'")!)!* )()&%#""!        !$%&''+))*''%$"  !%&(+,,+-,))(%"!   !&*.1468;;===>?@?@AABBBADCCCCCCDCBBBB??><:8875320.-+)(&$"     !#&()+-0112122223335555321/-+)&$!  !!"#!$$%&&))(+,-/1237568799987754210-,)'%$!           # %'+.15:>BGJNSVXZ_^abbcbcb`]YWT O L K GC@=;620,)%!      #$' ( ) +++*,,+-,,.///00!1!1"2$2'2*2*1-2.3031606382:291;0<0=0?0>/>-?.>.>-=.;*;):&8&7%6&4%3#1$.#-#+")"*"(!( '&&&&##$#$$# # " ! !! !!       "!"%%% ' ' (*,-..00232454677678889 778654421/..-,+*++()(''&$"!  "$)+.359=@CDEFHGHIIIIIGHGDA?;763.,)&&$""  !#%'),.03467899::::9875 3 21/-+(& #""%!(+/136:< ?CDGHIKJLLJJIHGFGGFGHGGHIGGEDCAAA@>=;;:9676443200...,+)) ) '%#""$'*,.. //.-..+*(&%$"!"    #%%()*,.02368 8 :==>@ABBC D!D!D!E F EEFEFEDDCBB A @ ? > ;9 8 6 4 1 0 -**&%#   !####%''()****+*,--./110/100000///..+)'$#      # ( +- 0 467:<@CFIILNOQRUUWVVW W VSRROLJGB@>:842.*(#        !"# % '"'#)#)#($+$*#*"*#*!* )!) )!( ' &"'#&##&%&$'$)%*%+%+%+%+%+&+%(%&&%%$&#' &&&%&&$%%%$$&$#$# ###"!!!           !$&)*++,***''&%#!    !""$%&(()++/1233688::<>>===>>=;;:97441.-*'&"    "%',0269;>?AADCCCB@@>;:642-,(%$    !$()-1579<>>?>> ? = >>=<:961.+#($$& *-.258 > ACFHJKLKLKKKKLKKJIIHGDDECCBACCB@@?<;864210.,***('&%%&%%%&'&&'''%$ %$%"#"     #%&)),-/ 3 368:= ?$?(B)C,C/E/F0G2H2I2J3J2K2K2J/K.H,G)G(D&C%B$?!;97321,+)(&#$"  !#%%'((,-./01111100/0/00.....--,+,++*****'''$$#"           !&)*.12346788 : ; = ? ? @ @BBCCAABA>>?<!:"9$7#2$/&,&(&$'"' '(()()( ('%"       $(*-.023345554332221344555667654441-+)&$!       !&)*,.0224655776520-+)&#! !  $'(*-///2333210/+)(%#   "$$&'(''(((&&%#"    "&'+-/13 3 6 556330/.*&%!!"'(+ / 0255788:99:999877666556666767785563321-+('#      %'+ .135788;=<<= > ="<!<#:#8#6#5"3"1"1!/ .-,+('&$!   !"!"###""!             !!"###$$$% $ $ $$#!!!     "$&')+,.0000131012210101100...,+'&% !                     !%()+-../ 0 1 0 1 4 35 556 5 4 4 423200,+*(%##       !"$$ ' ' & ( '(''''%" !# # &()**+---,..,+))('&&%&%%%%%%&'&&&&(&&%#!     !$'' ( +**)))**))()((''&%$#!                                      !###&'&&&('((&(&'&%%$##$$#""# !!        !%&)) , / 0 1 2 568779888888666 5 4 3 10.,*)'&%$""!    !#$%%&(()*+,,- + , , + + + + *) ' '&"!   ! $%&((''('&'''('''''''&&''&'&%$#$"!!      "#%&')))+,./011233 4 4 2 121/.-,+))' $ $ # !                      !!!!""""! "     !$').0246999;;<::96864320.+++('%$$     !#$$%$$&'%%%$ % % %%%&&&&'((('&&&&$##    !"""$$#%&&&&&&%%$"!   !!"$$$$#"!! ""#&&())+,./1236789:: 8 8 75222.,*))'%#"          !""$% % % $ %$""""!"#!   !#"!                            !" % & ' ' ) **,-/0023 3 5 6 6 66656777988999875430/,))'$"                      $ ( , 0 6 : < > B D G I K M M M M K K HHGDBA@<;;75330..,+(&%#   ""%'&(+,-+ , ,++(('%%$## $ ##$$$#$" !   ""#$$$"$#"#""!""!    "#"$%&'*)*,,-//.--.--**) ( &%&#"                ""%())++***)** (!&"&#&#$$##!#"!                                   "'(+-/2234578:;;<<?>???A?@A@@@@@@? ? ? = ?@@ @ >><;;998 753 1 0 / , - **(''$%%#"!!        "##$$##$$%&&%%&&''''&&&%%$""! !  ! !  !   !!!#$ ' *!- / 2!5 7 < AC F IJMOQPQSRSSRRPPQPOOOMMMJHG E DB@=;:852/,*%!  !!!"""##$%%&&'*++../222010//...,,,++*)))((++-. / 0134456679!;":$;'<(<*;+;+<-:.:.:/9/8/705/6-5.2.1-0.0./,.+,****)()%*%)#(#(!(((((()****,-, - - . . / / 0 2 000////0/./0/121222110//-+)'%#!   !$&')+.--.--+*))'&&%#"" !  #$$&(((*+ + - - - 0 0 1 1 1 22 5 5 6 7 6 6 9 8 8 9 9 ;: ::989886443 1 / .,+*))'%&'&& & %!%!% $!$ $$$"" ! "!!!$%% & %$##!          "%&())+!,!.$/%.%/&/%-&-%+&*%)$(%&$"$!##"               "##$&((()('( ' ' &&%#"!! !        "##%&&))()))*)()'))'(((((('%%%%"                                       $ % % ' ' ( (' ( )(ukui-session-manager-4.0.0.1/data/logout.wav0000644000175000017500000364104614277116241017324 0ustar fengfengRIFFBWAVEfmt D b data-+;Eu'!n" J~!$.ơ-!8,G"$"G=!$"asϠŬQ!Fdg5\a ]FrbjZ"`=!qšL/,9™ :L";~!`;!XǼ!a!c"+ !D/8!5Ӊ@ H! +xqP!V "ZqU![R@(!GO)n3k, !!YN!ȋ: 0o!kw!3|| ˃!i9" a "p<#40ʎ!e!!SZ!I -ll#ai< fա("{ g!V!YIF>!>ʟR; #k!ji!]ܖ"`U!E>!+$" v!mء:""66! !Rš!">Ԯ "l!#"D!\/ѡ칟 1!O!ײ!a'A!LVEv)ء!| !֖n4ߡدUk!2!UI!ˑ" `/{С _!)4!". r"%g!k?}!Z_!I!~x (H"-ꡤƃ!6 Y!ݡC!P"p "? `!c%:Rס>hMӠݙ!8! b!d9! "S$!,U١"!N&fzW!O R桰$xt䡲_d":;"'h0 ,"38eҿZ|oOR"!tT NH"Ri!Kg!\!$⁡%}+G"6 lu)!Sɡu!!=X g!fZ!@̴~g! !lטt!J x!/!kX$*6NP!ӂ!gxࡂ!M 5("b ="\Q: e }^ ~ܙ!2V ](%"!!pv}!bz!J!G"y6 |!=s!X8_0W}/!Q!x`!hf!i!d k-'W!-Wp&!K{! m0!@28fE"v+!"x$*@Q/ȝF#4^Z5 (G!کQ!.!&\>u' EsDm2xv!5" sgw."!pL!US 屌!9sRҡA".Dlɡ"2-"nF6\ sg!3S򠹖}!9?>_:2!sOy", 5!!?!'CQ!$L`Z6=g!X J"d!.U@ X!l!d!V~*1v!K7I|?DOz{!" Pbӟ[ }+'ҙ8 60X0 1";!ª١@ucX!kO24֏bP95B55ԁJ5M5qoi5̭Y5۵+d3Kބ[6Op6bQ6ǁ_6&un6$*mt6o&K6?ǕQƲ6P6w+x M6P651i7b%6dS7 657Y7?Qd6^{755zy%76-5š6 %P16>3{7 +5)64vԴE柷ڐw0io9<"j$:%y`6d.6WV47у70Т 8=. 8?OL$8;s 8`79j"57Уda6i+n)ⅷE8f&8;8 Qֶ|38:|$8m|7 NR69VA?K]X%jH8[Xj|_Kƫ털;ݺ "FÅk^BI`D,i۷a4@UR7_7H7p8(#I813858~.8; 8q w8̸ʐ8չZ8+Z*8$ȸ67> 6V, >]? 8܆g_NJ  YNv*Ƕ C@K +'bz9#,I<6 `EyO8W1|8fkB8sM9fs3 9^ 9k׊C(9*69`Q9]8W988ٸ8c->C87I䷓0{V4YAEo fڹӷ@K乡➷ϹQ8`ڹ848ֹq9)й<=9L?[9gf鹢9WU8鹲98۹ 9w˹9͹<-9N,Lѓ8o6v ]a6ۚ7J4hy87ߩ> R6ĪW 7F񛹴7*$7'7X(iʹB#Ϲ hи.U'⬹׸+@(5&@ 9Aq҇kםc-FOhj$ %e>(yd@1Źcu.,a8$I)(չ(ӹ>viyϹ8H.{+&ac>tʭ h^o/X]CU#t-" 3K 0jD돹c帎V y9չ+иTEj$/[nHu踨_ X +h,`ٹBùڌ"V̡IS7-7\ð7(`8Pg8(QFj]cTa͸m·T$ӸF l#]ƸCDR-m a{FN?nz@%@9Pg9w Ź 9㹼8̹k8"~ѹ\77̹&88Y8VڹDY8w8kW8q9 1\<9ǁgx9Ț 2M9>=9ZO 92R-׿9Jڳ9^Hw9I䡅9KmB9e>`9781¹겫8' չsI7t8fx18I-$b`_Ng 5+8:18K9E9% u<9w9.J9ް9IF9Z>&99ŒϹ)9ѹOҤ9-й-]9X<9<ϹH#93۹}8OzԹ i7 P7ˏ]Pֶ8U߹L,99nƹ(8^UF8ܷ 82DG9b!9k}9aQ9❹.P9׃r9&E $9'P 8I8re8â^W8ps"1wh %C?DW0Nϸ%ki蒹ױb朹`ڄc㸅5.6 <";C`W8븂"cKPh9 )9˧9s߂9DXP"c9\894e9\Y9LF9o7Z9  Y9ը]49g9hh^ 3s9tA49UaU9 Tj9&ud9Q 9;Q8^YŰ8H= 8_84]fˡ88(8'L8t_k8%p8%9#'69P".o9)iQ9Glj9D?c9 ~9uWQ9NrqXSQ9h4V9z-8풹nm|8^Q=8<> %8W7Lָ-_66Xq7d]8ZH88P8B9fPp9`4#9gha9dh9"qD9GG9๲9Y߹^A9$k9op%3/9vp@8pIY8N2e}.$wGQ1ԥ5t8H|9 +8>.9":?9n(:6.C:Zݹq:6/::DU(;eP;p9j~;W萸*;w8\;[9;ΗM:;T:V ;?: Kd⑼p9ϻrjp練ɬUuu<Dw'\Ɯĺ \oԙػZƪ93һQa:mػ:X޻];WoS6%;̵eR;?G{; ;ڠ;,;Ի>';`;yx;4a;K:&;,;X !';#;n#;`"w; ;I<" <I<z<͎.<'NA<ɻW<Cn< n>=_؞v<ן<4 <<ʻ{h\;#H;\;LJ{;:Ӑ;Ov^;KY;n&5;z˝;m;w1 ш;0G;؁Zq;] jfp;~uy{Xq;tゼ3~;k !;];jhr;b";Qɚ;̝һ; W<;5;猜"; <"<⌼E(<&8<GsKH<K[X<B9i<%C5x< <9$߻Vh鄅<4}zj{Zqo? a O¼ ۺż;wBlƼ33ļNx:hǿʧ ;ոQh;TK; U;r<79=X\qn =be =p2L=#}=-am=ߜM{=OZ =ځ=)cػĪ9{`S$ѻc;1jG#vUJ{&yY%@U"?J U9(&&g %+ݼ0%)ܽ<׻\{Zϻrֻr)Ybv*Ż r$2;P2F2;j)<۞zd8>=;(=2 <= C< =^tԼ};ټW;ZۼL%< <ڼr<}ռy*<μ A<Ƈļ/U<%0nc濼+ü/˼ʼ>=ּ+8]@=cL:4I};(;DT3/ =hDh@=`Rx=_e<`~A0<ꌼ<, \< e1`Nݻ9'8QZ /:;@M;'8ț;PS;xo *%JF+hͻKU߻پOEQV"bP{廅Nl_I:껯Cﻃ;bK2{E'œ1a)F'kUѼӧƼߘP{'c D1.;n%\M3Q2ߺ>':HX;$N>;Q]AU;E+X"< e:<?2u<y<IF *<f<*߼;#Y;! <v%Sta<=XA'='<n𻄾aݻ,eüNǕ߼t`ɹj2:] !p;0 j; <3!<ɴ. ѻ<Ȗd77;pNX;O\H;Me"<=Kx=`nio=if^t}= [|=y^>m{=@`z=oy=aky=$oy= D y=`vx=v=ӻ1s=]&o= 3i= b=`3hZ=6 H_UR=*^G=r<=惼0=e[w$={=řn =M<$dz?<ȿ<5˼4<ռ;#m廰@: `CWe7xE\dԺF"HxHκßHhGYaE-D1(DúڻdDG[yD 'E{[D BNrg?lu:Dw52n,/_2~(Њ-; 9;A@D*<f< ӎ< <.< $G-7=1=i;;,={Zڹ&=u =)=|L=} =ףS=(< ;<<̼!G;.ݺ;X;zO\: c,!޼ԈȻN'Upg7|LŦ᭻Y>tּ9;~,;bcYp=t#=&Ӽ=/=܏'=I F=ϑ= TC%=[dh,=aS5=EeC?=(I= S=F]=Vu6e=޼=m=)~̼cZt=l_y=*[!z=' z=gv=:g o=LMe=9xwX=C,I=?"K9=]D)=wM ==bjuhn7f((]A8jS{Y6VH7o*>ׁ 4͑$)IA_`SM缌żNa>ߍpM}dܲ2>rirƻZW͋ :=j;s;m1<ɾ4` Tg2/Ivs?Q3DK‚1T)>Zى9]# ^낽t]Sc=X6X}ٍPvF[ pP8|hi*b&^j\PM\c\ż9M`gieQ]nuIxq$G @3 ԻaVؗZazP;2;Q<W7L<$=,c3<R:rU[*jcIH0x"Eùﻂ. 9>k9..9@XA<2z<̼ <Ȓ== '2=1=?=O;hB=@!<;= Bah=CyT=[,=Ƕm'u;8rAq6">3̼\4>z@ ?>U B>/bd;[=>.<2>d< ">`< >{=-^C=ي=[=B=)ӽ3M=vx=󽙋=4=P j=OG=~i=~%=OI,Y=&|2 =y9d=?=kEoa=H_t=cFX==a=.Ě=p=W`<=wd1=R½+ =c= =nh=Q=:+BW=@b=$DM4T=>f{)=sP s2F̼Lw 7B4Z&.|d=='ǽ#Z`Z _](<*[31u9mV6tBN9<:و":7]CTd2w;rz*%itj>6=f=a=('MA=ގH* ; >L /C >n>r;N>~;'>׾9 >6Q+{ >϶>γS>-ټ#F>>H > k>8Wk $>WJ¼&,>FF->;+&>Ҹ<>)= >Pq=a=-=]==A=1=<=m;=uVz= =M,W=L]=߃Wr=FLw=}85>p0#q >j>X >0+ڪ>uS>ׄo>i>> :۽QT>L >- >< @>E >U X>8>I>qý>rpV>%M2>mb >܆Hf>Q@>{Lc>[>HoU!>D5?(>y掽).>U?1>֙Ǚ.>•%>.>k%>ޝ=DE='Z=>{?u?=58 =>]vn>inںK&)>C<ݚ0>~I<Ş3>=R1>G>=4)>JK='>(.>= >g$=\>nap%>`37>VI>Z>'ig>8S.)o>*sTq>cCl>(Nb>@̼rLP>6> ̷jk>;=vS<%=<{Cj#Nw$U{0DV8``R]$7I:͡>Q)"2Nw&;6&1F H@r;A\r<n< އ< =v=wQٷ3=^1нZ/ =r >=]= >k=<7 >ՠ= >ۃ=>&= >JÖ= >D=I1>[^=x>{7-=Q+>tB<#=>?:n:_>q9Gl>BOr>8r>̼gSk>ʼ*`>]ԷzQ>xڜ?>Bw+>2-0_>ɻs>@=y:=;=j;=Ĥ;=+;M=oM;,= ;n=PC=ť=@z=\i=D("=_(<0{Qi4!^᳽`.;ܞG9,ϣ<*mH!l`旻y < <Ō<Y-=I=ͽX}=ߥg=IIu=ů=/=j=2ȟ:=\.=a\[=鵽Y7=Ž=>'۽g>=l(<::<}`=B=s=z=L=R=~=`==Dw=0==-k=w=DG=#=2*=I=}= R¼?=>' =U[;=n_=oZx0=Yu^=iJ=Wh='=X==r=BOC= =JbÕ=/@=Eh7$=oys=K T=cQQ==&<(dF)=m== .=ճ=a=ڿ=yu==9=-=R=HR=o=3x=)=E==M=0.=OL<[=:Z<=wy*;; >-b> >ĭ#>α >2>TG>ڻ+=2h\tC=&=nK$4=>:TA<6=>W@U>l'c>ײ~e>u!N^>3O>CE;>ُK@%>%H>P8+= =(⼕r=xT= 8=@cV7`=>o=p>2ؚ=,>=>=d>!=>=]>=i>,====ԍ=E=m ==G=}===â=&==^=9Y=O=r9= >(=1> =ҷ>F=A>=h>(=- >%B<Ϩ=Ͽ8;i;4:ݼQΘ=q޼=vt =Nΰ=1 *=Cv=!EVQ-=?d~M~s>`<5=x];K! >I>cO>Wb#>WL+>7t&II2>~K_6>,Ojp>6>/}uT0>xh$>hst>W=L*0$=FGХ== $G_=;"H= vڽ}?:). ֽ)|ᦽʽUH4mVԼE-C=J/!k=&̼k=s =F9*=<=o>[='/>A=6 >>m΄= /G>Khf=+I>B=SE>=z:>=SK,> R<>^==$"=ӧ=VC==T3e===Ϲt=z='e==^=Y=U=1R=n.G=n=0=N=>0=&=k<<і}ý^|G8׽Q?; ZFĽE Sʽ !ֿϽm*ҽ1߽0ϽȽR!/‚S%6I⤽)7ü^|vfCJ˻wٯk (>9oV.>d+>>Խ >%.<==p=/p=#`ڤ+=zؽ(<@<ΎB <9_H<;;UOۻY ;D\d< :g=8Q=&D=Zߢ=ڵ=b7\=*Gm=܊C==q=jR}=턽y`j=e>%J<ڐ>'<$><[(><}+>K=>.>V=\f/>e= D.>=)>(=q"> >o>e>>:i#>/6=&>=!>ٸ=>s=>"E="m====Z=Y=(:z=Y=vF=y=Q)=s=v=5ɽx=޽|h>50> :>{*Ǜ=>Ᵹ:>z/>} >% >=vجĴ=X%ۦ=<.j=}KtԂ=9O_|=#2Y|=ټ&|=D4j6u=M;lf=/< S==>==+=-#=='=V=<=<*=#<=. "=5{8=A߼- V==y=*C=91v=& =X}=伒 =X=$CK=V=;=Y <(n=7<6=@X==A=<_=}UhUL,%@ۯ!J/$`(va.Ǘ4B&;)?(<?j<9OT<.|"=EO{T=N: ߂=H=ҽ==+=-?=+|=.aXZ=4&=^<\dX= >Qሽ+ >e >5>gZ=?2=%{3=g e=?k?=r @=Q=ʺߩ=^<'=; ==U='=,=Z==>==N=c=}Y==|-=B=a==i?=a=^"=-=/=L=ѷ=I=~ =ӽb=sA=޽D==n=p/=j_=~#==&=(`===s =h=K =]=@#=T=E*==hx:==S=շ=q=L==Ѩ=>==ٓ==#=`v==%>=‘=) >=.>Ty=](>ªq=0>@C=y5>=3>S W_<">j;g>2:C>4=Zq=$+/ř=`*r=䐻:=i>;h=9<,R5j=1KJ=V=㺒=ER0؎= W>=$~o=ԦO=c&=?cg,<ǽkV=2؛=୽Ф=I뿽9='ҽzP=9){=?JY=$7= 齦= ۽a<;Ƚ{ =>Kϼ$>3}f.>D5>WzWb:>u<>%|<>8>C72>3Bt+>ytD#>DG>>T>f>G>>o9 >>ꊽr=#o^=4A=y$e==?=9=:=0y;&J=?&;s<=Mi;(@<$*¼(h/:CS#E^fcyҎwue4B}i$}rӆDd!3SOu<0ȽmXѽKݼ.bսDA2ӽsP;˽<\;=aP=މ=LQx= Q=P0V=z;=;< I>Y8= >9:=>rD==@4=A=l ==՚<=ȕ[e=cɼm=qO=Nպ}=ҽl=L2f=hh=L[̸n='@t=* y=/N{=꨿B}=Hvj=.P=1~ӻ}@=<x=l!=7*i=>gY=VP=y=.===Iy=><f=23D>A;.>p<=>|= >==>F*=0~==1=@====W=^== =A=S)=c=3=Ed=е=VI==*=A9=.=h=+<=5i؄>dqA$>xO="*I=݈3=rއ=lx==:ae= mA<Ђ= <>W`<9%>c<*>e'<->h;}/>u%;V0>ܵc:52>c83>8I4>{Y:U3>/ ;l1>2;= .>S;?+>Ar <)>{s+<*>ֈKz<4=1>@i6=Q8>:=9>8z=U8>F=i2>=r'>=>O=ue>P=Q==[=Ǭ=6֢=v=e#==G=={=@=)<_=9<== <8ʹ=T;aԛ=D}:K=:F==pW [j > >y>`&>9W >Nn>u#==w=7=8L=R=5hUa=_x@=wX'=`# = 9=|=.m,!=W)==2=9{!w>=(J=ͽS=IK U=U5AH=ީ+=r/+=j-<Ga;u2(g3b 3SK'L~&aӒd,3̄5sCiW_;೼)ې<:>=<18;=m<}wȼX>8Ҽ!>d('>+ (> 1_&>_"!>0_^>4P>W>,ZνV>xܽ]~>l:p>J > ὰ >Qֽd>ǽ>l=Zh=i=~↽=bn$=vM<Ԓ=X/ʀv=>wLN=+O.=%B=* =<:/O< F.,=o[>4n=]=_-==M'=Pf=@=0==K=@==-=N=m==`=d̤=\===ݺ=$d=a=I= =t====WT=D>=Ab>=G>v=0 >=6v >C#=] >=9 >}!=+b >> >̚> >7>8>>_=>:=M===q=JՐ=zg=[_X=q=x9=;O=I<Z7=s<;=;P5)=Kpt =<>Whq]v85Ľnֽp < 꽋=ʥ<= == L=>>= >d=Rd>=l>ା= >=C>۲=U >ɻ=x>==ں==7=Ž=&=ܦ=Y=n^=fϰ=Is=攱=S=W=<=S=*== =="==1==LN=T&=r=== JW==Z-=c,=P =W=2 <˵=<=Bf<Bt>=A=88?=ϓ0@=T7&o=##ތu=1 6=M=ѽRF<=<`=&8=X=RL= ==r==~=5f=q=G>9=ٯ >=!/> = !>=U(>a=R->H=68.> 6=X*>)= ">l=+>(f= ><J=x<k=;UL=$q|=O5=b g6!w=5`=ڽG\=׭,g=7 |=W!2,='= )i=$=M7y=i4 =@=$Z>LGS>*F>08F>jT>9 >9ﻼ> m =Z${=g7o=ѽ.=@v=sq@=0ǁ=@֦< J <Piߍ<,yPX<L/1-)''2K50 TBLNνZ38LiQ\S}sgH摽ݽ5;8D'O~=`frK=D>NY=P>n= %>Q=Y(> c=O+> =k.>=)0>c=0>=|0>g%v=0>\Y=1>lO;=2>= d4>0o߉<\5>;w6>Õ4xr8>GT;>ËM>>xTXXZA>2IA>.s#>>D O<7>5T<,>l:[>Py$=s=m ml=X<#=펽 =D=j=qx:=O=DN==`&P=Y=E== 7==*==ǔ!=:=m===!=W =d.=)>@= >W=+>fn=Y&>=n5>Om=x)E>=uT>=Өc>=r>= >9=݄>U=7>9=->=ւ>=v{>C=BXm>U=]>{=BL>>9>B >d&>>KW>2>=R> =k#>H=Z'>='>=g#>n=>Z}=`>{=&=oƒ=-L=`=,=]=9z=!C=l=0=:=9=nr==fc=n=i5X=T=Q==YS=]n=Ĕ_=Bq="]o=č=y=;=Hq=z =N=9K^=R ==I<={&=jt<1~e; ս~&(Km5ټ[B>5 <"5t(8)3zQx=-oΥF#'M\ASP㑡'OæKD49^T-jBs@` 뽽K׽C1! E T>QM,4;\;ͽ ;e1ŴQ+> >;q->W(:>E>bO>#໲W><=\>=*\>!= X>2=kR>5=&L>#.=6G> =sD>ͬ =YC><>D>6< G>\b<J>5L>m3%AK>ͼp H>)B>0 <>q3>\Լn*>V >uNv>v;><>= =y=U==g=_= ==^+=~.=n=ɷ=w3=%==o==1R=K=V=k==*=3=,<m=.<9=D=0 k?=@b=H#~=|$ 6y=<*@\v=2,.+v="14y=3]=ze5/=i5=Zm4=0@<=*3=`!=YLW=qj=F/_='D2"=pɊ? >"PI>,=>t>a>30H<5_>>=|!>= >$=>M= 2>?='>%=n7>c>cG>)=xV>2l=a>= 6i>t =Suk> =g>k=]>?.=SM>l2:w>3.=:ɽ=?x=k=V; ችt񃽂ǒga8Apc,sTِܻ;i:;E</ֺ<O: TѼnl4Hb;?·}a<=LP7FPџAS[i ϼ[M}Uo D`RւÌ2&4/Z, Ơ Ht "Ƽ?Εuu6C$uD;iRFV<1`< <cƆ>Z2`>FP%>cT,+>X;Z,>WV)>I#>4'W>C> R>ǼdG >è>c6Փ>H$:Y><>Dwm<> <Q>`<>N߫<:>/< >du<|>o;>Ԃ,C =HEu= k=b"<жL/;ݠnC]=$0̞G E.RqjDI.Pżx Y|Żǫ;#9>/R>.#>1e&>Sg(>Uwo%>愽q >~>fp>b_6>I p >'/;>5>koC=Lʼk=L 6=Èp > .g^>\Ph>`Lt*>N8>SQ=D>3J/ P>$~[> ϻff>*&P n>;Or><=r>ƐAZB=b>=sU>=$E>=1>=o> >%><>P=2>֯=L>%=>EZ=6>r/=ۣ>S== <=M<3=t&=6 > =>] G*魾MX(̼% z]`Wa~2tpy1Dta#|v"BҴnvV?7< {!xE;5p\><߽8<ŵFUc=>!=,>@=9<>cd=KL>T=,]>=Ml>˽=y>71=b>>B>>>E#>>s1>4%>!=>(>8"I>> S>~W>`[>x>_a>'m>b>>nn`>C> PZ>->eQ>Վ>qG>!><>ێ>2>>D*>$>`$>>>̃>l>"y>U!>f>(>KR>a2>=>>>\[)>L>>X>p >e> >r>+D=~>CW=e>*m=Ŋ>|>u>@I>>}>:>ʥ>s>$>>U >ѓq>=YfY>u=L>>ް=!>=a>ۀ9=m_=/;=3ݽK=R A|=?&"nk=O2f4b=>(j=*'潲L==6&轝2=8F<zG 㠽%=_ӽ?@Rͽg- ͽh$/ѽ%G=׽*U۽o/,޽'8ὒD/Oe}6_It-J<YILkkϽE [,0BGI;u༐5㼉м"껸!;g=(>=qF>== c>*=~>B,=>;>ڼ{>k">ӜS<>^ݽ>pr>gp>xl\>G>)4>Tٽ%%>\/>@#(>ꏼ>u&=x,1>=H>>M׾=aH>t=O'M>=4IL>=E>X=9>gp=?&>c.="u >7=>b=Rͽ4=`n h<'^8e,=l66Un"2vA k }Pֈ[;4щjX;I.=oص=ŲG >vُ9>/ȼZ>lDl>j}Yn>^Og>쀳gZ>ýH>½5>Y!> >}=I{=\U'd=ӫY=h1ɛ=Te =J|S=˽3f=-@)=9z$p== >?==q== = =f"=>l->5>C;5U>tO^>[Nh>K4 > J>a+`>`?>-⨼A>;>/=>yn=>=>=v>=d>u=Q>W==D?>]=->=h>!#=8 >uPӽ >(-=`H2ʫ=uQ,=%iE,Ƚw$=ޗS='h=a?=}5W=SK:=:_{Ƽ%o6Ľ?߽TֽR潦ཋR׽ٽ8yR<Ma=5+>>=J>p=q`>ؿ=>l>a=wQo>bD={g>6>S>E>7>L=> >>s >)>>/>>d3>q>4>=s4>o =ж3>~u=|2>y0m=.><+>i5J:)>+:(>bD*>޽(*>j:#>mн>ݏj=T ur= _ƫ*>8L5>2~T0>oU>ν=]*~I=}Gp@<<IHƽ͐ Sx0$@νI0=5 oyӂݟ'"Pм4}D`VhK(~ZQ>tҼ(O[f ?eC[oFJ;TQNdX}ÍdVE6= =Z,>Z_< @>=p+J>tHw==T6>O=)B >={=!=Yd.=={M<& =hw;=edJ >3>6 4>-f>ԱY>']>}>>ᤏ>> >֏>֩>/[>6T>`>oV>Œ>~>?>>@>E6>-V>O> v>j>yjz>5>D>=ǯ>B(=&T>)Y!6>@Tm>m.zQ>;{<2>BP;>E=K:=n;Q=<5C=%X<G=z= I>c=h<Tm==+Z="="X<=b&>,=}@>6=D>Q=r3>lCy='>^==X==P=#h=Y >-=>Q@=,>f6=y2>j=)->I >K>D>`>6p>_=W>=>qa=PH>'=">]<.>Ҿ;=Gk>E(=>G=mn>]=}>(d=f>'[==>L=֑>nD6=~n=W=iq =v <ŵ )xI>e舼"o>z >Æl>f>u}>?3kl>'XfU>-<>D&>6nsl>QOR>>9*fB>I+$>\A,>1y+>-<# >'i= >Ņ=!?=J=j=.==y=.Kk='=x =)z= <=p<_>=>R= S>n=a<5>,=զ=-t>=>>Z=+B>=o>=>6%=> <[>Ӽ>щl>E>,ܽ7>G!J>?*>hi=шD=/p9d $IP,voVTYiǪTd31~Kt¾eeȾӺȾF!þ oL3Ν+u`pnB=x yzfʽqI_uhۃ5MDOCrÕg4PRI0S;M!=<=f޽=6mIW>mPJ><4=Nz>=>=>V=^>I=(L>V=>=H8>Ŀ=v>{=f>3=di>O}=D>l=6 >6u=Ӡ>=>=V> w=ָ>">H>U>C߼>N> >>+>->1>> >z>+>CM>>wR> >i>0W>hߐ>>}>y>4^>\>BG>Z6>^:>8>3k6>Co>[3>2>|.>=C%>/|=4>/S=>0> >t;$>Bq(>K{5>m=>`O?>ԼA8>(缄.>'>P^r">&>罇>am=*)=MB՜=}Wh^=ctQ=fE5".*|N4+cTa̼PqgTH3^ nwj*vz];Kl3Tb5_b.%f;ص< <4g=ۈ<=1?J<> !<6>#JU<# >B2 =>Ğ2<>]<[=}R=>4%=)>F=I4>=]C7>3*F->qQ">Uj> >ݽ?3=P=y9=va= E]=JLVb=*z=7E"n1$t~!mlCa#1DK><_BH@=8A=D׼= LB=[>v">-݌=*0=w(=Ҵ_y=絾D=5#o*=ɭ.+==R?=yky\=b#z=vPdF=m"$쮕=K=ս=_Dx=}@=*= Gu=m=kqu>%\[=>=H=G>/|B=Rֆ>Q=d>$j=>(1=bۄ>a=>+ = k>=Q>x=x=>T=4>0$=@&4>'[>';<>L:F>c:MR>ُ8<.q^>&8I=Dd>Bʖ=4b>=3b>\=^>,'>jm >@>՜>pV$>c >O>*>=B=&=ϼ=5==<=֛<=<{=WF=;νuG'ȽK_kH:VR\A@Od7䛚ɉ*C(>'> [H>=v+=F:=;</&2j:K>G4@3s>C:߅>2ӻ}8><A>"T7=g;>]=7>=p>GD=M>>̯!> >]=>==R==Jq=!=I=8==q<~<-QY=0󻭟=u;<)cbg.>Ľ<>4E@>:w7> $>M) >=B߽2m= O=69=A3pk=;Pc=)*v=岋=Iֽ=Kdo9ۦ=v)=w=C=l==y+>N=|Q>=m>=>4=V{>|=L>ԃ>}>>f>^@ >N>fB#>D7>1>m!>v>m>k={=:=}=%=+=pF=TxQ)=;==}=/-=k=m===?==q=Q>_=" >c=>==; ==ᣚ:=1+[9Ǭ)ٽ+)9 [e(ܽF9ٽs07˫Z |M}^6>$;;[U;12x>J >A>?>]>s=g>4f=Kc>c7=1V>=F>-E=7>4=Ӂ+>_W= >GS=WN>=]>W>e>>#>_,>Ξ;>E:>fX>gD>9u>N>>Y>>te>>Ep> >vw>'Z>|>Lۭ>0M~>r>~>+>z>H>*n>Q>!V>>&3>%>D >Yz>t6=T>i=->J<>8$a=c'=AsV =au=Nbs0=J`̠@=;U]=IU:<]<98o^ F:`vJax`u[|\l$UA||!(􂿽$05N,GI_z@l}e; GJWي苾s]So$VI/I[o~|k%p@fZbҾ9Mݾs>߾ÓM޾`޾gۑ !(Y?PoOվ}?RM33r^$򽣴c龽iW09wjp(CL?9S+v-ۻ{ [U^!.Ѽj#[_cLeꂻ1<֔?">Ţ >F>P=> &8>X>?W>p>cq>\>@m>>s>y>>{>Y>Dh>Ď>-v>'>^>}>D>g>s+>V>8>znP>>PP>r=$O>)=4J>K=VA>j>8>->s0>%>x(>f2>u>N<>>EC> >oG>c>I>m>ؖG>}>E> >rGC>h+>.C>Р0>XqC>=A>'>#?:>I>e+>6>$d>?"=b=}m=#= Rgt#ua++9J1vW<1X/3+SԽ&-P0(oCCνI諒`G~YtCȓ=;,dM uA+1*u۽^+NdȽ<tǽ/pн\CBݽ0;&K |\3L7{ nQMջvq A|vd'k;. :L.5<&<+<"=N='Y=hy==L=0f=n==Ý==ң= ==e=U= t=*UE=B~,>=I>?=$`>==1l>=i>&=T>e=->&=*>'==]I=W=5=}7;M =n#Z=P>=eyl>z=y>>2}> >Cz>Dx >3r>L=`>t=)E>y}="> <">5=-n4=L2=Ȕ^=Ľ=?3@;fEߨCE@w DHd5A;'?U;>;c:R=g=w>1=$Q{ zO gMIº Ͻcl<->2;(<=A`=8 =|lW>AB>8>d>HZ>5>Ky>ʉ> >o>ь>> >i>L> >}>>/'j>[>VS>>;>Iܖ>&>!>c>y>r>WV>{ >q>2+>V>\ >8>F>>y=[=hL=9=ჼ=ϩ/=_*!s=zg1=S<w2<=< vk:֪b)Z۽꼓iTط&Y0L8*,O=BWcI~q5QZd$agqY*?uw/3?HkEq{6J0"Z\EyZкkǻPyR4hgUdv 1Bѽ*Iɿ'Vc"/ #v^@ӽK[TĽ+9Ž[̽`]SԽ3ںpֽ;Խ5;нO;Qͽ E=[3>=(Q>;>|g>>,s>!->Kx>.>w>l'>t>N+ >qm>+ !>bd>t*> Z>`6>Q>">>7H>=>=>;_4>%u0>K$>>]>{>.=;= \=5 =6„=Y=ywK=D=M=@=Ĉ==:=S===BY>^!>G(>u1>F2>~5>9>E>h=>J>҅8>\F>&>y6>>7Z>=o=Ǥq= =xB=>=D'>>:>z0>pL>SpC>}tW> wI> V>]C>I>6>':>$>81> >t3>\>=f@>ʪ=W$P>Ed=x\>f=ra> s<_>:W>OyKJ>޼Ü7> 0>ɃkJ/>`P A=go=M=R=z̘>=j= 7c =I=;=18<=[.3=>_=0=Tq==g=0=;=S=I&=Zt=Ǻ?>=ne>>ZI>>霍>:>>v>壘>gk>>C>>Z> k>(> >=>tl>cEL>>K>?>=>,>5*>|>K>t>>^*i>=[>1ް=XL>z=>>ne/=}0>=<&> =.>>4=>U=ģ >Ё=*>b=9>=PRI> >V>a(><`>ɉ>>je>K>Ag>0R>c>U>5Z>wW>ҩI>US>3>>E>6>.>z>V>h~=4= =`?=,0=='=`K=>=~=m=<4=<|OuHǣC ncl8 r_`D~ၽ8";vH8#Ƽ9H)\U,g3*-V;@K%`Y< e`<<^Xl8.G0mۼC7!}=R  IQ7˽ϊ4BNzýqս ڽнH A?i}: {Uɼ1- EG#*QC!!G[6\ iB4ʽ5Ƚ:k9r½Y36 .D*5-M';A"xOI+A޽_u ν'z[4iHyQogu*27oz^j`Um?^w>OR"H※7'@< > F=aE><=wJj>=Ԅ>=~G>=g>>=[/>=bǚ>N=;>>`>T>>R5>n>Z"R>Y>ah>Iav>p ;>5>p@-> >UY>Э> >y>W=T^>Xξ=G.>s=5v> 95=|^>3:9>&>43>K=k+Bc#=s"J=3bPD=u花=m{kdߧ=?=pͽ|%iwGK"ܢ7 L cqy3([h(e v4[C,@CU&X[Zvh1ߜ\*D袾茾䠾ىoWeZ;5ʢ}Io8j^n&KTLN6+Y&fシ ۤʽ5ֽ٭iϽ䚽)ӽKDٽܽA۽XOmrԽ{qOP >,<3>͍=/C>_=O>> W>2> Z>fN>[>h>(a><~> o>#8>9>>h>>3>>^>U>-7>^>Xk>6>I>>n>>>x>d>2>>j>KO>>F>A>K>>@>B>->z>>&Wn>n>i>c> h>53a>Ji>:Eg>yl>Fn>~>nOm>Y>d>ؠ>5Q>E>6>Ғ>s>~|t> =R>=+>7=>_$=Q;=kZX<:=Jtsb=| ]=5#\p<1 ?9 bp>&m<&,> 3<57>&<[->ʊ!<>8>=W&r=^zl> >Z>N,>c[(>>(>"> >_>>( >=5 >=>w=)>0=9>l<=8Db=\~wA=tB= -=gy%[=]ӻ4'=QY<H]EH&V!4bӼ(toEŇ1;kMsXxn;!!.=">$=l1>k=Y;>=uC>)=I>~O=J>=E>=}C<>(~f=1>r<O&>=ۍ>+/W=_=Vs=o<+B*ˬ;:/ oV-Fϻř&3ԇㅕ: ;l;)< < ԽL<Ͻq=ҽ( (w\R6zEgM̶ z֏  JքڽmVN4/'O41m0r( R\*L̽G:h6;TD{;pp;j;oE >>T:>6*>9>s;>sW>I>{s>@Q> >ewR>{>2 K>>>)0;>{>Ю#>l> > +V>I=6>h= >SV=N===g<* 6=]J&ܽQf<5"pEF  % ڽG1!+: pS}h)u+nuTҞfMק\2JXU |&ŽEc, zDսu}ͽsMֽv} > ju󟢽l ( ٚivamҽ2bͷÉ/SpE3W$WO]<]ɼQ =v=!h=:oL= <7e===EA_=>-D=C >HI='<>*=V>=)m>/='|>'>> '(>D>7>y>]>>mg>T>>M>9>:->2> >(>=G>=B >^,==<=<=xl;{=ЁK;=w+<=3<=I==E6=>g,G=>F=Ӭ >G9=C>x"=  >=>G=Q4V$Х<5~=[tŽNl=p= IQ9=-=⺻ga >`ˉ<>s=9 > M=l.>?v=#>>є= SP>,=ec>=ku>">>D>>X[>>i>> r>=>fw>Y">kz>o>]x>t>pm>Y>Y>4?>AB>*2'>,>o>>+|=C>*=o>&=>Q=> =N)>= B>z>\> >|p>z!>y>>v><!>Dm>ux*>Jc>p3> Z>7};>L>B>Ձ7>|G> *>+gG>=S>>=B*>Y=O >?<:=d5O_=6.<K.?q4rcԽ4B/7cl(.`;5B ϽQihcߝul,냾BMnj)_|0ϝ}]s4ykeJ(Y3PҜ,IBOv=hR?|2HM]S7Y쇾oV&I;J9δ*Uǔj֏|ӽ〾kp2T;0_ [PFż E}>n 92 <'gC< =gWOc= V=xBQP%=6=h>>=X>8=}o>>.>>->Z#>>$>Rl>>>E>Y i> >L9H>=)>7=F5>>=P= /==,=8==U=-=j==u=|=;=d=s==H=+v=@a=[U=47=<==-=z=+=v====]=9=}==5"=wNj==΁=l= |=O=K{=n2=}=?=ʛ=<#=3w<\ǚ=;=C:=~9߃= 87="'<4Hz'J6 +FoW ({1EH'OO Hah̽5W8ͽ%cnҧ,1Uˈ5;ݽQUZG½q kf/!A)&ƽeἧҽ,S߽YW*| ᜽ ֥75ÜϋB&GgF_HL{P89b^Dĭkj's h5sgo2ԜͿj*넽geQ9=y[śmHLGBdw:)>* 5>|,<3> a==><0V=756*=.Bv=o =H½ī= ~#ډ=3[=!p+=p)<*<;r+=QN6S=!żU= ,f@=&[.=NSx<%v|]:iü~3/@=ksdMl>2#4׼ͼ(]$ C4=P7>71=4P>:=P_>ג=f>P=wb>^=T>`=c @>=(>U>|>G">>e >= 3>@=xL>=b\>z>b>̇>s>>Ʀ >wD>m>?2>r.>؇>=>E<=>=1>r=ps>=&k>*=;On>;=rD{>,W >Ȳ>#>>9> 8>Q7K>.>T>`N>HT>>ոJ>A>D6>8>d>>>=>滬=[>O=><u>ܽA$>Y >5:w>l3j>V_>p7pV>uQzK> нY=>ǯܽ +>$⽤=>Ὅ>)ڽܷ=ǽV=8=ֈ= Q5N;Y@#$ݼ Q0*hH{M½ ҅Jq{sa0qpA-ȽcJc-w1`㗽#5붽,^潧z`:9w%Ia3^憘OA{͘䜛+Y!vzg>~w~Mmc < ]J)w3%j= 䵾g(AڸôE y-| 2[ 14P뙾W&=(Chsؽ h_ʽXO½Ev1+E!)Il(u 77U߽o=E=DH> 6>x<d>*B=GЅ>5=k>>/>3>hi>$<>+>8>S>@/>>%>>>U\>>h>,>ҷ>t >f> >>o#>>" >b>S>X>d)&>gs>1>;h>9>^>vC>W>*KQ>R>b>rQ>Xq>U>lw>^>Lu>Zbk>qk>Xx>s^>>YM>i>8>>6t>[`>=>=Yq>\=C\>nݝ=|kB>=%>Lc=f >=U=}=?==*=Վ=q=+V=*%=G<< HЌrn ѽ|FT P- 8 ^ >([(N½ ꠛ,q*-{$i5i aZӽG_&WR+ZIr-9",7-&{**Pt&+Y"($h!I"E $`$)ὤ!׍潍i] ޽*Hy뱽CA^npVd<:X<=^==0ӥ=ʖ=7=I >=%>-=_R1>qy=62>9=k/>k=.+>=&>7=ę>w=` >`= &=V=J=#==5=5=r>R==<@=,==.b= S=/=ӓ=rZ=t3=?=y==2G>z=>=g.&>N=(>cy >%>>3>M>M>>}M>z>R=Ζ>r]=*>~==c>D<=:X>=>j0=>=p<}O>;">9T>ea=@O=Ǔ0=r½p)=̹;g; χW_)j ;NF#2ӽ&H7@a4)i%`޽Q׽H3޽ٽѽڽ`& o   D"id.!.6]9.9?5@O*,>:Х8K _~0~&-潈Q˲ٽI~ѽީ"$$˽s,Qb5ﺲ)6 -#.|$нq>=7 =)=E=iO=)|=p<"=#:=JS=8=OР=𬃽=ܖ2s=‘F2=,6qc<- O<Ժۼ&20gfgN ½*C<@7{Y+~cyf悔ԽҦf,Jʽ,׽B샽~Kན';z7Q >< ޽8:=Uؽ#=iֽ/=Z:޽=;s_=`7=+ t=F=m@=L=<$=>D=/ |t=Rhh=y=>+^ <=kk7>=^>0=|>= >QN> >9>N>>+>B!>~3>)>Y> 2>><>|I>yG>#>P>Z?>hV>㠄>tY>T4>~Z>Ot>7W>lk>ER>i>PJ>dp>nV?>kHz>3>l >)>|v>#>^>>$?;>#\>>>V`= >P=c@>+<[>D= U=[=ktd=W\<=(Ώ=BԼĆ==U<ӎ=t=y=Dy=9U=1==`=&<䇋=PJ=4㼖<ѪgQ]<]ѭ(7 7_֒v6c伳󸽍巽1&Uѽ@dpgzVMF==xE<Ѕ/;!>Gԅ3/>w;68>t<=>A<+?># l#=r5>H=+>U=P!>;=>rD<>T?=,s=yC=Mu+'=ކb<˳?yGO;0[8qƽKs}㽋7%EaJ&޽OXν?E쯽 W|c~l/_ ׽nMཔ&B߽L6Cٽx' ˽K紽6.+4~nwW dJcRtn|^m )D^HýܖhϽM摾)}wį?sK!z@?"UXzap!RM ) Y  cǽNH봽fpxM4ý{XսX۽\J9HϽt,Dž҅&P\0T;J#F;l:f;ʼk<7(=l;#]h!S; l7~_ =Oy=>!>U>_@> h>[>9||>4Yq>f>Ө>!|>FQ>e>[G>>/>>P>k>^>W~>u>Ϛq>m>]Dh>$>`>.}>i\>uk>x_>PVX>l>ڈD>>1>N>~5">_>f>ɮ>1e>y;>~>>j3>S>>Z>>8>m,>>k>O%`>R>g:>={>=>>==lo= L*=1=됗 f -Y_uR畳5oqFځY퉾piѐ쥾nr-P\?pQQLQQcT`(|e~qHtlpi:ZT]/"\RsKJ;{GeL.YV4:)ds@g‾%$""5ͷ6ǀ?kjpKF'&čz}2y\sbOSSm˼W,䑏:s}<=u\*DT=<ж=MK=Y=IP==/G==c=I>*=KS>L=2>2=E>Q=U>0=*ja>_=3g>6I="\h>4>f>>e>Ƕ'>g>e6>m>JC> u>XP>O~>U^^>3>i>>m> >i>و>_>>V>L>P!T>E>cZ>A>f>JӮ>t>;>s>>>S>Z>ti>`<>N>>ݲ>>>Au>:>'>˳>>>5>i>|>>%>[a>@~>^9>l>,>%B>m0=A>=>Xi=F=-=͵=O <3=ܒҽu&=}ֽ>=ٽ\=T佊=m02= =a=ab2=10D+\;SCS^#(ehj^TgQI6cd 5VԽZWC6, TR۽νB`ݨlm5t:F_f;V?s=ѻC=IXD=Խ6 =#$< !:FΧ/;' )nwY(lN5_ SZ,r%AX6[y}2;.Ἒ1<T;л ,Ȼʤ;<eLƽ==4_^=K^=r= ٩=#y׼㒒=tReu=ZnK=^D|T.=ݙJ(=⼞@=QYs='8=70= =WE >Yb#<">`=6>;=?JF>=;S>,>]>f>yg>I>K!q>eJ>Ky>J>}>J>Zh}>=g@w>=k>U3=g[>:VX=\F>x=,>և=j=A\p=>=W==o=7= >=>~=/>=47>p؆=i5>W='>==>~=/=n==W?=<=Vr=pi=u< <;勷[=Ψݼ}}W/UE39@o;"D:ӇhS.6],} :- d߽>Xֽս׽pP὇79\_E "#vIƻ}jeE`pb$:c7`yS=|L>S=1x>`p=؏>=q->܍=- >+=Yb>>r>>2>>>ϒ%>n7>e)>ɩ>R?+>O>)>ݍ>#>6>>ē>s >Û>>>Q=>%=>m=]>`=>=b>7̞=*ɮ>*=>V=)~>s=ԋ>#<)>%0͗d>%F>$>%5>,?=I== =qv=H9=;g =m;=VF= =ObFm=X˚3U=ѽJ<~[HRjjwmK2 CG7Q"N&TgX'Ƚ7]⽒cH& i{w/n0pMpfc lkndfn5 X(iE[ e~.b>lz`x[r½SK{! I\KM%YQW>#qB'dB-t愾#)˽vٓn~ョαǾ(3Ѿ˿ٽ^վƽԾ|֯оa:FȾbW'Hm>&;Ri'~<36J= ؝=Z̽r=DW>^j_w>YNpd*>v[`:>jG>;P>aɽ2S>ٽvQ>ڽhL>&ֽG>+ӽϐB>н>>!ǽB+:>6> 5>`'d4>8,JG3>AE0>Z+>teh%>:G> ;͆>x&<>MQ =m)> 1=c>d=##>=|->3=8>5=CC>"=|,K>L,>vK>>0SA>I >Zj->U>%>>+~>o=>=Y=iJ=j"=:< y=*,(=@ 0=Dc\=dm=k`E=I==Nнr<4RD;UxZ~9r=-ؽ„]y1UCaI $r;=_N<|?=> %>B&>>*>o>G(>T>vw >!>M>.>>eK>> >< >=,>=l:>?=G>t=#Y>;=o>-=:G>~=h'>=e>,=x#>E>>G >L>z> V>>v>=iCb>=F>=t%>ͱ=>\7==Mz=g=b =0=="=}=M==O>B{=>=4K>}=؍=?=*==/=b?=n=@==L=p;L=ϼz=.GZØ=h1=ʽzo=; '=A5<ݢSrͻ(D7 jBhݽJ9vP6SSO*Qz caHu_: (^lgiTD`aD<`Efxi*k1-@`;-2Xr}HQP4fK݁LQV&~hg'}L@OpC&)*쿞w㘾4#BY}݆rהmYlpܞ_n]6m?rG˝ 531VȽ]{j|SkøndrIJ9zI&66kͼ3#i0*.J>U<6  =ήT]=-T$^I= F;f֨=zs<^_=<=8=&|=}=#=".= =tJ=9ɸ=t=e=;o=-j==,DG====J>K="(>Pi=IA>€=LV>={d>lF=k>C=Nk>V=-"d>=mY>=hK>AԘ=Nn:>U=v&>˩=>A=B>x==)n==zj=7=vI==[:5=L=W$=[=1t=৲)>}M;=\<[=v.=J=E[==d= =@e=MsI=b=;=?=[6=E(>?=5>y=x%6>=ɷ(>Sc= >=P===Ap==n"=m=n<ҝ =U\:#P=u=Ь=A=.ս&= %=w >|G<8>W=8^>G=eĂ>">q>cJ>q5>@h>?>y>>/4>S>$>^>V>'>$>G>>>T|>v>)o>>,c>@>;\>+>[>B؍>H`>a>NEg>!8>o>+>Fy>>?*>c>Q>p>$>T>ʻ>{6>$y>>c>+=9O>(=@C>Z:=>>j=>>FE=uC>>;=X:>F=V]4>RZ\=E->q=7G&>^}=V>=3z>s=G=X=Ya=-=;;-ǗO-s剾Im`#f͟Rrz%tޒjq5 sRpORqF19srՀoi*8c&8s]8'YPCUHjbmQNzN@`]N퇾EoO*N{vjIa>Jj/.  CF kؽFL QUo=|OLu@6t:Mk=Չ =C=B; ͥ=p<һ==w=tF= >܌=g%>=\d>>.g=S>B >4oe>>Rr>8{$>w>c)>N@t> ->Bg>r0>(T>3>B>ú6>7>M:>z3>T?>4>rbI>/3>qW>0>Rh>O.>z>H1>>N;>?:>&7H>u>NT>ދ>\>>zb>SN>Ph>+{>9p>/1m>x>xq^>'i>BQ>R >bF>Ay>3=>FB>1>>">Q>%_>߅>=Z_>k=j>v"=f|N>]=0>k 9Ŵ>?;=Kp*=8<ǽ)ڽ]Ɠ%A޽ؽTս`vý7 Msn[޼ ˜h4LШP+Rؽ>1.KB8% h2sԀ>"[CH+DKKDq%~56>x~vzrjIҽ[TN8E奟*=96F7<1Z3kpX>"^+*>'MY4>.#9><>%:R@>;HD>=)F>YO=p@>R=ȯ5>W=U%>#e=$>e=B=-=׼= =ւ==#=Yp=RW<#=ϓ٬=Lp=|>-2<=vJ=)c=^{=*=] P=!=5[*=;3=ŖCsK=Bel=o8=~9'dʷ=uuh=,) > Ѫ>3\y>*>1>k;l1>X<(>g<>>c<{=4Q*<=J;w=jyq;<=4&;]{>07;> T;q>b;x%>}:G1>]{A<>$ZC>d̼WDH>~\J>5 L>gML>Y&J>XaD>(>2>|kg >qtZ<)=W< [=+t==~;=S _=xW=<b=x;Y=FsB=7ļ\$=N=Wdw<21[<_ Aꪽe96@>&Ώ;1Ƚc>bܶō#z\C;cw`Ž1wlx 9)ʯEuH ^oew]f 0RB=(#IT/摾R僾|=ҽzmY0Jt5>@hT;8NW=Y<+H4yY=9FNihqM^tHr=y;ztî/|Ƚp~kٽ^}#}sA޽4^\нs97&Nѽq=L5 )c޼,<"<'=n===K=====ж=]H=gz===0=y=.=A>5#=2 >R>J">T` >P>q >k>Q >!>HQ>d&>=A'>==>$>ǒ=>!>t=q">"="(>wM=Z{/>=5>(h>,:>$>x@>9>NJ>K>^W>mY>c>6b>k>i>%n>o>m>Qs>m>"u>{p>gfv>s>^w>4Gq>b|>i>^>:^>ʉ>PR>>H>7\>&?>u>QF4> >u&>硁>>t{n><> S>)=$3>=K>!Ο=O=J_=g}==|s >m=>4=2>=_>=^>=q>2{={)>O=L4>W=<>>V@>^ >@>>v<>b,>5>š=->=%>/=E> x=>5= >=,>+=:!>Vx= >= >V=>=r={=j=F=z=&===H=sҀ=ܜ;=B]=2<#=Bmk߻0㼡OH C.ހ]] Ľm߼1A5B|h~ چҗ_,HR"'OLŽzzQ]"þPFcľ j,þX!뿾 %= n3/za;cE#NQա J=LV-6ZmxjvƧi2$a|\ $k])N_e(n4XAv DxjRPt^Y.i|TXC_A!- #O oͪis7 ݽx3½>fM.<)>ncL=*>>V=$D>,=4E>(=E>=LA>nh=^:>{=2>G=u+>=}>'>=N]'>^=J->=5z8>i=UG>= W>4=of>=qJu>>>/O >g>n>8>Ho>>f>>([ >>]G >t>rd>N> >T>oO>>O>ّ>o>ӌ>#>=ȅ>)>sex>l1>\_>B>>\Y@>1Q>$>]c>Gu=so>Y=o>Wz;=a>!Z)JƊ">F:(=V㓽{=jOŽe;pN tǽ3c1/E@$gO:a)4[h,]a,b̲(` x[qoVU!ӇQgN 1콄yOgU;~bst[ u-/Gʎ2={C FHF`:= .,ϦH=">{]=>x=l[.>Tɴ=8>8=;>=y<>=6=>k>?>>A> ]>O?>1 >?8>>->*>$>߸:> >_yK>cI>,i\>|>Xl>Ek>Oz>`>7́>&>ق>;>P>cW>kv>[Js>]g>b>T>g>?>< >2*>:>>so>w>X>4=V<>@ =>\=9>5R==8=u=W=ɒ=c=JG====dh=q = =,%=/=m==>>5>3c >P>>T>z=4~>S=>0F==2==BS޾j D$<Ľǽ9n,?LDwgk lV;}^d<-WP!=;QS=#TRj=Z`f=ƀTM=󹗽#=G<Ľ;ӽ6!f6- VO 5|F :՟ZvMt>_dqGؽ޼hx>uL3ˉRlxx;6k[$Y+gTcMZhAO*0,s_} n=нA!ĽbFüݾcj%]u9弽.!{˽d0NݽQռ! s۽S½bWdb|,t3:S7;Ǝ={!>>v@>>|`>1> }>I>]>Zc>2>{>>d>Z4>>&>>>Β>g>Ri> >>0>> g>=8k>ʶ>TsU>\H>LA>&>ق2>os> '>>% > >V>q͢>>@>>>A>h,>Q>&ۏ>gb>t#>d>c>=u >P=)>TL=u>Ev=f>u=GRS>_=?>7=r+>7=>=G >}==&c==d6='=s@';и>7ռN>gu>#C>:x'=d#M=-xQ=JFK[9s^eTENijL>c̰ݬNVh⚾s*OF17zƅ&|Z x7xԘ|!źo@뱾剾 wӷj. ﺾ%6ug']{ۯ!sbak{K}:1[mE뽎\ v,k>Y 3;z<f=<߽=T=) \V>z;=`/>ڔ=R7>==>= B>,>FG>|.>q@O>>>KY>G>Jd>&L>tq>R>+J|>*V>9̂>veY>X>>]W>UP> Z>`>9g>:>N{>>J,>|>~>w>>7vv>pr> y>>T}>揄>j>+> >>^>t>2>%>J>>z}>>/n>?>ü[>ލ>[E>LJ>v/>>;.>X>>Ę>7=_5>=>&~=>+=_><'7>n+;$ >q䟼H=28|=QՈb]>7*> v8>=nA>+fD>8zVA?>xk02>ܰ>>>Bc?=ۚ;=6<8@=P8=\=0= =n=<ƺ= 꺲=ӻ=ڙ!=ך|7S=o =ҀX<9(; -~ R8t{>Ho.=5O26߷(Y(7Qܼ_㏿ Im׽μ̽-]ȽҍC:ƽOɽHϽfpٽƽuνPսHٽ_ؽӽQ˽q y,Lәe)7\N }0ռYKa6(e$Xż|ꇻFY:~<< n="">.=:4>ܜ=@>̴=G>6=VH>&]=SD>#=۔=>`=x6>A&=k/>=l(>ÎW=!>az7=>1&=4>!T =}> i#=>9=R>%^n=J>=k >=z>~=>7=9~>=^'>(=D>+8=>7B=GI">y=S,>=5>#=C=>z=A>2h=TB>Mm=>>ߞ=8>'4=e1>Ϩ=)>=">(=Sw>=X>s= >k=>=U=='='N=ޭ=<䃼o.ݤg_eRƺԽ5~7ؽࣽHͽ!{?৽ӴXaWpu#Ve@ h範M$KC ;\Rν^+ؽbjm>اGmqgʽ5ͽcܽK~yIf?H$)<+4=0DiS^G༛cA /bʚ,[WbSxQSI6?M9x5<*<2!&<'#3=Bt=t.=Ѹv=8=2=MgS=p=e ޴=il<=.I= `= Vp=~ >> ,>ʏ.2>?g0>+>$^&>!>l[>,)>1Ƚǫ>>MIG>M]>%Q>` >;޼,>"Ū7>AcB>,DM><T>5A;wT>G;I>;U.6>(;*>j=XZA=]WL0=$D~;߇R PըƾýX ٽDݽ4`2Hؽj޽_齽Wٽ渝ĉϽ^)XP4>R1O\ ŵo"˽b彗.]- Uz,K R)ܼx8C17@bI =)D>V >6>H>K`YT>A>Y>(;W>LEt=lK>a%=9YC>I=9> <.>'<>="><[>;:<">12;=٪r=vn=@k={:^;7=\:se=R筻ٻ)*|;Z ª޽Lbq{O0Q#!F1/>iJ(QyRN ⽜J>۽tK!ؽMbQ ڽ+[߽ocmgig sMesbabA񽇒[S*P轟@潋."-p.?? G~p/~彃ɽ=޻~;_@M+p=<.>u=?G> >&Y>,>`d>G>_j>^>Ro>zo>s>'~>=v>n>y>G>z~>ߘ>>zK>a>r.>>r>䎌>>30>ǡ>܉>2>ܣ>3>>>>>j>>>Қ>06>c>\>À>>Ό>>b >R>>t>ٔ> >U>+j>YH>>'>_>y>%>c>~>MK>'d>/>{>y>X">H =r*>=%>|~=>x=>;i>Dʼߙr>qvP>jǽ?)(>d =0y=LS$=rh񲘾-$|[ܚl~J<*Ϩf Ffut\]H^SW?9Fք-̻-"`<c#=H -SP=CjD=s=D=N㽋F= ʽ2=Ef=465=~> =~cjz;->[G<G>G{4=c>.U=܍>S=G>o=!>W%=hӞ>k >z>>~>>>;!>C>ׯ">Ah>"% >P>-7>f>z > S>Y.=Y>=>S=p>}=Gc>=\>u!=Z>=L\>g>E_>7>L+a>!'>a>5c1>m`>2>-\>z,>sU>I>>:> >E)>L>ݷ>=>$=n=R==85$=Z=<8=eX=ٿ=@'(<5[~Its5spebpVe Q5Խy|V>dRX42IX%cWp:@Mؽ/:w4ýQo<0<F9=&Dd=إmp=PL_=7^@>=#=v 1IG\C6ITD Z9i A'/NR89۽2߽_ۣ̽LYB s-%Rqlb/J]i3n]`q"q,YI < +<<_X(=h匽F=싽 T=@ʄRoN=k5=tB= _X;H<<=kB=#=j=j5=:=sXI=l=PPb==%р==#+=>Π= >ƪ=' >e= >=%t > =>=dI>=j >6=>=s>S=>=>2<7>*_U">8 >f)>> ͟ =Mdܹ=椫?ė= Iüu=<,ݼ!)H=꼈*=̕漱&=[ϼ@=ܿ9x=:=f>=4>iE=W>S= ><=#;I=Tn ؃S=q}듾ybˑ2d Q Pޚm֖цZ8F|p#yNh$ٚcE"q_pZ=uB2V .QLJxg?S/ܽ /5䧽xU ý'[]@`>nɼNQF=>%=BU.>=F>Y=]>=!r>J=r>B=kj>F=8U>l=R>=v>=?>9=>+D=y>=+>=>e=0 >=%> >a>5>x>n>n>>ll>> s>#>#>+>> 4>[>:>f>=>2><>k>5>~>"+>j>>>K;>>^=i>=_w>+=^d>m=_pP>q=:>WO=k%>Ͻ;=R>1=[>)=f=!=Y|===]=ڡ=<=}<)G=:Y;@=`g<̼߼>ἻS[Y!OļŽNE콣B> >&u;ܱ)\r;dr3#9g =ID=MGTz;Y𖽙eƘ)qbFtkz܊ʽ'ŀއν|\ɽV Yބ Rߋx|Wlss3vpn8mۼ:m,{shb<'^$=JiQhg=kF=?q=:=\^4=x)0>JY> ===a{½\6=9=Qz*=:DTժ= h%i)=6t=c.$_=w*=?0w@=;k>AOc>ky>ł(>? 5>sy]A>O>p[H_>c<1p>42=5>TP=>={ڇ>|>>>A>>cv>e>ӄc>X>-I>6=)>$ܼ='>uG=ݾ=4f=ϋk=Tz =<9@<ㅻlsIDzA>߅Uy[׽xi91bu/SյRVna}A둽"C+v$ҽ@&`0+jL>n1إp9(CG4RQ|X9d7}0t()!ز:QTYmV8\ݵ]ĽȲǙ^D΋bQnޘW3;#f`mW;=0 >0R<>/*=#0&>AJ=d0>EX=+&;>ZW=C|G>HJ=rU>V;=:a>0=g>F.=h>\4=d>?=X^>K=U>{T=I>^=5>y|o= >τ=>=C=[=DĻ==ް==t=Q>=e>R=j=,>8>"?>>@O>f~>xY>$>^>Q'>K_>63!>Y>wa> oM>5>;>"= V&>`=^>n== $==0;=Y=:^=zuf&=<~<2';:'ؼoH+hG+gm4y9Vr[O=kj4n`^#N*Jڢapv73ǽ[ ýԽ/+нYݽ使佷TN e{];'f14G@YM<νb\<ҽ9i=bHP=·=+Xr=ż7t=j@=06>&9=/>l%=x?>>ڃK>3>~R>(6%>S>U'>K^P>)&>N#J>gR>C>>y=>=DP:>.?=9>O =^<>="A>l=pG>K==V>!6>=[>uY=9-\>=X>@=T>=P>W=*O>=O>hi=P>= O>=bO>S =KgO>&=(O>w=O>O>=L>=@>/=fi7>$=$">M=>=fd >=Qu>:=nY=h=W=N=o@=۸=x=}=5=ʝ==~==E=l=E=^`==xV;==b?=F]>S=8>4<^>+<>u=zR>g=|>N`&=>j-=&@>fa%=K>Z =>4<>=1;>|t>>ȼ>$,%>V\/1>_>;>R B>:GD>@>ыƗ5>ub >`>F=)G=*B{T<ʽ#l;Zܽ[뽮7VWE`p>I2ؽ3siǽ;XR<鰽=7w1=B=4˽M!B=U$=<)r9,&q~;x%HP5/S)G#\Qtbj"4d/ cAo5_ 3[/ތV-P%3Ƶ@i䌾msFlS.,!׏"C"۵Ӡ SG {`0ꍾeսE򁾻sv7xjRb:`W#S CN`uk0T<8Df== R=å>zϽ$>0) E>-ob:c>Q C{>|>H\;><#>5xe=/>[=>>>+0>#(>W>w>y>j:>97>7>V>+>I;>D>>>>T>>ҏ>e&>>K>?>ܐw>> l>>ulc>>]>T>]>빎>~d>ؔ>Mp>ݖ>ɥ}>g>I>T>0>>ى>ʝ>᫊>HL>,>8>5>Խu>>^W>=gn>:>1Q>+`>2>8>>C=Z[=2+==v=(=p.=`=wp<,~=Z;)o=x=>0>P=:>|=<>=U8>=L=4>=f}1> =<%0>=c0>͖=h3>Ư=s:><>_hH>{.>]>J>u>nf>xg>?p>iJ>B>>F>+>>j>FQ>R>3> >>a>>~ľ>{(>N>Ƈ>>>Od>[r>e>ua>>N>>s9>N>!>N}>M>zt>p=Ok>N=`>`=C}S>}=(G>:6>Yr@v1>(@+>"=">2>8!i >,*>{:B=O=Pk'=4=)= Lj<3ԽCGqz!4>ύǽ*$Qc2A1"Q+a3w)qK:}*Ao%E DΈ!?u΢8q؅P13vɀ0Ar0_i{/:J I/~ 5g146 :>tDʽ E^W[?4ae'&+4 ߼ZtrºN4;L$A>&>^诽E->7 i0>~ gk1>Virz,1>A/>*->27)>Ƚ+$>2Uڽ- >0>Ca>> >e#N>Zs=ܽ="ӽHY=tǽBU==m NH=#ڍ$=b1e>fi>WtG>&>η=C=:;W;=-Q%<=]Ԃ<_=ǚ#JuE>rb!>IfhL.>%9>&B>t40G>V=R>Y=O`>?=$s>= y>hާ=4>*m=>{=R><=>A=;s>=|A>IM=ە>4=&>|=2>Zg=>=X >x= ?>=>:=">= {>`!= Z>=@>1׽=W.2>#= ),>= ->) >'3>@a>ڂ=>#>]K>)>Y>)>Ja>$>%b>z>X>>,G>>qV1>=P>=>l==d_W=`= =mE=<So=>]= >e?=?i>D=.>K< >tY<2=v;G=9/Pl=i-=ʠ gq<;-ջR;Y#X[<;,{K'<@>ga 8I;$pP>ûCH>3ξ9>ȉڼ@*>#&>pO>6>⚽\ >q"u>Ů!Y=xǽ=Fƽ[=?q=O=z=|yf=wQ=!=m=9Ķ=~;= <31|=7l;&=M;F<Ϊ8:'_/( 8&sQZ ,B%Ͻ D cTkqA ہ4Vڽ >A׽IG 8_ M  tPɽ|糽>ݽܞ]L봍FuIď=g㣽@!L ;ɽR=X=k=D$=G==q=O==A3==0C=G4c=F3=[A==:/=2=,-==V"A=ڌ=j=Z ==u==Ca==b=f=Xg>=>$>֥>Ƶ>4 >jS>h>v>}= >d=RU=ԯ==.\=w=Mi=Yw=6=p=<<5k׻W$<d(:/Xfޫ פ>sѽoCP3 _!q%18,jk0zҬ2d״3=/6k ;L-@³nDYI4н7gM8O⽶P1ORhRZ }OJ2nBiF;NXZ4he/8y^,ʭ+E-jnV2g8А6?+0Bɂcu@ u):rd/Sߍ! ODCn8X2DgҽZ1æ{1Ju-S#T_ 9[<33=O8ӽޏ=ƾ>9-Y7G=DK>i=o]>>=i>f>Em>x>l>Hz>h>"> )d>>`>?>^>w >M^>>H`>Q>Me> >0/n>'>{>>ۅ>Q2>";>*`L>>k>䷡>9>>/֒>H>\>>[>;Ľ>M>P>5>i>E>[>|>L>A> >q)><>>>>Y>\>Cɱ>>>>r>օ>M>6x>gy>f>Y>qzU>}T8>D>>2>=M >K`=%>=>B=PT=?=g,׽кq; >k8<>7h =!>G=>|=v >n=j>W=&>=>=Y>i=>b= >n9Z=>A&=[<>C=?='{9%r%# ?"k.ýk17>3". (pn<&=G> >ǿ>+:>->[)>MF>X4>c>0<>.>[C>y>G>%C>6F>>?>{>b5>.>D(>XD>>> >S>g=&l>*==>=>=>=4 >ؠ=P>p=ؼ>V=>;=ai>%=A>TI=i> =~ئ>=>^=9k>=w>=,O>0=>P=C>=*>z FJ<j>;{_>HRKP>j<>2| B%>͑ >@][=|=.`;%{= (V>=,kc=Ź9=Y=zn=`> >s>B`6>ML, >XD >Xx, >!>S-!>; > <>X6=\!>Q=EQ&>5=/>A=X:>>)PF>@>>LS>\>`>Йw>`Qo> >]~>s>>G>>{>ᝍ>֥>U>l>王>V>{>Wv>S>Xڜ>y>>ki>>*Y>C·>J>ae>#=>mp> 2>ka>2*>T>">J>>B>A>u9>b>q1>h>.,>r >/.>6>u6>@q=|cC>S= N>F=U>=AW>=UT>S=M>/=C>g=4>=6>W=6>_==u{=ڰ=M/=j)= <~<ʠ:@,(F}9RiUPYϏ=}@–w0s)Ƹ 7CNɳκLŽWAҽƽ޽x޽齦l# Hvb $'۽Q%L˽Q"WZ< Aս\x(m_+wENAB=-< 0)B:^@ ;߲]|=J`>.棽 :PŽ0$&9'ۼ.཯YԽ0lƽ/;U <"ݝ=qaS=P:>4=T b=1s=|C+e=$%:b=w_l< >=Q> =>=1_>}m+=>oA=>}WW=4>Va=>ruZ=V>A=1>)=%>&<%>gD4<ԃ>Mϻء=ǼR=a$x=U%N_N=dYk=sDU$=<ؼDgEF>/9!<ࣆ==NN=Qd><ͽ>8;#>ߚqo#>>k0|>m >In >&& >U4>e>#=:SV=G4h<=_<==-==H7q=nH=Y==3=?=}3=>!o >` >+N>6 >->m >H8>l>q?>=owA>A=ݗ>>_=6>!=+>E=>٬=V >M= r=K=n=H<=t<=|Oxa=Z =AqӋ=AC=> 2={=$=,=$=*=aH==ٌ =1=!<[=6P< =E<o\?>K9|+>-@>ՑԼhT>Z<"r>==Gk{>d=^>"=5>5f=og|>ֶ=oLq>{=da>"=P>c=#h?>=s->={>s=8>=k==?==p3='=\x=,a=gA=?=H=}*=jq㷽(cǚԶy=@=r=h>[׭=\ >=[> = !>>hp&>B >'>O>K0&>nX>%>>b)> >4>Q>nC>">uU>S)>f>4>v>qC>܃>GUT>,>f>k>ow>G?>>>>:>" >D>\\>[>ۦ>>Ɨ>q> >">#>>>>>ی> >>݆>>N>i>b>ϑ>2N>>S6>a>C>S:> =^>l==c^}=Ш=mZ= Fw=B_#><?Ē鍾*Sdpo!pt%xf6OՄhً|dԔZ8ͳ"4EK͵n{nڱ$߾о8G r{6h*r.5{]7zuI.fu6VT#YE;4 =1,>Ž(.K,x.s2WF@64)5ڀؼn,t4Ҵ2<*:c<^ὺc9=)kĽ= B=|Z=1: >S#>GRj8>":WI> ޏW=a>1t=i>c[=q>f= |>|=>li>~Ŋ>'>c|>a:>ė>bLH>x>PP>߃>* S>Mm>bR>ţ>R>>T>>.U>a>hfT>$>߷Q>?4>O>v>0R>5$>&Z>-V>5e>M>;r>+*>x}>o>Ѱ>FȠ>I>{>J>># >R>O9>r>>B> >w>5|s>^>eb>ֹC>Q> 1'>A>M >1>b=(!#>ߢ=>U=^>*<> >->bV=홽+=hŽз==yス= z (6=#n<C/aW;GcETVN_΄V@~_zgqnOt,z/oǀlA* 1&߁YeI銾]xٻm_/ɺާ8/vɋԩѻ~\J{rFܤcx~Ϭ,RUμsv;]XjY*'[RIy\p7u%wQBlfѽ5 TýeYR꽏*d$+(t/*9]ޮx<`؇3L= '/5o=S3m=:c=:A7=F6=֢J=/LK+=I=/>A{@=O1l=5=cC=Iνx=\Sw^=/߃@=TY=t0޼\:5?;ˁ;ae<|.\<g:= > =>E>z)>^ > 4>`Z><> >3C>a>OG>Z > M>>C(U>8==`>=5k>w:=v>=أ>u='V>E=v>=>b=R>=wF=>3)>E>/>{>>>\">C̓>+>j>4>w>nT>>>kH>~z>IS>9~r>*[>;i>a>]>d>.L>!d>5>))_>S>xT>(=D>@= ->f=0>Ç>=dl=Fa f{ωdvk`}牦caإ|ֽ+%=C<5U.$2Z@oM?F(VR<[ [.V<&ZL6w`#?dS.*N/778z)[Ƭ#Lk7Y} ;}bl"<=;\-=or="> =6>k=F*G>r=6U>=c>W$=rp>={>e =.1>s=|>u=m>0T=^=>!=>=>=Ϝ>=Ͽ>Z:=Q>h=->t=>V=I>=s><=F><=GU>=,>p=>=و>d=V~>"~=j>a=?R>:7>ž>{>}ƽ%=c=4o=!+>N=8G=x'Cر="K=;Q9E=F3Uy2=:$WS=WZx=PEU~K=Rֽ=1MN=@H›=B!=9|l=1dO=B1)U<=LU#!1= )%=2=`?C-:!eм],63b53b t3p<7"< ?A<\G\$ =O=T=\W(=ZYF"=f\,=``>=c'Y=dz=ck)=_b=@2bP=%b(=na4|=@\ >RH>`C">1-> G:>~5G>zmU>Nb>jwZn>B;y>uL>:>@y<ن>$<.>=:>#-=6s>b*=B?b>4=^M><:8>{< ">hE; > غ=k7%= {=꼳=%j=y܈=Ӽ$p=qƼ$O=9FݼI_.=W_ =|=<ǡtG RZ = >e=>ڳ=Y>ɥ=Wt(><=XO6>>ɱD>>R>/>^>K?>k>M>y>V[>Y>i>c>Dv>.>.>Ѣ>>>Ɏ>[>0Β>>!>%>>ms>M>D>Ə>L$>g(>!>>>>9˂> s>u>g>)Te>]>BU>T9U>~=J>hM>PC>E>>>&H=>9>3>0>*>'#>!>1>>~>X>N=>>%=U;=2=Ĝ=9=3==&\=-Z=eB=1Mq= < ]=suɸ:=u2Lsg)D.opi|kcw~?^Z6ݽ:XHн~vVɽ~]UGýU+W+#(YJAgZ-؄Ylg%UU^KcM[1QBՎ5' VH輀 %fѽ `/޼P1KVg]b:L%3<ڒY1B=>o=>eEr6<)=:==) )v4= D= =('=G; =x0B8>;?>CC>'yt&>l#޼K.>x05>o;:>9QqP=>`B=>s5m":>xxr5>;z->T~">s0M>Dze]>oV^8=d%=OsB=P={C =`=݇}=ۅU=;~dv=]l=d#e=bPRd= ;k=N<+~=C1==}y=nj===+=9=J=k =L==Q=#=)q=_!=J=I=8=7=Y=L==3=B^=磴= /===t==ҭD!:=.2>9k=F>^=z{W>{=rd>1,=o>~>/vx>>l>g/>5|>C>V>nV>ی>Ush>>v>>>0>l7>g>>H>#>$> > (>>X>>҈>LP>ކ>>]>>|>>w>>VTt>RR>t>">v>>>Hx>]>v>>.q>F>i>1>^>1>AR>>6C>R?>1>c+>f>܍>>ZB~> =1`>-ɨ=C>Fi=7(><F>!:Y>RM߼!=#d=4~)=4佑>@=?ɾ<|).;5B͋EXWvieUtE{Jb~KӽUyvgnc1t.!p_v]Y!0IV@²RR7qfg7~7(닾BF×gɠ*Dw;XT*:U@Đ, Kx[of]ULRHG9:)%/%㽅ƽ+':( Dz@[~ٓW^+,2Q{ HԼ5λʒwy< =e;7Q=8[=}>R=771>=G>f=A^>1>s>%><>-n7>䔊>O>A>\.d>夓>r>ˍ>|>>$>噓>ݻ>>{>&j>>+>Il>{>K>pm>>_>Q>4!R> >~D>>|u8>>3->>l">e >h9>Kx>t>T}>]>S~p>[>ma><=P>(=o.?> ?=`/>)>h$>>J>D>>@P>>1v=><+=x>=>)>=j|6>=wC>=bN>~|=&\R> D=~P>F =9mI><ӻ=>$;,>)Mh>3=cݼ[==b=L"@aoOMa6;~?|r<9r;:=DOeɬ=a+V=/yG=U<=6>G56>%B5">33><01E">)*d$>B$1`&>,5(>6)>+>J->нg->WOd->&,>-R,>g+>L(B,>|&->X\1>M!؝7>95D A>PL>gY>ayw^f>Kҁq> z>-c>݁>6dVj>B .}>.ԼOHu>i>ȕ;\>eh1ô<Ĕ:>h<8%>ٕ<[} >>GK >^2>l:>vw9'>~n4>H=B>Z1O>zX>?A]>񕃾ŝ_>84b>Πf>7yom>oau>b)>S >VBt>0x#\> ><ϽR>⬽s>&)Ȝ>Pgo>9>G(>IHؼ,|>wif>bt/3S>ZA>*ܹL2>:b">: S>G|=C=K5=Efἦx=!S=>Qvv< {f[ܺ㍽9l,傽}/@jRΦ,5C>?@Kz>TPP-v N 0Mh OPz0Sq:XWl_ljZn{rz(AJl㘽' :VƨReٵm6?tn?hQ[ZA8PC9&(1;!ǽ;`ך~;s;ȞB\;>0n;9;C0);*FG;i֔AZ<;<^5t-<~@+t<%P< G =$>FJ=t>k=>#=>=k>=>ފ=M= $=C=Q=l=o=r[=B;=GV=xj=F=Ű=^===.=Q7=P=9R=Z=k=@A=!==l>6ɋ=#==e===*==L=C==9==8<_E=;~Ӭ=8O= B߱=˻޼j=꼁=IQS=ؼ= ɼF=?=蟼=9=}=U Y| =(Ԓ'x=zfGe=ؽ8>.=B$|/=x >V>, >8 > >c > , >$n >>1T>>K=$g=@=A=='= =F=J===L===>C=>=>=#>=)>=,>3/=L/>Q=1>!F=C2>r=0>=b*>s=*">d=^>Z=>DS= >M=Z< >H=>Ֆ@==1=q=M==cLaeLʙؽAMɽhN½ QIȽ=T4+۽mYD„='> =>>=U>U=uk>=~>=>+>j>>G>>#>{>ʪ>>>>Z>2>%>&> ֱ>"> m>+>_V>.5>y>A>>O>>q]>>5g>~5>fl>>5k>=>of>>7^>|s>Q>pkh>(@>c_>R*>W>> TS>: =ZP>7=9M>=RH>=}B>9-s=:>S=1>85=7'>˛=G>< >f<=/H<+t=6=/}=ID;縉8>孽j UPѽ~J>ùF#>ej4ZBu<M󢝾V1n^w*cQа.c>o`YNYMQ2׀JfCDK;/{ /qcNJ ۽?½ix]:N\k 5UJ9PKlU>k\O~h\(y(Rq\BRfOX/RVvt@Z⼔5(Lp cj:{޽|M=ׂ>SU=-t>d=A#>`]=)>TF=.>w=_0>R=1>=2>G>3>>25> >6>>8>DP >9;>3>-}@>4=5F>)]=[M> =T>0=[><=Tc>=(l>(>NDv>o>G>%>@>8>Ș>:M>3N>;_>Z.>l>[>t>->Wy>m>R{>[> |>W>7w>S~>m>:$c>f]>&E>J>(&>/ 7>A>$>b=?>Y=x>H_=Q#==3=R<=bv 9>_[6>3 >q2>L35+==^=*DIk=gVkr=?a== iۙ=/lb=|mS~=jU=bH҆=\Tr= @є=ư* 1=_===]Žޮ=R=S"z=kT=7ة=w4>=&M>=O|_>ע=yk>[=s> = y>=~>=ʀ>+q=>je=v~>֓d=|>Nl=~>!z=ہ>HJ=@dž>޴=8$>М=>/=>=>=?ɳ>F=6>GC=>P=V>=IN>x =G>(=e>=VU>=L>:=d`>o=0>+=>=G>=h>=v> =M>;=d>69= >_!=>q`=x>9=lv>=Y><f7>/P;DO=E=h\=.;}Ǽf!S1Ͻ!Ľ)`T^bn%c])] Epv&&b .! =i#P)gf/kDw7ѥR> B;xDɩVbC` y{钾4ǽNh%q)jV;-=켌@(]{N8+ @=rѱ>3;S?0>I<H>PT=_>W=~t>=[Ճ>M>:>ԧ>>0>x>@>>M>$>jZ>>xd>h>tk>亣>[p>̡>eu>{>}>>>4*>m> >7|>>> > >tO>1>U>>'>_R>S>膣>b>w>`s> `>c>9>P>vq>:>{">A>;>* +>=>N=J>h=%=6==`@;>uS>v$>u9!,>Ƽz)1>8v 3>++;1> ;->;Dr&>;>@:(>܍fH=M<"G=5Y=)O=:E=B&H=r)="=x+=4K=؃=ؼ<~=lͱi=IׂI=({Ϛ=Co<}ܼy0%uʆQ6֫Vt=ͽs4\`mĽѽs˹6%6/d<8# v? C& *DA pAaٟ8+1Hܽe.* u{W6ʽ\̼Wa,m;"8M\P<-)Ŗ<=&T>,9=[/>FԾ=z"?>O=> L>#=AV>]7=]>=c>=h>.%=+m>5=p>*C=EBt>E>w> >*z> >.|>a >I}>3 >}>Ø>&U|>q>@Rz>>[x>i=Dv>=t>=_s>t= q>=q>=dq> =Ss>.Ō=w>fj=|>w={?>j\=o>=u>%s=>wא=Xz>Gz=(p>FH=bb>=PP>M;<ˍ;>d_R!@g#+ >$=pH=le0m=Y={Z;(vH`"覽uL&׽@ՖLh_l9h꽧70Yg཈CмwҽTP4Ľtw½g>X >YQw>#>"Zv>j >K1>0=7^=E =y =Yͅ=}3f%E=Rݼ=qݹ<BCJBuн&T꽁@_%}ig4~git aZOMG63>Dxl 46 n1 } K _Mm<ͼ):;5'&Xy.s3ۆT9zc@✽I\ǥFU항a_Skӷs61 z̝$3 tq䁾{yX.tR[d $ PRyͼl:"["{g;=9>a=y5> F=tS>'>p>B> >*>h>?> >(S>4|>sh>>h{>?>\Q>k>>^x>]>(> >P>>>%>>\>x>,_>>u>Z>|t>>j>@>C`>ui>PW>>&O>>d?G>>s>>`>4>>&>U>_>4>>>Q=i> =>s=H>׀ =Cr>/&193 ]>'UQ@>E &>j5+1>&=νRC=Ļ=* qP=VIl#adܐgWKO_݁UP6g_2/k͏&0j FbePSG9xT@|X+'Ⓗ/HlԽʼG<:sS=.{C; >@\p>q0>z3A>oQ>WQa>ㅽ:p>wT?<ه>ٙ<\>=`>C=m+>n=>C=VÝ>=q>֥=pÙ>4=L>v׫=޷>"K=:>y=t>{=>=5>s=?c>=>=>ȫ=҅>>7> >K>>y>x>sN>I >_> >s>a>o>1>Gւ>2q={M}>(=۩t>T=k>=Ya>˰=%]U>I=G>VM=6>W=$>'=>=.=Yd=ty=0=:c=le=BB="|=/y >h >M>Jf>6 &>*@.=S2c=8o=<>Ϭ='<"=d3hj=""D=%=̼=AEq̓=lO=I<{=?=|=~==`=&==S=H==< ><=>g=>v=1>b=>om=,>ۥ==>6==/wT=b=] = =x<"==(L=g0Xs=:==L=mw=|=ۭ==B>ǽ>ν; >ֽr >4޽7< >u>>4>w>Q~%>_,>]3>6:>&lA>9ٽb4H>Ӹ̽M>|O>I HM>r؆F>F<>&0>}yl">)ϝa>e>)K=`N+ܬ=-ø=uCkJ@= Rb  ǽe:i;%;Tu0=1?<ځ='<=V<>=s>A= g>#`= >8w=S(>E=j0>r= 2:>K=fC>3~=HM>t=V>i=X`>Y=j>lC=r>'='ax>=!tz>*̏<1u>J;"Rp>.Vj>2qC -c>ՠY>uG˼aCO>|޼^D>kA޼*8<>VѼy4>s>.>^&>wem>E*>N~ >U>?6>u^=>=mU=Ȼ4=D57=L5=z=M[=d0=~CV=~,/=# =)ƼT=`=< >I>Ҭ>!>:(>>шS/>DU}4>!8>Jkqm<>R;F&?>+]b <<>e=FY9>L=5>t=L0>>$=1+>Q+=%>2=IQ!>Y7=>$8=>]D8=Z>¨==<>K=ލ>7^=>p=>z=Q>g|=^>~y=>5Uu=uS>q=>h=A, >V=0>j7=+D={=1w=L<*=b<=;*=94Ţ=W8Լ3~=:(P=`b=Ï9{|mWCtViEĽ]FXPvBP>6Y3+j"Vr_e lh;:<< $}"̼ >L0Qx> U$;>l <(>v=o >$=$>@=j)>0_=[/>ʇ=v5> i=8=>=oH>o=zT>y=*u`>=h>C^>l>>k>0>-g>H >[}`>M&>>V>e)> I>+>39>t,>Z'>?->X>b,>+ >'*>k=<)>='>=J&>=#>U=} >5=e> =^->{=m >w=>a==h=~===vm==Օ=С=Wf==s==O@=Lm==?O=3<r.=_+bOm4y=9%,Ġ;F=C$ /Ď <úϑډD>S`$>ͼn4>s演YC>|鼐oP>֧Ӽ)4[>ƭ~c>Ϭj>*@!lp>xջv>,9A{> S<؉>=>;}/=Y>QSX=Yؗ>S=ܘ>Z=DO>@=>W=K>=c>+=T>=?>% =m>H=[>{=5օ>="{>2=yi>ׇ]=2W>A=FD>Gk6=η1>69=Up >F=> &]=&>y=u=Y=6====н=a=D=~"==4=A=<>=1$>Aȉ= >9h=>Sc1=>͜<6><>}1(ݬ>4y*>Nws >Mh`+ >eîMo>B˽>>0z >{ `s>yV)=cG /=wO+=6"~=Sf@R!<=K=Uf>HXf4=bD{34b*.#Il*[02нc "T+0cڽ"3xH jO|Ea )Fb5SOH M H x*\::HUR`wpXj[,T(x[%Zv$VMQU;BKa==2:a@x=5͘=%0f=&)W<=O# /=Լ=q u=zi=6= н=٬Ȩ=9m=pB=pq==NZ=O;6==\>A>>>`*>ѝ.>#6>pC>B>yW>LL>g>ES>s>U>QD~>S>c>64L>0H>@>>hh2> >O">>>|>J>s>=i>]=^>ZN=vP>=yB>!=6>=da.>=(>ok=J#>&R=7>M<=>x*=> >==R==e=M ==C=a=S<= is E#z J##MoNv5MJ~H{@LyL5vx't=smM/ ۀg]8bSaq["I̽9V伽P&}!?Hį;ϯjI,Ƕs)M*⽥˵7SճQ 0.soxfe[:<3>y<iSH<+ţ%=ɨoW=J9Ƹ=<=7=X<=Iy=7==7=G$= > X >>!>6A3>488>7F>'K>AY>Z>l>@f>mZ>\m>>>yq>>s>:>\t>[M>J/s>>}q>罣>q>[>xq>@R>ir>n>5Du>Ҥ>'lw>ƣ>x>ʣ>Dw>2*>gQu>> *q>>k>(>e>v>=s_>u>X>>Q>9c>J>A>ɾC>d><>K>M5>a#>|+>>>>_>2>l>`>3z>I=el>=Q[>v=\H>,=o3>*<>3Ha`>i+_ =jn=ӽ$5= <)G[|dB|.BMs;zKJ9㠔ژR ,:5!xɪ浩xͦUJEUmɽ ׽ΜKXΚ_6 Ǜt՟(Ɯ%=Y$.'i.+1Ѫ/A8\6A?)Iå%hS 1z ]lh^IBsUOh|>|,,!iyfF o–@jo]'{9uhFSlm`볻f(;2c<.c3}<]>ny}!>$s0>h?>Mk^JN>{S/\>ߛHfi>>~u>/$6#>i.0Z>%f>U$C>{q>q=|<>j߅>jqֽv>Žk|>Ƿ6r>5f>Z>C>C6>P9'>)~ý<>xƽ>>Ƚ^=HȽ@=Oǽ=ýT=@jJ=߬ =Fܟ#эۼz #n'*i&Ce3^0;_0xx{ؽ> ۽ >8Cܽx(>'ڽr->"ؽ/0>"׽2>A׽w3>@ٽ4>)I۽cI4>& ۽~4>׽6>sн :>½IT@>zׯTG>򗽮N>#w/V>o:cE_>`7j>wu>H4B\>p>:>;H>j;ˆ>|*E.<_;>HԗzA <_q>Xe<\/h>S =R_>(=d2X>H=P>g=7F>m=4:>=;->_=- >Q=>n={U>Së==!Q=1U==>=4=G=m=='=)=j==C+~=v=n=`=_=@I=DR=d0=E==[:=<۩-=d< EZ1;=4{8(=p4=\X42=*#>0=,6>==ijG>>yW>S>ld>co>@>;[x>M>+>V>M>+[>D>n]>:f>_>>>b>@>d>+%>l/f>>gf>ۓ|>e>v>d>p>b>xi>b>%c>O4a>/C_>"_>U[>o\>pX>Z>U>Z>kR>I[>N>f\>cJ>a]>E>[>?>4Y>9>V>/-2>Q>~)>AdK>>@>Ŕ>1>t]>H>=6 >]=PB=^=`==o֔=?5P=<%J=]<3AE ZU IpL' e0>X6qQ8=7֣94;Q1Ys-lꧾH'圧;7n;.轘ӂϽkܞŷB_@^X旾3Tސtf{zvܼ ̼|ż]lż)[9Ѽ8LG[ݼq1"9ϼ>PݽO\=z޼wʽ=$%=I;ܙ=5<=>L=V>d=>'=k>=, >C=,>=s0>=>%=&>{=>6>R> >=>m= x$>a=vC5>=G>3=#Z>j=хl>=z{>=H>=5>S=a>G>r>_ >W>B>>KJ>J>>h{>N >t>N}>&k>(>=x`>ʴ>*S>f>G>G=G<>S=2>ܰ=(>ժ= >SI=(> I=N>wz=S= _i=l=W=;=?C=o=/$=Ü=< =(L<0=_yw=MӗCM=YgQ[= TI,=>BB|c|PE'a/5bn08 ׽OA`InP] ]>6"g^TPo{r 1u<aw|qmu'=3o8fۖZ&KЁ>7 nӪ!Y 8Dv&.jӽ3K[=]nÆսozнթԽd½>UٽSJ>5*J+G׽9Q?EJ͛G~Yobbvq[F<8P%:0=?F=;=kH0 >-"V!>eP+>=^/>3>8>=>A?>>~>>-Xx=>G?> OFE>\> k>}>e%>m>vܽF>fνNh>6Ք>s>ȱIʌ>iD>>:is>a>)(J>񰏽*.>>Qoo>.Vf)=ї:=(>}>ϼr#>NgǢ4> ֊HD>CNG=s]>9=ab>>~a>vs"> V>|=>@>T>"!>-n>=1>!=Օ> CO=c>5<>Te;>tr Q> o>> 9~y>(^>Լ >M>u C>!>h;U>U?>rW>M> >"׬(>1ල>Mܸb>H=֔>_ഽ> >Qt½̨>Mнd>+Rɞ>9g?$>k3>cGy> \>tz>cg>,U>sDFrF>t^s:>|w`2>;<(->'>Mߚj> 0 >ʩf=ҁ=)ۅ=>Dz=C"0<᪾v;wKxvӽ..d<pU}[;)XAqCB90᱆7 䋾.+>C <^1ӅܑNG䖾.AFYJTj5ըW!Fu-챾]43a4-#ž8 CȾp.ɾD}Vƾ½#בDIγ1ҼϬ"7<G|5d%>y'@> -0T>4G|^>^>EaR-=A1>C6=U >nQ=>~><>ѳ<><,t>Q=>mM4=>7R=e>ҁ=o===V6=c= =V9= > <$>3>-C>jܦT>R2c>"M,m>#@gm>|b>[.LP>z>>ڮX0>d&>-!>jO>{>0>Zcq>[T>1Ǟ>B >fC>r}If>G~<<>c7>a2 >)U=#=(%̴=7.=8^=rC.#> ;1>P<4>b*<%->#=k>R<P>S`<3=n;?=01 =ҁ=щO`=jU=м=Hp=l =x廾=Qg:==M< >U<@*>0=c>>&x=^I>3=K>G=ƯF>*=i>>=7>d=E3>!M=]1>?=A./>a=(>'=>=4>۾< >,a= >L=W >D@Sr(<>R; >[<5>dN|*>Aƽ8>IܒIA>lO2RF>S AG>;D>i=>m<>4> )=O">&E=o> M=r>KR=!>e=R">=>q=>2=H >==>g=>$=<>U>=I>V<>&û4s>k!>L;#>ӽ$,> [3>#6> `$ :4>4x0>8F\1>AU7>y^eA>j]PJ>SIO>2DP>b4VN>&H>~"ҷ@>6>+>1: >Ƅ>b ,=A=3Y=ȋqߒ=ӃRD_;S>^[m>#;~>ga>&~>xXs>xDe>1mT>&M B>JF2>I$$>=>Ai{=ܶ>|=fB>w=%K>U=lz >>چ >B=WP>(e= >=^0>Չ=#2D>+wQ=9V>9;%=Ib>r="d>;3= 1]><=}N>d=<>#=T)>kf=>===w=%=[K==t=g?=rpW=8=G&=I?=nv*OzlBjp*FQ3XU#Qսs+k472"_)Q-.ۄ3|*3%%t/tR@)j]_0%@r̽%:(+>;*I $A-=_ C|q|޽ů.ý@}r1BL 3?h?/ (>p9>!(B> )E>B>:>Tɽm@,>AL>=D: ==[=xp==+=<==i=Q=c=Ƚ=2==Ų=x=E=м=c==X#=3==m>==+3=ޤ={===F=q=`q=w)=o=v<=7;}>z#>LT6>r;[J><`>bR=Qw>4=d>K=)>YyV=ő>_=0>s=>=7>5=:ؔ>T=>^x=}>=q>vp=k>_=8>=y*>U=S>)=֌>g=Y> =,>r?s>E/vh>$X>1B>O׽D&> 7>HF=y B=k3=\q<;$9-<%)*Q*,Ą($!RqD 2Ci 'ٽhfӽ<ѽ??ֽW{ཊ 9Jn3$K5KAM^ZbBn qBwzx$pbKBUHM^> 3nՁ:(J~m%v@n 8j :iild.o .oQh \%YHK4./BpnJ'彉ALצg|FJj:q)ԃ<h1=}:L}=AɽXv={p =Y[ٜ=&3=2Ļ=9#<=?=D=b^=p=J=i==f>=6 0>B=P>4=Rs> 9=t>z=>\G=>'=\>,=ۦ>5W=>c=>q=L?>=uR>L=>>J=>}x>R>v>>Ux>X3> z>|K>h{>a>_v~>)t>>>釆>>'>꼈>;̑> >4>g>Uʕ>,}>9֐>8n>*_>u^`>=iw>AS>`> H>@8O>g@>w@B> <>7>4}=>W.>?@>$>B>>IB> >=>P= &4>p=a%>=>=ޜ=2D=7-=b= E=0<Լh=T6,.=iz*x =s< s#s<BE~fϽK6>ǒ̽8T>½=l>?i{>=Yw>ׁ>\}>$Cyq>N3T(^> F>{#0*>: >PS_<=>JMѽf->۽"6>&ؽP/7>Ͻ4>KŽ@1>ŸuT,>B">>"W@=H|=\gq=3Ǚx9<9:;<$<5j(=AN\=i2\=;ߋ=אN=V͡=:x=0ֽ%]=1#>= ={\Ub8>[)>"] @9>E_B>%\'BG>UqX8F>zW@>R]6>>a)>`Y|>? >O='nj=d`=JLWm={[<;E ;2,<4: B\>D/>g<>ާ߽!oG>۽gM>*ܽ.M>jE>8>h)>e(C^>S >r(!5&=@m% =(L+=A+=H+_== 'dW=8Ƥ>=T-;=I=Iν|pa=ஞ/r=FXr=FмnX^=/";,9="xO0>̏cIC>EZ>vr>>f>yv5>_Z >Obq>ν5e>[>zP>dNE>Ԃ.s7>>b(>uK>&Vۗ>ai>ۗmz=/y^(=ǁ =]慾UZ=S2=刾'<6A$μs5OQgmO\ZP?OBM1`na ' ,ܼ^ ^@!x޽$;LhZӱS.2MӼ(  +JE!Ϙ'k%۽S;!N| 9\]Kfi ֏WgZ` _楧Q0?8(! 愪y̽9~ۼ;{<֜sB=La~=ݕ=p=!ᒾ=1叾\=؋#=<놾o==ہq@=8{s=hxM=M|y-=izsD=/wM=mD_=r]+=kGfR=-E=JO= ð=޽w=Lʽ={ֹȺ=͡B={ٌ\=2"ec=|7=c:=kl=\=(>]:l6> KnT>GX4k>D `$x>b\Fd|>e|C:]x>+n>92b>4\ET>[<=G>K{=:>%5,=.>!==|!>kk>=o>[%4=o>(#=I >m =><֓>U<>3$>yɳ)>F *>YNI4#>.{2>1\?%>;==9%Ƚ)Z=*OȽe̹m?=gП>=z?Q====ص=cgu=I~h=>䝎;(>u<9>r={H>NB=V>Ri=sa>k̉=j>=ir>=Wz>Q[=7#>/=w>=g>p="<>_=E'>=ˋ>n= >s=D>=4}>k e=xk>.=V>0ORD<&>79~>|b=ܻMB=O[=`;=LX==q[>:=>*=j= =U==5<T=<9)^<吽?r,X.89ֽɃE$۽|[ҽ2 Hʽnªh߽r$⇽iQ?7.-4.S ~Pp5 Fu`o5p7ZZ ԱU ϵŽ +9 0 q ģ\ 3-m$M׽x+Ft̋XrU5]qÃoʻtj;Ba;= =P=ߺ==sϼ==۰=w>?=U>Gc=**>=ރ5>w*iӿb& =N>?) =W@>< 0>2TW >S(>?=Vi6=cE=H =NI.=d=҄r=*Zy=~>(O>ثl>c>쁽=g6U=))U=!%=:ȼ=|0Y={(vŠ=&uZ=$< YPmah=B-ӘнDǽWV " 1/>w+8*^"=#*>oMю3 B>9cF>HXH>ڣ H>*G>A7C>zGN<>1>s $>>o#>H$>UK >: >9<Ӑ> ;v< >5k=p*>A=|;>]={N>~*c==_>I=:;k>:=m> <9g>g8<_~X>2D> ).>C$;>#i>y=5p=2*X=<'B7qRQ*J%P+˾ #シͽﵼ1 5]'n詑YսE9'ӽ y0QQG f,|Y@?dNݽfýSaǥ)X_Lf@AULg35I^((Z[o k*ڽœS3̽7L ɽR2yѽ;B-2/ &&{3%fν~' %$!x^ [g< D=y=@3=oy =xy=Ͻq=w}p=eDI z=jb(=s;Ѣ=s<\=u A==j=z >뤮=q#>>=1:>Q=`M>=ɚZ>=8a>p=Fb>B=_>|=Н[>=U>M=PO>=TH>`=HA>=8>F>W/> >%>6> >E>S>%!M>>rN>>]L>0>mJ>O2>WI>Հ>6K>؆>%N>>Q>C>$U>M >\V> > >W>C=EW>e=@mX>V=Z>h=\>05K=Z>Se

>>8`1)>->$j >Sн'=on=f lA=n=V(2=+41o?6Y> =E> hKlʕ>^͑>o~b>HY{y>6|>Jk>B!>q>PQ>;Fs>$<[>==>.z=R>qZ=>O=> d=>#=s>a=ܴ>=U>sj=>b=>S|<>>_ؼ#>>_#/>{H>f>B+>Aw>4uKe>;2P>h;6>j/N|B=_G53[=J=a = x=쇾=Jk1=;=N۠=<*v=l̥B=o =ŤՕ==e=D_5 >&&> HT@>*|X>Њg,k>3R;{>X=а>[s)ꏉ>o>] ~;>R>S >ӧћ>З>D >@j1>h*}> %c>-G>E7+>R@>AD> @"=5rm=z#= ߺ=۽s=9H=).=ӕ#=0c==i=wO==}w=+=2.<(=|U=7IP]=ۜs0=޽X; =Q@<*0#<:O< jR<u}5<<<4D<4+E")z W[8=8D=jt=5=.=*=%<=V(h= ^Tє|=L=WJ =G(=>^7=* 1=.G =Ҽ$J=K=܃=ԝ4֡=ۘټ|=d1-=r21%+!@{Xᨽ@&G? }'YH#@u1R;"<$f3=* G=xp=0Y#І=5X=ZiO<ũ=&><#^=F= >"~=є>A===y=K==T >؜i=sY>k< (&>,u3)4>>F=>_=>_۽1>V>F<>ȥ罜j=XȽ<=`=t=n-xrs=]߼P'=v}7<^c70S>KTH>E>&K} >r4_E=sz=MA 7i=HB,k.ýaձt?Iݬ*ڌ͟mVʽ:սLuݽ,:潆(4zc"*FRkʽ#i%("WDu & {`ڽ νhͭo佪%kսCĽhs)Aqh`ig=]=wa=$== =X-K=UÅ=z58}*!>)iZq.>;t5> TM bb; "> >_&‹>b=X/=Jǽz=@=T=q] =A_=&R1=n"wi=E">e=Q L&===E˽`j=i<<ҸW󀽈Kj`̽C7V4/"O"'_8 2DPKHx IEx o=3Վf)I >Ľ"Ͻwս MԽ'ܘƽ$ 誽`Ƃ#?"D۽&@o½&xg>>^<ظ>"U=6I>Q=>=R'>=l.>>/3>4,> l5>f>o3>ƪ%>T />U+>'>Bs0>g><4>{>I4>,>0.>+>$>0 >y>װ>v>a>AD >> >Q)>0>)0>>k2>EM>w0>?>,>->C)>,!>)>#>4.>(>k6>G]->@>!0>K>/>V>,>ڕ_>,'>g>M>em>.7>Qm>"l >7e>=X>j=i>H>8=8>o?=(>s=6p>i4=J >=f=`:=>=$>W=D&>l=#>a= >:Ǝ=J% >M&== <~=Ҽ׎=` z?= ƽ\<eAM"&wI>X=N?V5NeZ>/[HZ^z[l]٘'La0Cd6f9xi:*'ocl@b>;%>̽4>/A>ڭM>28X>ZºJ`>#˽f>MXܽ l>X:r>K[z> >Ҭbs>~b>o%>8)>48>:@l>B:ڐ>2zB>& I>Á>r m>,X>I!w>ӽ>tf>#5W>QȠElI>D#;>,E.>rQ >r>C>9;|=6<=i==b9O==~=%h==\==L==m=7t==y=f[ 4 >9->:n*>3 EC> {%_>Z90z>]>+|>dz:>J>Z>2e>aü9>: />GZɍ>sq>Hֽ>\a>%> A>0Wס>lvi?3x>UFuZnl>[zRa>k3w9X>5k6UR>lX[R>JA^tW>O(`&^>a>:ǫ^>⽓mU>vӽ9G>zνb|6>rҽ!>۽O>T 潐F=(=rYm >I>3h!>_+>83>(!+8>u";>, >;>2v@8>ʻ5>_< 2>. =Y.>T=<,>t=K+>y֝=*>ї=(>=S%>=">= >= >RA= ">w=@&>=@y->>5>g>v_<> >=>>"8>>5.>F>&N#>>>}> >&#>O=Z)>=d+>-V=w&>=>t =>:7=,q=ř=Ľ=;2?]=1_<<=@#=-ء<-<߀&R쀾* Yvkfl.0UV]`bUt>w(>(i3>M<^<>ߌ9=gB>6=ΚD>[g=L B>咰=<>o=7>l=I3>H=80>=->&=S+>-=)>X#=|'>< n$>:2 >_|>Ӈ>>9Ӽ>eT >~x >O > %'1=A i=+Z=evcp=`f=nGn!zVaV*'nݍ"J٦R؀SFoԆ$Y6wn>'D <#ͲlL:|kO1=pY=:=C=( != =L=:t =1ٍ8= >N= >}W= >9N= >{8= >|=La>w<">n<5>%;VJ>vm^>yr>!W>Ǎ3>CJʎ>||>25>γks>ݶŽ>{Ƚhm>|G\S>w7>܅Nں>ݼ@>7f=F<9=mK w>UO>oV>&Xj>lS(>bIR&>>i1>-;~:>D=>>^#=>Q9>:6>C6>O8>'ʽ+ :>gнS8>ӽ44>KӽJ->mҽt%>ϟϽi>{ɽn>½HS>&E>l>繽!>u;'>-v.>r8>~{C>`#}N>TX>>13<^>FP^>p Y>uO>-B>YZ3>7">fA>ĽU=Au۽=ev=CYҀ=20Y@=۽ =fL<}5>L>9i=ټo=Ҭ=͛Kfk=q!yVDK.ܞQM9vY35aI8iDsr}f惾;TS/ƈVk q98J\ن:Vlݶ<,3= l=Jl=+I=L=}=JO]=󠡾&1=<Kq2<y::A=J>WX= >Xg=/>@i=sIE>!b=!]>!W=p6u>|AL=7>B=> 1=Ռ>e?=iČ><܊>j=ϼ;M>k9m>] &z>LPz>Lq>?Qalg>bX[>hN>@>|1>">]>;>ԙ< x=g<=s(=^=J=j*=M1_=ȍ=B&i=B=!l=Ρ=qo== -v=BS=|=ذ =)}=}v~u>==ypZ>ſv=v>J=>_=F>l=x>e.=jˠ>҆=>`z;=F> <@^>>;hǥ>edR> !ʼOy>t[>޼>X,ޑ>Se8Ñ>A܏>X`7>/˅>ɣ4~>>>Nfq>f>]>:N2V>~*O>Tᐽ\ G><>Ǘg0>pX,">3>+8r== [K=kH=>=ۥ;:AclX/Gc:;QNKӽSju{L$4AKQ)`)Utiri3މF|ƐmEν4ܽ(佻ڒYdLډt t$^Qi<ͽΰQ ua!kox}WpymotqmZm$RlciY.f]O~a8D<\d:ÛV#?.R N+)MNnLͽjM=)F2Xq=!m@=:5=5+=m0;g=)=!#=#>_>T*T>va==н=ȭ=Az=.=ҙ>5=:| $>8}<>h=H>tG=O=3>i=,I>=_>]="r>c"=^>_= >=>=x>0=i>h=V>=}@>_=='>Y= >M=`=]=M=k|=5==N=]>_= ">= l$>>>K>O>>^>K,=%=nf=W=v=$=h@===;<=0;;c=v=9{ң===*蠩="չ=nd>=坛X=e2ɽU=N==< %0h<~6o?(=~oq V}o;Xf <*#iZ=ټe=aeE=Vֻ=!; >Z<><<3>nV<\R&>0<,>=<>6=F>M=R>7S=_>$F= k>:(=eeu>j =Q|>*G}[<>-;>w꺗u>`f %`>v%y>5Ƽ>$ >;5>`~>y>ӉAt>I l>Д%b>q_T>~)B>^10>m>(>{::[><#=2M<:=} =:=k&=!=z(=='GB=="0a=x=w=#=Py==8.h=r5=AH={< `"= 3OdZ>1XPP >JE >F: >ز+] >E>S.->è@> |>>z>>>>b2ýo)>q. ,>CCj$>A}@> 0:>.I$>x />K7> ;>(:>-42>-U$><$>&>h&Z=^%ރ=$=FP%Iʮ=A%7!=U8',=o)_u=H.O=4&=;!n<xA=Aɽ>b=Lɪd=G= W7.=L=r={b>w.<* ><(>A2=9$>~=)H>R=.>=f >=>X=Z|> F=E>'_=e>=> H=e>H>}=e>-=G > =<> 5=f!>`==A-=b~== =z<>"3< +>ݒ)<$=>Wn1=b>a"=Nm>!Q)=*%w>"= ?><#>o q>` .Hz>.Wp>ڽ&^c>R>1$A?>#N(*>`f>Ke( >Z\>ՔDx>&S>dk >Ͻ >D >-"cE > J>Z߼=`=戼=Qמ='|=P QO=N*= 2F=_51 =M*B6aB# ^`=jн$$y. +G0 5S;4 AlC[L[WHb毐?nE_zɉͼ4 +<ޜ-="r=琾=A|=A(== =~=b ɲ=Ə =|uj=_als=4K,E\=4G= .= ru =<Y9^<>-[7=*>"=8>-=E>b5=gQ>E="[>uwj=bc>2=g>(=2i>= i>=Ah>qs=.ug>a=e>=c>8=Q^>6=~ Y>ZX="O>=WmB>~=j0>@6=><)*>]e>{>sS'>PuS2>&#KJ=>-[H> KR>5X>Xʼ~7Z>ddU>0zJ>;o;> 6?V<><>[j=eHJ=lǼA==:=6`B=e=j=6=d"Q=ș7;=<:r=?g?=y(f==|h:Hq=l ټ"@ >== >>Y>=K~=k=8Y[=o߾>=»_=sٴ.=iէ{']=(f=I9=%>rS=v?>=tS>+=V_>>Ic> >{^>Չ >Q>>v?>P='>1=h>T==Z-b=J==Pt=!=Z=t ==Q6==i=#=m=X= =r>=]@#>5=۫4>=?B>h=L>g9=KR>Q=S> =aR>+= }O>t@=)L>_K=(I>yrH= WF>di==gD>2=C>Q2,=׷C> =&= B>=P<> =W1>*<{W">e^u<=<^=0S<,==r=R,=E =>=m<F=ջC=wA]9=Ll"+=}:=D\jP=K|<:R6><>@N<5e=j=.=H'ƽ3=b)Ͻ )=x˽ =!bI1";AT?<5<ͽ= W1=N={w. e=Cm=iU`=2bN<=jkJ= q&j3Y}jwPD F-6;?F-=@ݼ!)*̮ҽ,>;U{ս >}b>Y>p>y c>{ ^;>< e > ><=, =]`= pò>̶۽ >7>S>[KA >\>ἁ=ռb=r%=<=ꄽub;\OؽC'OŽiOֽ PN#߽*>^ǽK%VOѽY1*8Uuv,MꌽᏽG2ɏ6a<='Іҳ5=B~W=zoW{o='fq=,`d =;f=k =2j#=r^N=N=CKy=FCAc >I7O>YF.>io/??>RnNL>%HV>0]>Q`>xМ`>F^>U ]Z>ՕQ>OFB>=;r<> = >B:L=ґ= u==1E==v=h==E 2==W<6=\U<.== ^k=ySZFF=,=Tj{4JȤFo^ZVӴr.j>sjI~ス=oڽ<ď*_ꑼk'2 vÇX .aƼ1? t† l4 @6bj!Ls@rHpG [)*:͑ ߓ58Ejz:h \F<+<4=v[=AoB=+ǽ'=N=ƏjX=? 5=0>+<4><->==}0>*=#NA>dN7=xO>>=Y>B=\>1>=SX>(4=lM>ݦ*=%=>#=SW+>v=> =k> =<>Q=P}.>Z|>D>nD> V>Xb>`>Xw>2c>g>]]>+>PDO>K>؍9>e>>>fx=qr>=>jm=>&<ȃ>C<">:B>9v >ѻ; >)kc< ~>Xݱ<r><=j>,== =c>n ={d>F- =03g>¯=h><şi>fx;3p>%]P:p>PHk>߮a>X鼷R>; _?>~' +>L(>4>LN=%ս(=w:=$Tj=CD=eI< <y;C;Ns):;^{C[#>IF,fYR;d:./r2{RL#a%i~3˧|P*lxbp꽫tԮ'r"}svuk w |ow,r4v-v0=z|LJx>_߽JֽQS˽𽽱&|3g]tC,o3O;m.luv[Xn<,qgWvZ{\;t~̔<}ǭ=w`=kpb=Y7=C{>I+RY>->4G7B>+ʽ1V>klxk>X@>>r4:>͝<>6S>5 >㒬Yt>⼦ε>d ޴>г>L#ݩ>Z ̽j>A~4>#>&ƍ>w#w>\k>=~d>rb>qd>G߼Gi>֩;Sn>=Kq>[o=#po>=Pe>J=R>=5>=v>D==]=u0f=F=_6>,>2fD>0W>u/cd>&-Cm>*xBt>'Dzz>!2~>)0~>w ߐx>T {m>jug`>qMS>$BI>L@>l:>D46>+ {5>L8>o$+#>>u.ݦD>4VI>- 7J>_6|E>X3ŭ<>Q).1>&7&>(0>Ց z>>ý7=ۓ0=y7:\E=z}=4;`=V< $=W=a<= >c2=F=T=ݬ='н=P%r=|C=nuh=z!=p =!%= t W=g춐=ܽ=|b=_vϗ="h=m=Wת;="#쇰=v^LB=hW>KfQ;&>\>ld>k9l>B[OE >w3=.==s3BE=2#H;ͼ>\ҝH>' >?:O>3-<#>q=5>+7D=D>{=ݷL>"=L>y=B>L=!3>=J >u=u >.==̺==d==ş\==sN=Ǜ='c=-=a==ߨ==~=q==>T=b>D =g*>'=i3>L=J6>#=~2>=s)>=I>p=t>=N=&=ѕ=$=,==s9<aN=b9X\d޽K佢ܽԽڽ47ֽ/EKн\KcȽ{a|J:MbhE)RSnl8ӆ wK#Լ+fԧSu+iK| o܂f:<|l;}Oüj;.ۼ<:v^7=V /==ǢX= H=aE==]?=ת) =]<==7==,=ڄ==<4=#;=<>ǒ;>A (>Y:5>YA?>2F>tK>=? M>ZjL>GPj[J>qqkG>t@xB>]r9>k ,>i>p>}={O"=M=zUc=]Q=: t=h>0~=F,>F>6>>9>S>f4>!>%>of">>>|=>0=U> Ri=>t =Z>v<->҃:r> wR>V=?ST=ߧơ=K/=_)=(}=ۯ=WF>{r >U\>>c: > <$>u-=F#>mi= >=!> =N>N=>zt=;Q=h=pl==i=m=Ċ{==za=5=y{Z= [=]=$=c=OS]=/=P<{|;xgT u%lg޽ ۧB':zFW?0dNѡ޽4Vh߽gV彔aT}/Pj \Lj HMEW& F32(K~=TdHa4R?l+[vrbcqj,]jvq@9_ Hw!"T|nL~I~L}{M.T tN\)hjaMZ:aJ\85S$bK$Bf3;컽;|4:-t'NuU& | ŤA D;S;4>HLX`>4^ Q8>ĸʼ>d>4޼ـ>>-]>n>4v>R½d>dս>#ԫ>m>F:>u>$ا>&|>2#{>>P>Z`Ů>V޽ >>j;>WX_>; >Xq>) xN<'v>;?={>v= U>D=Լ1>sĜ= >C\=K===j=֮='t=,=ϝO=<9=M;D8=nI=EAj=r2=, =Ep=#U>UXnb">WN!A>I:=^> iu>>Z'>VSм5>?̼p>s;ܼp>$ɋ>D>< /X`>ƾK7>Tp>.i>10w>o̽E> ('U> >$>^;>Rm>g?>ÚyV>كKv> [>uB0>x>tČD9>v>7> 9\>&|~>q0ۄ>dHp>&VW>EdC>{3;R5>!->&l*> ;M'>>#>u>$=eӽ[`=ϋý%=g=U@=x)v"=|ᮽ=LSA<,0KJ^0['9j(|C`:Pn/c$ t?>B3(>%%y3>r}29>4D:>2hĽC6>Qߙ->T*CR!>ۼ>>;=-< G=Lgg= >=>}=>.v=>O=>==T=fB=̆==p=4=MA=R===Q'=7<4=`U$<`{=&;h==Zj`6<;M:VO;9;)j;_뻽'@R|x6h-쓼Wu%sX{!8^:}߻c 6y{jNIlR$żWl*;8<$h=)᜻)J=`3d=?ûc=t$K=L:=9<<,*,<8ͱ;,<ߍ &=7EӼ=&ȴ= *=i"'>H!<7h>a =&,>n=^E<>$ =J>=@W>3=C(`>O >5d>gx>4c>ʈ>\>##>`Q>I%> kB>cF$>p|2>">#>(!>a.>">H>#%>T >c+>4>c4>>?>դ>M>T>-\>Y >mi>_>t>$>Mz>S'>z>.>Lu>tm2>f>T45>vP>8>^3>|=>>>S@A>=xB>=U?>-&>4Wg> =2_i=:lJ="(AĽﱾ|ýo. Eq~?PWOn޴(P 8(>r?ENYc҃?22"Q2@ƽ1q8н_ ׽rQ_@޽^NGHyf?n8,E31;)!ۅͽ!v5b]XfT|J3;vOO %yJ3޽Zxp $^׀[;(< $ǽ9ҽ|ݽuRx6>$N?/>z]a>6jF7)>>q7>q0F>(|j*R>*`9Y>T[>"KٖW>9DN>?nA>g<3>7&>82@>C,>n%a>o>;h>Rd >7>~ۊ>>(>9p=4G=w=4h=O;Fרֽ;,Uɽ"mhx_hߔ8IoJ#<=8;<Ԡ!bo=^<]j=T=f=#5=e =X==%k=o=sh=j;>\1L=A>h_= +>6:&&>!=nQ*>vԼX80>8>LEtC>O>XZ>^v(<@c>A=r>F=x>T=>=#>,?=><=r>:=4>횽=PQ>q=->է=x>o=͖>=%=>n}=6ӝ>گ]=7>Ѝ6=z>= >w<[>ǒ;>ŻF>Vw[r>.t(a>#DQ>$:S9F>+C@> @G\A> ,M2G>rWSnO>`eW>t[[>A[> `X>7Q>G;=gJ>_ʃO=Į<> =l}4>=)>=(><=j>e=R=l>=&>5 O=*j>O)=rS>0> 5>hI>ؽ{Y>՛rnd>8`k>3p>OIps>ABV_v>el<>v>h<)Cp>=8a>=vJ>=R->8< >:ۉ==OZ^=O1=xb&<-WK >GQ>rY/>㢎Tb@>ỤtnO>P\> ҽKf>彀m> t>ho{>r>Ӆ͆>ȴsZ>ϽA>@J>ԥԛ>搽%8>zb>Yj>@U>U1y>:&J\>u>pe>μrY>BaFP>Jw:H>>Y=)>='>Á\g >/ i >(N,>‹> jv=L=y=ճE=^Cy=K=ڎ= =ڋ֖a=9~='j#CXڽ› /=Kp,!A&e$ð0)@0IG9C2y#2SOAP0Z:Vs{=6=eKE=*o=ҀY>2>7X">g?.> wi6>;t:>5^&;>[_E:>>*A:>:>eQ:>:.:>L8>͜5> 0>x׽7*>|L!>r>g >$>ټ=h?=` = D<+==:h>-=T$>LE=6>=4I>=D:[>=i>Ku='u> 1="|>u|=~>u=R=@|>?=Ov>wKK=(m>pp=`>>=N>R=Z:>n=6#>> >j>T=p>&=>ϏX=9Y>+<}>];*_>?!>,>W7>>>=>5)ܽ3>y#>p>z"=/q}=)9_=1?=`?}?=9=vQ= 0$a=#<-< n<O:<P=><<[<8<"l<9><<<߶.=WtjG>=ϼ΄=Y R=&&=pJEJ=փo0>"*> @>fŽT>rнNFh>x|νK&y>r>{?>z͌>}:_>M*>Wȼ#>:Ǽ>Wk{>'m>-YeZ>p/B>ޟ'>Ā+ >7н"=N=fVb=):= 8n< v _z L] y-1=ݤbcVŽŽ.߽mh'u;|f+PHwy޽+"ֽuAҽ|ElӽsֽmR۽,HO^s֟T0YA>b"K~S8a OFqoc?q99t \66< ^$V~gcXvEm4;5df<Z_+>$>Mѽ*>޽0>~kY6>!<> 뽠?>潦 B>j ݽ0kD>QϽa\H>,O>4ܣZ>bMg>rgYr>k1Kz>{>?Zw>C6m>0^a>oS>.A>FŽ*> 彿>2=d =v2r=ݾ/=q=j=f7-=VN=QXս9Qn=O8=Kу=9 |=q&e=U_:F=<&=<z>J< {>=>VL=F> => =c>=d>=ݞ>R=>O% >U+>>8>>F>>xS>t>_>>Dl>`>sy>>>~> >6C>h>Z8>\=I>wP=X>=>w=l>#7=Q>$<;>፼;͙>R<@9>e3G>ݽ>b> +>Dc>Xlo>!gY>qr>v>w.>VctE>JoN>gn9>\TUr>GNc>4=W>+>M>=wE> *>>s:>2뽧6>9S3>\۽+{->%޽*%>[B>8N>?*հ>Jy=yd=e=F| =[QٽO= Ƚr=K]H=2Z#n 3;:,R@&h齸H}*=н9En!ᖾ@],+_`Dcľhh;H̾dHcҾuhA־΄׾7}]>EraQt/:עI;S2SxԼխ Zo`OC-S7>=Wg>SӒ >f<<&>ǝ=n2>=/9>n<ݸ@><$I>Ӿ;uQ>cλޕ[>chҠe>r o>ͤOy>ӓr> N;>]Ι<>-=>j?=Fv>l=N"> 4=*ћ>=c>~>F!> S>.>#>[>_/>X>7>>z<>>Zw>jB>5d>SxB>IP>\m<>@:>/>!>>b>>=qp==JJ=$=ѹ=L.;⣺=˼=}CL=T=>Z߽E>`> >4 > >u1| >  >A >>-C>Tj$>􈜽q0/>u_Q7>M><>@>UjD>XɼH>YѼK>YW L>94UJ>2kzF>T2D>1PB>}죽A>4@>qa>>v[;>pb<8>^D7>g9>"uH>> HB>DF>`]=J>M>̇O>X:u-P>lM>SaA>08>]"Ri$=d> D=%7>~Q==Q==*L==ϝE=(W=b<=dڽ_ Rн ǽЏ"!ƽ,1ѽA>(LnHQmN= jQ @8CR9.R?%CTbYUPoVp}U^QwI2S>Bs731?p'a(L݈3냾AyM⽷Vh.Rp;nJ#輴:e t%3<Ὅ<+=нu&=Ƚ,U=6vǽv=O@ʽ = E̽re=0ǽ == =ğ2=wˎ=!j=t=!;\=ؗ=9^=@I=l3=K=;=pAH=Ռ=?=Q|=J6=N>+=, /> =H>;=y\> Lz< j>[u"<K>i<#B>I<9;>C=2:4>nM.=¦->G='>]=B!>Qi=->m0l= >1n=F=u=]= ==V=E=I3=u=Ylb=DƼ7=lvn=(RB= <=s7R,>Ɯݍ">]">La½>~>=-O=H2/g=GO=nZZ<%h"#o=;[>itqL>ma?4>ۍU.H>WZ>.펽wk>Wy}> :`>ἥ׍>G;>>uTa>i$>į5G>ȼzX>3-bb>JK>Pȍ>2нg>~sz>[y.f>o Q> j=> Q)>#) >^+=\ =xbׁ=*C=77;r߽J׽.u?ɽi,:AU{PX; {XշHjK;qdڽ,>ͽ B> ̬U>nd>?%en>r8,r>kռn>֕d>0< E>=4>r2= m#>==>P4=={==%<=s<=su~k ?<0½e^iνڄoν ƽ, ̽ޢZҽ+ѽ-˽ٜtĽR򧼽2ٯX)j L yhBF:bQJ^hg?ހ|r;͍Nb̍n$_|5DY{J*6 cW:Cݼ _X᛻&g<`ċ8Xv=ϵ >1y=>_d=>>naC=> =>=V> ?<"f>8a<>Bj` >n\>z:wx]>cGP%>L.>I26>ta9>o<\:>YF=8>#+={7>D=8>5N=<>!N=uA>4:I=B2E>A=G>vB>=fPI>C=K>9M=L>(V=gK>xZ=/pI>U=E>yD=Up>>!=5>< t,>;t!>3} >i+B>B4=ͽF~= = cb=Zd9Lr=(MUQ=[H$=bC5KcJ (֐B%RnǦtvV3 ]&))2½6bf]nwvk¤;:wlOQj ҄ļ U'#.6pW,Lzlk:nzc]?&<ۑAd钾ļܓO(e/;ʐj*<$n-=i[==dg\=?K=%0_=+S=K>#нg >٩ >I~">~C3>8.o>Eot>ʩ"Z!><(>L<*1>0p<>5;M> a[>[Mh>ds>% -~> f >ƖS>Ьq>^,H;hۚ>pJ=qӫ>wė=> _=>=˯>v=^>>W>>"e> >`>=@>?=>=->mV=@Ͳ>u=3N>>J>إ>>F>(>p>4e>! >i;>N$>x >+>L=B3>(==>Xg}|E>e&q}&K>pƽO>SvS>E/U>m"V>J)DX>,&[> . )`>1g>4> o>̹9v>SQ@%|>lGo~>Pb}>dZx>U.da>r>$Gn k>wc>7 ~i(^>zY>O>E>7V\7><ۙ*>۞">) >ݦ]#>ޓ(>;5 ->PڲR3/>/> 0>dT2>~6>dѢ9>[C;>|2:>h6>4H0>Y,2%>-G7>c=བྷ{=>eν>[=ǽsYK.ܽ;ZOν뽄]8J\ڽ #Ta̽ x=FL ^G<4A >"xIO>[3W>KΝ:%><\*>8=%>c'E=>*=M >5==!=/=]==>>=z%>l<8>4< >99M>lN>yudBjN>A&;\M>\M<_L><L>, =I>EW=`QB>߃=S5>#Ԛ= $>=@8>A=>r=r= >8)=A>w=e&>=/>e=g6>.|=<>=D>U+=N>J=X>N=_>}?= G_>h_=mX>.=wK>=:9>+ȹ=d">F<= >⓲==I== D=Ǝ==i=Bh=K=k=PG=،\=X=ȵQ=Yz=(B==)'==<=Q<=j1.=z˼_=N=렽=,ݽH=0 D3=X!T<ȉ/;Z4<1@AhDG(NX `ļ+4ν$ +;ĉy62aX]lLU9u SV C{945|u%:A|ټ5ӽ b9}콶OEsfɽӭ EٽjڽKU?нd~f۽.ĽUYb \KbDYH;Q/"۽[#>c佢L,>m +>4 >M >T=<V=N R=_ 4= r<V<`>=s2;@=z?:D=u~==Zv=p<3͒=CZʼ=>-&$>/ ->1z;s3>G<8>Hm =6>/=W.>=u-!>ph<)>C<0G=U;Z=s =sDļt=aO!PO3=ZV9=}>`A#=> %= >Z$=*>&=6H>(0=>D=(>-f==^====^i=1=J=)"=2=Jg=">-=>1}= >w"= 5>dž=>=$>t=>g=S8=~=ݩ= ==㴋=>c=>=>=rr>B=\ >3=(> -=->m=Sr3>=:>)>@>ƌ>[E>>[F>>D>>v\=>u>3>=)'>=>=R >M=>r<]P=h>F 퍾o P㮒 `02*ozQEџĕqw-'դ>񆾗)ᅾe4?S-o|okRD0Xw- >#?` 8Z8l8ڼxý':YZi~@"'xc ܂_{<9= =z[==]F½=oཟ=h0>)'>R7>C>2M>սW>%ǽPb>6)o>=>R ; >Zb>kh>#nMA>p86>2z>;{>qM>''_M>l>Bl>Vn>1r>dsA>:sr>stc>4|B>f9$&>@ >D=Ň%==,PkZ=2M6 =8T&.#v<7BHӺlS슼p\;mØ<̝4=_-=]}CdU=)=lꇽ=R=l !=7OPp=0=*>f Y >Fz >67{>8/mh>x:=Ͻ>=1M%=B$۩=CӚ=^~f=jt,>>O>N}+>K5>)F<> XB>0~VG>y@J>,v|K>xsG>KrF?>qh1>*qE >q% >`qQ=q1Z=oͱ=k_9=pd<9Y1<1KKI; ; 8(¹l9M*:սvs:n:zȷE;g1j;_\&g;Ѻ߹ zͺӻ흼HTg׋4?[Y 仱"kn:q<= Z< vE<^z܅<Ľk/<1齘'=Qh%>&m=;fE>d=g>P>-q>#>9>+>d>j!>>+<$>`»>y$>>2#>.> >>>3>>U>>->>>>M> >> >'l>>@]Q>T=8>~ = >=<^ >=f==wj=@=R=QY=\w==H==<69=q8(=[񼊖=L0=%?=dϫ=7=̽=̽=*w=oj.=> =cz.=lKh<+EyQ*8À1 ɽEMʽؽ5=V#a-1ݽ81ʽN1Ϯ!V,Xm$Z ׆nཥȄ;ٷQ{<ϖ?<\UL<@ ˦<xӘ9<_s2݇Ya:'/<"C !JD*><½-&?>w˽FP>̽Q ]>ƽe>k>#Ωn>;o>ʉ_l>xZSh>hrb>-KinF[>NVxQ>剽dD>N_4>V P >ཨ >Gv =X=H-) =2=5H=i 29E=;)=t=9 =-`=q~=A*ɽ=ڕ=aF=1ڙv=y;=sP=8oqJ<5o;A5jk|]-GGH)V49H% z˼Xa|zན$<C:G# >$ͽN. >M >;>K x=V=%mr==VC(=g?* >)H+>𽃉>wb>4*B >>< ==(A=K:`=F T>Pj/ >lnֽq>ʥw>\+U">>ʜ> s<=O>b<=>v=>=(>=ۼ=Z =A==K=s@=&=?===T=t=e={= =/B=F=/V=3.=mm6=^=+=R=5=j=L=n>߂g=>= >0= ><=0>\=d=t~==Af=z=BC=0~Xüs|GjpxD.X? N)lP l&߼i =a[ @%&@&E f`'3 qN^=t/*>ThM>n>Lj>?(> ؃ >j{ߟ>nt8>x>}>[Đ>{k½>o2>Tw>k>о=Lr> 7;z>s>lj>(`>boT>.)hE>;!3>:,=>0>ɆP =uԼr=پ^=$j=PiF7!>fl8;>NO>Y& \>,}e> 6j>ϯؽNo>ý>q>M.o>j>_ݪa>'V>梽1J>>>gnh&4>7d3*> 4U">*>ῼg->JH>E|>_T">r'K>Oӻ->ap='>|_;_6>5r<.F>M~<i>-<{>.<>ۗ<>I;.q>"лv>a>9I>,. 1>ܬO+>XmW>Gņz=ǚ=_Ð=/Aj=ֽC=IC(=&=C '=<#< ,teJ꽌dw@xA;m j<<==lr= j=E':1=|y/>(7?>=*>(A>U@s>< >!%6O>_.y>`%%->>҂!>!Hn!>R.>^>'p >ӈ="ӽZ=~2=᜽l:=oV = 2L=Ҕ=Ѻ=wD<̓=<=|j<=Vޫ<=S><|0>B-<5n>g }>IlYz>ۼ[q>חt+b>X@\xO>dW;>A[:&>Iq>FJ"X>$׼%=,T=x;=M<g+G;v2,:#Ӷ@`tEG˽VEo CA=@*9@M"k5dSg1CP.. $[*7&Nֽ athr' y O#)@Xܽ =a'>=+D>Ũ>4!d>>>:%>>P0> >]7>DJ>ģ<>>>|@>D>eB>\>C>؟>C>:"> B>F>guB>tI>(D> _>ЙH>I>H+K>Ȟ>=I>>!@>>/>q%>>'>>>õ=o>=BX>d==@>aы=&>~=! >=_=}=2E=Ĉ=p=6=.*H=(=g = 1=);<=#`< >nU<>,<>;' >{T#N=3ME=eݼ⫩=jP>вi==ݏk=_ǽO(<1rݻ"*1qѼDkAV*弰keͼsȥ/n*%p(κ`RqYq?,-nȼ|Ż+%'~5Jv~ޡxq)` ҽhVѣ.I1qP~[ -^c=Q ڳOнGٽES<̽kǼG׻l;s5T=(F =K@L=Oѡ=\Pb=HNx=xIU=TAݴ=\6 =)=G6`==,x=lد=5o`=v:G=F={G'=~=4J=r_=x[kw=<7=x=t߽ػ=ս3=ʽ[<SO'|pdQI=pW *7# i:oxϧ<#=&l$>u=)7>xȻ=hE>(=pyO>=V>=[>Z>^>>a>*>a>r3>n%`>Ջ:>?[>k?>uT>jC>|K>G>@>WK>g3>6P>#>S>>,GV>D&=X>Tt=>\>c=a>X= i>ƜjU;3y>R4Ӽ>F ><">L>q/Q>g@}>@RfS>(}Ӌ>)";ڧ>=<ס>Y< >U<:>;x.>D>I^%m>ʼW{\> )P>7zI>OH>jpFL>VԼ*S>MflZ>a>19of>Ҽg>e? BY>+J>,S#7>f:">Fg>p`N= Ns=H%={N;Ar=u$, =h°; ۼFwuԂ 7" O l] *B}d;B@5z;<;a)߼$>gu5>D 5E>F)R>.^,=d>G=fi>=l>3=m>.=l>,=h>M=+ǻf= 7X>==J>=;:><&>EH<>i^ >J-->Ο6)<>g WWH> R>ؘC[>?Pa>(sb>4L]>q轁R>߽$C>)׽ҁ1>Ͻ>cǽS >\3`=)=p= @=TR:= P=E[=.<>>L/>RQ:˫>-:$>9+> ƺhd.>LCD +> 5K!> y>qn;>/<=%<=L<==ڠ=F2=m=L=aM=od=d2=t=rL="y= = l=;m =ER==K-=J)=&N=:=<;=u½c=xL覽&ʽ~Չ d}2npui2rLY3iRUضzн!н@ػήehU4%_/*>&7CgD ߼oN0V/ٻ~\u:3`;bA<<*aNd<^{<=[X9j._w+ =DR24Ǫ3pbd$?;o:W=">B9=v4>=]D>R|6_>䊼(Ti>o꼔wp>-^t>$u>>t>r>Am>duf><3u\>cfQ=RA>fG=512>}=#>=[>"=Y >|=>==w=Hx=f>_=>O=====~=NД===b!=:ʡ=U<—=c39=K=ZDXK= tI= }=Ԉ>~O >VX >\>|>=D;J'=D<8=q[==%=4==`Q=C=M==Ħ=?=%jk?=]NBT=|p=ʽcU==c !r=n=1 =VAd,=~RN3=FgUy=kVk>S/ >M >%G>_{B4>%A>@FJ>_P >^7p>n=}N=9D=EEկ=Pz =5jR=[:=/[Y=w=`P=@sE:n=Ȭ'<= Q2_=۽2F=F%4B=pq^O=XLh=VֹE`=_[=~!>=0>4= >>p=7J>=S>=k*[>$R=0^>=$_><Z>j"ߔQ>cC>(%81>g7zŀ>Ok>6@=r̽=ZϽ.=ƽu=OR=59M=j~R=cH T4FEF_5?J#!3H!< n۽-뚼̻twߍ/l"᲼?KFƻUH- ;E9;NiDbgh)TB Q2HY;栛 =>φ=v >V=A5)>7==0>=Y4>=B8> >3=>>B>= >\(J>-> R>R:>eY>"gE>7^>N>A^>oU>SW>_Y>K>d[>:>f2[>1$>DY>c >@V> n=R>0=0M>)j=7H>;=F> 3<Z<ȸ_>'J;<`p>nI<>pit }<0> <9><->?="/>T)=ƪ>j8=!x>*>=?p>U;=i>B6=d>)2=?_>sB4= [>8=9V>x?=5S>C=4Q>yC=5tQ>M>='U>>4=\N\>J%=f>2 =2p><y><>k<;>:;W>O2t>bټ郇>g4'>7n΀>w{o>çԽmV>F6> g[p>(m=9)j=ҸG=QckTWQoZV[gj[[)T\[4i\T 8F]5]I/_&`A/c~Zf~-}jT oB'r5RsHҌq]0lVsidi]qI6YqlYyZ\s>ob붾i֝6qƾAxʾ~{Q̾fЁװʾ(*mƾu9}#,cn>"Z\BrŤS*C_1[Pܽ"Ku䕾"2 Jdx z0m{Ya@E䂽F2xp>!peOI(U& ,yn佴.ܽMp<Խ =bͽ#a=yý~=F=?C>H.>;82>V$sCJ>T0nd>->N!u>T>5Kέ>!x|<90>=h>F==>'n= >_]=>=zy>= V>=:>}Z=5>ɓ*=>B<>4Ü<>Z<<>UFB<_>ļ< >E=>LS={>=Oq>=cg>=]>7=R>F=F>S>9>n>M:*> W>> %>>)><=-*>BV=|(>3=$>;=< > m{_:*>!>_'>dwp>2>z½>@!>:],>1 7>3հB>1L>0*PGV>_6[^>J1A;wf>jK#m>Tiq>o?^q>ܐf_Im>xl8b>fo-R>o>>|lG(>Gg>R_1=UG=LJ9=<=-=oLd=S '@=VK=<۽P=Ž=6)=Ϫ==pݫ=)4=t_ =F9l=H N=Nv5=|; =;ѽ̼ؽEؽ3&|==4! =fU8='+>+>?>K">%^;o=<}=XO<=63===dg==f=:o=e=P==JSo='Μ=]=*=WJ=Ԡ|=0=A= = <</u=L=v=sE=@l<=2(=My=r='礽# _=Pɽ&=͙kO<^y<){ |>>ѽhC.q齺B:d,DG!6X׽!&$m˽Pz뱽5.ꔽ5ڼL-jt/Z>(۝μ<!Y< ;SW5I<>R<q&>:1>S n7>䅼Y8>]4Z6>'Ti1>vc+>F 3&> "><7 >>O<Я >5=Vf#>WA=v'>rb=->2=4>ȑ=$=>=E>=fL>=cOP>~=N>ܳ=G>=h 9>6=,&>:u=> =wU=g==w=_v=G=/=G=ɽB=鲍=o=Pט= C=J<ܧ=D<=A4==@l=m= =`=p=n==0=|==a=5=)c==-=Ǒ=<*q=~A<>Q<>hcG<ݯ>b=% >=P>==|(=Ժ=9==tK=E={]==|Dl=Ǣ=v=$=Ű~===1==~O=)=r= =Գ=~=Ƀ=r=M=`=8>G=|>%=&S!>T< Q0>;v.>mh:">۴ݼƼ>V*Z=q^S@=%ЄA-=PɔV; HfQֽmU vƜM&^/(ӽ]D'Ƚ #b%F̳Q,39**H5ĽĠWٽd8Kp Mz8Qc#EA1Ti>K+lWK97`C˨f/glee͕E_uW~QOZBGߞ&?H8H웾<@38k/:,Փ/*V'&\ %d6!QqMxo_X KU@6)C"lϽ-Ͷԟfo#qh߽5߽^6 nܽ]<_7нq"=ܺmh=1C~=R=l >H,43>{f\>Բ; W> T)=u>iRe=L>=&>0=E>y=5>~t=n >=6~>=lW>N=>d=>J=w>=E>=>ȩ=>-=>=>M:=;>0=N>=J>-=>=>e>=>6>3>}>+>6>k>vA>La>HCL>fU>lV>H3I>n_> G=>݉f>1>Ij>4%>j>>rd> >̇W>g> F>=F3>l=*+">=:>}=" >H=.>\=7 >m1=ۿ>7=5$>ô<9XL>EKһO`>4S4r>K->翄>ôx>fΉ>x p>P>+>$R6w>&:<5+j>@;>[>Z=^K><9>t?>sFw=R=ZaoC=r=Q====C=z)I=``='j=A/=E2d<Ɓ?<*z-rEljy&S\bnH)[>hTz`PwLGbIJF EYz?x0F6ӂ+o猽qٕ1Y頽2Bý*蹧m\Ľ3ӽ}ڄx,\v÷ w'#-.ɄY6ycTʽ;k½$<쾽L=-ה==͢ƽ=wν2>Uڽ>Ύ齨>!>u~>y>*>-A > gi> =X*=ɽ=VN-;=R|=Gu=jQ=.=x:=>Gy=/ >ۀ=}e>^=>S= >=y$>ۺ='>y=(>=V(>i=w(>h(=t(>'< )>3<ĸ*>v<,>k =c/>s=W.>B=+>b=}&>>H>\a3>9>@P>l>dah>_= z>=>2=Ƅ>7F=h>.:Av0<\>';j>>S<3>'< *=<O<5B= o<K=z<B=pJb;?}$<,ׇJ=Q/U===b=$O= ԽP~=&?5S=M $=jo<" ଵ< ;Ň<ʬJ