ukui-greeter/0000755000175000017500000000000014204635151012107 5ustar fengfengukui-greeter/AUTHORS0000644000175000017500000000004314204635151013154 0ustar fengfengUIUI: yanghao ukui-greeter/ukui-greeter.pro0000644000175000017500000000006614204635151015243 0ustar fengfengTEMPLATE = subdirs SUBDIRS += \ ukui-greeter ukui-greeter/COPYING0000644000175000017500000004353614204635151013155 0ustar fengfengNOTE! The GPL below is copyrighted by the Free Software Foundation, but the instance of code that it refers to (the kde programs) are copyrighted by the authors who actually wrote it. --------------------------------------------------------------------------- GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19yy name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. ukui-greeter/.github/0000755000175000017500000000000014204635151013447 5ustar fengfengukui-greeter/.github/workflows/0000755000175000017500000000000014204635151015504 5ustar fengfengukui-greeter/.github/workflows/build.yml0000644000175000017500000000551714204635151017336 0ustar fengfengname: Check build on: push: branches: - master pull_request: branches: - master schedule: - cron: '0 0 * * *' jobs: archlinux: name: on Archlinux runs-on: ubuntu-20.04 container: docker.io/library/archlinux:latest steps: - name: Checkout ukui-greeter source code uses: actions/checkout@v2 - name: Refresh pacman repository run: pacman -Sy - name: Install build dependencies run: pacman -S --noconfirm base-devel qt5-base qt5-tools pkg-config qt5-x11extras qt5-svg liblightdm-qt5 libx11 libxtst libxrandr imlib2 - name: QMake configure & Make run: | mkdir build; cd build; qmake-qt5 ..; make -j$(nproc); debian: name: on Debian Sid runs-on: ubuntu-20.04 container: docker.io/library/debian:sid env: DEBIAN_FRONTEND: noninteractive steps: - name: Checkout ukui-greeter source code uses: actions/checkout@v2 - name: Update apt repository run: apt-get update -y - name: Install build dependcies run: apt-get install -y build-essential qt5-default qttools5-dev-tools debhelper-compat pkg-config qtbase5-dev libqt5x11extras5-dev libqt5svg5-dev liblightdm-qt5-3-dev libx11-dev libxtst-dev libxrandr-dev qttools5-dev-tools libimlib2-dev - name: QMake configure & Make run: | mkdir build; cd build; qmake ..; make -j$(nproc); fedora: name: on Fedora 32 runs-on: ubuntu-20.04 container: docker.io/library/fedora:32 steps: - name: Checkout ukui-greeter source code uses: actions/checkout@v2 - name: Install build dependencies run: dnf install -y which gcc gcc-c++ make cmake cmake-rpm-macros autoconf automake intltool rpm-build qt5-rpm-macros qt5-qtbase-devel qt5-qtx11extras-devel qt5-qtsvg-devel lightdm-qt5-devel libX11-devel libXtst-devel libXrandr-devel qt5-qttools-devel imlib2-devel - name: QMake configure & Make run: | mkdir build; cd build; qmake-qt5 ..; make -j$(nproc); ubuntu: name: on Ubuntu 20.04 runs-on: ubuntu-20.04 container: docker.io/library/ubuntu:focal env: DEBIAN_FRONTEND: noninteractive steps: - name: Checkout ukui-greeter source code uses: actions/checkout@v2 - name: Update apt repository run: apt-get update -y - name: Install build dependcies run: apt-get install -y build-essential qt5-default qttools5-dev-tools debhelper-compat pkg-config qtbase5-dev libqt5x11extras5-dev libqt5svg5-dev liblightdm-qt5-3-dev libx11-dev libxtst-dev libxrandr-dev qttools5-dev-tools libimlib2-dev - name: QMake configure & Make run: | mkdir build; cd build; qmake ..; make -j$(nproc);ukui-greeter/ukui-greeter/0000755000175000017500000000000014204635151014517 5ustar fengfengukui-greeter/ukui-greeter/BiometricAuth/0000755000175000017500000000000014204635501017255 5ustar fengfengukui-greeter/ukui-greeter/BiometricAuth/biometricdeviceinfo.h0000644000175000017500000000777014204635151023453 0ustar fengfeng/** * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 BIOMETRICDEVICEINFO_H #define BIOMETRICDEVICEINFO_H #include #include #define BIOMETRIC_DBUS_SERVICE "org.ukui.Biometric" #define BIOMETRIC_DBUS_PATH "/org/ukui/Biometric" #define BIOMETRIC_DBUS_INTERFACE "org.ukui.Biometric" #define UKUI_BIOMETRIC_IMAGES_PATH "/usr/share/ukui-biometric/images/" #define UKUI_BIOMETRIC_CONFIG_PATH ".biometric_auth/ukui_biometric.conf" #define UKUI_BIOMETRIC_SYS_CONFIG_PATH "/etc/biometric-auth/ukui-biometric.conf" #define BIOMETRIC_PAM_DOUBLE "BIOMETRIC_PAM_DOUBLE" #define BIOMETRIC_PAM "BIOMETRIC_PAM" #define BIOMETRIC_IGNORE "BIOMETRIC_IGNORE" #define BIOMETRIC_SUCCESS "BIOMETRIC_SUCCESS" /** * @brief 设备类型 */ class DeviceType : public QObject { Q_OBJECT public: DeviceType(); enum Type { FingerPrint, FingerVein, Iris, Face, VoicePrint, __MAX_NR_TYPES }; Q_ENUM(Type) /** * @brief 获取设备类型的字符串表现形式 * @param deviceType 设备类型 * @return */ static QString getDeviceType(int deviceType); /** * @brief 获取设备类型的国际化字符串 * @param deviceType 设备类型 * @return */ static QString getDeviceType_tr(int deviceType); }; /** * @brief StatusChanged D-Bus 信号触发时的状态变化类型 */ enum StatusType { STATUS_DEVICE, STATUS_OPERATION, STATUS_NOTIFY }; /** * @brief 识别、终止操作等DBus调用的结果,即返回值里的 result */ enum DBusResult { DBUS_RESULT_SUCCESS = 0, DBUS_RESULT_NOTMATCH = -1, DBUS_RESULT_ERROR = -2, DBUS_RESULT_DEVICEBUSY = -3, DBUS_RESULT_NOSUCHDEVICE = -4, DBUS_RESULT_PERMISSIONDENIED = -5 }; /** * @brief 识别操作(Identify)的ops状态 */ enum IdentifyOpsStatus { IDENTIFY_MATCH = 400, IDENTIFY_NOTMATCH, IDENTIFY_ERROR, IDENTIFY_STOPBYUSER, IDENTIFY_TIMEOUT, IDENTIFY_MAX }; /** * @brief 设备的信息 */ struct DeviceInfo { int id; QString shortName; QString fullName; int driverEnable; int deviceNum; int deviceType; int storageType; int eigType; int verifyType; int identifyType; int busType; int deviceStatus; int OpsStatus; }; QDBusArgument &operator <<(QDBusArgument &arg, const DeviceInfo &deviceInfo); const QDBusArgument &operator >>(const QDBusArgument &arg, DeviceInfo &deviceInfo); void registerMetaType(); typedef std::shared_ptr DeviceInfoPtr; typedef QList DeviceList; typedef QMap DeviceMap; QDebug operator <<(QDebug stream, const DeviceInfo &deviceInfo); Q_DECLARE_METATYPE(DeviceInfo) /** * @brief 获取默认设备 * @return */ QString GetDefaultDevice(const QString &userName); /** * @brief 获取失败后自动重新开始的最大次数 * @param userName * @return */ int GetMaxFailedAutoRetry(const QString &userName); int GetFailedTimes(); bool GetHiddenSwitchButton(); /** * @brief 获取超时后自动重新开始的最大次数 * @param userName * @return */ int GetMaxTimeoutAutoRetry(const QString &userName); #endif // BIOMETRICDEVICEINFO_H ukui-greeter/ukui-greeter/BiometricAuth/biometricauthwidget.h0000644000175000017500000000522214204635501023472 0ustar fengfeng/** * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 BIOMETRICAUTHWIDGET_H #define BIOMETRICAUTHWIDGET_H #include #include "biometricproxy.h" class QLabel; //class QDBusPendingCallWatcher; //class BiometricProxy; //class DeviceIdentityPtr; class BiometricAuthWidget : public QWidget { Q_OBJECT public: explicit BiometricAuthWidget(BiometricProxy *proxy, QWidget *parent = 0); /** * @brief 进行生物识别认证 * @param deviceInfo 使用的设备 * @param uid 待认证的用户id */ void startAuth(DeviceInfoPtr device, int uid); /** * @brief 终止生物识别认证 */ void stopAuth(); /** * @brief 是否正在认证 * @return */ bool isAuthenticating() { return isInAuth; } void setMinImage(float val); protected: void resizeEvent(QResizeEvent *event); Q_SIGNALS: /** * @brief 认证完成 * @param result 认证结果 */ void authComplete(bool result); private Q_SLOTS: void onIdentifyComplete(QDBusPendingCallWatcher *watcher); void onStatusChanged(int drvid, int status); void onMoviePixmapUpdate(); void onFrameWritten(int drvid); private: void initUI(); void updateImage(int type = 0); void setImage(const QString &path); void startAuth_(); bool getAuthDouble(); private: QLabel *lblNotify; QLabel *lblDevice; QLabel *lblImage; BiometricProxy *proxy; int uid; QString userName; DeviceInfoPtr device; bool isInAuth; QTimer *movieTimer; int failedCount; bool beStopped; //被强制终止 int timeoutCount; QTimer *retrytimer; bool usebind; int fd = -1; int dup_fd = -1; }; #endif // BIOMETRICAUTHWIDGET_H ukui-greeter/ukui-greeter/BiometricAuth/BiometricAuth.pro0000644000175000017500000000223414204635151022540 0ustar fengfeng#------------------------------------------------- # # Project created by QtCreator 2018-12-06T09:17:41 # #------------------------------------------------- QT += core gui dbus greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = BiometricAuth TEMPLATE = app # The following define makes your compiler emit warnings if you use # any feature of Qt which has been marked as deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the # deprecated API in order to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS # You can also make your code fail to compile if you use deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += \ main.cpp \ biometricauthwidget.cpp \ biometricproxy.cpp \ biometricdeviceswidget.cpp \ giodbus.cpp HEADERS += \ biometricauthwidget.h \ biometricproxy.h \ biometricdeviceswidget.h \ giodbus.h FORMS += ukui-greeter/ukui-greeter/BiometricAuth/biometricauthwidget.cpp0000644000175000017500000002263614204635501024035 0ustar fengfeng/** * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "biometricauthwidget.h" #include #include #include #include #include #include "biometricdeviceinfo.h" #include "giodbus.h" BiometricAuthWidget::BiometricAuthWidget(BiometricProxy *proxy, QWidget *parent) : QWidget(parent), proxy(proxy), isInAuth(false), movieTimer(nullptr), retrytimer(nullptr), failedCount(0), usebind(false), beStopped(false), timeoutCount(0) { usebind = getAuthDouble(); qDebug() << "BiometricAuthWidget::BiometricAuthWidget"; initUI(); resize(400, 260); if(this->proxy) { connect(this->proxy, &BiometricProxy::StatusChanged, this, &BiometricAuthWidget::onStatusChanged); connect(this->proxy, &BiometricProxy::FrameWritten, this, &BiometricAuthWidget::onFrameWritten); } } void BiometricAuthWidget::initUI() { //显示提示信息 lblNotify = new QLabel(this); lblNotify->setWordWrap(true); lblNotify->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); //显示当前设备 lblDevice = new QLabel(this); lblDevice->setWordWrap(true); lblDevice->setAlignment(Qt::AlignCenter); //显示图片 lblImage = new QLabel(this); lblImage->setFixedSize(100, 100); } void BiometricAuthWidget::resizeEvent(QResizeEvent */*event*/) { lblNotify->setGeometry(0, 0, width(), 45); lblDevice->setGeometry(0, lblNotify->geometry().bottom()+5, width(), 30); lblImage->setGeometry((width() - lblImage->width()) / 2, lblDevice->geometry().bottom() + 10, lblImage->width(), lblImage->height()); } void BiometricAuthWidget::startAuth(DeviceInfoPtr device, int uid) { if(!proxy) { qWarning() << "BiometricProxy doesn't exist."; return; } if(isInAuth) { qDebug() << "Identification is currently under way, stop it"; stopAuth(); } this->device = device; this->uid = uid; this->userName = getpwuid(uid)->pw_name; this->failedCount = 0; this->timeoutCount = 0; this->beStopped = false; startAuth_(); } void BiometricAuthWidget::startAuth_() { lblDevice->setText(tr("Current device: ") + device->shortName); qDebug().noquote() << QString("Identify:[drvid: %1, uid: %2]").arg(device->id).arg(uid); isInAuth = true; dup_fd = -1; QDBusPendingCall call = proxy->Identify(device->id, uid); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, &BiometricAuthWidget::onIdentifyComplete); updateImage(1); } void BiometricAuthWidget::stopAuth() { beStopped = true; if(!isInAuth) { return; } proxy->StopOps(device->id); if(retrytimer&&retrytimer->isActive()){ retrytimer->stop(); delete retrytimer; retrytimer = nullptr; } isInAuth = false; updateImage(0); } void BiometricAuthWidget::onIdentifyComplete(QDBusPendingCallWatcher *watcher) { if(beStopped == true) return ; QDBusPendingReply reply = *watcher; if(reply.isError()) { qWarning() << "Identify error: " << reply.error().message(); Q_EMIT authComplete(false); updateImage(0); return; } int result = reply.argumentAt(0).toInt(); int authUid = reply.argumentAt(1).toInt(); qDebug() << result << authUid << uid; // 特征识别成功,而且用户id匹配 if(result == DBUS_RESULT_SUCCESS && authUid == uid) { qDebug() << "Identify success"; Q_EMIT authComplete(true); } // 特征识别不匹配 else if(result == DBUS_RESULT_NOTMATCH) { if(usebind){ Q_EMIT authComplete(false); return; } qDebug() << "Identify failed"; failedCount++; qDebug() << "max auto retry: " << GetMaxFailedAutoRetry(userName) << failedCount; if(failedCount >= GetMaxFailedAutoRetry(userName)) { Q_EMIT authComplete(false); } else { lblNotify->setText(tr("Identify failed, Please retry.")); if(!beStopped){ // QTimer::singleShot(1000, this, &BiometricAuthWidget::startAuth_); if(!retrytimer){ retrytimer = new QTimer(this); retrytimer->setSingleShot(true); connect(retrytimer, &QTimer::timeout, this, &BiometricAuthWidget::startAuth_); } retrytimer->start(1000); } } } //识别发生错误 else if(result == DBUS_RESULT_ERROR) { if(usebind){ Q_EMIT authComplete(false); return; } StatusReslut ret = proxy->UpdateStatus(device->id); //识别操作超时 if(ret.result == 0 && ret.opsStatus == IDENTIFY_TIMEOUT) { timeoutCount++; if(timeoutCount >= GetMaxTimeoutAutoRetry(userName)) { Q_EMIT authComplete(false); } else { QTimer::singleShot(1000, [&]{ if(!beStopped) { startAuth_(); } }); } }else{ Q_EMIT authComplete(false); } }else{ Q_EMIT authComplete(false); } updateImage(0); } void BiometricAuthWidget::onFrameWritten(int drvid) { if(dup_fd == -1){ dup_fd = get_server_gvariant_stdout(drvid); } if(dup_fd <= 0) return ; cv::Mat img; lseek(dup_fd, 0, SEEK_SET); char base64_bufferData[1024*1024]; int rc = read(dup_fd, base64_bufferData, 1024*1024); printf("rc = %d\n", rc); cv::Mat mat2(1, sizeof(base64_bufferData), CV_8U, base64_bufferData); img = cv::imdecode(mat2, cv::IMREAD_COLOR); cv::cvtColor(img,img,cv::COLOR_BGR2RGB); QImage srcQImage = QImage((uchar*)(img.data), img.cols, img.rows, QImage::Format_RGB888); lblImage->setFixedSize(160,160); lblImage->setGeometry((width() - lblImage->width()) / 2, lblDevice->geometry().bottom() + 10, lblImage->width(), lblImage->height()); lblImage->setPixmap(QPixmap::fromImage(srcQImage).scaled(lblImage->size())); //lblImage->setPixmap(QPixmap::fromImage(srcQImage)); // QPixmap image = QPixmap::fromImage(srcQImage); // image = image.scaled(lblImage->width(), lblImage->height(), // Qt::IgnoreAspectRatio, Qt::SmoothTransformation); // lblImage->setPixmap(image); } void BiometricAuthWidget::onStatusChanged(int drvid, int status) { if(!isInAuth) { return; } if(drvid != device->id) { return; } // 显示来自服务的提示信息 if(status == STATUS_NOTIFY) { QString notifyMsg = proxy->GetNotifyMesg(drvid); lblNotify->setText(notifyMsg); } } static int count = 0; void BiometricAuthWidget::updateImage(int type) { if(device->deviceType == DeviceType::Type::Face) return ; if(type == 0) { if(movieTimer && movieTimer->isActive()) { movieTimer->stop(); } QString imagePath = QString(UKUI_BIOMETRIC_IMAGES_PATH "%1/01.png") .arg(DeviceType::getDeviceType(device->deviceType)); setImage(imagePath); } else { if(!movieTimer) { movieTimer = new QTimer(this); movieTimer->setInterval(100); connect(movieTimer, &QTimer::timeout, this, &BiometricAuthWidget::onMoviePixmapUpdate); } count = 0; movieTimer->start(); } } void BiometricAuthWidget::onMoviePixmapUpdate() { if(count >= 18) { count = 0; } count++; QString fileName = (count < 10 ? "0" : "") + QString::number(count); QString imagePath = QString(UKUI_BIOMETRIC_IMAGES_PATH "%1/%2.png") .arg(DeviceType::getDeviceType(device->deviceType)) .arg(fileName); setImage(imagePath); } void BiometricAuthWidget::setImage(const QString &path) { QPixmap image(path); image = image.scaled(lblImage->width(), lblImage->height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); lblImage->setPixmap(image); } bool BiometricAuthWidget::getAuthDouble() { QSettings settings("/etc/biometric-auth/ukui-biometric.conf", QSettings::IniFormat); bool distribId = settings.value("DoubleAuth").toBool(); return distribId; } void BiometricAuthWidget::setMinImage(float val) { resize(400,100+100*val); lblImage->setFixedSize(100*val, 100*val); } ukui-greeter/ukui-greeter/BiometricAuth/biometricdeviceswidget.h0000644000175000017500000000451314204635151024156 0ustar fengfeng/** * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 BIOMETRICDEVICESWIDGET_H #define BIOMETRICDEVICESWIDGET_H #include #include "biometricproxy.h" class QLabel; class QPushButton; class QComboBox; class BiometricDevicesWidget : public QWidget { Q_OBJECT public: explicit BiometricDevicesWidget(BiometricProxy *proxy,int uid,QWidget *parent = nullptr); void setCurrentDevice(int drvid); void setCurrentDevice(const QString &deviceName); void setCurrentDevice(const DeviceInfoPtr &pDeviceInfo); DeviceInfoPtr findDeviceById(int drvid); DeviceInfoPtr findDeviceByName(const QString &name); bool deviceExists(int drvid); bool deviceExists(const QString &deviceName); void setUser(int uid); protected: void resizeEvent(QResizeEvent *event); signals: void deviceChanged(const DeviceInfoPtr &pDeviceInfo); void deviceCountChanged(int newCount); public slots: void onOKButtonClicked(); private slots: void onCmbDeviceTypeCurrentIndexChanged(int index); void onUSBDeviceHotPlug(int drvid, int action, int devNum); private: void initUI(); void updateDevice(); private: typedef QMap QButtonMap; QLabel *lblPrompt; QLabel *lblDeviceType; QLabel *lblDeviceName; QComboBox *cmbDeviceType; QComboBox *cmbDeviceName; QPushButton *btnOK; QPushButton *btnCancel; BiometricProxy *proxy; DeviceMap deviceMap; DeviceInfoPtr currentDevice; int m_uid; }; #endif // BIOMETRICDEVICESWIDGET_H ukui-greeter/ukui-greeter/BiometricAuth/biometricdeviceinfo.cpp0000644000175000017500000001327714204635151024005 0ustar fengfeng/** * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "biometricdeviceinfo.h" QString DeviceType::getDeviceType(int deviceType) { if(deviceType >= __MAX_NR_TYPES) { return ""; } QMetaEnum meta = QMetaEnum::fromType(); const char *typeString = meta.valueToKey(deviceType); return QString(typeString); } QString DeviceType::getDeviceType_tr(int deviceType) { switch(deviceType) { case FingerPrint: return tr("FingerPrint"); case FingerVein: return tr("FingerVein"); case Iris: return tr("Iris"); case Face: return tr("Face"); case VoicePrint: return tr("VoicePrint"); default: return ""; } } QDebug operator <<(QDebug stream, const DeviceInfo &deviceInfo) { stream << "[" << deviceInfo.id << deviceInfo.shortName << deviceInfo.fullName << deviceInfo.deviceType << deviceInfo.driverEnable << deviceInfo.deviceNum << "]"; return stream; } QDBusArgument &operator <<(QDBusArgument &arg, const DeviceInfo &deviceInfo) { arg.beginStructure(); arg << deviceInfo.id << deviceInfo.shortName << deviceInfo.fullName << deviceInfo.driverEnable << deviceInfo.deviceNum << deviceInfo.deviceType << deviceInfo.storageType << deviceInfo.eigType << deviceInfo.verifyType << deviceInfo.identifyType << deviceInfo.busType << deviceInfo.deviceStatus << deviceInfo.OpsStatus; arg.endStructure(); return arg; } const QDBusArgument &operator >>(const QDBusArgument &arg, DeviceInfo &deviceInfo) { arg.beginStructure(); arg >> deviceInfo.id >> deviceInfo.shortName >> deviceInfo.fullName >> deviceInfo.driverEnable >> deviceInfo.deviceNum >> deviceInfo.deviceType >> deviceInfo.storageType >> deviceInfo.eigType >> deviceInfo.verifyType >> deviceInfo.identifyType >> deviceInfo.busType >> deviceInfo.deviceStatus >> deviceInfo.OpsStatus; arg.endStructure(); return arg; } void registerMetaType() { qRegisterMetaType("DeviceInfo"); qDBusRegisterMetaType(); } QString GetDefaultDevice(const QString &userName) { // QString configPath = QString("/home/%1/" UKUI_BIOMETRIC_CONFIG_PATH).arg(userName); // QSettings settings(configPath, QSettings::IniFormat); // qDebug() << "configure path: " << settings.fileName(); QString configPath = QDir::homePath() + "/" + UKUI_BIOMETRIC_CONFIG_PATH; QSettings settings(configPath, QSettings::IniFormat); QString defaultDevice = settings.value("DefaultDevice").toString(); if(defaultDevice.isEmpty()) { QString configPath = QString("/var/lib/lightdm-data/%1/" UKUI_BIOMETRIC_CONFIG_PATH).arg(userName); QSettings settings(configPath, QSettings::IniFormat); defaultDevice = settings.value("DefaultDevice").toString(); } if(defaultDevice.isEmpty() && userName == "root"){ QSettings sysSettings(UKUI_BIOMETRIC_SYS_CONFIG_PATH, QSettings::IniFormat); defaultDevice = sysSettings.value("rootUserDefaultDevice").toString(); } if(defaultDevice.isEmpty()) { QSettings sysSettings(UKUI_BIOMETRIC_SYS_CONFIG_PATH, QSettings::IniFormat); defaultDevice = sysSettings.value("DefaultDevice").toString(); } return defaultDevice; } static int getValueFromSettings(const QString &userName, const QString &key, int defaultValue = 3) { //从家目录下的配置文件中获取 // QString configPath = QString("/home/%1/" UKUI_BIOMETRIC_CONFIG_PATH).arg(userName); QString configPath = QDir::homePath() + "/" + UKUI_BIOMETRIC_CONFIG_PATH; QSettings settings(configPath, QSettings::IniFormat); QString valueStr = settings.value(key).toString(); //如果没有获取到,则从系统配置文件中获取 if(valueStr.isEmpty()) { QSettings sysSettings(UKUI_BIOMETRIC_SYS_CONFIG_PATH, QSettings::IniFormat); valueStr = sysSettings.value(key).toString(); } bool ok; int value = valueStr.toInt(&ok); if( (value == 0 && !ok) || valueStr.isEmpty() ) { value = defaultValue; } return value; } bool GetHiddenSwitchButton() { QSettings sysSettings(UKUI_BIOMETRIC_SYS_CONFIG_PATH, QSettings::IniFormat); if(sysSettings.contains("HiddenSwitchButton")) return sysSettings.value("HiddenSwitchButton").toBool(); else return false; } int GetFailedTimes() { QSettings sysSettings(UKUI_BIOMETRIC_SYS_CONFIG_PATH, QSettings::IniFormat); if(sysSettings.contains("MaxFailedTime")) return sysSettings.value("MaxFailedTime").toInt(); else return 3; } int GetMaxFailedAutoRetry(const QString &userName) { return getValueFromSettings(userName, "MaxFailedAutoRetry"); } int GetMaxTimeoutAutoRetry(const QString &userName) { return getValueFromSettings(userName, "MaxTimeoutAutoRetry"); } ukui-greeter/ukui-greeter/BiometricAuth/giodbus.h0000644000175000017500000000156014204635151021065 0ustar fengfeng/* giodbus.h * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 GIODBUS_H #define GIODBUS_H int get_server_gvariant_stdout(int drvid); #endif ukui-greeter/ukui-greeter/BiometricAuth/giodbus.cpp0000644000175000017500000000542214204635151021421 0ustar fengfeng/* giodbus.cpp * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "giodbus.h" #include #include #include int get_server_gvariant_stdout (int drvid) { GDBusMessage *method_call_message; GDBusMessage *method_reply_message; GUnixFDList *fd_list; GError **error = NULL; gint fd,dup_fd; const gchar * response; fd = -1; dup_fd = -1; method_call_message = NULL; method_reply_message = NULL; GDBusConnection *con = g_bus_get_sync(G_BUS_TYPE_SYSTEM,NULL,NULL); method_call_message = g_dbus_message_new_method_call ("org.ukui.Biometric", "/org/ukui/Biometric", "org.ukui.Biometric", "GetFrameFd"); g_dbus_message_set_body (method_call_message, g_variant_new ("(i)", drvid)); method_reply_message = g_dbus_connection_send_message_with_reply_sync (con, method_call_message, G_DBUS_SEND_MESSAGE_FLAGS_NONE, -1, NULL, /* out_serial */ NULL, /* cancellable */ error); if (method_reply_message == NULL) goto out; if (g_dbus_message_get_message_type (method_reply_message) == G_DBUS_MESSAGE_TYPE_ERROR) { g_dbus_message_to_gerror (method_reply_message, error); goto out; } fd_list = g_dbus_message_get_unix_fd_list(method_reply_message); fd = g_unix_fd_list_get(fd_list,0,error); dup_fd = dup(fd); out: g_object_unref (method_call_message); g_object_unref (method_reply_message); return dup_fd; } ukui-greeter/ukui-greeter/BiometricAuth/biometricdeviceswidget.cpp0000644000175000017500000002125614204635151024514 0ustar fengfeng/** * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "biometricdeviceswidget.h" #include #include #include #include #include #include BiometricDevicesWidget::BiometricDevicesWidget(BiometricProxy *proxy,int uid, QWidget *parent) : QWidget(parent), proxy(proxy), m_uid(uid) { qDebug() << "BiometricDevicesWidget::BiometricDevicesWidget"; initUI(); if(proxy && proxy->isValid()) { connect(proxy, &BiometricProxy::USBDeviceHotPlug, this, &BiometricDevicesWidget::onUSBDeviceHotPlug); updateDevice(); } resize(500, 500); } void BiometricDevicesWidget::initUI() { lblPrompt = new QLabel(this); lblPrompt->setObjectName(QStringLiteral("lblBioetricDevicesPrompt")); lblPrompt->setText(tr("Please select the biometric device")); lblPrompt->setAlignment(Qt::AlignHCenter); lblDeviceType = new QLabel(this); lblDeviceType->setObjectName(QStringLiteral("lblDeviceType")); lblDeviceType->setText(tr("Device type:")); lblDeviceType->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); QStyledItemDelegate* itemDelegate = new QStyledItemDelegate(); cmbDeviceType = new QComboBox(this); cmbDeviceType->setObjectName(QStringLiteral("cmbDeviceType")); cmbDeviceType->setMaxVisibleItems(5); cmbDeviceType->setItemDelegate(itemDelegate); cmbDeviceType->view()->parentWidget()->setWindowFlags(Qt::Popup|Qt::FramelessWindowHint); cmbDeviceType->view()->parentWidget()->setAttribute(Qt::WA_TranslucentBackground); connect(cmbDeviceType, SIGNAL(currentIndexChanged(int)), this, SLOT(onCmbDeviceTypeCurrentIndexChanged(int))); lblDeviceName = new QLabel(this); lblDeviceName->setObjectName(QStringLiteral("lblDeviceName")); lblDeviceName->setText(tr("Device name:")); lblDeviceName->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); cmbDeviceName = new QComboBox(this); cmbDeviceName->setObjectName(QStringLiteral("cmbDeviceName")); cmbDeviceName->setMaxVisibleItems(5); cmbDeviceName->setItemDelegate(itemDelegate); cmbDeviceName->view()->parentWidget()->setWindowFlags(Qt::Popup|Qt::FramelessWindowHint); cmbDeviceName->view()->parentWidget()->setAttribute(Qt::WA_TranslucentBackground); btnOK = new QPushButton(tr("OK"), this); btnOK->setObjectName(QStringLiteral("OKButton")); btnOK->setCursor(Qt::PointingHandCursor); connect(btnOK, &QPushButton::clicked, this, &BiometricDevicesWidget::onOKButtonClicked); } void BiometricDevicesWidget::resizeEvent(QResizeEvent */*event*/) { lblPrompt->setGeometry(0, 0, width(), 40); lblDeviceType->setGeometry(100, lblPrompt->geometry().bottom() + 40, 120, 20); cmbDeviceType->setGeometry(100, lblDeviceType->geometry().bottom() + 15, 300, 40); lblDeviceName->setGeometry(100, cmbDeviceType->geometry().bottom() + 80, 120, 20); cmbDeviceName->setGeometry(100, lblDeviceName->geometry().bottom() + 15, 300, 40); btnOK->setGeometry(100, cmbDeviceName->geometry().bottom() + 80, 140, 38); } void BiometricDevicesWidget::setUser(int uid) { m_uid = uid; updateDevice(); } void BiometricDevicesWidget::updateDevice() { deviceMap.clear(); DeviceList deviceList = proxy->GetDevList(); for(auto pDeviceInfo : deviceList) { qDebug() << *pDeviceInfo; if(proxy->GetUserDevFeatureCount(m_uid,pDeviceInfo->id) > 0) deviceMap[pDeviceInfo->deviceType].push_back(pDeviceInfo); } cmbDeviceType->clear(); for(int type : deviceMap.keys()) { QString iconPath = QString(UKUI_BIOMETRIC_IMAGES_PATH"icon/%1.png") .arg(DeviceType::getDeviceType(type)); // qDebug() << iconPath; cmbDeviceType->addItem(QIcon(iconPath), DeviceType::getDeviceType_tr(type), type); } if(deviceMap.size() > 0) { int index = deviceMap.keys().at(0); setCurrentDevice(deviceMap[index].at(0)); } } void BiometricDevicesWidget::setCurrentDevice(int drvid) { DeviceInfoPtr pDeviceInfo = findDeviceById(drvid); if(pDeviceInfo) { setCurrentDevice(pDeviceInfo); } } void BiometricDevicesWidget::setCurrentDevice(const QString &deviceName) { DeviceInfoPtr pDeviceInfo = findDeviceByName(deviceName); if(pDeviceInfo) { setCurrentDevice(pDeviceInfo); } } void BiometricDevicesWidget::setCurrentDevice(const DeviceInfoPtr &pDeviceInfo) { this->currentDevice = pDeviceInfo; cmbDeviceType->setCurrentText(DeviceType::getDeviceType_tr(pDeviceInfo->deviceType)); cmbDeviceName->setCurrentText(pDeviceInfo->shortName); } bool BiometricDevicesWidget::deviceExists(int drvid) { return (findDeviceById(drvid) != nullptr); } bool BiometricDevicesWidget::deviceExists(const QString &deviceName) { return (findDeviceByName(deviceName) != nullptr); } DeviceInfoPtr BiometricDevicesWidget::findDeviceById(int drvid) { for(int type : deviceMap.keys()) { DeviceList &deviceList = deviceMap[type]; auto iter = std::find_if(deviceList.begin(), deviceList.end(), [&](DeviceInfoPtr ptr){ return ptr->id == drvid; }); if(iter != deviceList.end()) { return *iter; } } return DeviceInfoPtr(); } DeviceInfoPtr BiometricDevicesWidget::findDeviceByName(const QString &name) { for(int type : deviceMap.keys()) { DeviceList &deviceList = deviceMap[type]; auto iter = std::find_if(deviceList.begin(), deviceList.end(), [&](DeviceInfoPtr ptr){ return ptr->shortName == name; }); if(iter != deviceList.end()) { return *iter; } } return DeviceInfoPtr(); } void BiometricDevicesWidget::onCmbDeviceTypeCurrentIndexChanged(int index) { if(index < 0 || index >= deviceMap.keys().size()) { return; } int type = cmbDeviceType->itemData(index).toInt(); cmbDeviceName->clear(); for(auto &deviceInfo : deviceMap.value(type)) { cmbDeviceName->addItem(deviceInfo->shortName); } } void BiometricDevicesWidget::onOKButtonClicked() { int type = cmbDeviceType->currentData().toInt(); int index = cmbDeviceName->currentIndex(); qDebug() << type << index; DeviceInfoPtr deviceInfo = deviceMap.value(type).at(index); Q_EMIT deviceChanged(deviceInfo); hide(); } void BiometricDevicesWidget::onUSBDeviceHotPlug(int drvid, int action, int /*devNum*/) { int savedDeviceId = currentDevice->id; int savedCount = 0; for(int type : deviceMap.keys()) savedCount += deviceMap.value(type).count(); switch(action) { case ACTION_ATTACHED: { //插入设备后,需要更新设备列表 deviceMap.clear(); updateDevice(); setCurrentDevice(savedDeviceId); break; } case ACTION_DETACHED: { DeviceInfoPtr pDeviceInfo = findDeviceById(drvid); if(pDeviceInfo) { int type = pDeviceInfo->deviceType; deviceMap[type].removeOne(pDeviceInfo); int index = cmbDeviceName->findText(pDeviceInfo->shortName); cmbDeviceName->removeItem(index); //如果该类型的设备全被移除,删除该类型相关的列表 if(deviceMap[type].isEmpty()) { deviceMap.remove(type); index = cmbDeviceType->findData(type); cmbDeviceType->removeItem(index); } } if(savedDeviceId != drvid) { setCurrentDevice(savedDeviceId); } break; } } int count = 0; for(int type : deviceMap.keys()) count += deviceMap.value(type).count(); //设备数量发生了变化 if(count != savedCount) { Q_EMIT deviceCountChanged(count); } } ukui-greeter/ukui-greeter/BiometricAuth/biometricproxy.h0000644000175000017500000000743314204635151022515 0ustar fengfeng/** * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 BIOMETRICPROXY_H #define BIOMETRICPROXY_H #include #include "biometricdeviceinfo.h" /** * @brief UpdateStauts调用返回的结果 */ struct StatusReslut { int result; int enable; int devNum; int devStatus; int opsStatus; int notifyMessageId; }; /** * @brief USB设备插拔动作 */ enum USBDeviceAction { ACTION_ATTACHED = 1, ACTION_DETACHED = -1 }; /** * @brief DBus代理类,负责调用对应的DBus接口 */ class BiometricProxy : public QDBusAbstractInterface { Q_OBJECT public: explicit BiometricProxy(QObject *parent = nullptr); public Q_SLOTS: /** * @brief 使用指定id的设备进行用户认证 * @param drvid 驱动(设备)id * @param uid 用户id * @param indexStart 用于认证的特征索引范围 * @param indexEnd * @return 结果: (结果,用户id) */ QDBusPendingCall Identify(int drvid, int uid, int indexStart = 0, int indexEnd = -1); /** * @brief 终止设备上正在进行的操作 * @param drvid 设备id * @param waiting 等待时间(秒) * @return */ int StopOps(int drvid, int waiting = 3000); /** * @brief 获取当前用户已连接设备对应特征数目 * @param uid 用户id * @param indexStart 用于认证的特征索引范围 * @param indexEnd * @return */ int GetFeatureCount(int uid, int indexStart = 0, int indexEnd = -1); /** * @brief 获取已连接的设备列表 * @return */ DeviceList GetDevList(); /** * @brief 获取设备数量 * @return */ int GetDevCount(); /** * @brief 获取设备消息 * @param drvid 驱动id * @return */ QString GetDevMesg(int drvid); /** * @brief GetNotifyMesg 获取通知消息 * @param drvid 驱动id * @return */ QString GetNotifyMesg(int drvid); /** * @brief GetOpsMesg 获取操作消息 * @param drvid 驱动id * @return */ QString GetOpsMesg(int drvid); /** * @brief UpdateStatus 获取更新的设备状态 * @param drvid 驱动id * @return 结果: */ StatusReslut UpdateStatus(int drvid); int GetUserDevCount(int uid); int GetUserDevFeatureCount(int uid,int drvid); int GetFrameFd(int drvid); Q_SIGNALS: /** * @brief 设备状态发生变化 * @param drvid 设备id * @param status 设备状态 */ void StatusChanged(int drvid, int status); /** * @brief USB设备热插拔 * @param drvid 设备id * @param action 插拔动作(1:插入,-1:拔出) * @param deviceNum 插拔动作后该驱动拥有的设备数量 */ void USBDeviceHotPlug(int drvid, int action, int deviceNum); void FrameWritten(int drvid); }; #endif // BIOMETRICPROXY_H ukui-greeter/ukui-greeter/BiometricAuth/BiometricAuth.pri0000644000175000017500000000060114204635151022526 0ustar fengfengSOURCES += \ $$PWD/biometricproxy.cpp \ $$PWD/biometricauthwidget.cpp \ $$PWD/biometricdeviceswidget.cpp \ $$PWD/biometricdeviceinfo.cpp \ $$PWD/giodbus.cpp HEADERS += \ $$PWD/biometricproxy.h \ $$PWD/biometricauthwidget.h \ $$PWD/biometricdeviceswidget.h \ $$PWD/biometricdeviceinfo.h \ $$PWD/giodbus.h ukui-greeter/ukui-greeter/BiometricAuth/biometricproxy.cpp0000644000175000017500000001652114204635151023046 0ustar fengfeng/** * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "biometricproxy.h" #include BiometricProxy::BiometricProxy(QObject *parent) : QDBusAbstractInterface(BIOMETRIC_DBUS_SERVICE, BIOMETRIC_DBUS_PATH, BIOMETRIC_DBUS_INTERFACE, QDBusConnection::systemBus(), parent) { registerMetaType(); setTimeout(2147483647); } QDBusPendingCall BiometricProxy::Identify(int drvid, int uid, int indexStart, int indexEnd) { QList argList; argList << drvid << uid << indexStart << indexEnd; return asyncCallWithArgumentList(QStringLiteral("Identify"), argList); } int BiometricProxy::GetFeatureCount(int uid, int indexStart, int indexEnd) { QDBusMessage result = call(QStringLiteral("GetDevList")); if(result.type() == QDBusMessage::ErrorMessage) { qWarning() << "GetDevList error:" << result.errorMessage(); return 0; } auto dbusArg = result.arguments().at(1).value(); QList variantList; dbusArg >> variantList; int res = 0; for(int i = 0; i < variantList.size(); i++) { DeviceInfoPtr pDeviceInfo = std::make_shared(); auto arg = variantList.at(i).value(); arg >> *pDeviceInfo; QDBusMessage FeatureResult = call(QStringLiteral("GetFeatureList"),pDeviceInfo->id,uid,indexStart,indexEnd); if(FeatureResult.type() == QDBusMessage::ErrorMessage) { qWarning() << "GetFeatureList error:" << FeatureResult.errorMessage(); return 0; } res += FeatureResult.arguments().takeFirst().toInt(); } return res; } int BiometricProxy::StopOps(int drvid, int waiting) { QDBusReply reply = call(QStringLiteral("StopOps"), drvid, waiting); if(!reply.isValid()) { qWarning() << "StopOps error:" << reply.error(); return -1; } return reply.value(); } int BiometricProxy::GetFrameFd(int drvid) { QDBusInterface *interface = new QDBusInterface("org.demo.pictureshow", "/org/demo/pictureshow", "org.demo.pictureshow", QDBusConnection::sessionBus()); QDBusReply reply = interface->call("SendFrame"); //QDBusReply reply = call(QStringLiteral("GetFrameFd"), drvid); if(!reply.isValid()) { qWarning() << "StopOps error:" << reply.error(); return -1; }else{ qDebug()<<"111111111111111111111111111111111111111111"<(); QList variantList; DeviceList deviceList; dbusArg >> variantList; for(int i = 0; i < variantList.size(); i++) { DeviceInfoPtr pDeviceInfo = std::make_shared(); auto arg = variantList.at(i).value(); arg >> *pDeviceInfo; deviceList.push_back(pDeviceInfo); } return deviceList; } int BiometricProxy::GetUserDevCount(int uid) { QDBusMessage result = call(QStringLiteral("GetDevList")); if(result.type() == QDBusMessage::ErrorMessage) { qWarning() << "GetDevList error:" << result.errorMessage(); return 0; } auto dbusArg = result.arguments().at(1).value(); QList variantList; DeviceList deviceList; dbusArg >> variantList; for(int i = 0; i < variantList.size(); i++) { DeviceInfoPtr pDeviceInfo = std::make_shared(); auto arg = variantList.at(i).value(); arg >> *pDeviceInfo; int count = GetUserDevFeatureCount(uid,pDeviceInfo->id); if(count>0) deviceList.push_back(pDeviceInfo); } return deviceList.count(); } int BiometricProxy::GetUserDevFeatureCount(int uid,int drvid) { StopOps(drvid); QDBusMessage FeatureResult = call(QStringLiteral("GetFeatureList"),drvid,uid,0,-1); if(FeatureResult.type() == QDBusMessage::ErrorMessage) { qWarning() << "GetFeatureList error:" << FeatureResult.errorMessage(); return 0; } return FeatureResult.arguments().takeFirst().toInt(); } int BiometricProxy::GetDevCount() { QDBusMessage result = call(QStringLiteral("GetDevList")); if(result.type() == QDBusMessage::ErrorMessage) { qWarning() << "GetDevList error:" << result.errorMessage(); return 0; } int count = result.arguments().at(0).value(); return count; } QString BiometricProxy::GetDevMesg(int drvid) { QDBusMessage result = call(QStringLiteral("GetDevMesg"), drvid); if(result.type() == QDBusMessage::ErrorMessage) { qWarning() << "GetDevMesg error:" << result.errorMessage(); return ""; } return result.arguments().at(0).toString(); } QString BiometricProxy::GetNotifyMesg(int drvid) { QDBusMessage result = call(QStringLiteral("GetNotifyMesg"), drvid); if(result.type() == QDBusMessage::ErrorMessage) { qWarning() << "GetNotifyMesg error:" << result.errorMessage(); return ""; } return result.arguments().at(0).toString(); } QString BiometricProxy::GetOpsMesg(int drvid) { QDBusMessage result = call(QStringLiteral("GetOpsMesg"), drvid); if(result.type() == QDBusMessage::ErrorMessage) { qWarning() << "GetOpsMesg error:" << result.errorMessage(); return ""; } return result.arguments().at(0).toString(); } StatusReslut BiometricProxy::UpdateStatus(int drvid) { StatusReslut status; QDBusMessage result = call(QStringLiteral("UpdateStatus"), drvid); if(result.type() == QDBusMessage::ErrorMessage) { qWarning() << "UpdateStatus error:" << result.errorMessage(); status.result = -1; return status; } status.result = result.arguments().at(0).toInt(); status.enable = result.arguments().at(1).toInt(); status.devNum = result.arguments().at(2).toInt(); status.devStatus = result.arguments().at(3).toInt(); status.opsStatus = result.arguments().at(4).toInt(); status.notifyMessageId = result.arguments().at(5).toInt(); return status; } ukui-greeter/ukui-greeter/BiometricAuth/BiometricAuth.pro.user0000644000175000017500000005672314204635151023531 0ustar fengfeng EnvironmentId {53b257c8-4e5e-4799-9437-c1cd5d6878be} ProjectExplorer.Project.ActiveTarget 0 ProjectExplorer.Project.EditorSettings true false true Cpp CppGlobal QmlJS QmlJSGlobal 2 UTF-8 false 4 false 80 true true 1 true false 0 true true 0 8 true 2 true true true false ProjectExplorer.Project.PluginSettings ProjectExplorer.Project.Target.0 桌面 桌面 {0be3a3c7-d1d8-460a-946d-2a882ae7fd58} 0 0 0 /home/kylin/test/BiometricAuth/build true qmake QtProjectManager.QMakeBuildStep true false false false true Make Qt4ProjectManager.MakeStep -w -r false 2 Build ProjectExplorer.BuildSteps.Build true Make Qt4ProjectManager.MakeStep -w -r true clean 1 Clean ProjectExplorer.BuildSteps.Clean 2 false Debug Debug Qt4ProjectManager.Qt4BuildConfiguration 2 true /home/kylin/test/BiometricAuth/build true qmake QtProjectManager.QMakeBuildStep false false false false true Make Qt4ProjectManager.MakeStep -w -r false 2 Build ProjectExplorer.BuildSteps.Build true Make Qt4ProjectManager.MakeStep -w -r true clean 1 Clean ProjectExplorer.BuildSteps.Clean 2 false Release Release Qt4ProjectManager.Qt4BuildConfiguration 0 true /home/kylin/test/BiometricAuth/build true qmake QtProjectManager.QMakeBuildStep true false true false true Make Qt4ProjectManager.MakeStep -w -r false 2 Build ProjectExplorer.BuildSteps.Build true Make Qt4ProjectManager.MakeStep -w -r true clean 1 Clean ProjectExplorer.BuildSteps.Clean 2 false Profile Profile Qt4ProjectManager.Qt4BuildConfiguration 0 true 3 0 部署 ProjectExplorer.BuildSteps.Deploy 1 部署设置 ProjectExplorer.DefaultDeployConfiguration 1 false false 1000 true false false false false true 0.01 10 true 1 25 1 true false true valgrind 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 2 BiometricAuth BiometricAuth2 Qt4ProjectManager.Qt4RunConfiguration:/home/kylin/workspace/ukui-greeter/BiometricAuth/BiometricAuth.pro true BiometricAuth.pro false /home/kylin/test/BiometricAuth/build 3768 false true false false true 1 ProjectExplorer.Project.TargetCount 1 ProjectExplorer.Project.Updater.FileVersion 18 Version 18 ukui-greeter/ukui-greeter/BiometricAuth/main.cpp0000644000175000017500000000317614204635151020715 0ustar fengfeng/** * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "biometricproxy.h" #include "biometricauthwidget.h" #include "biometricdeviceswidget.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); BiometricProxy proxy; BiometricAuthWidget biometricAuthWidget(&proxy); biometricAuthWidget.hide(); BiometricDevicesWidget biometricDeviceWidget(&proxy); QObject::connect(&biometricDeviceWidget, &BiometricDevicesWidget::deviceChanged, &a, [&](const DeviceInfoPtr &pDeviceInfo){ biometricAuthWidget.startAuth(pDeviceInfo, 1000); biometricAuthWidget.show(); }); QObject::connect(&biometricDeviceWidget, &BiometricDevicesWidget::deviceCountChanged, &a, [&](int count){ qDebug() << "device count changed: " << count; }); biometricDeviceWidget.show(); return a.exec(); } ukui-greeter/ukui-greeter/image.qrc0000644000175000017500000000332614204635151016314 0ustar fengfeng images/arrow_left_active.png images/arrow_left_prelight.png images/arrow_left.png images/arrow_right.png images/cof.png images/default_face.png images/dialog_close_highlight.png images/dialog_close_press.png images/dialog_close.png images/keyboard.png images/next.png images/next-40.png images/power.png images/prev.png images/switchUser.png images/prev-40.png images/reboot.png images/shutdown.png images/waiting.png images/suspend.png images/hibernate.png images/show-password.png images/hide-password.png images/login-button.png images/login-button-hover.png images/login-button-pressed.png images/scrollbar_down.png images/scrollbar_down_clicked.png images/scrollbar_down_hover.png images/scrollbar_up.png images/reboot.png images/avatar.png images/scrollbar_up_clicked.png images/scrollbar_up_hover.png images/combobox_down.png images/capslock.png images/is_logined.png images/bio-login.png images/password-login.png ukui-greeter/ukui-greeter/ukui-greeter.conf0000644000175000017500000000101414204635151017772 0ustar fengfeng[Greeter] # Whether to draw user backgrounds draw-user-background=true # Background file to use, either an image path or a color background=/usr/share/ukui-greeter/images/background-ubuntu.png # Background color background-color=#035290 # Logo file to use logo=/usr/share/ukui-greeter/images/logo.png # cof file to use cof=/usr/share/ukui-greeter/images/cof.png # 双屏时设置显示模式 # DISPLAY_MODE_ORI 1 # DISPLAY_MODE_CLONE 2 # DISPLAY_MODE_EXTEND 3 # DISPLAY_MODE_ONLY_OUT 4 display-mode=3 ukui-greeter/ukui-greeter/ukui-greeter.pro0000644000175000017500000000547714204635151017666 0ustar fengfeng#------------------------------------------------- # # Project created by QtCreator 2017-12-18T09:53:53 # #------------------------------------------------- QT += core gui widgets dbus x11extras svg greaterThan(QT_MAJOR_VERSION, 4) TARGET = ukui-greeter TEMPLATE = app PREFIX = /usr/share/ukui-greeter DEFINES += QT_MESSAGELOGCONTEXT \ #使用qInstallMessageHandler定制日志输出格式 GREETER_CONFIG=/usr/share/lightdm/ukui-greeter/ \ RESOURCE_PATH=$${PREFIX} \ QT_DISABLE_DEPRECATED_BEFORE=0 include(display-switch/display-switch.pri) include(common/common.pri) include(VirtualKeyboard/VirtualKeyboard.pri) include(BiometricAuth/BiometricAuth.pri) INCLUDEPATH += \ common \ VirtualKeyboard/src \ BiometricAuth SOURCES += \ greeter/greeterwindow.cpp \ greeter/iconedit.cpp \ greeter/loginwindow.cpp \ greeter/main.cpp \ greeter/securityuser.cpp \ greeter/usersmodel.cpp \ greeter/greeterwrapper.cpp \ greeter/screenmodel.cpp \ greeter/mainwindow.cpp \ greeter/sessionwindow.cpp \ greeter/powerwindow.cpp \ greeter/proxymodel.cpp \ greeter/userentry.cpp \ greeter/usersview.cpp \ greeter/language.cpp \ greeter/languagewidget.cpp \ greeter/rootWindowBackground.cpp \ greeter/pam-tally.c \ HEADERS += \ greeter/greeterwindow.h \ greeter/iconedit.h \ greeter/pam-tally.h \ greeter/loginwindow.h \ greeter/securityuser.h \ greeter/usersmodel.h \ greeter/greeterwrapper.h \ greeter/screenmodel.h \ greeter/mainwindow.h \ greeter/sessionwindow.h \ greeter/powerwindow.h \ greeter/proxymodel.h \ greeter/userentry.h \ greeter/usersview.h \ greeter/language.h \ greeter/languagewidget.h \ greeter/rootWindowBackground.h \ CONFIG += c++11 debug link_pkgconfig LIBS += -lrt QMAKE_CXXFLAGS += -Wdeprecated-declarations PKGCONFIG += liblightdm-qt5-3 x11 xrandr xtst imlib2 opencv4 gio-2.0 xi RESOURCES += image.qrc \ qss.qrc TRANSLATIONS += translations/zh_CN.ts \ translations/ru.ts \ translations/fr.ts \ translations/pt.ts \ translations/tr.ts \ translations/es.ts system("lrelease translations/*.ts") qm_file.files = translations/*.qm qm_file.path = $${PREFIX}/translations/ resourcefiles.files = images resourcefiles.path = $${PREFIX}/ configfile.files = 95-ukui-greeter.conf configfile.path = /usr/share/lightdm/lightdm.conf.d/ etcfile.files = ukui-greeter.conf etcfile.path = /etc/lightdm/ desktopfile.files = ukui-greeter.desktop desktopfile.path = /usr/share/xgreeters/ manfile.files = man/ukui-greeter.1.gz manfile.path = /usr/share/man/man1/ target.path = /usr/sbin/ INSTALLS += target configfile etcfile desktopfile resourcefiles qm_file manfile ukui-greeter/ukui-greeter/95-ukui-greeter.conf0000644000175000017500000000007014204635151020226 0ustar fengfeng[Seat:*] greeter-session=ukui-greeter user-session=ukui ukui-greeter/ukui-greeter/common/0000755000175000017500000000000014204635151016007 5ustar fengfengukui-greeter/ukui-greeter/common/borderdialog.cpp0000644000175000017500000001001514204635151021145 0ustar fengfeng/** * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "borderdialog.h" #include #include #include #include #include #include BorderDialog::BorderDialog(QWidget *parent) : QWidget(parent) { initUI(); } QWidget *BorderDialog::center() const { return m_centerWidget; } void BorderDialog::initUI() { setWindowFlags(Qt::FramelessWindowHint); setAttribute(Qt::WA_TranslucentBackground, true); setAttribute(Qt::WA_DeleteOnClose, true); setGeometry(parentWidget()->rect()); m_borderWidget = new QWidget(this); m_borderWidget->setObjectName("BorderDialogBorder"); m_centerWidget = new QWidget(m_borderWidget); m_centerWidget->setObjectName("BorderDialogWidget"); /* 窗口关闭按钮 */ m_close = new QPushButton(m_borderWidget); m_close->setObjectName(QStringLiteral("close")); connect(m_close, &QPushButton::clicked, this, &BorderDialog::close); setBorderWidth(); setBorderColor(); setDialogColor(); } void BorderDialog::setBorderWidth(int width) { borderWidth = width; setDialogSize(this->width(), this->height()); } void BorderDialog::setBorderColor(QColor color) { setWidgetColor(m_borderWidget, color); } void BorderDialog::setDialogSize(int w, int h) { m_borderWidget->setGeometry((width() - w) / 2, (height() - h) / 2, w, h); m_centerWidget->setGeometry(borderWidth, borderWidth, m_borderWidget->width() - borderWidth * 2, m_borderWidget->height() - borderWidth * 2); m_close->setGeometry(QRect(0, 0, borderWidth, borderWidth)); } void BorderDialog::setDialogColor(QColor color) { setWidgetColor(m_centerWidget, color); } void BorderDialog::setWidgetColor(QWidget *widget, QColor color) { QString borderStyle = QString("#%1{background: rgb(%2, %3, %4, %5)}") .arg(widget->objectName()) .arg(QString::number(color.red())) .arg(QString::number(color.green())) .arg(QString::number(color.blue())) .arg(QString::number(color.alpha())); widget->setStyleSheet(borderStyle); } void BorderDialog::paintEvent(QPaintEvent *e) { QPainter painter(this); painter.setPen(Qt::transparent); painter.setBrush(QColor(0, 0, 0, m_opacity)); painter.drawRect(rect()); QWidget::paintEvent(e); } void BorderDialog::mousePressEvent(QMouseEvent *event) { if(!m_borderWidget->geometry().contains(event->pos())) close(); } void BorderDialog::close() { m_borderWidget->close(); startBackground(80, 0, false); } void BorderDialog::showEvent(QShowEvent *) { startBackground(0, 80, true); } void BorderDialog::startBackground(int begin, int end, bool show) { QPropertyAnimation *animation = new QPropertyAnimation(this, "opacity"); animation->setDuration(300); animation->setStartValue(begin); animation->setEndValue(end); if(!show) connect(animation, &QPropertyAnimation::finished, this, &QWidget::close); animation->start(); } void BorderDialog::setOpacity(int opacity) { m_opacity = opacity; update(); } int BorderDialog::opacity() { return m_opacity; } ukui-greeter/ukui-greeter/common/borderdialog.h0000644000175000017500000000346114204635151020621 0ustar fengfeng/** * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 BORDERDIALOG_H #define BORDERDIALOG_H #include class QPushButton; class BorderDialog : public QWidget { Q_OBJECT Q_PROPERTY(int opacity READ opacity WRITE setOpacity) public: explicit BorderDialog(QWidget *parent = 0); virtual void close(); void setBorderWidth(int width = 24); void setBorderColor(QColor color = QColor(0, 0, 0, 50)); void setDialogSize(int width, int h); void setDialogColor(QColor color = QColor("#2D698E")); protected: void initUI(); QWidget *center() const; private: void startBackground(int begin, int end, bool show); int opacity(); void setOpacity(int); void setWidgetColor(QWidget *widget, QColor color); protected: void paintEvent(QPaintEvent *); void showEvent(QShowEvent *event); void mousePressEvent(QMouseEvent *event); signals: void aboutToClose(); private: qreal m_opacity; QWidget *m_centerWidget; QWidget *m_borderWidget; QPushButton *m_close; int borderWidth; }; #endif // BORDERDIALOG_H ukui-greeter/ukui-greeter/common/xeventmonitor.h0000644000175000017500000000413614204635151021105 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 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: static XEventMonitor *instance() { return instance_; } private: XEventMonitor(QObject *parent = 0); ~XEventMonitor(); Q_SIGNALS: /// /// \brief 鼠标按下 /// \param x /// \param y /// void buttonPress(int x, int y); /// /// \brief 鼠标拖动 /// \param x /// \param y /// void buttonDrag(int x, int y); /// /// \brief 鼠标松开 /// \param x /// \param y /// void buttonRelease(int x, int y); /// /// \brief keyPress /// \param 按键键码,不包含修饰键 /// void keyPress(int keyCode); void keyRelease(int keyCode); /// /// \brief keyPress /// \param 按键名称,如果是组合键,则包含修饰键的名称,如Ctrl_L+r /// void keyPress(const QString &key); void keyRelease(const QString &key); protected: void run(); private: Q_DECLARE_PRIVATE(XEventMonitor) XEventMonitorPrivate *d_ptr; static XEventMonitor *instance_; }; /** * @brief 检查大写开关状态 * @return 开(true),关(false) */ bool checkCapsState(); bool checkNumLockState(); #endif ukui-greeter/ukui-greeter/common/monitorwatcher.cpp0000644000175000017500000001015414204635151021561 0ustar fengfeng/* * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "monitorwatcher.h" #include #include #include #include #include #include #define DRM_DIR "/sys/class/drm/" MonitorWatcher::MonitorWatcher(QObject *parent) : QThread(parent), virtualSize(0, 0), monitorCount(0), monitorFisrst("") { } MonitorWatcher::~MonitorWatcher() { requestInterruption(); terminate(); wait(); } void MonitorWatcher::getMonitors() { QProcess subProcess; subProcess.setProgram(QStandardPaths::findExecutable("xrandr")); subProcess.setArguments({"-q"}); subProcess.start(QIODevice::ReadOnly); subProcess.waitForFinished(); QString outputs = subProcess.readAll(); QStringList lines = outputs.split('\n'); QString name; QVector modes; bool find = false; QString lastName; monitorNames.clear(); monitors.clear(); for(auto & line : lines) { if(line.indexOf(" connected") != -1) { //找显示器名称 name = line.left(line.indexOf(' ')); monitorNames.push_back(name); if(find) //又找到了一个显示器,将上一个显示器的信息存到map monitors[lastName] = modes; find = true; lastName = name; modes.clear(); } else { if(line.startsWith(' ')) { //获取分辨率 QString mode = line.trimmed().split(' ').at(0); modes.push_back(mode); } } } if(!name.isEmpty()) monitors[name] = modes; //将最后一个显示器的信息存储到map } void MonitorWatcher::run() { /* 每隔3秒遍历一次显卡接口的连接状态 */ while(!isInterruptionRequested()) { getMonitors(); int tmp = monitors.keys().size(); if(tmp == 1 && monitorCount == 1){ if(monitorFisrst != monitorNames[0]){ monitorFisrst = monitorNames[0]; Q_EMIT monitorCountChanged(monitorCount); } }else if(tmp != monitorCount){ if(monitorFisrst == "") monitorFisrst = monitorNames[0]; monitorCount = tmp; Q_EMIT monitorCountChanged(monitorCount); } sleep(3); } } /** * 获取显示器的最大分辨率 */ QSize MonitorWatcher::getMonitorMaxSize(const QString &drm) { // int width, height; // QFile drmModeFile(DRM_DIR + drm + "/modes"); // if(drmModeFile.open(QIODevice::ReadOnly | QIODevice::Text)) { // QTextStream in2(&drmModeFile); // QString maxMode; // do // { // maxMode = in2.readLine(); // }while(maxMode=="preferred"); // int xpos = maxMode.indexOf('x', 0); // width = maxMode.left(xpos).toInt(); // int ipos = maxMode.indexOf('i', 0); // if(ipos != -1) // height = maxMode.mid(xpos+1, ipos-xpos-1).toInt(); // else // { // int p_pos = maxMode.mid(xpos+1).indexOf('p', 0); // if (p_pos != -1) // { // height = maxMode.mid(xpos+1).left(p_pos).toInt(); // } // else // height = maxMode.mid(xpos+1).toInt(); // } // } // return QSize(width, height); return virtualSize; } QSize MonitorWatcher::getVirtualSize() { return virtualSize; } int MonitorWatcher::getMonitorCount() { return monitorCount; } ukui-greeter/ukui-greeter/common/configuration.h0000644000175000017500000000331314204635151021027 0ustar fengfeng/* configuration.h * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 CONFIGURATION_H #define CONFIGURATION_H #include #include #include #include class Configuration : public QObject { Q_OBJECT private: explicit Configuration(QObject *parent = nullptr); public: static Configuration *instance(QObject *parent = nullptr); QVariant getValue(const QString &); void setValue(const QString&, const QVariant &); bool hasValue(const QString &); QString getLastLoginUser(); void saveLastLoginUser(const QString &); void saveLastLoginUser1(const QString &); bool getLastNumLock(); void saveLastNumLock(bool value); QPixmap getLogo(); QString getDefaultBackgroundName(); bool getUseFirstDevice(); QTranslator *m_trans; private: QSettings *configSettings; QSettings *recordSettings; static Configuration *instance_; }; #endif // CONFIGURATION_H ukui-greeter/ukui-greeter/common/xeventmonitor.cpp0000644000175000017500000001626714204635151021450 0ustar fengfeng/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*- * -*- coding: utf-8 -*- * * Copyright (C) 2011 ~ 2017 Deepin, Inc. * 2011 ~ 2017 Wang Yong * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 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 #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 XEventMonitor *XEventMonitor::instance_ = new XEventMonitor(); QVector ModifiersVec{ XK_Control_L, XK_Control_R, XK_Shift_L, XK_Shift_R, XK_Super_L, XK_Super_R, XK_Alt_L, XK_Alt_R }; class XEventMonitorPrivate { public: XEventMonitorPrivate(XEventMonitor *parent); virtual ~XEventMonitorPrivate(); void run(); protected: XEventMonitor *q_ptr; QSet modifiers; 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); void updateModifier(xEvent *event, bool isAdd); 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); QString keyStrSplice; for(auto modifier : modifiers) { keyStrSplice += QString(XKeysymToString(modifier)) + "+"; } //按键是修饰键 if(ModifiersVec.contains(keySym) && !modifiers.isEmpty()) keyStrSplice.remove(keyStrSplice.length() - 1, 1); else keyStrSplice += XKeysymToString(keySym); QMetaObject::invokeMethod(q_ptr, member, Qt::AutoConnection, Q_ARG(int, keyCode)); QMetaObject::invokeMethod(q_ptr, member, Qt::AutoConnection, Q_ARG(QString, keyStrSplice)); 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: updateModifier(event, true); emitKeySignal("keyPress", event); break; case KeyRelease: updateModifier(event, false); emitKeySignal("keyRelease", event); break; default: break; } } fflush(stdout); XRecordFreeData(data); } bool XEventMonitorPrivate::filterWheelEvent(int detail) { return detail != WheelUp && detail != WheelDown && detail != WheelLeft && detail != WheelRight; } void XEventMonitorPrivate::updateModifier(xEvent *event, bool isAdd) { Display *display = XOpenDisplay(NULL); KeySym keySym = XkbKeycodeToKeysym(display, event->u.u.detail, 0, 0); if(ModifiersVec.contains(keySym)) { if(isAdd) { modifiers.insert(keySym); } else { modifiers.remove(keySym); } } XCloseDisplay(display); } 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(); } } bool checkCapsState() { //判断大写键状态 bool capsState = false; unsigned int n; XkbGetIndicatorState(QX11Info::display(), XkbUseCoreKbd, &n); capsState = (n & 0x01) == 1; return capsState; } bool checkNumLockState() { XKeyboardState x; XGetKeyboardControl(QX11Info::display(), &x); return x.led_mask & 2; } ukui-greeter/ukui-greeter/common/configuration.cpp0000644000175000017500000001424214204635151021365 0ustar fengfeng/* * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "configuration.h" #include #include #include #include #include #include #include #include "globalv.h" #define CONFIG_FILE "/etc/lightdm/ukui-greeter.conf" Configuration* Configuration::instance_ = nullptr; Configuration::Configuration(QObject *parent) : QObject(parent), m_trans(nullptr) { QString recodfile = QDir::homePath() + "/.cache/ukui-greeter.conf"; qDebug() << recodfile; configSettings = new QSettings(CONFIG_FILE, QSettings::IniFormat, this); recordSettings = new QSettings(recodfile, QSettings::IniFormat, this); } Configuration* Configuration::instance(QObject *parent) { if(instance_ == nullptr) instance_ = new Configuration(parent); return instance_; } QVariant Configuration::getValue(const QString &key) { configSettings->beginGroup("Greeter"); QVariant value = configSettings->value(key); configSettings->endGroup(); return value; } void Configuration::setValue(const QString &key, const QVariant &value) { configSettings->beginGroup("Greeter"); configSettings->setValue(key, value); configSettings->endGroup(); } bool Configuration::hasValue(const QString &key) { configSettings->beginGroup("Greeter"); bool value = configSettings->contains(key); configSettings->endGroup(); return value; } QString Configuration::getLastLoginUser() { recordSettings->beginGroup("Greeter"); QString lastLoginUser = recordSettings->value("lastLoginUser").toString(); recordSettings->endGroup(); return lastLoginUser; } void Configuration::saveLastLoginUser(const QString &userRealName) { recordSettings->beginGroup("Greeter"); recordSettings->setValue("lastLoginUser", userRealName); recordSettings->endGroup(); recordSettings->sync(); } void Configuration::saveLastLoginUser1(const QString &userRealName) { recordSettings->beginGroup("Greeter"); recordSettings->setValue("lastLoginUser1", userRealName); recordSettings->endGroup(); recordSettings->sync(); } bool Configuration::getLastNumLock() { recordSettings->beginGroup("Greeter"); if(recordSettings->contains("numlock") == false){ recordSettings->setValue("numlock", true); recordSettings->sync(); recordSettings->endGroup(); return true; } bool lastNumLock = recordSettings->value("numlock").toBool(); recordSettings->endGroup(); return lastNumLock; } void Configuration::saveLastNumLock(bool value) { recordSettings->beginGroup("Greeter"); recordSettings->setValue("numlock", value); recordSettings->endGroup(); recordSettings->sync(); } /** * 获取系统版本号 */ QString getSystemVersion() { QSettings settings("/etc/lsb-release", QSettings::IniFormat); QString release = settings.value("DISTRIB_RELEASE").toString(); QString description = settings.value("DISTRIB_DESCRIPTION").toString(); if(description.right(3) == "LTS") release = release + " LTS"; return release; } QString getSystemDistrib() { QSettings settings("/etc/lsb-release", QSettings::IniFormat); QString distribId = settings.value("DISTRIB_ID").toString(); return distribId; } bool Configuration::getUseFirstDevice() { QSettings settings("/etc/biometric-auth/ukui-biometric.conf", QSettings::IniFormat); return settings.value("UseFirstDevice").toBool(); } /** * @brief logoGenerator * @param text * @return * 生成logo */ QPixmap logoGenerator(const QString &text) { // if(getSystemDistrib() == "Kylin") // return QPixmap(":/images/kylin-logo.png"); // QString logoFile(IMAGE_DIR + "logo.png"); // if(QFile(logoFile).exists()) // { // QPixmap logo(IMAGE_DIR + "logo.png"); // return logo; // } QPixmap logoBare; logoBare.load(":/images/uk-logo.png"); QFont font("ubuntu", 18); QFontMetrics fm(font); int textPixelSize = fm.width(text); QPixmap logo(logoBare.width() + textPixelSize + 3 + 30, logoBare.height()); logo.fill(Qt::transparent); QRect logoBareRect(30 , 0, logoBare.width(), logoBare.height()); QPainter painter; painter.begin(&logo); painter.drawPixmap(logoBareRect, logoBare, logoBare.rect()); painter.setPen(Qt::white); painter.setFont(font); QTextOption option(Qt::AlignLeft | Qt::AlignVCenter); option.setWrapMode(QTextOption::WordWrap); QRect versionRect(logoBareRect.right()+3, 3, logo.width() - logoBare.width(), logo.height()); painter.drawText(versionRect, text, option); return logo; } QPixmap Configuration::getLogo() { /** * 先从配置文件中读取logo文件,如果不存在,则生成logo图片 */ QString logoFilePath = getValue("logo").toString(); if(QFile(logoFilePath).exists()) return QPixmap(logoFilePath); return logoGenerator(getSystemVersion()); } QString Configuration::getDefaultBackgroundName() { /* if(getSystemDistrib().contains("Ubuntu",Qt::CaseInsensitive)) return "/usr/share/backgrounds/warty-final-ubuntukylin.jpg"; else if(getSystemVersion().contains("V10.1",Qt::CaseInsensitive)) return "/usr/share/backgrounds/warty-final-ubuntukylin.jpg"; else if(getSystemVersion().contains("V10",Qt::CaseInsensitive)) return "/usr/share/backgrounds/kylin/kylin-background.png"; else return "/usr/share/backgrounds/warty-final-ubuntukylin.jpg"; */ return "/usr/share/backgrounds/warty-final-ubuntukylin.jpg"; } ukui-greeter/ukui-greeter/common/fakedialog.cpp0000644000175000017500000000467214204635151020612 0ustar fengfeng/** * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "fakedialog.h" #include #include #include #include #define BORDER_WIDTH 24 FakeDialog::FakeDialog(QWidget *parent) : QWidget(parent) { initUI(); } void FakeDialog::setDialogSize(int w, int h) { dialogWidth = w; dialogHeight = h; resizeEvent(nullptr); } QWidget* FakeDialog::centerWidget() { return m_centerWidget; } QWidget* FakeDialog::dialog() { return m_dialog; } void FakeDialog::initUI() { setObjectName("fakeDialogBackground"); setAttribute(Qt::WA_TranslucentBackground, true); setGeometry(parentWidget()->rect()); m_dialog = new QWidget(this); m_dialog->setObjectName(QStringLiteral("fakeDialog")); m_centerWidget = new QWidget(m_dialog); m_centerWidget->setObjectName(QStringLiteral("dialogCenterWidget")); m_closeButton = new QPushButton(m_dialog); m_closeButton->setText(tr("cancel")); connect(m_closeButton, &QPushButton::clicked, this, &FakeDialog::close); } void FakeDialog::resizeEvent(QResizeEvent */*event*/) { m_dialog->setGeometry((width() - dialogWidth) / 2, (height() - dialogHeight) / 2, dialogWidth, dialogHeight); m_centerWidget->setGeometry(BORDER_WIDTH, BORDER_WIDTH, m_dialog->width() - BORDER_WIDTH * 2 , m_dialog->height() - BORDER_WIDTH * 2); m_closeButton->setGeometry(m_centerWidget->width()*2/3, m_centerWidget->y() + m_centerWidget->height() - 80, 80, 30); } void FakeDialog::keyReleaseEvent(QKeyEvent *event) { if(event->key() == Qt::Key_Escape) { close(); } } ukui-greeter/ukui-greeter/common/globalv.h0000644000175000017500000000253214204635151017610 0ustar fengfeng/* globalv.h * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 GLOBALV_H #define GLOBALV_H #include #include #include #include #include #define STR(s) #s #define GET_STR(s) STR(s) #define PREFIX QString(GET_STR(RESOURCE_PATH)) #define IMAGE_DIR PREFIX+"/images/" #define QM_DIR PREFIX+"/translations/" class QPoint; class QRect; extern float scale; //缩放比例 extern int fontSize; //字体大小 QPixmap scaledPixmap(int width, int height, QString url); bool isPicture(QString filepath); QString ElideText(QFont font,int width,QString strInfo); #endif // GLOBALV_H ukui-greeter/ukui-greeter/common/monitorwatcher.h0000644000175000017500000000317714204635151021235 0ustar fengfeng/* monitorwatcher.h * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 MONITORWATCHER_H #define MONITORWATCHER_H #include #include #include #include /*! * \brief The MonitorWatcher class * 监控显示器的插拔 */ class MonitorWatcher : public QThread { Q_OBJECT public: MonitorWatcher(QObject *parent=nullptr); ~MonitorWatcher(); QSize getVirtualSize(); int getMonitorCount(); signals: void monitorCountChanged(int newCount); void virtualSizeChanged(const QSize &newVirtualSize); protected: void run(); private: QSize getMonitorMaxSize(const QString &drm); void getMonitors(); QMap> monitors; QStringList monitorNames; QString monitorFisrst; QMap drmStatus; QSize virtualSize; int monitorCount; }; #endif // MONITORWATCHER_H ukui-greeter/ukui-greeter/common/globalv.cpp0000644000175000017500000000517414204635151020150 0ustar fengfeng/* globalv.cpp * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "globalv.h" #include #include #include #include #include #include /** * @brief scaledPixmap * @param width * @param height * @param url * @return * 图片缩放 */ QPixmap scaledPixmap(int width, int height, QString url) { QFile imgFile(url); if(!imgFile.exists()){ qDebug()<< "not find the pixmap file"; return QPixmap(); } QImage image(url); QPixmap pixmap = QPixmap::fromImage(image); if(pixmap.isNull()) { qDebug() << "pixmap is null"; QProcess exec; QString program("file " + url); exec.start(program); exec.waitForFinished(1000); QString output = exec.readAllStandardOutput(); qDebug() << output; if(output.contains("SVG")){ qDebug() << "image format is SVG"; QSvgRenderer render(url); QImage image(width, height, QImage::Format_ARGB32); image.fill(Qt::transparent); QPainter painter(&image); render.render(&painter, image.rect()); pixmap.convertFromImage(image); } else if(output.contains("TIFF")) { qDebug() << "image format is TIFF"; } } return pixmap.scaled(width, height, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); } bool isPicture(QString filepath) { QMimeDatabase db; QMimeType mime = db.mimeTypeForFile(filepath); qDebug() << "mime: " << filepath << mime.name(); return mime.name().startsWith("image/"); } QString ElideText(QFont font,int width,QString strInfo) { QFontMetrics fontMetrics(font); //如果当前字体下,字符串长度大于指定宽度 if(fontMetrics.width(strInfo) > width) { strInfo= QFontMetrics(font).elidedText(strInfo, Qt::ElideRight, width); } return strInfo; } ukui-greeter/ukui-greeter/common/fakedialog.h0000644000175000017500000000265314204635151020254 0ustar fengfeng/** * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 FAKEDIALOG_H #define FAKEDIALOG_H #include class QPushButton; class FakeDialog : public QWidget { Q_OBJECT public: explicit FakeDialog(QWidget *parent = nullptr); void setDialogSize(int w, int h); QWidget *centerWidget(); QWidget *dialog(); protected: void resizeEvent(QResizeEvent *event); void keyReleaseEvent(QKeyEvent *event); private: void initUI(); private: int dialogWidth; int dialogHeight; QWidget *m_dialog; QWidget *m_centerWidget; QPushButton *m_closeButton; // QPushButton *m_okButton; // QPushButton *m_cancelButton; }; #endif // FAKEDIALOG_H ukui-greeter/ukui-greeter/common/common.pri0000644000175000017500000000054114204635151020013 0ustar fengfengHEADERS += \ $$PWD/globalv.h \ $$PWD/configuration.h \ $$PWD/monitorwatcher.h \ $$PWD/borderdialog.h \ $$PWD/fakedialog.h \ $$PWD/xeventmonitor.h SOURCES += \ $$PWD/globalv.cpp \ $$PWD/configuration.cpp \ $$PWD/monitorwatcher.cpp \ $$PWD/borderdialog.cpp \ $$PWD/fakedialog.cpp \ $$PWD/xeventmonitor.cpp ukui-greeter/ukui-greeter/greeter/0000755000175000017500000000000014204635501016153 5ustar fengfengukui-greeter/ukui-greeter/greeter/screenmodel.cpp0000644000175000017500000000442014204635151021160 0ustar fengfeng/* screenmodel.cpp * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "screenmodel.h" #include #include #include ScreenModel::ScreenModel(QObject *parent) : QAbstractListModel(parent), _desktop(QApplication::desktop()) { loadScreens(); connect(_desktop, &QDesktopWidget::workAreaResized, this, &ScreenModel::onScreenResized); connect(_desktop, &QDesktopWidget::resized, this, &ScreenModel::onScreenResized); connect(_desktop, &QDesktopWidget::screenCountChanged, this, &ScreenModel::onScreenCountChanged); } int ScreenModel::rowCount(const QModelIndex &parent) const { if(parent == QModelIndex()){ return m_screen.size(); } return 0; } QVariant ScreenModel::data(const QModelIndex &index, int role) const { if(!index.isValid()){ return QVariant(); } int row = index.row(); if(row < m_screen.size() && role == Qt::UserRole){ return m_screen[row]; } else { return QVariant(); } } void ScreenModel::onScreenResized(int screen) { if(screen >= 0 && screen < m_screen.size()){ m_screen[screen] = _desktop->screenGeometry(screen); } QModelIndex index = createIndex(screen, 0); emit dataChanged(index, index); } void ScreenModel::onScreenCountChanged(int newCount) { Q_UNUSED(newCount) loadScreens(); } void ScreenModel::loadScreens() { beginResetModel(); m_screen.clear(); for(int i = 0; i < _desktop->screenCount(); i++){ m_screen.append(_desktop->screenGeometry(i)); } endResetModel(); } ukui-greeter/ukui-greeter/greeter/userentry.cpp0000644000175000017500000002214114204635151020720 0ustar fengfeng/* userentry.cpp * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "userentry.h" #include #include #include #include #include #include #include #include #include "globalv.h" int UserEntry::count = 0; int UserEntry::selectedId = 0; UserEntry::UserEntry(const QString &name, const QString &facePath, bool isLogin, QWidget *parent) : QWidget(parent), id(count++) { initUI(); setUserName(name); setFace(facePath); setLogin(isLogin); } UserEntry::UserEntry(QWidget *parent) : UserEntry("kylin", "", true, parent) { setUserName("Kylin"+QString::number(id)); } void UserEntry::initUI() { if (objectName().isEmpty()) setObjectName(QString::fromUtf8("Entry")+QString::number(id)); m_faceLabel = new QLabel(this); m_faceLabel->setObjectName(QString::fromUtf8("faceLabel")); m_faceLabel->setScaledContents(true); m_faceLabel->installEventFilter(this); m_nameLabel = new QLabel(this); m_nameLabel->setObjectName(QString::fromUtf8("nameLabel")); m_nameLabel->setFont(QFont("Ubuntu", fontSize)); m_loginLabel = new QLabel(this); m_loginLabel->setObjectName(QString::fromUtf8("isloginLabel")); m_loginLabel->setAlignment(Qt::AlignCenter); m_loginLabel->setPixmap(QPixmap(":/images/is_logined.png")); m_loginLabel->resize(24,24); } void UserEntry::resizeEvent(QResizeEvent *) { setResize(); } bool UserEntry::eventFilter(QObject *obj, QEvent *event) { if(obj == m_faceLabel){ if(event->type() == QEvent::MouseButtonPress){ QMouseEvent *mouseEvent = static_cast(event); if(mouseEvent->button() == Qt::LeftButton){ Q_EMIT pressed(); return true; } } } return QWidget::eventFilter(obj, event); } void UserEntry::onClicked() { this->setSelected(); emit clicked(index.row()); } QPixmap UserEntry::PixmapToRound(const QPixmap &src, int radius) { if (src.isNull()) { return QPixmap(); } QPixmap pixmapa(src); QPixmap pixmap(radius*2,radius*2); pixmap.fill(Qt::transparent); QPainter painter(&pixmap); painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); QPainterPath path; path.addEllipse(0, 0, radius*2, radius*2); painter.setClipPath(path); painter.drawPixmap(0, 0, radius*2, radius*2, pixmapa); return pixmap; } void UserEntry::setFace(const QString &facePath) { this->m_face = facePath; QFile faceFile(facePath); if(!faceFile.exists()) this->m_face = ":/images/default_face.png"; // if(id == selectedId){ const QString SheetStyle = QString("border-radius: %1px; border:0px solid white;").arg(CENTER_IMG_WIDTH/2); m_faceLabel->setStyleSheet(SheetStyle); userface = scaledPixmap(CENTER_IMG_WIDTH, CENTER_IMG_WIDTH, m_face); userface = PixmapToRound(userface, CENTER_IMG_WIDTH/2); m_faceLabel->setPixmap(PixmapToOpacity(userface,1)); // } // else{ // const QString SheetStyle = QString("border-radius: %1px; border:0px solid white;").arg(IMG_WIDTH/2); // m_faceLabel->setStyleSheet(SheetStyle); // userface = scaledPixmap(IMG_WIDTH, IMG_WIDTH, m_face); // userface = PixmapToRound(userface, IMG_WIDTH/2); // m_faceLabel->setPixmap(PixmapToOpacity(userface,0.8)); // } m_faceLabel->setAlignment(Qt::AlignCenter); } const QString& UserEntry::userName() { return this->m_name; } void UserEntry::setUserName(const QString &name) { if(m_name != name) m_name = name; QFont font; font.setPixelSize(16); QString str = ElideText(font,IMG_WIDTH,name); if(name != str) m_nameLabel->setToolTip(name); m_nameLabel->setText(str); m_nameLabel->setAlignment(Qt::AlignCenter); m_nameLabel->adjustSize(); m_nameLabel->move((width() - m_nameLabel->width())/2,m_faceLabel->y() + m_faceLabel->height() + 32); } void UserEntry::setLogin(bool isLogin) { if(m_login != isLogin) m_login = isLogin; if(isLogin) m_loginLabel->show(); else m_loginLabel->hide(); } void UserEntry::setEnterEvent(bool isEnter) { // if(id == selectedId) // return; // QRect faceRect,nameRect,loginRect; // const QString SheetStyle = QString("border-radius: %1px; border:0px solid white;").arg(width()/2); // m_faceLabel->setStyleSheet(SheetStyle); // faceRect.setRect(0, 0, width(), width()); // userface = scaledPixmap(width(), width(), m_face); // userface = PixmapToRound(userface, width()/2); // m_faceLabel->setGeom/*etry(faceRect); // m_faceLabel->move((width() - m_faceLabel->width())*//2,m_faceLabel->y()); // m_faceLabel->setPixmap(userface); // m_loginLabel->setGeometry(m_faceLabel->x() + m_faceLabel->width() - 24,m_faceLabel->y(),24,24); // if(isEnter) // { // QFont font = m_nameLabel->font(); // font.setPixelSize(16); // m_nameLabel->setFont(font); // m_nameLabel->adjustSize(); // //距离头像保持25距离 // m_nameLabel->move((width() - m_nameLabel->width())/2,m_faceLabel->y() + m_faceLabel->height() + 25); // } // else // { // QFont font = m_nameLabel->font(); // font.setPixelSize(14); // m_nameLabel->setFont(font); // m_nameLabel->adjustSize(); // m_nameLabel->move((width() - m_nameLabel->width())/2,m_faceLabel->y() + m_faceLabel->height() + 32); // } userface = scaledPixmap(width(), width(), m_face); userface = PixmapToRound(userface, width()/2); if(isEnter) m_faceLabel->setPixmap(PixmapToOpacity(userface,1)); else m_faceLabel->setPixmap(PixmapToOpacity(userface,0.8)); } //返回一张带有透明度的图片,0<=val<=1 QPixmap UserEntry::PixmapToOpacity(const QPixmap src, double val) { QPixmap temp(src.size()); temp.fill(Qt::transparent); QPainter p(&temp); p.setCompositionMode(QPainter::CompositionMode_Source); p.drawPixmap(0, 0, src); p.setCompositionMode(QPainter::CompositionMode_DestinationIn); p.fillRect(temp.rect(), QColor(0, 0, 0, val*255)); return temp; } void UserEntry::setResize() { QRect faceRect,nameRect,loginRect; const QString SheetStyle = QString("border-radius: %1px; border:0px solid white;").arg(width()/2); m_faceLabel->setStyleSheet(SheetStyle); faceRect.setRect(0, 0, width(), width()); userface = scaledPixmap(width(), width(), m_face); userface = PixmapToRound(userface, width()/2); m_faceLabel->setGeometry(faceRect); m_faceLabel->move((width() - m_faceLabel->width())/2,m_faceLabel->y()); m_loginLabel->setGeometry(m_faceLabel->x() + m_faceLabel->width() - 24,m_faceLabel->y(),24,24); // if(id == selectedId) // { // QFont font = m_nameLabel->font(); // font.setPixelSize(16); // m_nameLabel->setFont(font); // setUserName(m_name); // m_nameLabel->adjustSize(); // //距离头像保持25距离 // m_nameLabel->move((width() - m_nameLabel->width())/2,m_faceLabel->y() + m_faceLabel->height() + 25); // m_faceLabel->setPixmap(PixmapToOpacity(userface,1)); // } // else // { QFont font = m_nameLabel->font(); font.setPixelSize(14); m_nameLabel->setFont(font); //当前头像,用户名距离头像保持32距离 setUserName(m_name); m_nameLabel->adjustSize(); m_nameLabel->move((width() - m_nameLabel->width())/2,m_faceLabel->y() + m_faceLabel->height() + 32); m_faceLabel->setPixmap(PixmapToOpacity(userface,0.8)); // } } void UserEntry::setSelected(bool selected) { if(selected) { selectedId = this->id; } setResize(); } bool UserEntry::selected() { return id == selectedId; } void UserEntry::setUserIndex(const QPersistentModelIndex &index) { Q_ASSERT(index.isValid()); this->index = index; QString realName = index.data(QLightDM::UsersModel::RealNameRole).toString(); QString name = index.data(QLightDM::UsersModel::NameRole).toString(); QString facePath = index.data(QLightDM::UsersModel::ImagePathRole).toString(); bool isLoggedIn = index.data(QLightDM::UsersModel::LoggedInRole).toBool(); setUserName(realName.isEmpty() ? name : realName); setFace(facePath); setLogin(isLoggedIn); } QPersistentModelIndex UserEntry::userIndex() { return index; } ukui-greeter/ukui-greeter/greeter/mainwindow.h0000644000175000017500000000710214204635151020501 0ustar fengfeng/* mainwindow.h * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 #include #include #include #include "screenmodel.h" enum BackgroundMode { DRAW_USER_BACKGROUND, DRAW_BACKGROUND, DRAW_COLOR, DRAW_DEFAULT }; enum BackgroundType { BACKGROUND_IMAGE, BACKGROUND_COLOR }; struct Background { BackgroundType type; QColor color; QString image; }; struct Transition { QSharedPointer from; QSharedPointer to; float stage; bool started; }; class GreeterWindow; class Configuration; class MonitorWatcher; class QTimer; class MainWindow : public QWidget , public QAbstractNativeEventFilter { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); void setBackground(QSharedPointer &); protected: void paintEvent(QPaintEvent *); void mouseMoveEvent(QMouseEvent *); virtual bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) override; signals: void activeScreenChanged(const QRect& rect); private slots: void onScreenResized(); void onScreenCountChanged(int newCount); void onTransition(); void screenCountEvent(); void RRScreenChangeEvent(); QString findTouchScreen(); private: void moveToScreen(QScreen *screen = nullptr); void startTransition(QSharedPointer &, QSharedPointer &); void stopTransition(); void drawTransitionAlpha(const QRect &rect); void drawBackground(QSharedPointer &, const QRect &, float alpha = 1.0); QPixmap * getBackground(const QString &path, const QRect &rect); void showLater(); private: ScreenModel *m_screenModel; GreeterWindow *m_greeterWnd; Configuration *m_configuration; QScreen *m_activeScreen; bool m_drawUserBackground; QString m_defaultBackgroundPath; QString m_backgroundPath; QString m_backgroundColor; QString m_lastBackgroundPath; QPixmap m_logo; QPixmap m_cof; QPixmap *pixmap; //QMap, background> //对每张背景图片的不同分辨率进行缓存,减少CPU占用率(这里分辨率格式:1080x960) QMap, QPixmap*> m_backgrounds; static bool m_first; //MonitorWatcher *m_monitorWatcher; QTimer *m_timer = nullptr; // bool m_switchBackground; QSharedPointer m_background; Transition m_transition; // QPainter *m_painter; BackgroundMode m_backgroundMode; QFuture future; int rr_event_base; int rr_error_base; int m_monitorCount; }; #endif // MAINWINDOW_H ukui-greeter/ukui-greeter/greeter/greeterwrapper.h0000644000175000017500000000350314204635151021364 0ustar fengfeng/* greeterwrapper.h * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 GREETERWRAPPER_H #define GREETERWRAPPER_H #include enum{ rootWinPicture, rootWinColor }; class GreeterWrapper : public QLightDM::Greeter { Q_OBJECT public: explicit GreeterWrapper(QObject *parent=0); void setLang(const QString&); QString lang(); void setSession(const QString&); QString session(); void startSession(); QString userName(); void setUserName(const QString &); void setrootWindow(); bool getdrawBackgroundIsStarted(); //type 0代表绘制背景图片,1代表绘制背景颜色 void setrootWindowBackground(int type,unsigned int color,QString filename); QString getEnsureShareDir(QString username); private: QString m_language; QString m_session; QString m_userName; QString m_rootWindowBackground; int drawBackgroundType; unsigned int drawBackgroundColor; bool drawBackgroundIsStarted = false; signals: void aboutToLogin(); void startSessionFailed(); void authenticationSucess(); }; #endif // GREETERWRAPPER_H ukui-greeter/ukui-greeter/greeter/greeterwrapper.cpp0000644000175000017500000000627714204635151021732 0ustar fengfeng/* greeterwrapper.cpp * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "greeterwrapper.h" #include "rootWindowBackground.h" #include #include #include #include #include "globalv.h" #include "common/configuration.h" GreeterWrapper::GreeterWrapper(QObject *parent) : QLightDM::Greeter(parent) { //连接到lightdm if(!connectToDaemonSync()){ qDebug() << "connect to Daemon failed"; exit(1); } drawBackgroundType = 0; drawBackgroundColor = 0x0; } void GreeterWrapper::setLang(const QString &lang) { this->m_language = lang; } QString GreeterWrapper::lang() { return this->m_language; } void GreeterWrapper::setSession(const QString &session) { m_session = session; } QString GreeterWrapper::session() { return m_session; } QString GreeterWrapper::userName() { return m_userName; } void GreeterWrapper::setUserName(const QString &userName) { m_userName = userName; } void GreeterWrapper::setrootWindowBackground(int type,unsigned int color,QString filename) { drawBackgroundType = type; if(drawBackgroundType == rootWinPicture) m_rootWindowBackground = filename; else if(drawBackgroundType == rootWinColor) drawBackgroundColor = color; } bool GreeterWrapper::getdrawBackgroundIsStarted() { return drawBackgroundIsStarted; } void GreeterWrapper::setrootWindow() { drawBackgroundIsStarted = true; Configuration *m_configure = Configuration::instance(); QString m_defaultBackgroundPath = m_configure->getDefaultBackgroundName(); if(m_rootWindowBackground.isEmpty()) m_rootWindowBackground = m_defaultBackgroundPath; if(m_rootWindowBackground.isEmpty()) return; char* path; QByteArray ba = m_rootWindowBackground.toLatin1(); // must path=ba.data(); if(drawBackgroundType == 0) setRootWindowBackground(0,0,path); else setRootWindowBackground(1,drawBackgroundColor,NULL); drawBackgroundIsStarted = false; } QString GreeterWrapper::getEnsureShareDir(QString username) { return ensureSharedDataDirSync(username); } void GreeterWrapper::startSession() { if(isAuthenticated()) Q_EMIT authenticationSucess(); //启动session //setrootWindow(); draw_background(); qDebug()<<"start session"; if(!startSessionSync(m_session)) { Q_EMIT startSessionFailed(); Q_EMIT showMessage(tr("failed to start session."), QLightDM::Greeter::MessageTypeError); } } ukui-greeter/ukui-greeter/greeter/securityuser.h0000644000175000017500000000246714204635151021104 0ustar fengfeng/* securityuser.h * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 SECURITYUSER_H #define SECURITYUSER_H #include #include enum SECURITYTYPE { NORMAL, WHITELIST, BLACKLIST }; class SecurityUser { public: SecurityUser(); int getMode(); QStringList getBlackList(); QStringList getWhiteList(); bool isSecrityUser(QString username); static SecurityUser *instance(); private: int mode; QStringList blacklist; QStringList whitelist; void loadJsonFile(); static SecurityUser *instance_; }; #endif // SECURITYUSER_H ukui-greeter/ukui-greeter/greeter/usersmodel.h0000644000175000017500000000262314204635151020512 0ustar fengfeng/* usersmodel.h * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 USERSMODEL_H #define USERSMODEL_H #include "proxymodel.h" class SecurityUser; class UsersModel : public ProxyModel { Q_OBJECT Q_PROPERTY(bool m_showGuest READ showGuest WRITE setShowGuest) Q_PROPERTY(bool m_showManualLogin READ showManualLogin WRITE setShowManualLogin) public: explicit UsersModel(bool hideUsers=false, QObject *parent=0); void setShowGuest(bool); bool showGuest() const; void setShowManualLogin(bool); bool showManualLogin() const; private: int getSecUserCount(); bool m_showGuest; bool m_showManualLogin; SecurityUser *secUser; }; #endif // USERSMODEL_H ukui-greeter/ukui-greeter/greeter/languagewidget.h0000644000175000017500000000302614204635151021315 0ustar fengfeng/** * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 LANGUAGEWIDGET_H #define LANGUAGEWIDGET_H #include #include #include "language.h" #include "fakedialog.h" class QListWidget; class QListWidgetItem; class QLabel; class LanguageWidget : public QMenu { Q_OBJECT public: explicit LanguageWidget(QWidget *parent = nullptr); void setCurrentLanguage(const QString &language); protected: bool eventFilter(QObject *obj, QEvent *event); private: void initUserMenu(); private Q_SLOTS: void onLanguageAdded(QString lang); void onLanguageDeleted(QString lang); void onLanguageMenuTrigged(QAction *action); Q_SIGNALS: void languageChanged(const Language &language); private: LanguagesVector &languagesVector; QMenu *menu; }; #endif // LANGUAGEWIDGET_H ukui-greeter/ukui-greeter/greeter/userentry.h0000644000175000017500000000476314204635151020377 0ustar fengfeng/* userentry.h * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 USERENTRY_H #define USERENTRY_H #include #include #include #define FACE_WIDTH (160 * scale) #define BORDER_WIDTH (95 * scale) #define C_BORDER_WIDTH (60 *scale) #define C_BORDER_HEIGHT C_BORDER_WIDTH #define IMG_WIDTH (160 * scale) #define ENTRY_HEIGHT (FACE_WIDTH + 60) #define ENTRY_WIDTH (160 * scale) #define CENTER_IMG_WIDTH (160 * scale) #define CENTER_FACE_WIDTH (160 * scale) #define CENTER_ENTRY_WIDTH (160 * scale ) #define CENTER_ENTRY_HEIGHT (CENTER_FACE_WIDTH + 60) extern float scale; class UserEntry : public QWidget { Q_OBJECT public: UserEntry(const QString &name, const QString &facePath, bool isLogin, QWidget *parent); UserEntry(QWidget *parent=0); void setUserIndex(const QPersistentModelIndex &index); QPersistentModelIndex userIndex(); void setFace(const QString &facePath); const QString& userName(); void setUserName(const QString &name); void setLogin(bool isLogin); void setSelected(bool selected=true); bool selected(); void setResize(); void setEnterEvent(bool isEnter); void setMoveSize(); protected: bool eventFilter(QObject *obj, QEvent *event); void resizeEvent(QResizeEvent *); private: void onClicked(); void initUI(); QPixmap PixmapToRound(const QPixmap &src, int radius); QPixmap PixmapToOpacity(const QPixmap src , double val); signals: void clicked(int row); void pressed(); private: QLabel *m_faceLabel; QLabel *m_nameLabel; QLabel *m_loginLabel; QPixmap userface; QPersistentModelIndex index; QString m_face; QString m_name; bool m_login; int id; static int count; static int selectedId; }; #endif // USERENTRY_H ukui-greeter/ukui-greeter/greeter/powerwindow.h0000644000175000017500000000376314204635151020722 0ustar fengfeng/* powerwindow.h * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 POWERMANAGER_H #define POWERMANAGER_H #include #include #include #include #include #include #define ITEM_WIDTH 168 #define ITEM_HEIGHT ITEM_WIDTH #define ITEM_SPACING (ITEM_WIDTH/8) class QListWidget; class QListWidgetItem; class QDBusInterface; class PowerManager:public QListWidget { Q_OBJECT public: PowerManager(QWidget *parent = 0); QSize windowSize(); private: void initUI(); QListWidget *list; QWidget *lockWidget; QWidget *switchWidget = nullptr; QWidget *suspendWidget; QWidget *rebootWidget; QWidget *shutdownWidget; QWidget *hibernateWidget; QLabel *switchLabel = nullptr; QLabel *rebootLabel; QLabel *shutdownLabel; QLightDM::PowerInterface *m_power; QTime lasttime; QDBusInterface *actService; private: void lockWidgetClicked(); void switchWidgetClicked(); void suspendWidgetClicked(); void rebootWidgetClicked(); void shutdownWidgetClicked(); void hibernateWidgetClicked(); void refreshTranslate(); private Q_SLOTS: void powerClicked(QListWidgetItem *item); Q_SIGNALS: void switchToUser(); void lock(); }; #endif // POWERMANAGER_H ukui-greeter/ukui-greeter/greeter/sessionwindow.h0000644000175000017500000000340214204635151021237 0ustar fengfeng/* sessionwindow.h * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 SESSIONWINDOW_H #define SESSIONWINDOW_H #include #include #include #include class QLabel; class QPushButton; class IconLabel; class QListWidget; class QListWidgetItem; class SessionWindow : public QMenu { Q_OBJECT public: explicit SessionWindow(QAbstractItemModel *model, QWidget *parent = 0); void setSessionModel(QAbstractItemModel *model); void setCurrentSession(const QString &session); protected: bool eventFilter(QObject *obj, QEvent *event); signals: void sessionChanged(const QString &session); private Q_SLOTS: void onSessionMenuTrigged(QAction *action); private: void initUI(); QString getSessionIcon(const QString&); void addSessionLabels(); void testAddSessionLabels(); QString m_defaultSession; QAbstractItemModel *m_sessionsModel; //UI QLabel *m_prompt; QListWidget *m_sessionsList; }; #endif // SESSIONWINDOW_H ukui-greeter/ukui-greeter/greeter/iconedit.cpp0000644000175000017500000001660614204635151020467 0ustar fengfeng/* iconedit.cpp * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "iconedit.h" #include #include #include #include #include #include #include #include #include #include #include "xeventmonitor.h" #include "common/configuration.h" IconEdit::IconEdit(QWidget *parent) : QWidget(parent), m_timer(nullptr), m_configuration(Configuration::instance()) { m_edit = new QLineEdit(this); m_edit->setObjectName(QStringLiteral("passwdEdit")); m_capsIcon = new QLabel(this); m_capsIcon->setObjectName(QStringLiteral("capsIconLabel")); m_iconButton = new QPushButton(this); m_iconButton->setObjectName(QStringLiteral("loginButton")); m_modeButton = new QPushButton(this); m_modeButton->setObjectName(QStringLiteral("echoModeButton")); QHBoxLayout *layout = new QHBoxLayout(this); QtConcurrent::run([=](){ layout->setContentsMargins(1, 1, 1, 1); layout->setSpacing(0); layout->addStretch(); layout->addWidget(m_capsIcon); layout->addWidget(m_modeButton); layout->addWidget(m_iconButton); m_edit->setAttribute(Qt::WA_InputMethodEnabled, false); //禁用输入法 m_edit->setContextMenuPolicy(Qt::NoContextMenu); //禁用右键菜单 m_edit->installEventFilter(this); //m_edit->setMaxLength(1000); m_iconButton->setFocusPolicy(Qt::NoFocus); m_iconButton->setCursor(QCursor(Qt::PointingHandCursor)); m_iconButton->installEventFilter(this); m_modeButton->setCheckable(true); m_modeButton->setFocusPolicy(Qt::NoFocus); m_modeButton->setCursor(Qt::PointingHandCursor); connect(m_modeButton, &QPushButton::clicked, this, [&](bool checked){ setType(checked ? QLineEdit::Normal : QLineEdit::Password); }); connect(m_edit, &QLineEdit::returnPressed, this, &IconEdit::clicked_cb); connect(m_iconButton, &QPushButton::clicked, this, &IconEdit::clicked_cb); connect(XEventMonitor::instance(), SIGNAL(keyRelease(const QString &)), this, SLOT(onGlobalKeyRelease(const QString &))); setFocusProxy(m_edit); setCapsState(checkCapsState()); }); } bool IconEdit::eventFilter(QObject *obj, QEvent *event) { if(obj == m_edit){ if(event->type() == QEvent::KeyPress){ QKeyEvent *keyEvent = static_cast(event); if(keyEvent->matches(QKeySequence::Copy) || keyEvent->matches(QKeySequence::Cut) || keyEvent->matches(QKeySequence::Paste)){ event->ignore(); return true; }else if((keyEvent->modifiers() & Qt::MetaModifier) || (keyEvent->modifiers() & Qt::AltModifier)){//当meta或者alt键被按下时,忽略按键事件 event->ignore(); return true; } } } if(obj == m_iconButton){ if(m_timer && m_timer->isActive()) { return false; } if(event->type() == QEvent::HoverEnter){ setIcon(QIcon(":/images/login-button-hover.png")); } else if(event->type() == QEvent::HoverLeave){ setIcon(QIcon(":/images/login-button.png")); } } return false; } void IconEdit::setType(QLineEdit::EchoMode type) { m_edit->setEchoMode(type); if(type == 0) m_modeButton->setChecked(true); else m_modeButton->setChecked(false); } void IconEdit::clearText() { m_edit->setText(""); } void IconEdit::resizeEvent(QResizeEvent *) { // 设置输入框中文件输入区,不让输入的文字在被隐藏在按钮下 int w = m_iconButton->width() + m_modeButton->width(); m_edit->setTextMargins(1, 1, m_capsIcon->isVisible() ? w + m_capsIcon->width() : w, 1); m_edit->setFixedSize(size()); } void IconEdit::clicked_cb() { // m_iconButton->setFocus(); //按回车后输入框光标会消失或者不再闪烁,先让其他控件获取焦点,就会解决该问题 startWaiting(); emit clicked(m_edit->text()); } void IconEdit::onGlobalKeyRelease(const QString &key) { if(key == "Caps_Lock") { setCapsState(!m_capsIcon->isVisible()); } else if(key == "Num_Lock") { bool numlockState = m_configuration->getLastNumLock(); m_configuration->saveLastNumLock(!numlockState); } } void IconEdit::setCapsState(bool capsState) { m_capsIcon->setVisible(capsState); int w = m_iconButton->width() + m_modeButton->width(); m_edit->setTextMargins(1, 1, capsState ? w + m_capsIcon->width() : w, 1); } void IconEdit::setIcon(const QString &text) { m_iconButton->setIcon(QIcon()); m_iconButton->setText(text); m_iconText = text; m_icon = QIcon(); } void IconEdit::setIcon(const QIcon &icon) { m_iconButton->setIcon(icon); m_iconButton->setText(""); m_icon = icon; m_iconText = ""; } void IconEdit::clear() { m_edit->setText(""); setPrompt(""); } void IconEdit::setPrompt(const QString &prompt) { // m_edit->setPlaceholderText(prompt); if(prompt == "Password: " || prompt == "密码:") m_edit->setPlaceholderText(tr("Password: ")); else if(prompt == "Username" || prompt == "用户名") m_edit->setPlaceholderText(tr("Username")); else m_edit->setPlaceholderText(prompt); } const QString IconEdit::text() { return m_edit->text(); } void IconEdit::startWaiting() { m_edit->setReadOnly(true); m_iconButton->setEnabled(false); if(!m_timer) { m_timer = new QTimer(this); m_timer->setInterval(120); connect(m_timer, &QTimer::timeout, this, &IconEdit::updatePixmap); } m_waitingPixmap.load(":/images/waiting.png"); m_iconButton->setIconSize(m_iconButton->size()); m_iconButton->setIcon(QIcon(m_waitingPixmap)); m_timer->start(); } void IconEdit::refreshTranslate() { if(m_edit->placeholderText() == "Password: " || m_edit->placeholderText() == "密码:") m_edit->setPlaceholderText(tr("Password: ")); if(m_edit->placeholderText() == "Username" || m_edit->placeholderText() == "用户名") m_edit->setPlaceholderText(tr("Username")); } void IconEdit::stopWaiting() { if(m_timer && m_timer->isActive()) { m_timer->stop(); } m_edit->setReadOnly(false); m_iconButton->setEnabled(true); if(!m_icon.isNull()) m_iconButton->setIcon(m_icon); else m_iconButton->setText(m_iconText); } void IconEdit::updatePixmap() { QMatrix matrix; matrix.rotate(90.0); m_waitingPixmap = m_waitingPixmap.transformed(matrix, Qt::FastTransformation); m_iconButton->setIcon(QIcon(m_waitingPixmap)); } ukui-greeter/ukui-greeter/greeter/usersmodel.cpp0000644000175000017500000000616014204635151021045 0ustar fengfeng/* usersmodel.cpp * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "usersmodel.h" #include #include #include "securityuser.h" UsersModel::UsersModel(bool hideUsers, QObject *parent) : ProxyModel(parent), secUser(SecurityUser::instance()), m_showGuest(false) { if(!hideUsers){ setSourceModel(new QLightDM::UsersModel(this)); if(getSecUserCount() == 0){ setShowManualLogin(true); } } else { qDebug() << "hide users, show manual"; setShowManualLogin(true); } } void UsersModel::setShowGuest(bool isShowGuest) { if(m_showGuest == isShowGuest) return; m_showGuest = isShowGuest; if(m_showGuest) { QStandardItem *guest = new QStandardItem(tr("Guest")); guest->setData("*guest", QLightDM::UsersModel::NameRole); guest->setData(tr("Guest"), QLightDM::UsersModel::RealNameRole); extraRowModel()->appendRow(guest); } else { QStandardItemModel *model = extraRowModel(); for(int i = 0; i < model->rowCount(); i++){ QStandardItem *item = model->item(i, 0); if(item->text() == tr("Guest Session")){ model->removeRow(i); } } } } bool UsersModel::showGuest() const { return m_showGuest; } void UsersModel::setShowManualLogin(bool isShowManualLogin) { if(m_showManualLogin == isShowManualLogin) return; m_showManualLogin = isShowManualLogin; if(m_showManualLogin){ QStandardItem *manualLogin = new QStandardItem(tr("Login")); manualLogin->setData("*login", QLightDM::UsersModel::NameRole); manualLogin->setData(tr("Login"), QLightDM::UsersModel::RealNameRole); extraRowModel()->appendRow(manualLogin); } else{ QStandardItemModel *model = extraRowModel(); for(int i = 0; i < model->rowCount(); i++){ QStandardItem *item = model->item(i, 0); if(item->text() == tr("Login")){ model->removeRow(i); } } } } int UsersModel::getSecUserCount() { int count = 0; for(int i = 0; i < this->rowCount(); i++){ QString name = this->index(i).data(QLightDM::UsersModel::NameRole).toString(); if(secUser->isSecrityUser(name)) count++; } return count; } bool UsersModel::showManualLogin() const { return m_showManualLogin; } ukui-greeter/ukui-greeter/greeter/mainwindow.cpp0000644000175000017500000004201414204635501021034 0ustar fengfeng/* mainwindow.cpp * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "globalv.h" #include "greeterwindow.h" #include "common/configuration.h" #include "common/monitorwatcher.h" #include "display-switch/displayservice.h" #include #include #include #include #include #include #include #include #include #include #include #include "xeventmonitor.h" bool MainWindow::m_first = true; 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 QPixmap * blurPixmap(QPixmap *pixmap) { QPainter painter(pixmap); QImage srcImg = pixmap->toImage(); qt_blurImage(&painter, srcImg, BLUR_RADIUS, false, false); painter.end(); return pixmap; } MainWindow::MainWindow(QWidget *parent) : QWidget(parent), m_screenModel(new ScreenModel(this)), m_configuration(Configuration::instance()), m_activeScreen(nullptr), m_background(nullptr), m_monitorCount(0) { setWindowFlags(Qt::X11BypassWindowManagerHint); XRRQueryExtension(QX11Info::display(), &rr_event_base, &rr_error_base); XRRSelectInput(QX11Info::display(), QX11Info::appRootWindow(), RRScreenChangeNotifyMask); QDesktopWidget *_desktop = QApplication::desktop(); connect(_desktop, &QDesktopWidget::resized, this, &MainWindow::onScreenResized); connect(_desktop, &QDesktopWidget::screenCountChanged, this, &MainWindow::onScreenResized); //设置窗口大小 qDebug()<<_desktop->geometry(); setGeometry(_desktop->geometry()); //设置监控鼠标移动 setMouseTracking(true); //背景图片 优先级:用户桌面背景、背景图片、背景颜色 m_defaultBackgroundPath = m_configuration->getDefaultBackgroundName(); bool drawUserBackground = m_configuration->getValue("draw-user-background").toBool(); if(drawUserBackground) { m_backgroundMode = DRAW_USER_BACKGROUND; } else { m_background = QSharedPointer(new Background); QString backgroundPath = m_configuration->getValue("background").toString(); if(!backgroundPath.isEmpty()) { m_backgroundMode = DRAW_BACKGROUND; m_background->type = BACKGROUND_IMAGE; m_background->image = backgroundPath; } else { QString color = m_configuration->getValue("background-color").toString(); if(!color.isEmpty()) { m_backgroundMode = DRAW_COLOR; m_background->type = BACKGROUND_COLOR; m_background->color = color; } else { m_backgroundMode = DRAW_DEFAULT; m_background->type = BACKGROUND_IMAGE; m_background->image = m_defaultBackgroundPath; } } } m_transition.started = false; m_greeterWnd = new GreeterWindow(this); future = QtConcurrent::run([=](){ QString backgroundname = m_greeterWnd->guessBackground(); QString resolution = QString("%1x%2").arg(QApplication::primaryScreen()->geometry().width()).arg(QApplication::primaryScreen()->geometry().height()); QPair key(backgroundname, resolution); if(!m_backgrounds.contains(key)){ QPixmap *pixmap = new QPixmap(scaledPixmap(QApplication::primaryScreen()->geometry().width(), QApplication::primaryScreen()->geometry().height(), backgroundname)); m_backgrounds[key] = blurPixmap(pixmap); } }); //登录界面移动到鼠标所在的屏幕上 if(QApplication::screens().count() > 1){ QPoint point = QCursor::pos(); QScreen *curScreen = nullptr; for(QScreen *screen : QApplication::screens()){ QRect screenRect = screen->geometry(); if(screenRect.contains(point)) { curScreen = screen; break; } } if(curScreen != m_activeScreen && curScreen != nullptr){ qDebug() << "active screen: from " << m_activeScreen << "to " << curScreen; moveToScreen(curScreen); } //多屏时,触屏和显示接口映射,由于无法智能,选择主屏幕作为触屏映射,如果多个触屏也会像windows一样出现异常 QString strScreenName = QApplication::primaryScreen()->name(); QProcess enableMonitors; QString strDevID = findTouchScreen(); if (false == strDevID.isEmpty())//有触屏设备才做映射 { //设置映射关系 示例 xinput map-to-output 7 HDMI-1 QString xinputCmd = "xinput map-to-output " + strDevID + " " + strScreenName; qDebug() << "find touchscreen " << strDevID << " " << strScreenName; enableMonitors.start(xinputCmd); enableMonitors.waitForFinished(-1); } }else{ moveToScreen(QApplication::primaryScreen()); } m_greeterWnd->initUI(); qApp->installNativeEventFilter(this); showLater(); } //识别触摸屏设备,并获取第一个设备id QString MainWindow::findTouchScreen(){ int ndevices = 0; Display *dpy = XOpenDisplay(NULL); XIDeviceInfo *info = XIQueryDevice(dpy, XIAllDevices, &ndevices); QString devicesid=""; for (int i = 0; i < ndevices; i++) { XIDeviceInfo* dev = &info[i]; // 判断当前设备是不是触摸屏 if(dev->use != XISlavePointer) continue; if(!dev->enabled) continue; for (int j = 0; j < dev->num_classes; j++) { if (dev->classes[j]->type == XITouchClass) { devicesid = tr("%1").arg(dev->deviceid); break;//获取第一个触屏设备id } } } XIFreeDeviceInfo(info); XCloseDisplay(dpy); return devicesid; } void MainWindow::paintEvent(QPaintEvent *e) { for(QScreen *screen : QApplication::screens()){ //在每个屏幕上绘制背景 QRect rect = screen->geometry(); if(m_transition.started) drawTransitionAlpha(rect); else { drawBackground(m_background, rect); } QPainter painter(this); } return QWidget::paintEvent(e); } /** * 根据鼠标指针移动位置移动Greeter窗口所在屏幕 */ void MainWindow::mouseMoveEvent(QMouseEvent *e) { if(QApplication::screens().count() > 1){ QPoint point = e->pos(); QScreen *curScreen = nullptr; for(QScreen *screen : QApplication::screens()){ QRect screenRect = screen->geometry(); if(screenRect.contains(point)) { curScreen = screen; break; } } if(curScreen != m_activeScreen && curScreen != nullptr){ qDebug() << "active screen: from " << m_activeScreen << "to " << curScreen; moveToScreen(curScreen); } } return QWidget::mouseMoveEvent(e); } void MainWindow::showLater() { if(m_configuration->hasValue("numlock") && m_configuration->getValue("numlock").toBool()){ unsigned int num_mask = XkbKeysymToModifiers (QX11Info::display(), XK_Num_Lock); XkbLockModifiers (QX11Info::display(), XkbUseCoreKbd, num_mask, 0); XkbLockModifiers (QX11Info::display(), XkbUseCoreKbd, num_mask, num_mask); }else if(m_configuration->hasValue("numlock") && !m_configuration->getValue("numlock").toBool()){ unsigned int num_mask = XkbKeysymToModifiers (QX11Info::display(), XK_Num_Lock); XkbLockModifiers (QX11Info::display(), XkbUseCoreKbd, num_mask, num_mask); XkbLockModifiers (QX11Info::display(), XkbUseCoreKbd, num_mask, 0); } else if(checkNumLockState()){ unsigned int num_mask = XkbKeysymToModifiers (QX11Info::display(), XK_Num_Lock); XkbLockModifiers (QX11Info::display(), XkbUseCoreKbd, num_mask, 0); XkbLockModifiers (QX11Info::display(), XkbUseCoreKbd, num_mask, num_mask); }else{ unsigned int num_mask = XkbKeysymToModifiers (QX11Info::display(), XK_Num_Lock); XkbLockModifiers (QX11Info::display(), XkbUseCoreKbd, num_mask, num_mask); XkbLockModifiers (QX11Info::display(), XkbUseCoreKbd, num_mask, 0); } if(checkCapsState()){ unsigned int caps_mask = XkbKeysymToModifiers (QX11Info::display(), XK_Caps_Lock); XkbLockModifiers (QX11Info::display(), XkbUseCoreKbd, caps_mask, 0); XkbLockModifiers (QX11Info::display(), XkbUseCoreKbd, caps_mask, caps_mask); }else{ unsigned int caps_mask = XkbKeysymToModifiers (QX11Info::display(), XK_Caps_Lock); XkbLockModifiers (QX11Info::display(), XkbUseCoreKbd, caps_mask, caps_mask); XkbLockModifiers (QX11Info::display(), XkbUseCoreKbd, caps_mask, 0); } XDefineCursor(QX11Info::display(), QX11Info::appRootWindow(), XCreateFontCursor(QX11Info::display(), XC_arrow)); bindtextdomain("Linux-PAM","/usr/share/locale"); textdomain("Linux-PAM"); XEventMonitor::instance()->start(); } void MainWindow::RRScreenChangeEvent() { XRRScreenResources *screen; screen = XRRGetScreenResources(QX11Info::display(), QX11Info::appRootWindow()); XRROutputInfo *info; int count = 0; for (int i = 0; i < screen->noutput; i++) { info = XRRGetOutputInfo(QX11Info::display(), screen, screen->outputs[i]); if (info->connection == RR_Connected) { count++; } XRRFreeOutputInfo(info); } onScreenCountChanged(count); XRRFreeScreenResources(screen); } bool MainWindow::nativeEventFilter(const QByteArray &eventType, void *message, long *result) { Q_UNUSED(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 == rr_event_base + RRScreenChangeNotify){ RRScreenChangeEvent(); } return false; } /** * 有屏幕分辨率发生改变,移动Greeter窗口位置 */ void MainWindow::onScreenResized() { hide(); QDesktopWidget *desktop = QApplication::desktop(); setGeometry(desktop->geometry()); qDebug() << "screen resize to " << geometry(); moveToScreen(QApplication::primaryScreen()); show(); m_greeterWnd->setRootWindow(); } void MainWindow::screenCountEvent() { QDesktopWidget *desktop = QApplication::desktop(); setGeometry(desktop->geometry()); moveToScreen(QApplication::primaryScreen()); //需要重新绘制,否则背景图片大小会不正确 repaint(); } /** * 有屏幕插拔,移动GreeterWindow到主屏幕 */ void MainWindow::onScreenCountChanged(int newCount) { if(newCount == m_monitorCount) return; m_monitorCount = newCount; DisplayService displayService; if(newCount < 2) { QProcess enableMonitors; //默认设置显示最大分辨率 // enableMonitors.start("xrandr --auto"); QString strXrandr = displayService.getFirstDisplayXrandrCmd(); enableMonitors.start(strXrandr); enableMonitors.waitForFinished(-1); } else { int mode = m_configuration->getValue("display-mode").toInt(); displayService.switchDisplayMode((DisplayMode)mode); //多屏时,触屏和显示接口映射,由于无法智能,选择主屏幕作为触屏映射,如果多个触屏也会像windows一样出现异常 QString strScreenName = QApplication::primaryScreen()->name(); QProcess enableMonitors; QString strDevID = findTouchScreen(); if (false == strDevID.isEmpty())//有触屏设备才做映射 { //设置映射关系 示例 xinput map-to-output 7 HDMI-1 QString xinputCmd = "xinput map-to-output " + strDevID + " " + strScreenName; qDebug() << "find touchscreen " << strDevID << " " << strScreenName; enableMonitors.start(xinputCmd); enableMonitors.waitForFinished(-1); } } //在调用xrandr打开显示器以后,不能马上设置窗口大小,会设置不正确的 //分辨率,延时1000ms正常。 QTimer::singleShot(1000,this,SLOT(screenCountEvent())); } /** * 移动Greeter窗口到screen屏幕上 */ void MainWindow::moveToScreen(QScreen *screen) { m_activeScreen = screen; QRect activeScreenRect = m_activeScreen->geometry(); qDebug() << "moveToScreen activeScreenRect " << activeScreenRect; if(QApplication::desktop()->screenCount() == 1) m_greeterWnd->setGeometry(QApplication::primaryScreen()->geometry()); else m_greeterWnd->setGeometry(activeScreenRect); // Q_EMIT activeScreenChanged(activeScreenRect); update(); } void MainWindow::setBackground(QSharedPointer &background) { if(background) qDebug() << background->image; if(m_backgroundMode != DRAW_USER_BACKGROUND) return; stopTransition(); if(background) { if(background->image.isEmpty()) background->image = m_defaultBackgroundPath; } if(m_background && background && m_background->image == background->image){ repaint(); return; } //如果是第一次绘制背景,则不需要渐变 //现在由于绘制模糊背景,不需要渐变了 if(!m_background.isNull()){ startTransition(m_background, background); } m_background = background; } void MainWindow::startTransition(QSharedPointer &from, QSharedPointer &to) { if(!m_timer){ m_timer = new QTimer(this); connect(m_timer, &QTimer::timeout, this, &MainWindow::onTransition); } stopTransition(); m_transition.from = from; m_transition.to = to; m_transition.stage = 0.0; m_transition.started = true; m_timer->start(20); } void MainWindow::stopTransition() { if(m_timer && m_timer->isActive()) m_timer->stop(); m_transition.stage = 1.0; m_transition.started = false; } void MainWindow::onTransition() { m_transition.stage += 0.1;//= (1 - cos(M_PI * m_transition.stage)) / 2; if(m_transition.stage >= 1.0) stopTransition(); //repaint(); update(); } void MainWindow::drawTransitionAlpha(const QRect &rect) { drawBackground(m_transition.from, rect, 1.0 - m_transition.stage); drawBackground(m_transition.to, rect, m_transition.stage); } void MainWindow::drawBackground(QSharedPointer &background, const QRect &rect, float alpha) { if(background == nullptr) return; QPainter painter(this); painter.setOpacity(alpha); switch(background->type) { case BACKGROUND_IMAGE: { QPixmap *pixmap = getBackground(background->image, rect); if(pixmap->isNull()) { QString color = m_configuration->getValue("background-color").toString(); QColor cor; if(!color.isEmpty()) cor = color; else cor = "#035290"; painter.setBrush(cor); painter.drawRect(rect); } else { painter.drawPixmap(rect, *pixmap); } break; } case BACKGROUND_COLOR: { painter.setBrush(background->color); painter.drawRect(rect); break; } } } QPixmap * MainWindow::getBackground(const QString &path, const QRect &rect) { QString resolution = QString("%1x%2").arg(rect.width()).arg(rect.height()); QPair key(path, resolution); if(m_backgrounds.isEmpty()&&future.isRunning()){ future.waitForFinished(); }else{ if(!future.isFinished() && future.isStarted()){ future.waitForFinished(); } } if(!m_backgrounds.contains(key)){ QPixmap *pixmap = new QPixmap(scaledPixmap(width(), height(), path)); m_backgrounds[key] = blurPixmap(pixmap); } return m_backgrounds[key]; } ukui-greeter/ukui-greeter/greeter/usersview.h0000644000175000017500000000574114204635151020370 0ustar fengfeng/* usersview.h * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 USERSVIEW_H #define USERSVIEW_H #include #include #include #include #include "userentry.h" class QPushButton; class QAbstractListModel; class SecurityUser; class UsersView : public QWidget { Q_OBJECT public: explicit UsersView(QWidget *parent = 0); ~UsersView(); void setModel(QAbstractListModel *); void setCurrentUser(const QString &userName, bool entered = false); void pageUp(); void pageDown(); QSize getSize(); /*qpair记录用户的上一个用户和下一用户的在list的位置*/ //QList>> userlist; QList userlist; bool getIsUser(QString username); bool getLoginStaus(QString username); protected: // void keyReleaseEvent(QKeyEvent *event); void showEvent(QShowEvent *event); bool eventFilter(QObject *obj, QEvent *event); void resizeEvent(QResizeEvent *); private slots: void onUserAdded(const QModelIndex&, int, int); void onUserRemoved(const QModelIndex&, int, int ); void onUserChanged(const QModelIndex&, const QModelIndex&); void setCurrentRow(int row); void onGlobalKeyRelease(const QString &key); signals: void userSelected(const QModelIndex&); void currentUserChanged(const QModelIndex&); void userNotFound(const QString &userName); private: void insertUserEntry(int row); void removeUserEntry(int row); void moveUserEntry(int from,int to); void moveCurrentToCenter(int row); void leftToRight(); void leftToLeft(); void rightToRight(); void rightToLeft(); void centerToleft(); void centerToRight(); void moveAnimation(UserEntry *entry,QRect preRect,QRect nextRect); /*左方向建按下,isChoosed代表下个用户是否为选中用户,如果不是,则不触发用户改变 信号,只进行头像的动画效果。 */ void leftKeyPressed(bool isChoosed); void rightKeyPressed(bool isChoosed); private: QAbstractListModel *usersModel; int currentUser; SecurityUser *secUser; QTime lasttime; QTime mouseClickLast; QPushButton *prevArrow; QPushButton *nextArrow; int leftCount; int rightCount; }; #endif // USERSVIEW_H ukui-greeter/ukui-greeter/greeter/proxymodel.h0000644000175000017500000000410714204635151020531 0ustar fengfeng/* proxymodel.h * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 PROXYMODEL_H #define PROXYMODEL_H #include #include #include /* 扩展QAbstractListModel, 用代理的方式实现在固定的model中增加额外的数据 */ class ProxyModel : public QAbstractListModel { Q_OBJECT public: ProxyModel(QObject *parent = 0); QVariant data(const QModelIndex &index, int role) const; int rowCount(const QModelIndex &parent = QModelIndex()) const; QHash roleNames() const; void setSourceModel(QAbstractListModel *sourceModel); QStandardItemModel* extraRowModel(); public slots: void onSourceDataChanged ( const QModelIndex & topLeft, const QModelIndex & bottomRight ); void onSourceRowsInserted ( const QModelIndex & parent, int start, int end ); void onSourceRowsRemoved ( const QModelIndex & parent, int start, int end ); void onExtraDataChanged ( const QModelIndex & topLeft, const QModelIndex & bottomRight ); void onExtraRowsInserted ( const QModelIndex & parent, int start, int end ); void onExtraRowsRemoved ( const QModelIndex & parent, int start, int end ); private: // QWeakPointer m_model; QAbstractListModel *m_model; QStandardItemModel *m_extraModel; int sourceRowCount() const; }; #endif // PROXYMODEL_H ukui-greeter/ukui-greeter/greeter/proxymodel.cpp0000644000175000017500000001076414204635151021072 0ustar fengfeng/* proxymodel.cpp * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "proxymodel.h" #include ProxyModel::ProxyModel(QObject *parent) : QAbstractListModel(parent), m_model(nullptr), m_extraModel(new QStandardItemModel(this)) { connect(m_extraModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)), this, SLOT(onExtraRowsInserted(const QModelIndex&, int, int))); connect(m_extraModel, SIGNAL(rowsRemoved(const QModelIndex&, int, int)), this, SLOT(onExtraRowsRemoved(const QModelIndex&, int, int))); connect(m_extraModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), this, SLOT(onExtraDataChanged(const QModelIndex&, const QModelIndex&))); } QVariant ProxyModel::data(const QModelIndex &index, int role) const { if(index.row() < sourceRowCount()) return m_model->index(index.row(), 0).data(role); else return m_extraModel->index(index.row() - sourceRowCount(), 0).data(role); } int ProxyModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return sourceRowCount() + m_extraModel->rowCount(); } QHash ProxyModel::roleNames() const { return m_model == nullptr ? QHash() : m_model->roleNames(); } QStandardItemModel* ProxyModel::extraRowModel() { return m_extraModel; } void ProxyModel::setSourceModel(QAbstractListModel *sourceModel) { if(m_model) { disconnect(m_model, SIGNAL(rowsInserted(const QModelIndex&, int, int)), this, SLOT(onSourceRowsInserted(const QModelIndex&, int, int))); disconnect(m_model, SIGNAL(rowsRemoved(const QModelIndex&, int, int)), this, SLOT(onSourceRowsRemoved(const QModelIndex&, int, int))); disconnect(m_model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(onSourceDataChanged(const QModelIndex&, const QModelIndex&))); } // m_model = QWeakPointer(sourceModel); m_model = sourceModel; // reset(); beginResetModel(); endResetModel(); // setRoleNames(m_model->roleNames()); connect(m_model, SIGNAL(rowsInserted(const QModelIndex&, int, int)), this, SLOT(onSourceRowsInserted(const QModelIndex&, int, int))); connect(m_model, SIGNAL(rowsRemoved(const QModelIndex&, int, int)), this, SLOT(onSourceRowsRemoved(const QModelIndex&, int, int))); connect(m_model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(onSourceDataChanged(const QModelIndex&, const QModelIndex&))); } int ProxyModel::sourceRowCount() const { return m_model == nullptr ? 0 : m_model->rowCount(); } void ProxyModel::onSourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { dataChanged(createIndex(topLeft.row(), 0), createIndex(bottomRight.row(), 0)); } void ProxyModel::onSourceRowsInserted(const QModelIndex &parent, int start, int end) { Q_UNUSED(parent); beginInsertRows(parent, start, end); endInsertRows(); } void ProxyModel::onSourceRowsRemoved(const QModelIndex &parent, int start, int end) { Q_UNUSED(parent); beginRemoveRows(parent, start, end); endRemoveRows(); } void ProxyModel::onExtraDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { dataChanged(createIndex(sourceRowCount() + topLeft.row(), 0), createIndex(sourceRowCount() + bottomRight.row(), 0)); } void ProxyModel::onExtraRowsInserted(const QModelIndex &parent, int start, int end) { Q_UNUSED(parent); beginInsertRows(parent, sourceRowCount() + start, sourceRowCount() + end); endInsertRows(); } void ProxyModel::onExtraRowsRemoved(const QModelIndex &parent, int start, int end) { Q_UNUSED(parent); beginRemoveRows(parent, sourceRowCount() + start, sourceRowCount() + end); endRemoveRows(); } ukui-greeter/ukui-greeter/greeter/loginwindow.h0000644000175000017500000001431014204635501020663 0ustar fengfeng/* loginwindow.h * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 LOGINWINDOW_H #define LOGINWINDOW_H #include #include #include #include #include "biometricdeviceinfo.h" #include "pam-tally.h" class QTimer; class QLabel; class QPushButton; class QListWidget; class GreeterWrapper; class IconEdit; class BiometricProxy; class BiometricAuthWidget; class BiometricDevicesWidget; class LoginWindow : public QWidget { Q_OBJECT public: explicit LoginWindow(GreeterWrapper *greeter, QWidget *parent = 0); ~LoginWindow(){} bool setUserIndex(const QModelIndex& index); void setGreeter(GreeterWrapper *greeter); void setUserName(const QString& userName); QString getUserName(); QString getRealName(); void setFace(const QString& faceFile); void setLoggedIn(bool isLoggedIn); void setPrompt(const QString& text); void setChildrenGeometry(); QString getPassword(); void reset(); bool isloginauth; void setDirLogin(); bool isinput_passwd; protected: void showEvent(QShowEvent *); void resizeEvent(QResizeEvent *); void keyReleaseEvent(QKeyEvent *event); signals: /** * @brief 手动输入用户名 * @param userName 用户名 */ void userChangedByManual(const QString &userName); public slots: void onShowMessage(QString text, QLightDM::Greeter::MessageType type); void onShowPrompt(QString text, QLightDM::Greeter::PromptType type); void onAuthenticationComplete(); void setUserNotInView(const QString &userName); void pamBioSuccess(); private slots: void onLogin(const QString &str); void onBiometricButtonClicked(); void onPasswordButtonClicked(); void onOtherDevicesButtonClicked(); void onRetryButtonClicked(); void onDeviceChanged(const DeviceInfoPtr &deviceInfo); void onBiometricAuthComplete(bool result); void onMessageButtonClicked(); void onPrepareForSleep(bool sleep); private: void initUI(); void initBiometricWidget(); void initBiometricButtonWidget(); void setBiometricWidgetGeometry(); void setBiometricButtonWidgetGeometry(); void startAuthentication(); void startWaiting(); void stopWaiting(); void clearMessage(); void performBiometricAuth(); void skipBiometricAuth(); void showPasswordAuthWidget(); void showBiometricAuthWidget(); void showBiometricDeviceWidget(); QPixmap DrawRound(QPixmap &src, int radius); QPixmap PixmapToRound(const QPixmap &src, int radius); void show_authenticated (bool successful = true); void refreshTranslate(); void startBioAuth(); void restartBioAuth(); void waitBiometricServiceStatus(); void root_unlock_countdown(); void unlock_countdown(); private: GreeterWrapper *m_greeter; QString m_name; //m_nameLabel显示的是全名(显示的),m_name保存的是真名(用于登录的) qint32 m_uid; //用户id //手动输入用户标记,设置该标记的原因是判断是不是手动输入用户, //如果输入了无法登录的用户,就会一直输出密码错误信息 bool isManual = false; //密码错误标记,设置该标志的原因是,在有生物识别模块的情况下用户选择了密码登录,输入了错误的密码, //此时应该直接进入密码登录,而不是再次进入登录生物识别设备选择界面 // bool isPasswordError; enum AuthMode { PASSWORD, BIOMETRIC, UNKNOWN }; AuthMode authMode = UNKNOWN; // 生物识别认证 int m_deviceCount = -1; int m_featureCount; QString m_deviceName; DeviceInfoPtr m_deviceInfo; BiometricProxy *m_biometricProxy = nullptr; BiometricAuthWidget *m_biometricAuthWidget = nullptr; BiometricDevicesWidget *m_biometricDevicesWidget = nullptr; QWidget *m_buttonsWidget = nullptr; QPushButton *m_biometricButton = nullptr; QPushButton *m_passwordButton = nullptr; QPushButton *m_otherDeviceButton = nullptr; QPushButton *m_retryButton = nullptr; // UI QWidget *m_userWidget = nullptr; //放置用户信息Label QLabel *m_faceLabel = nullptr; //头像 QLabel *m_nameLabel = nullptr; //用户名 QLabel *m_isLoginLabel = nullptr; //提示是否已登录 QWidget *m_passwdWidget; //放置密码输入框和信息列表 IconEdit *m_passwordEdit; //密码输入框 QLabel *m_messageLabel; //PAM消息显示 QPushButton *m_messageButton; QTimer *m_bioTimer = nullptr; bool isretry = true; bool prompted = false; bool unacknowledged_messages = false; bool m_name_is_login = false; bool isauto_switch = false; bool direct_login = false; bool isChooseDev = false; bool useDoubleAuth = false; bool isBioSuccess = false; bool manualStopBio = false; bool useFirstDevice = false; QMap m_failMap; int maxFailedTimes; bool isHiddenSwitchButton; QString manualLoginName; QString m_face; QString m_preStrMessage; int m_preStrMessageType; bool isLockingFlg; //判断当前是否正在锁定倒计时 QTimer *m_timer; }; #endif // LOGINWINDOW_H ukui-greeter/ukui-greeter/greeter/pam-tally.c0000644000175000017500000002330514204635501020222 0ustar fengfeng/* * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * **/ #include "pam-tally.h" #include #include #include #include #include #include #include #include #include char shm_tally_real[128]; #define FILE_MODE (S_IRUSR | S_IWUSR) #define CONFIG_FILE "/usr/share/lightdm/lightdm.conf.d/96-kylin-setting.conf" struct tallylog { char fail_line[52]; /* rhost or tty of last failure */ uint16_t reserved; /* reserved for future use */ uint16_t fail_cnt; /* failures since last success */ uint64_t fail_time; /* time of last failure */ }; static int get_is_open_other_authentication() { char buf[128]; FILE *config_file; if( (config_file = fopen(CONFIG_FILE, "r")) == NULL) { //gs_debug("open %s failed", CONFIG_FILE); return 0; } int open_other_authentication = 0; while(fgets(buf, sizeof(buf), config_file)) { if(strlen(buf) == 0 || buf[0] == '#') { memset(buf, sizeof(buf), 0); continue; } if(buf[strlen(buf)-1] == '\n') buf[strlen(buf)-1] = '\0'; char *p = strchr(buf, '='); if(!p) continue; *p = '\0'; size_t len = strlen(buf); if(len == 0) continue; //去掉=之前的空格 while(len--) if(buf[len] == ' ' || buf[len] == '\t') buf[len] = '\0'; if(strcmp(buf, "open-other-authentication") != 0) continue; p++; len = strlen(p); if(len == 0) break; //去掉等号之后的空格 while(*p == ' ' || *p == '\t') { p++; len--; } //去掉尾部空格 while(len--) if(*(p+len) == ' ' || *(p+len) == '\t') *(p+len) = '\0'; if(*p == '0') break; if(*p == '1') { open_other_authentication = 1; break; } } fclose(config_file); return open_other_authentication; } static int get_pam_tally(int *deny, int *unlock_time , int *root_unlock_time) { char buf[128]; FILE *auth_file; if( (auth_file = fopen("/etc/pam.d/common-auth", "r")) == NULL) return -1; while(fgets(buf, sizeof(buf), auth_file)) { if(strlen(buf) == 0 || buf[0] == '#') continue; if(!strstr(buf, "deny")) continue; char *ptr = strtok(buf, " \t"); while(ptr) { if(strncmp(ptr, "deny=", 5)==0){ sscanf(ptr, "deny=%d", deny); // fprintf(stderr, "-------------------- deny=%d\n", *deny); } if(strncmp(ptr, "unlock_time=", 12)==0){ sscanf(ptr, "unlock_time=%d", unlock_time); // fprintf(stderr, "-------------------- unlock_time=%d\n", *unlock_time); } if(strncmp(ptr, "root_unlock_time=", 17)==0){ sscanf(ptr, "root_unlock_time=%d", root_unlock_time); // fprintf(stderr, "-------------------- root_unlock_time=%d\n", *root_unlock_time); } ptr = strtok(NULL, " \t"); } return 1; } return 0; } static void set_shm_tally_real() { sprintf(shm_tally_real, "%s_%d", SHM_TALLY, getuid()); } int pam_tally_init() { int fd; int deny = 0, unlock_time = 0 , root_unlock_time = 0; pam_tally *tally_ptr; set_shm_tally_real(); printf("shm path =========== : %s\n", shm_tally_real); shm_unlink(shm_tally_real); if(get_is_open_other_authentication()) { return 0; } if(!get_pam_tally(&deny, &unlock_time,&root_unlock_time)) { return 0; } if(deny <= 0) deny = 0; if(unlock_time <= 0) unlock_time = 0; if( (fd = shm_open(shm_tally_real, O_RDWR | O_CREAT, FILE_MODE)) == -1) { printf("shm_open error: %s\n", strerror(errno)); return -1; } ftruncate(fd, sizeof(pam_tally)); if( (tally_ptr = mmap(NULL, sizeof(pam_tally), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) { close(fd); return -1; } close(fd); tally_ptr->deny = deny; tally_ptr->unlock_time = unlock_time; tally_ptr->failed = 0; tally_ptr->lock_start_time = 0; tally_ptr->root_unlock_time = root_unlock_time; return 1; } static pam_tally* pam_tally_memory() { int fd; pam_tally *tally_ptr; set_shm_tally_real(); if( (fd = shm_open(shm_tally_real, O_RDWR, FILE_MODE)) == -1) { return NULL; } if( (tally_ptr = mmap(NULL, sizeof(pam_tally), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) { close(fd); return NULL; } close(fd); return tally_ptr; } int pam_tally_is_enbled() { int fd; set_shm_tally_real(); if( (fd = shm_open(shm_tally_real, O_RDONLY, FILE_MODE)) == -1) { printf("shm_open error: %s\n", strerror(errno)); close(fd); return 0; } pam_tally *tally_ptr; if((tally_ptr = pam_tally_memory()) == NULL){ close(fd); return -1; } if(tally_ptr->deny == 0 || tally_ptr->unlock_time == 0){ close(fd); return 0; } close(fd); return 1; } int pam_tally_add_failed() { pam_tally *tally_ptr; if((tally_ptr = pam_tally_memory()) == NULL) return -1; tally_ptr->failed++; //如果失败次数达到上限,开始计时 if(tally_ptr->failed >= tally_ptr->deny) tally_ptr->lock_start_time = time(NULL); return 0; } int pam_tally_clear_failed() { pam_tally *tally_ptr; if((tally_ptr = pam_tally_memory()) == NULL) return -1; tally_ptr->failed = 0; tally_ptr->lock_start_time = 0; return 0; } int pam_tally_failure_is_out() { pam_tally *tally_ptr; if((tally_ptr = pam_tally_memory()) == NULL) return -1; return (tally_ptr->failed >= tally_ptr->deny ? 1 : 0); } int pam_tally_deny() { pam_tally *tally_ptr; if((tally_ptr = pam_tally_memory()) == NULL) return -1; return tally_ptr->deny; } int pam_tally_failed_count() { pam_tally *tally_ptr; if((tally_ptr = pam_tally_memory()) == NULL) return -1; return tally_ptr->failed; } int pam_tally_unlock_time() { pam_tally *tally_ptr; if((tally_ptr = pam_tally_memory()) == NULL) return -1; printf("########################### unlock time = %d\n", tally_ptr->unlock_time); return tally_ptr->unlock_time; } int pam_tally_is_canUnlock() { pam_tally *tally_ptr; if((tally_ptr = pam_tally_memory()) == NULL) return -1; if(tally_ptr->failed >= tally_ptr->deny && time(NULL) - tally_ptr->lock_start_time < tally_ptr->unlock_time) return 0; return 1; } int pam_modutil_read(int fd, char *buffer, int count) { int block, offset = 0; while (count > 0) { block = read(fd, &buffer[offset], count); if (block < 0) { if (errno == EINTR) continue; return block; } if (block == 0) return offset; offset += block; count -= block; } return offset; } void get_tally(uid_t uid, int *tfile, struct tallylog *tally) { char filename[50]={0}; sprintf(filename,"%s","/tmp/.tallylog"); void *void_tally = tally; if ((*tfile = open(filename, O_RDONLY)) == -1){ fprintf(stderr, "open tallylog failed:%s \n", strerror(errno)); return ; } if (lseek(*tfile, (off_t)uid*(off_t)sizeof(*tally), SEEK_SET) == (off_t)-1) { fprintf(stderr, "lseek tallylog failed \n"); close(*tfile); return ; } if (pam_modutil_read(*tfile, void_tally, sizeof(*tally)) != sizeof(*tally)) { fprintf(stderr, "read tallylog failed \n"); memset(tally, 0, sizeof(*tally)); } close(*tfile); tally->fail_line[sizeof(tally->fail_line)-1] = '\0'; } int pam_tally_unlock_time_left(uid_t userID, int *fail_cnt,int *left_time, int *deny, int *fail_time, int *unlock_time1) { pam_tally *tally_ptr; if((tally_ptr = pam_tally_memory()) == NULL) return 0; int unlock_time = tally_ptr->unlock_time; *deny = tally_ptr->deny; if(unlock_time == 0) return 0; int tfile = -1; struct tallylog tally; tally.reserved = 0; tally.fail_cnt = 0; tally.fail_time = 0; get_tally(userID,&tfile,&tally); if(tally.fail_cnt<*deny) return 0; //连续输错,时间累加 if (tally.fail_cnt >= (*deny)*2 && tally.fail_cnt < (*deny)*3){ unlock_time = unlock_time*2; } else if(tally.fail_cnt >= (*deny)*3 && tally.fail_cnt < (*deny)*4){ unlock_time = unlock_time*8; } else if (tally.fail_cnt >= (*deny)*4){ *fail_cnt = 0xFFFF;//永久锁定 return 0; } int unlock_time_left = unlock_time - ((long)time(NULL) - tally.fail_time); // fprintf(stderr, "unlock_time:%d, (long)time(NULL):%d, tally.fail_time:%d\n",unlock_time,(long)time(NULL),tally.fail_time); *fail_time = tally.fail_time; *unlock_time1 = unlock_time; *left_time = unlock_time_left > 0 ? unlock_time_left : 0; *fail_cnt = tally.fail_cnt; return *left_time; } int pam_tally_root_unlock_time_left(uid_t userID, int *fail_cnt,int *left_time, int *deny1) { pam_tally *tally_ptr; if((tally_ptr = pam_tally_memory()) == NULL){ return 0; } int root_unlock_time = tally_ptr->root_unlock_time; int deny = tally_ptr->deny; if(root_unlock_time == 0) return 0; int tfile = -1; struct tallylog tally; tally.reserved = 0; tally.fail_cnt = 0; tally.fail_time = 0; get_tally(userID,&tfile,&tally); if(tally.fail_cnt= (deny)*2 && tally.fail_cnt < (deny)*3){ root_unlock_time = root_unlock_time*2; } else if(tally.fail_cnt >= (deny)*3 && tally.fail_cnt < (deny)*4){ root_unlock_time = root_unlock_time*8; } else if (tally.fail_cnt >= (deny)*4){ *fail_cnt = 0xFFFF;//永久锁定 return 0; } int root_unlock_time_left = root_unlock_time - ((long)time(NULL) - tally.fail_time); *left_time = root_unlock_time_left > 0 ? root_unlock_time_left : 0; *fail_cnt = tally.fail_cnt; return *left_time; } ukui-greeter/ukui-greeter/greeter/pam-tally.h0000644000175000017500000000342714204635501020232 0ustar fengfeng/* * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * **/ #ifndef PAM_TALLY_H #define PAM_TALLY_H #include #include #ifdef __cplusplus extern "C" { #endif #define SHM_TALLY "/shm_tally" struct _pam_tally { int deny; //失败次数上限 int unlock_time; //普通用户失败次数达到上限后,多少秒之后才能解锁 int root_unlock_time; //root用户失败次数达到上限后,多少秒之后才能解锁 int failed; //当前失败的次数 time_t lock_start_time; //失败次数达到上限后,开始计时 }; typedef struct _pam_tally pam_tally; int pam_tally_init(); int pam_tally_add_failed(); int pam_tally_clear_failed(); int pam_tally_falure_is_out(); int pam_tally_deny(); int pam_tally_failed_count(); int pam_tally_unlock_time(); int pam_tally_is_enbled(); int pam_tally_is_canUnlock(); int pam_tally_unlock_time_left(uid_t userID, int *fail_cnt, int *left_time, int *deny, int *fail_time, int *unlock_time); int pam_tally_root_unlock_time_left(uid_t userID, int *fail_cnt, int *left_time, int *deny); #ifdef __cplusplus } #endif #endif // PAM_TALLY_H ukui-greeter/ukui-greeter/greeter/securityuser.cpp0000644000175000017500000001016714204635151021433 0ustar fengfeng/* securityuser.cpp * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "securityuser.h" #define GREETER_SECURITY_CONFIG_PATH "/etc/ukui-greeter/ukui-greeter-security-config.json" SecurityUser* SecurityUser::instance_ = nullptr; SecurityUser::SecurityUser() :mode(NORMAL) { loadJsonFile(); } SecurityUser* SecurityUser::instance() { if(instance_ == nullptr) instance_ = new SecurityUser(); return instance_; } void SecurityUser::loadJsonFile() { QFile file(GREETER_SECURITY_CONFIG_PATH); if(file.exists() == false){ qDebug()<<"file is not exists"; return; } qDebug()<<"/etc/ukui-greeter/ukui-greeter-security-config.json is find!"; file.open(QIODevice::ReadOnly); QString value = file.readAll(); file.close(); QJsonParseError parseJsonErr; QJsonDocument document = QJsonDocument::fromJson(value.toUtf8(),&parseJsonErr); if(!(parseJsonErr.error == QJsonParseError::NoError)){ qWarning()<<"parser json file failed"; qWarning()< #include #include #include #include #include #include #include #include #include #include #include "greeterwrapper.h" #include "iconedit.h" #include "globalv.h" #include "configuration.h" #include "biometricproxy.h" #include "biometricauthwidget.h" #include "biometricdeviceswidget.h" #include #include #include #include #include #include #include "pam-tally.h" #define _(string) gettext(string) LoginWindow::LoginWindow(GreeterWrapper *greeter, QWidget *parent) : QWidget(parent), m_timer(nullptr), isLockingFlg(false), m_greeter(greeter) { initUI(); pam_tally_init(); // connect(m_greeter, SIGNAL(showMessage(QString,QLightDM::Greeter::MessageType)), this, SLOT(onShowMessage(QString,QLightDM::Greeter::MessageType))); connect(m_greeter, SIGNAL(showPrompt(QString,QLightDM::Greeter::PromptType)), this, SLOT(onShowPrompt(QString,QLightDM::Greeter::PromptType))); connect(m_greeter, SIGNAL(authenticationComplete()), this, SLOT(onAuthenticationComplete())); Configuration::instance()->saveLastLoginUser1(""); } QPixmap LoginWindow::PixmapToRound(const QPixmap &src, int radius) { if (src.isNull()) { return QPixmap(); } QPixmap pixmapa(src); QPixmap pixmap(radius*2,radius*2); pixmap.fill(Qt::transparent); QPainter painter(&pixmap); painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); QPainterPath path; path.addEllipse(0, 0, radius*2, radius*2); painter.setClipPath(path); painter.drawPixmap(0, 0, radius*2, radius*2, pixmapa); return pixmap; } void LoginWindow::initUI() { setFixedWidth(500); m_userWidget = new QWidget(this); m_userWidget->setObjectName(QStringLiteral("userWidget")); /* 头像 */ const QString SheetStyle = QString("border-radius: %1px; border:0px solid white;").arg(90*scale); m_faceLabel = new QLabel(m_userWidget); m_faceLabel->setObjectName(QStringLiteral("faceLabel")); m_faceLabel->setFocusPolicy(Qt::NoFocus); m_faceLabel->setStyleSheet(SheetStyle); m_faceLabel->setAlignment(Qt::AlignCenter); /* 用户名 */ m_nameLabel = new QLabel(m_userWidget); m_nameLabel->setObjectName(QStringLiteral("login_nameLabel")); m_nameLabel->setFocusPolicy(Qt::NoFocus); m_nameLabel->setAlignment(Qt::AlignCenter); /* 密码框所在窗口 */ m_passwdWidget = new QWidget(this); /* 密码框 */ m_passwordEdit = new IconEdit(m_passwdWidget); /* 密码认证信息显示列表 */ m_messageLabel = new QLabel(m_passwdWidget); m_messageButton = new QPushButton(m_passwdWidget); //QtConcurrent::run([=](){ m_passwdWidget->setObjectName(QStringLiteral("passwordWidget")); m_passwordEdit->setObjectName(QStringLiteral("passwordEdit")); m_messageLabel->setObjectName(QStringLiteral("messageLabel")); m_messageLabel->setAlignment(Qt::AlignCenter); m_messageButton->setObjectName(QStringLiteral("messageButton")); m_messageButton->hide(); m_passwordEdit->setIcon(QIcon(":/images/login-button.png")); m_passwordEdit->setFocusPolicy(Qt::StrongFocus); m_passwordEdit->installEventFilter(this); m_passwordEdit->hide(); //收到请求密码的prompt才显示出来 connect(m_passwordEdit, SIGNAL(clicked(const QString&)), this, SLOT(onLogin(const QString&))); setFocusProxy(m_passwordEdit); //}); isinput_passwd = false; } void LoginWindow::showEvent(QShowEvent *e) { QWidget::showEvent(e); if(!m_passwordEdit->isHidden()) { m_passwordEdit->setFocus(); } } void LoginWindow::resizeEvent(QResizeEvent *) { setChildrenGeometry(); } void LoginWindow::keyReleaseEvent(QKeyEvent *event) { // qDebug() << event->key(); if(event->key() == Qt::Key_Escape) { //退出设备选择 if(m_biometricDevicesWidget && m_biometricDevicesWidget->isVisible()) { if(authMode == BIOMETRIC) { showBiometricAuthWidget(); } else { showPasswordAuthWidget(); } return; } } QWidget::keyReleaseEvent(event); } void LoginWindow::refreshTranslate() { if(m_biometricButton){ m_biometricButton->setText(tr("Biometric Authentication")); } if(m_passwordButton){ m_passwordButton->setText(tr("Password Authentication")); } if(m_otherDeviceButton){ m_otherDeviceButton->setText(tr("Other Devices")); } if(m_retryButton){ m_retryButton->setText(tr("Retry")); } if(m_nameLabel){ if(m_nameLabel->text() == "Login" || m_nameLabel->text() == "登录") m_nameLabel->text() == tr("Login"); } } void LoginWindow::setChildrenGeometry() { // 用户信息显示位置 m_userWidget->setGeometry(0, 0, width(), 292*scale); const QString SheetStyle = QString("border-radius: %1px; border:0px solid white;").arg(90*scale); m_faceLabel->setStyleSheet(SheetStyle); setFace(m_face); m_faceLabel->setGeometry((width() - 180*scale) / 2 , 0, 180*scale, 180*scale); m_nameLabel->setGeometry(0, m_faceLabel->geometry().bottom() + 25*scale, width(), 40); if(scale < 0.5) setFixedWidth(360); else setFixedWidth(600); m_passwdWidget->setGeometry(0, m_nameLabel->geometry().bottom() + 45*scale, width(), 150); m_passwordEdit->setGeometry((m_passwdWidget->width() - 300)/2, 0, 300, 34); m_messageLabel->setGeometry((m_passwdWidget->width() - 600)/2, m_passwordEdit->geometry().bottom() + 25, 600, 25); m_messageButton->setGeometry((m_passwdWidget->width() - 200)/2, 0, 200, 40); setBiometricWidgetGeometry(); setBiometricButtonWidgetGeometry(); } /** * @brief LoginWindow::reset * 复原UI */ void LoginWindow::reset() { m_passwordEdit->clear(); m_passwordEdit->setType(QLineEdit::Password); m_messageButton->hide(); clearMessage(); showPasswordAuthWidget(); m_deviceCount = -1; authMode = UNKNOWN; m_deviceInfo = DeviceInfoPtr(); m_deviceName = ""; } void LoginWindow::clearMessage() { m_messageLabel->clear(); m_preStrMessage = ""; m_preStrMessageType = -1; } /** * @brief LoginWindow::setUserName * @param userName 用户名 * * 设置用户名 */ void LoginWindow::setUserName(const QString& name) { //m_nameLabel->setText(userName); if(m_nameLabel->text() == name) return ; QFont font; font.setPixelSize(24); QString str = ElideText(font,500,name); if(name != str) m_nameLabel->setToolTip(name); m_nameLabel->setText(str); m_nameLabel->setAlignment(Qt::AlignCenter); m_nameLabel->adjustSize(); m_nameLabel->move((width() - m_nameLabel->width())/2,m_faceLabel->y() + m_faceLabel->height() + 32); } /** * @brief LoginWindow::userName * @return 当前的用户名 * * 获取当前用户名 */ QString LoginWindow::getUserName() { if(m_nameLabel->text() == tr("Login")) return "Login"; return m_nameLabel->text(); } /** * @brief LoginWindow::getRealName * @return * 获取用户真名 */ QString LoginWindow::getRealName() { return m_name; } /** * @brief LoginWindow::setFace * @param faceFile 用户头像文件 * * 设置用户头像 */ void LoginWindow::setFace(const QString& facePath) { QFile faceFile(facePath); QPixmap faceImage; m_faceLabel->setFixedSize(180*scale,180*scale); //如果头像文件不存在,则使用默认头像 if(faceFile.exists()) faceImage = scaledPixmap(180*scale, 180*scale, facePath); else faceImage = scaledPixmap(180*scale, 180*scale, ":/images/default_face.png"); faceImage = PixmapToRound(faceImage,90*scale); m_faceLabel->setAlignment(Qt::AlignCenter); m_faceLabel->setPixmap(faceImage); m_face = facePath; } /** * @brief LoginWindow::setLoggedIn * @param isLoggedIn * * 设置当前用户是否已已经登录 */ void LoginWindow::setLoggedIn(bool isLoggedIn) { //m_isLoginLabel->setText(isLoggedIn ? tr("logged in") : ""); } /** * @brief LoginWindow::setPrompt * @param text * * 设置密码框的提示信息 */ void LoginWindow::setPrompt(const QString& text) { m_passwordEdit->setPrompt(text); } /** * @brief LoginWindow::password * @return * * 获取密码 */ QString LoginWindow::getPassword() { return m_passwordEdit->text(); } bool LoginWindow::setUserIndex(const QModelIndex& index) { isinput_passwd = false; if(!index.isValid()){ return false; } if(m_biometricAuthWidget){ m_biometricAuthWidget->stopAuth(); if(m_bioTimer && m_bioTimer->isActive()) m_bioTimer->stop(); } //设置用户名 QString name = index.data(QLightDM::UsersModel::RealNameRole).toString(); m_name = index.data(QLightDM::UsersModel::NameRole).toString(); setUserName(name.isEmpty() ? m_name : name); //设置头像 QString facePath = index.data(QLightDM::UsersModel::ImagePathRole).toString(); setFace(facePath); //显示是否已经登录 bool isLoggedIn = index.data(QLightDM::UsersModel::LoggedInRole).toBool(); setLoggedIn(isLoggedIn); //设置生物识别设备窗口的uid m_uid = index.data(QLightDM::UsersModel::UidRole).toInt(); setChildrenGeometry(); startAuthentication(); reset(); //设置用户名 return true; } /** * @brief 手动输入的用户名不在用户列表中(uid < 1000或者用户不存在) * @param userName */ void LoginWindow::setUserNotInView(const QString &userName) { isloginauth = false; m_name = userName; setUserName(userName); } void LoginWindow::startAuthentication() { prompted = false; unacknowledged_messages = false; isloginauth = false; //用户认证 if(m_name == "*guest") { //游客登录 qDebug() << "guest login"; m_passwordEdit->show(); setPrompt(tr("login")); } else if(m_name == "*login") { //手动输入用户名 m_greeter->authenticate(""); } else { qDebug() << "login: " << m_name; m_greeter->authenticate(m_name); } } /** * @brief LoginWindow::startWaiting * * 等待认证结果 */ void LoginWindow::startWaiting() { m_passwordEdit->startWaiting(); //等待认证结果期间不能再输入密码 } void LoginWindow::stopWaiting() { m_passwordEdit->stopWaiting(); } void LoginWindow::onLogin(const QString &str) { clearMessage(); unacknowledged_messages=false; isloginauth = false; qDebug()<authenticateAsGuest(); } else if(m_name == "*login") { //用户输入用户名 isloginauth = true; isManual = true; m_name_is_login = true; m_greeter->respond(str); manualLoginName = str; if (!isinput_passwd){ Q_EMIT userChangedByManual(str); isinput_passwd = true; } } else { //当密码长度超过1000多时,lightm会出异常,这里暂时先截断 if(str.length()>1000) m_greeter->respond(str.mid(0,1000)); else m_greeter->respond(str); startWaiting(); m_passwordEdit->setEnabled(false); } } void LoginWindow::onShowPrompt(QString text, QLightDM::Greeter::PromptType type) { qDebug()<< "prompt: "<< text; if(text == BIOMETRIC_PAM) { if(authMode == PASSWORD) { skipBiometricAuth(); } else { performBiometricAuth(); } }else if(text == BIOMETRIC_PAM_DOUBLE) { if(isBioSuccess){ m_greeter->respond(BIOMETRIC_SUCCESS); return ; } useDoubleAuth = true; if(authMode == PASSWORD) { skipBiometricAuth(); } else { performBiometricAuth(); } } else { showPasswordAuthWidget(); m_name_is_login = false; qDebug()<<"m_name_is_login = false"; stopWaiting(); if(!text.isEmpty()){ m_passwordEdit->setEnabled(true); m_passwordEdit->show(); } m_passwordEdit->setFocus(); if(type != QLightDM::Greeter::PromptTypeSecret){ isauto_switch = true; m_passwordEdit->setType(QLineEdit::Normal); } else if(isauto_switch) { m_passwordEdit->setType(QLineEdit::Password); isauto_switch = false; } prompted = true; unacknowledged_messages = false; m_preStrMessage = ""; qDebug()<<"unacknowledged_messages = false"; if(text == "Password: "||text == "密码:"){ if(useDoubleAuth){ if(!m_failMap.contains(m_uid) || m_failMap[m_uid] < maxFailedTimes) onShowMessage(tr("Please enter your password or enroll your fingerprint "), QLightDM::Greeter::MessageTypeInfo); } text = tr("Password: "); } if(text == "login:") { text = tr("Username"); m_name = "*login"; //m_nameLabel->setText(tr("login")); m_name_is_login = true; qDebug()<<"m_name_is_login = true"; } m_passwordEdit->clear(); setPrompt(text); } } void LoginWindow::onShowMessage(QString text, QLightDM::Greeter::MessageType type) { Q_UNUSED(type); QString strDisplay = "";//显示的pam返回字符串 qDebug()<< "message: "<< text<<"type = "<setInterval(400); connect(m_timer, &QTimer::timeout, this, &LoginWindow::unlock_countdown); } m_timer->start(); return; } else if (text.indexOf("No password received, please input password") != -1){//空密码 m_messageLabel->setText(tr("Password cannot be empty")); m_messageLabel->setToolTip(tr("Password cannot be empty")); return; } if(text == "密码为空,请输入密码" && qgetenv("LANGUAGE") == "en_US") text = "No password received, please input password"; unacknowledged_messages = true; if (!m_preStrMessage.isEmpty() && m_preStrMessageType != type && m_preStrMessageType != -1) { text = m_preStrMessage + "," + text; } // text = m_preStrMessage + "\n" + text; qDebug()<<"unacknowledged_messages = true"; // std::string texttmp = text.toStdString(); // const char* ch = texttmp.c_str(); // char str[1024]; // sprintf(str,"%s",_(ch)); strDisplay = text; //设置完整字符串的tooltip m_messageLabel->setToolTip(text); QFontMetrics font(m_messageLabel->font()); int font_size = font.width(strDisplay); //当字符宽度大于75,不再显示完整信息 if(font_size > 75) { //返回字符串末尾带省略号的字符串 strDisplay = font.elidedText(text, Qt::ElideRight, 600); } m_messageLabel->setText(strDisplay); m_preStrMessage = text; m_preStrMessageType = type; stopWaiting(); } void LoginWindow::onAuthenticationComplete() { stopWaiting(); if(m_biometricAuthWidget){ m_biometricAuthWidget->stopAuth(); } if(m_greeter->isAuthenticated()) { // 认证成功,启动session qDebug()<< "authentication success"; if((prompted && !unacknowledged_messages )||direct_login){ direct_login = false; Configuration::instance()->saveLastLoginUser(m_name); Configuration::instance()->saveLastLoginUser1(m_name); m_greeter->startSession(); } else { qDebug()<<"prompted = "<clear(); //如果用户输入了不存在的用户名导致的认证失败,让用户重新输入用户名 if(isManual){ if(m_name_is_login){ m_name_is_login = false; m_name = "*login"; } // qDebug() << "authentication failed--isManual"; if(!unacknowledged_messages && !isLockingFlg) onShowMessage(tr("Authentication failure, Please try again"), QLightDM::Greeter::MessageTypeError); } else{ if(!unacknowledged_messages && !isLockingFlg) onShowMessage(tr("Authentication failure, Please try again"), QLightDM::Greeter::MessageTypeError); if(useDoubleAuth){ authMode = BIOMETRIC; //qDebug() << "authentication failed--useDoubleAuth"; } else{ //qDebug() << "authentication failed--else"; authMode = PASSWORD; } } isinput_passwd = false; isManual = false; startAuthentication(); } else { show_authenticated (false); } } } void LoginWindow::show_authenticated(bool successful) { m_passwordEdit->hide(); m_messageButton->show(); m_messageButton->setFocus(); m_messageButton->setDefault(true); connect(m_messageButton, &QPushButton::clicked, this, &LoginWindow::onMessageButtonClicked); if(successful) { isretry = false; m_messageButton->setText(tr("Login")); } else { isretry = true; m_messageButton->setText(tr("Retry")); } } void LoginWindow::onMessageButtonClicked() { m_messageButton->setDefault(false); if(!isretry) { m_messageButton->hide(); Configuration::instance()->saveLastLoginUser(m_name); Configuration::instance()->saveLastLoginUser1(m_name); m_greeter->startSession(); } else { m_messageButton->hide(); authMode = PASSWORD; m_messageLabel->setText(""); startAuthentication(); } } void LoginWindow::restartBioAuth() { startBioAuth(); } void LoginWindow::onPrepareForSleep(bool sleep) { ///系统休眠时,会关闭总线,导致设备不可用,发生错误 ///在系统休眠之前停止认证,在系统唤醒后重新开始认证 if(sleep) { if(useDoubleAuth){ if(m_biometricAuthWidget){ manualStopBio = true; m_biometricAuthWidget->stopAuth(); if(m_bioTimer && m_bioTimer->isActive()) m_bioTimer->stop(); } } } else { if(useDoubleAuth){ manualStopBio = false; restartBioAuth(); } } } void LoginWindow::setDirLogin() { direct_login = true; } void LoginWindow::pamBioSuccess() { m_biometricAuthWidget->startAuth(m_deviceInfo, m_uid); onShowMessage(tr("Please enter your password or enroll your fingerprint "), QLightDM::Greeter::MessageTypeInfo); m_bioTimer->stop(); } void LoginWindow::startBioAuth() { if(!m_bioTimer){ m_bioTimer = new QTimer(this); connect(m_bioTimer, SIGNAL(timeout()), this, SLOT(pamBioSuccess())); } m_bioTimer->start(1000); } void LoginWindow::waitBiometricServiceStatus() { qDebug()<<"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~` get biometric status"; QDBusInterface iface("org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager",QDBusConnection::systemBus()); QDBusReply bioPath = iface.call("GetUnit","biometric-authentication.service"); if(!bioPath.isValid()){ return ; } QDBusInterface bioface("org.freedesktop.systemd1", bioPath.value().path(), "org.freedesktop.DBus.Properties", QDBusConnection::systemBus()); QDBusReply sessionReply = bioface.call("Get", "org.freedesktop.systemd1.Unit", "UnitFileState"); if(!sessionReply.isValid()) qWarning() << sessionReply.error(); else { QString res = sessionReply.value().variant().toString(); if(res == "disable") return; } qDebug()<<"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ get activeState"; int times = 0; while(times<20){ QDBusReply sessionReply = bioface.call("Get", "org.freedesktop.systemd1.Unit", "ActiveState"); if(!sessionReply.isValid()){ qWarning() << sessionReply.error(); return ; } else { QString res = sessionReply.value().variant().toString(); if(res == "activating"){ times ++; usleep(100000); }else{ break; } } } qDebug()<<"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ times = "<=maxFailedTimes) { useDoubleAuth = false; skipBiometricAuth(); return; } //服务没启动,或者打开DBus连接出错 if(!m_biometricProxy->isValid()) { qWarning() << "An error occurs when connect to the biometric DBus"; useDoubleAuth = false; skipBiometricAuth(); return; } //初始化设备数量 if(m_deviceCount < 0) { m_deviceCount = m_biometricProxy->GetUserDevCount(m_uid); } //没有可用设备,不启用生物识别认证 if(m_deviceCount < 1) { useDoubleAuth = false; qWarning() << "No available devices"; skipBiometricAuth(); return; } //初始化生物识别认证UI if(!useDoubleAuth) initBiometricButtonWidget(); initBiometricWidget(); if(!useDoubleAuth) clearMessage(); //获取默认设备 if(m_deviceName.isEmpty()) { if(m_name == "*login") m_deviceName = GetDefaultDevice(manualLoginName); else m_deviceName = GetDefaultDevice(m_name); } qDebug() << m_deviceName; // qDebug() << (m_deviceInfo ? m_deviceInfo->shortName : ""); //如果默认设备为空的话,第一次不启动生物识别认证 if(m_deviceName.isEmpty() && !m_deviceInfo) { qDebug() << "No default device"; if(m_deviceCount == 1 && useFirstDevice == true){ DeviceList deviceList = m_biometricProxy->GetDevList(); m_deviceInfo = deviceList.at(0); }else{ useDoubleAuth = false; skipBiometricAuth(); return; } } //第一次,获取默认设备的设备信息,之后使用的则是从设备选择窗口传出的设备信息 if(!m_deviceInfo) { m_deviceInfo = m_biometricDevicesWidget->findDeviceByName(m_deviceName); } if(!m_deviceInfo) { qDebug() << "Device Not found: " << m_deviceName; useDoubleAuth = false; skipBiometricAuth(); return; } if(useDoubleAuth){ startBioAuth(); skipBiometricAuth(); return ; } authMode = BIOMETRIC; m_biometricAuthWidget->startAuth(m_deviceInfo, m_uid); showBiometricAuthWidget(); } void LoginWindow::skipBiometricAuth() { m_greeter->respond(BIOMETRIC_IGNORE); showPasswordAuthWidget(); m_passwdWidget->show(); } void LoginWindow::initBiometricWidget() { if(m_biometricAuthWidget) { m_biometricDevicesWidget->setUser(m_uid); return; } m_biometricAuthWidget = new BiometricAuthWidget(m_biometricProxy, this); connect(m_biometricAuthWidget, &BiometricAuthWidget::authComplete, this, &LoginWindow::onBiometricAuthComplete); m_biometricDevicesWidget = new BiometricDevicesWidget(m_biometricProxy,m_uid, this); connect(m_biometricDevicesWidget, &BiometricDevicesWidget::deviceChanged, this, &LoginWindow::onDeviceChanged); setBiometricWidgetGeometry(); } void LoginWindow::initBiometricButtonWidget() { if(m_buttonsWidget) { m_buttonsWidget->setVisible(true); return; } m_buttonsWidget = new QWidget(this); m_buttonsWidget->setObjectName(QStringLiteral("buttonsWidget")); m_buttonsWidget->setFixedHeight(38); QSizePolicy sizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding); sizePolicy.setHorizontalStretch(0); sizePolicy.setVerticalStretch(0); m_biometricButton = new QPushButton(m_buttonsWidget); m_biometricButton->setObjectName(QStringLiteral("biometricButton")); m_biometricButton->setIcon(QIcon(":/images/bio-login.png")); m_biometricButton->setText(tr("Biometric Authentication")); m_biometricButton->setSizePolicy(sizePolicy); m_biometricButton->setVisible(false); m_biometricButton->setCursor(Qt::PointingHandCursor); QFontMetrics fm(m_biometricButton->font(), m_biometricButton); int width = fm.width(m_biometricButton->text()); m_biometricButton->setMaximumWidth(std::max(294, 190)); connect(m_biometricButton, &QPushButton::clicked, this, &LoginWindow::onBiometricButtonClicked); m_passwordButton = new QPushButton(m_buttonsWidget); m_passwordButton->setObjectName(QStringLiteral("passwordButton")); m_passwordButton->setIcon(QIcon(":/images/password-login.png")); m_passwordButton->setText(tr("Password Authentication")); fm = QFontMetrics(m_passwordButton->font(), m_passwordButton); width = fm.width(m_passwordButton->text()); m_passwordButton->setMaximumWidth(std::max(294, 140)); m_passwordButton->setSizePolicy(sizePolicy); m_passwordButton->setVisible(false); m_passwordButton->setCursor(Qt::PointingHandCursor); connect(m_passwordButton, &QPushButton::clicked, this, &LoginWindow::onPasswordButtonClicked); m_otherDeviceButton = new QPushButton(m_buttonsWidget); m_otherDeviceButton->setObjectName(QStringLiteral("otherDeviceButton")); m_otherDeviceButton->setText(tr("Other Devices")); m_otherDeviceButton->setSizePolicy(sizePolicy); m_otherDeviceButton->setMaximumWidth(std::max(width + 50, 140)); m_otherDeviceButton->setVisible(false); m_otherDeviceButton->setCursor(Qt::PointingHandCursor); connect(m_otherDeviceButton, &QPushButton::clicked, this, &LoginWindow::onOtherDevicesButtonClicked); m_retryButton = new QPushButton(m_buttonsWidget); m_retryButton->setObjectName(QStringLiteral("retryButton")); m_retryButton->setText(tr("Retry")); m_retryButton->setSizePolicy(sizePolicy); m_retryButton->setMaximumWidth(110); m_retryButton->setVisible(false); m_retryButton->setCursor(Qt::PointingHandCursor); connect(m_retryButton, &QPushButton::clicked, this, &LoginWindow::onRetryButtonClicked); QGridLayout *layout = new QGridLayout(); layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(m_biometricButton, 0, 0); layout->addWidget(m_passwordButton, 0, 1); layout->addWidget(m_otherDeviceButton, 0, 2); layout->addWidget(m_retryButton, 0, 3); m_buttonsWidget->setLayout(layout); m_buttonsWidget->show(); setBiometricButtonWidgetGeometry(); } void LoginWindow::setBiometricWidgetGeometry() { //生物识别 if(m_biometricAuthWidget) { if(scale > 0 && scale < 1) m_biometricAuthWidget->setMinImage(scale); m_biometricAuthWidget->setGeometry(0, m_userWidget->geometry().bottom(), width(), m_biometricAuthWidget->height()); } if(m_biometricDevicesWidget) { m_biometricDevicesWidget->setGeometry((width() - m_biometricDevicesWidget->width()) / 2, 0, m_biometricDevicesWidget->width(), m_biometricDevicesWidget->height()); } } void LoginWindow::setBiometricButtonWidgetGeometry() { if(m_buttonsWidget) { if(scale > 0.5) m_buttonsWidget->setGeometry(0, height() - 100*scale - m_buttonsWidget->height(), width(), m_buttonsWidget->height()); else m_buttonsWidget->setGeometry(0, height() - 20 - m_buttonsWidget->height(), width(), m_buttonsWidget->height()); } } void LoginWindow::onDeviceChanged(const DeviceInfoPtr &deviceInfo) { qDebug() << "device changed: " << *deviceInfo; m_deviceInfo = deviceInfo; if(authMode != BIOMETRIC){ authMode = BIOMETRIC; startAuthentication(); } else m_biometricAuthWidget->startAuth(m_deviceInfo, m_uid); showBiometricAuthWidget(); } void LoginWindow::onBiometricAuthComplete(bool result) { if(!result) { if(useDoubleAuth){ if(manualStopBio){ manualStopBio = false; return; } if(m_failMap.contains(m_uid)){ m_failMap[m_uid] = m_failMap[m_uid] + 1; }else{ m_failMap[m_uid] = 1; } if(m_failMap[m_uid] >= maxFailedTimes){ onShowMessage(tr("Too many unsuccessful attempts,please enter password."),QLightDM::Greeter::MessageTypeInfo); useDoubleAuth = false; return ; } onShowMessage(tr("Authentication failure, Please try again"), QLightDM::Greeter::MessageTypeInfo); if(!isBioSuccess) startBioAuth(); } else{ m_retryButton->setVisible(!m_biometricAuthWidget->isHidden()); } } else { if(useDoubleAuth){ //onShowMessage("验证成功!", QLightDM::Greeter::MessageTypeInfo); setDirLogin(); isBioSuccess = true; onBiometricButtonClicked(); }else{ setDirLogin(); m_greeter->respond(BIOMETRIC_SUCCESS); } } } void LoginWindow::onBiometricButtonClicked() { //当前没有设备,让用户选择设备 if(!m_deviceInfo) { if(m_deviceCount == 1) { if(m_biometricDevicesWidget) m_biometricDevicesWidget->onOKButtonClicked(); } else { m_otherDeviceButton->click(); } } else { authMode = BIOMETRIC; startAuthentication(); } } void LoginWindow::onPasswordButtonClicked() { skipBiometricAuth(); } void LoginWindow::onOtherDevicesButtonClicked() { m_biometricAuthWidget->stopAuth(); showBiometricDeviceWidget(); } void LoginWindow::onRetryButtonClicked() { m_biometricAuthWidget->startAuth(m_deviceInfo, m_uid); m_retryButton->setVisible(false); } void LoginWindow::showPasswordAuthWidget() { m_passwdWidget->setVisible(true); if(m_biometricAuthWidget) { m_biometricAuthWidget->setVisible(false); m_biometricDevicesWidget->setVisible(false); m_biometricAuthWidget->stopAuth(); } if(m_buttonsWidget) { if(m_deviceCount > 0) { m_buttonsWidget->setVisible(true); if(!isHiddenSwitchButton) m_biometricButton->setVisible(true); m_passwordButton->setVisible(false); m_otherDeviceButton->setVisible(false); m_retryButton->setVisible(false); } else { qDebug() << "hide buttons widget"; m_buttonsWidget->setVisible(false); } } } void LoginWindow::showBiometricAuthWidget() { m_userWidget->setVisible(true); m_passwdWidget->setVisible(false); m_messageButton->hide(); if(m_biometricAuthWidget) { m_biometricAuthWidget->setVisible(true); m_biometricDevicesWidget->setVisible(false); } if(m_buttonsWidget && m_deviceCount > 0) { m_buttonsWidget->setVisible(true); m_biometricButton->setVisible(false); if(!isHiddenSwitchButton) m_passwordButton->setVisible(true); m_otherDeviceButton->setVisible(m_deviceCount > 1); } else { m_buttonsWidget->setVisible(false); } } void LoginWindow::unlock_countdown() { int failed_count = 0; int time_left = 0; int deny = 0; int fail_time =0; int unlock_time = 0; if (NULL == m_name.toLatin1().data()) { qDebug() << "get user info failed." ; return; } struct passwd* user = NULL; user = getpwnam(m_name.toLatin1().data()); pam_tally_unlock_time_left(user->pw_uid, &failed_count, &time_left, &deny,&fail_time,&unlock_time); // qDebug() << "failed_count:" << failed_count << "time_left:" < 0)//请多少分钟后重试 { char ch[100]={0}; int nMinute = time_left/60 + 1; m_messageLabel->setText(tr("Please try again in %1 minutes.").arg(nMinute)); m_messageLabel->setToolTip(tr("Please try again in %1 minutes.").arg(nMinute)); m_passwordEdit->clearText(); m_passwordEdit->setDisabled(true); isLockingFlg = true; return ; } else if(time_left > 0)//请多少秒后重试 { char ch[100]={0}; m_messageLabel->setText(tr("Please try again in %1 seconds.").arg(time_left%60)); m_messageLabel->setToolTip(tr("Please try again in %1 seconds.").arg(time_left%60)); m_passwordEdit->clearText(); m_passwordEdit->setDisabled(true); isLockingFlg = true; return ; } else if (failed_count == 0xFFFF)//账号被永久锁定 { m_messageLabel->setText(tr("Account locked permanently.")); m_messageLabel->setToolTip(tr("Account locked permanently.")); m_passwordEdit->clearText(); m_passwordEdit->setDisabled(true); isLockingFlg = true; return ; } else { if(m_passwordEdit){ m_passwordEdit->setDisabled(false); m_passwordEdit->setFocus(); } // qDebug() << "清理 1 clearMessage"; if (isLockingFlg) { onShowMessage(tr("Authentication failure, Please try again"), QLightDM::Greeter::MessageTypeInfo); isLockingFlg = false; } m_timer->stop(); } return ; } void LoginWindow::root_unlock_countdown() { int failed_count = 0; int time_left = 0; int deny = 0; if (NULL == m_name.toLatin1().data()) { qDebug() << "get user info failed." ; return; } struct passwd* user = NULL; user = getpwnam(m_name.toLatin1().data()); pam_tally_root_unlock_time_left(user->pw_uid, &failed_count, &time_left, &deny); if(time_left/60 > 0)//请多少分钟后重试 { char ch[100]={0}; int nMinute = time_left/60 + 1; m_messageLabel->setText(tr("Please try again in %1 minutes.").arg(nMinute)); m_messageLabel->setToolTip(tr("Please try again in %1 minutes.").arg(nMinute)); m_passwordEdit->clearText(); m_passwordEdit->setDisabled(true); isLockingFlg = true; return ; } else if(time_left > 0)//请多少秒后重试 { char ch[100]={0}; m_messageLabel->setText(tr("Please try again in %1 seconds.").arg(time_left%60)); m_messageLabel->setToolTip(tr("Please try again in %1 seconds.").arg(time_left%60)); m_passwordEdit->clearText(); m_passwordEdit->setDisabled(true); isLockingFlg = true; return ; } else if (failed_count == 0xFFFF)//账号被永久锁定 { m_messageLabel->setText(tr("Account locked permanently.")); m_messageLabel->setToolTip(tr("Account locked permanently.")); m_passwordEdit->clearText(); m_passwordEdit->setDisabled(true); isLockingFlg = true; return ; } else { if(m_passwordEdit){ m_passwordEdit->setDisabled(false); m_passwordEdit->setFocus(); } if (isLockingFlg) { onShowMessage(tr("Authentication failure, Please try again"), QLightDM::Greeter::MessageTypeInfo); isLockingFlg = false; } m_timer->stop(); } return ; } void LoginWindow::showBiometricDeviceWidget() { m_userWidget->setVisible(false); m_passwdWidget->setVisible(false); m_messageButton->hide(); if(m_biometricAuthWidget) { m_biometricAuthWidget->setVisible(false); m_biometricDevicesWidget->setVisible(true); } if(m_buttonsWidget) { m_buttonsWidget->setVisible(false); m_biometricButton->setVisible(false); m_passwordButton->setVisible(false); m_otherDeviceButton->setVisible(false); m_retryButton->setVisible(false); } } ukui-greeter/ukui-greeter/greeter/languagewidget.cpp0000644000175000017500000000516014204635151021651 0ustar fengfeng/** * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "languagewidget.h" #include #include #include #include #include LanguageWidget::LanguageWidget(QWidget *parent) : QMenu (parent), languagesVector(getLanguages()) { setWindowFlags(Qt::FramelessWindowHint); setAttribute(Qt::WA_TranslucentBackground); this->installEventFilter(this); initUserMenu(); } void LanguageWidget::setCurrentLanguage(const QString &language) { for(auto action : this->actions()) { if(action->data().toString()== language) this->setActiveAction(action); } } void LanguageWidget::initUserMenu() { connect(this, &QMenu::triggered, this, &LanguageWidget::onLanguageMenuTrigged); for(Language &lang : languagesVector) { QString text = lang.name + (lang.territory.isEmpty() ? "" : ("-" + lang.territory)); QAction *action = new QAction (this); action->setText(text); action->setData(lang.code); addAction(action); } } void LanguageWidget::onLanguageAdded(QString lang) { } void LanguageWidget::onLanguageDeleted(QString lang) { for(auto action : this->actions()) { if(action->data().toString() == lang) this->removeAction(action); } } void LanguageWidget::onLanguageMenuTrigged(QAction *action) { QString languageCode = action->data().toString(); auto iter = std::find_if(languagesVector.begin(), languagesVector.end(), [&](const Language &language) { return language.code == languageCode; }); Q_EMIT languageChanged(*iter); close(); } bool LanguageWidget::eventFilter(QObject *obj, QEvent *event) { //失去焦点时隐藏窗口 if(event->type() == 23) { QWidget *par = (QWidget*)this->parent(); par->update(); close(); } return false; } ukui-greeter/ukui-greeter/greeter/language.cpp0000644000175000017500000001007514204635151020446 0ustar fengfeng/** * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "language.h" #include #include #include #include QString getLocaleName(const QString &code) { static QStringList locales; QString language; int at; if((at = code.indexOf('@')) != -1) language = code.left(at); else language = code; if(locales.isEmpty()) { QProcess process; QString command = "locale -a"; process.start(command); process.waitForFinished(3000); QString result = process.readAll(); locales = result.split("\n"); } for(auto locale : locales) { if(locale.lastIndexOf(".utf8") == -1) continue; if(!locale.isEmpty() && locale.indexOf(language) != -1) return locale; } return ""; } QString getName(const QString &code) { QString name; QString locale = getLocaleName(code); if(!locale.isEmpty()) { char *currentLocale = setlocale(LC_ALL, NULL); setlocale(LC_IDENTIFICATION, locale.toUtf8().data()); setlocale(LC_MESSAGES, ""); char *language = nl_langinfo(_NL_IDENTIFICATION_LANGUAGE); if(language && strlen(language) > 0) { name = QString(dgettext("iso_639_3", language)); } setlocale(LC_ALL, currentLocale); } return name; } QString getTerritory(const QString &code) { if(code.indexOf('_') == -1) return ""; QString territory; QString locale = getLocaleName(code); if(!locale.isEmpty()) { char *currentLocale = setlocale(LC_ALL, NULL); setlocale(LC_IDENTIFICATION, locale.toUtf8().data()); setlocale(LC_MESSAGES, ""); char *country = nl_langinfo(_NL_IDENTIFICATION_TERRITORY); if(country && strlen(country) > 0 && strcmp(country, "ISO") != 0) { territory = QString(dgettext("iso_3166", country)); QString str = QString(QLatin1String(country)); if(str.contains("taiwan",Qt::CaseInsensitive)) { territory = "台湾"; } } setlocale(LC_ALL, currentLocale); } return territory; } LanguagesVector& getLanguages() { static LanguagesVector languagesVector; if(!languagesVector.isEmpty()) languagesVector.clear(); QProcess process; QString command = "/usr/share/language-tools/language-options"; process.start(command); process.waitForFinished(3000); QString result = process.readAll(); QStringList langs = result.split("\n"); if(result.isEmpty()) return languagesVector; for(auto lang : langs) { QString name = getName(lang); QString territory = getTerritory(lang); Language language{lang, name, territory}; languagesVector.push_back(language); } return languagesVector; } Language getLanguage(const QString &lang) { LanguagesVector &languages = getLanguages(); QString code = lang; if(code.toLower().lastIndexOf(".utf-8") != -1) { code = code.left(code.length() - 6); } for(int i=0;i #include #include #include #include #include #include #include #include #include #include #include #include #include #include "xeventmonitor.h" #include "securityuser.h" /*记录五个节点的坐标*/ #define ARROW_WIDTH 120*scale #define LEFT_ARROW_X 0 #define ITEM1_X (ARROW_WIDTH) #define ITEM2_X (ITEM1_X + CENTER_ENTRY_WIDTH * 2 - ENTRY_WIDTH) #define ITEM3_X (ITEM2_X + CENTER_ENTRY_WIDTH * 2 - ENTRY_WIDTH ) #define ITEM4_X (ITEM3_X + CENTER_ENTRY_WIDTH*3 - ENTRY_WIDTH*2) #define ITEM5_X (ITEM4_X + CENTER_ENTRY_WIDTH * 2 - ENTRY_WIDTH) #define RIGHT_ARROW_X (CENTER_ENTRY_WIDTH*9 - ENTRY_WIDTH*4 + 220*scale) #define ITEM_Y CENTER_ENTRY_HEIGHT-ENTRY_HEIGHT #define ITEM_CENTER_Y 0 UsersView::UsersView(QWidget *parent) : QWidget(parent), usersModel(nullptr), currentUser(-1), prevArrow(nullptr), nextArrow(nullptr), secUser(SecurityUser::instance()), lasttime(QTime::currentTime()), mouseClickLast(QTime::currentTime()) { QSize size = QApplication::primaryScreen()->size(); scale = QString::number(size.width() / 1920.0, 'f', 1).toFloat(); if(scale > 1) scale = 1; setFocusPolicy(Qt::NoFocus); connect(XEventMonitor::instance(), SIGNAL(keyPress(const QString &)), this, SLOT(onGlobalKeyRelease(const QString &))); setAttribute(Qt::WA_TranslucentBackground); } UsersView::~UsersView() { } void UsersView::resizeEvent(QResizeEvent *) { setCurrentRow(currentUser); } bool UsersView::eventFilter(QObject *obj, QEvent *event) { if(userlist.count() < 1) return false; for(int i = 0;itype() == QEvent::MouseButtonRelease){ uid_t uid = userlist.at(i)->userIndex().data(QLightDM::UsersModel::UidRole).toUInt(); int x = 0; for(x = 0; x < usersModel->rowCount(); x++) { uid_t id= usersModel->index(x).data(QLightDM::UsersModel::UidRole).toUInt(); if(uid == id) break; } QModelIndex index = usersModel->index(x, 0); Q_EMIT currentUserChanged(index); Q_EMIT userSelected(index); break; } } /*鼠标移动到头像上事件*/ if(event->type() == QEvent::Enter){ for(int i=0;isetEnterEvent(true); } } } /*鼠标移出头像事件*/ if(event->type() == QEvent::Leave){ for(int i=0;isetEnterEvent(false); } } } return false; } void UsersView::setModel(QAbstractListModel *model) { usersModel = model; connect(usersModel, &QAbstractListModel::rowsInserted, this, &UsersView::onUserAdded); connect(usersModel, &QAbstractListModel::rowsRemoved, this, &UsersView::onUserRemoved); connect(usersModel, &QAbstractListModel::dataChanged, this, &UsersView::onUserChanged); for(int i = 0; i < usersModel->rowCount(); i++){ QString name = usersModel->index(i).data(QLightDM::UsersModel::NameRole).toString(); if(secUser->isSecrityUser(name)) insertUserEntry(i); } } void UsersView::setCurrentUser(const QString &userName, bool entered) { if(userName.isEmpty() || userlist.count() == 0) return ; for(int i = 0;iuserIndex().data(QLightDM::UsersModel::NameRole).toString() == userName) { if(i != currentUser) setCurrentRow(i); return ; } else if(entry->userIndex().data(QLightDM::UsersModel::NameRole).toString() == "*login") { entry->setUserName(userName); return ; } } } void UsersView::onGlobalKeyRelease(const QString &key) { } void UsersView::showEvent(QShowEvent *event) { QWidget::showEvent(event); } void UsersView::insertUserEntry(int row) { QModelIndex index = usersModel->index(row, 0); QPersistentModelIndex persistentIndex(index); UserEntry *entry = new UserEntry(this); entry->setUserIndex(persistentIndex); entry->installEventFilter(this); entry->hide(); userlist.append(entry); } void UsersView::removeUserEntry(int row) { } void UsersView::onUserAdded(const QModelIndex &parent, int left, int right) { Q_UNUSED(parent); for(int i = left; i <= right; i++){ QString name = usersModel->index(i).data(QLightDM::UsersModel::NameRole).toString(); if(secUser->isSecrityUser(name)) insertUserEntry(i); } } void UsersView::onUserRemoved(const QModelIndex &parent, int left, int right) { Q_UNUSED(parent); for(int i = left; i <= right; i++){ removeUserEntry(i); } } void UsersView::onUserChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { int left = topLeft.row(); int right = topLeft.row(); for(int i = left;i<=right;i++) { QModelIndex index = usersModel->index(i,0); uid_t uid = index.data(QLightDM::UsersModel::UidRole).toUInt(); for(int j = 0;juserIndex().data(QLightDM::UsersModel::UidRole).toUInt(); if(id == uid) { entry->setUserName(index.data(Qt::DisplayRole).toString()); entry->setFace(index.data(QLightDM::UsersModel::ImagePathRole).toString()); entry->setLogin(index.data(QLightDM::UsersModel::LoggedInRole).toBool()); break; } } } } bool UsersView::getIsUser(QString username) { for(int i = 0; i < usersModel->rowCount(); i++){ QString name = usersModel->index(i).data(QLightDM::UsersModel::NameRole).toString(); if(name == username) return true; } return false; } bool UsersView::getLoginStaus(QString username) { for(int i = 0; i < usersModel->rowCount(); i++){ QString name = usersModel->index(i).data(QLightDM::UsersModel::NameRole).toString(); if(name == username){ return usersModel->index(i).data(QLightDM::UsersModel::LoggedInRole).toBool(); } } } QSize UsersView::getSize() { int w,h; if(userlist.count() <= 7){ //小于等于7个用户时,只用显示一行 w = ENTRY_WIDTH*userlist.count() + C_BORDER_WIDTH*(userlist.count() - 1 ); h = ENTRY_HEIGHT; }else{ //大于7个用户时,设置多行 int line; if(userlist.count() % 7 == 0){ line = userlist.count()/7; }else{ line = userlist.count()/7 + 1; } w = ENTRY_WIDTH*7 + C_BORDER_WIDTH*6; h = ENTRY_HEIGHT*line + C_BORDER_HEIGHT*(line - 1); } QSize size(w,h); return size; } void UsersView::setCurrentRow(int user) { if(userlist.count() == 0) return ; if(user < 0 || user >= userlist.count()) user = 0; if(currentUser == user){ resize(getSize()); for(int i = 0;isetGeometry(x*(ENTRY_WIDTH + C_BORDER_WIDTH),y*(ENTRY_HEIGHT + C_BORDER_HEIGHT),ENTRY_WIDTH,ENTRY_HEIGHT); userlist.at(i)->show(); } return ; } currentUser = user; resize(getSize()); for(int i = 0;isetGeometry(x*(ENTRY_WIDTH + C_BORDER_WIDTH),y*(ENTRY_HEIGHT + C_BORDER_HEIGHT),ENTRY_WIDTH,ENTRY_HEIGHT); userlist.at(i)->show(); } //获取当前用户对应的model下标 UserEntry *entry = userlist.at(currentUser); uid_t uid = entry->userIndex().data(QLightDM::UsersModel::UidRole).toUInt(); int x = 0; for(x = 0; x < usersModel->rowCount(); x++) { uid_t id= usersModel->index(x).data(QLightDM::UsersModel::UidRole).toUInt(); if(uid == id) break; } QModelIndex index = usersModel->index(x, 0); Q_EMIT currentUserChanged(index); Q_EMIT userSelected(index); } ukui-greeter/ukui-greeter/greeter/greeterwindow.h0000644000175000017500000000670514204635151021222 0ustar fengfeng/* greeterwindow.h * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 GREETERWINDOW_H #define GREETERWINDOW_H #include #include #include #include #include #include #include #include #include "loginwindow.h" #include "sessionwindow.h" #include "usersmodel.h" #include "greeterwrapper.h" class UsersView; class PowerManager; class QProcess; class QGraphicsOpacityEffect; class Configuration; class VirtualKeyboard; class LanguageWidget; class Language; class GreeterWindow : public QWidget { Q_OBJECT public: GreeterWindow(QWidget *parent = 0); void initUI(); QString guessBackground(); void setRootWindow(); protected: void resizeEvent(QResizeEvent *event); void keyReleaseEvent(QKeyEvent *event); bool eventFilter(QObject *obj, QEvent *event); private slots: void onAuthenticationComplete1(); void onUserSelected(const QModelIndex &); void onCurrentUserChanged(const QModelIndex&); void timedAutologin(); void showPowerWnd(); void showVirtualKeyboard(); void onLanguageChanged(QString languageCode); void showSessionWnd(); void onSessionChanged(const QString &session); void onUserChangedByManual(const QString &userName); void setWindowVisible(); private: void switchWnd(int); void setBackground(const QModelIndex &index); void setVirkeyboardPos(); void setWindowPos(QWidget *widget, Qt::Alignment align = Qt::AlignCenter); bool sessionIsValid(const QString &session); void updateLanguage(QString userName); void updateSession(QString userName); void setUserWindowVisible(bool visible); void refreshTranslate(); void drawBackground(); QString getAccountBackground(uid_t uid); UsersView *m_userWnd; LoginWindow *m_loginWnd; SessionWindow *m_sessionWnd; PowerManager *m_powerWnd; QPushButton *m_keyboardLB; QPushButton *m_powerLB; QPushButton *m_btnSwitchUser = nullptr; QPushButton *m_sessionLB; VirtualKeyboard *m_virtualKeyboard; QScrollArea *scrollArea = nullptr; QWidget *scrollContents = nullptr; GreeterWrapper *m_greeter; UsersModel *m_usersModel; QAbstractItemModel *m_sessionsModel; Configuration *m_configuration; bool m_sessionHasChanged; QTimer *timer; QWidget *widgetTime; QLabel *lblDate; QLabel *lblTime; QLocale local; QString currentUser; QFuture future; }; #endif // GREETERWINDOW_H ukui-greeter/ukui-greeter/greeter/pagelistview.h0000644000175000017500000000472114204635151021034 0ustar fengfeng/* pagelistview.h * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 PAGELISTVIEW_H #define PAGELISTVIEW_H #include #include #include #include #include #include "userentry.h" #include "usersmodel.h" class PageListView : public QWidget { Q_OBJECT Q_PROPERTY(int page NOTIFY pageChanged) public: explicit PageListView(QWidget *parent = 0); void setModel(QSharedPointer); void pageUp(); //切换到上一页 留给父窗口通过按钮调用 void pageDown(); //切换到下一页 bool hasPrev(); bool hasNext(); protected: void keyReleaseEvent(QKeyEvent *); void resizeEvent(QResizeEvent *); private: UserEntry* createEntry(int ); void drawPage(); void goHome(); //切换到首页 void goEnd(); //切换到尾页 void preItem(); //切换到上一项 void nextItem(); //切换到下一项 void moveFocus(); //移动焦点 signals: void pageChanged(); //当焦点所在项发生变化时发出 void selectedChanged(const QModelIndex& index); //当选定用户时发出 private slots: void onEntrySelected(const QString &name); void onUserChanged(const QModelIndex&, const QModelIndex&); void onUserInserted(const QModelIndex&, int, int); void onUserRemoved(const QModelIndex&, int, int); private: QSharedPointer m_model; QVector m_itemList; QHBoxLayout *m_layout; int m_lastItem; //上一次选择的项 int m_curItem; //当前焦点所在项 int m_end; //当前页的最后一项 int m_lastend; //上一次的最后一项 int m_itemCount; }; #endif // PAGELISTVIEW_H ukui-greeter/ukui-greeter/greeter/powerwindow.cpp0000644000175000017500000002123514204635151021247 0ustar fengfeng/* powerwindow.cpp * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "powerwindow.h" PowerManager::PowerManager(QWidget *parent) : QListWidget(parent), m_power(new QLightDM::PowerInterface(this)), lasttime(QTime::currentTime()) { setFlow(QListWidget::LeftToRight); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setSelectionMode(QListWidget::NoSelection); QObject::connect(this,SIGNAL(itemClicked(QListWidgetItem*)),this,SLOT(powerClicked(QListWidgetItem*))); initUI(); } QSize PowerManager::windowSize() { return QSize(ITEM_WIDTH*this->count(), ITEM_HEIGHT); } void PowerManager::powerClicked(QListWidgetItem *item) { int interval = lasttime.msecsTo(QTime::currentTime()); if(interval < 200 && interval > -200) return ; lasttime = QTime::currentTime(); /* int x = row(item); switch (x) { case 0: switchWidgetClicked(); break; case 1: rebootWidgetClicked(); break; case 2: shutdownWidgetClicked(); break; case 3: if(m_power->canSuspend()) suspendWidgetCliced(); else if(m_power->canHibernate()) hibernateWidgetClicked(); break; case 4: if(m_power->canHibernate()) hibernateWidgetClicked(); default: break; } */ QString name = itemWidget(item)->objectName(); if(switchWidget && name == switchWidget->objectName()) switchWidgetClicked(); else if(name == rebootWidget->objectName()) rebootWidgetClicked(); else if(name == shutdownWidget->objectName()) shutdownWidgetClicked(); else if(suspendWidget && name == suspendWidget->objectName()) suspendWidgetClicked(); else if(hibernateWidget && name == hibernateWidget->objectName()) hibernateWidgetClicked(); } void PowerManager::switchWidgetClicked() { emit switchToUser(); } void PowerManager::shutdownWidgetClicked() { try{ qDebug() << "shutdown"; m_power->shutdown(); close(); }catch(QException &e) { qWarning() << e.what(); } } void PowerManager::rebootWidgetClicked() { try{ m_power->restart(); close(); }catch(QException &e) { qWarning() << e.what(); } } void PowerManager::suspendWidgetClicked() { try{ emit switchToUser(); m_power->suspend(); }catch(QException &e) { qWarning() << e.what(); } } void PowerManager::hibernateWidgetClicked() { try{ emit switchToUser(); m_power->hibernate(); }catch(QException &e) { qWarning() << e.what(); } } void PowerManager::refreshTranslate() { switchLabel->setText(tr("Switch User")); rebootLabel->setText(tr("Restart")); shutdownLabel->setText(tr("Power Off")); } void PowerManager::initUI() { actService = new QDBusInterface("org.freedesktop.Accounts", "/org/freedesktop/Accounts", "org.freedesktop.Accounts", QDBusConnection::systemBus()); QDBusMessage ret = actService->call("ListCachedUsers"); QList outArgs = ret.arguments(); QVariant first = outArgs.at(0); const QDBusArgument &dbusArgs = first.value(); dbusArgs.beginArray(); QDBusObjectPath path; int userCount =0; while (!dbusArgs.atEnd()) { userCount++; dbusArgs >> path; } dbusArgs.endArray(); if(userCount > 1){ switchWidget = new QWidget(this); switchWidget->setObjectName("switchWidget"); QLabel *switchFace = new QLabel(this); switchLabel = new QLabel(this); switchFace->setAlignment(Qt::AlignCenter); switchLabel->setAlignment(Qt::AlignCenter); switchFace->setPixmap(QPixmap(":/images/avatar.png").scaled(58,58)); switchLabel->setText(tr("Switch User")); switchWidget->setFixedSize(ITEM_WIDTH,ITEM_HEIGHT); QVBoxLayout *switchlayout = new QVBoxLayout(switchWidget); switchlayout->addWidget(switchFace); switchlayout->addWidget(switchLabel); } rebootWidget = new QWidget(this); rebootWidget->setObjectName("rebootWidget"); QLabel *rebootFace = new QLabel(this); rebootLabel = new QLabel(this); rebootFace->setAlignment(Qt::AlignCenter); rebootLabel->setAlignment(Qt::AlignCenter); rebootFace->setPixmap(QPixmap(":/images/reboot.png").scaled(58,58)); rebootLabel->setText(tr("Restart")); rebootWidget->setFixedSize(ITEM_WIDTH,ITEM_HEIGHT); QVBoxLayout *rebootlayout = new QVBoxLayout(rebootWidget); rebootlayout->addWidget(rebootFace); rebootlayout->addWidget(rebootLabel); shutdownWidget = new QWidget(this); shutdownWidget->setObjectName("shutdownWidget"); QLabel *shutdownFace = new QLabel(this); shutdownLabel = new QLabel(this); shutdownLabel->setAlignment(Qt::AlignCenter); shutdownFace->setAlignment(Qt::AlignCenter); shutdownFace->setPixmap(QPixmap(":/images/shutdown.png").scaled(58,58)); shutdownLabel->setText(tr("Power Off")); shutdownWidget->setFixedSize(ITEM_WIDTH,ITEM_HEIGHT); QVBoxLayout *shutdownlayout = new QVBoxLayout(shutdownWidget); shutdownlayout->addWidget(shutdownFace); shutdownlayout->addWidget(shutdownLabel); if(m_power->canSuspend()){ suspendWidget = new QWidget(this); suspendWidget->setObjectName("suspendWidget"); QLabel *suspendFace = new QLabel(this); QLabel *suspendLabel = new QLabel(this); suspendFace->setAlignment(Qt::AlignCenter); suspendLabel->setAlignment(Qt::AlignCenter); suspendFace->setPixmap(QPixmap(":/images/suspend.png").scaled(48,48)); suspendLabel->setText(tr("Suspend")); suspendWidget->setFixedSize(ITEM_WIDTH,ITEM_HEIGHT); QVBoxLayout *suspendlayout = new QVBoxLayout(suspendWidget); suspendlayout->addWidget(suspendFace); suspendlayout->addWidget(suspendLabel); } if(m_power->canHibernate()) { hibernateWidget = new QWidget(this); hibernateWidget->setObjectName("hibernateWidget"); QLabel *hibernateFace = new QLabel(this); QLabel *hibernateLabel = new QLabel(this); hibernateFace->setAlignment(Qt::AlignCenter); hibernateLabel->setAlignment(Qt::AlignCenter); hibernateFace->setPixmap(QPixmap(":/images/hibernate.png").scaled(48,48)); hibernateLabel->setText(tr("Sleep")); hibernateWidget->setFixedSize(ITEM_WIDTH,ITEM_HEIGHT); QVBoxLayout *hibernatelayout = new QVBoxLayout(hibernateWidget); hibernatelayout->addWidget(hibernateFace); hibernatelayout->addWidget(hibernateLabel); } if(userCount>1){ QListWidgetItem *item0 = new QListWidgetItem(); item0->setSizeHint(QSize(ITEM_WIDTH, ITEM_HEIGHT)); insertItem(this->count(), item0); setItemWidget(item0, switchWidget); } if(m_power->canHibernate()){ QListWidgetItem *item = new QListWidgetItem(); item->setSizeHint(QSize(ITEM_WIDTH, ITEM_HEIGHT)); insertItem(this->count(), item); setItemWidget(item,hibernateWidget); } if(m_power->canSuspend()){ QListWidgetItem *item3 = new QListWidgetItem(); item3->setSizeHint(QSize(ITEM_WIDTH, ITEM_HEIGHT)); insertItem(this->count(), item3); setItemWidget(item3, suspendWidget); } QListWidgetItem *item1 = new QListWidgetItem(); item1->setSizeHint(QSize(ITEM_WIDTH, ITEM_HEIGHT)); insertItem(this->count(), item1); setItemWidget(item1, rebootWidget); QListWidgetItem *item2 = new QListWidgetItem(); item2->setSizeHint(QSize(ITEM_WIDTH, ITEM_HEIGHT)); insertItem(this->count(), item2); setItemWidget(item2, shutdownWidget); adjustSize(); } ukui-greeter/ukui-greeter/greeter/iconedit.h0000644000175000017500000000403014204635151020120 0ustar fengfeng/* iconedit.h * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 ICONEDIT_H #define ICONEDIT_H #include #include #include #include #include class Configuration; class IconEdit : public QWidget { Q_OBJECT public: IconEdit(QWidget *parent = 0); void setIcon(const QString &text); void setIcon(const QIcon &icon); void clear(); void setPrompt(const QString &); const QString text(); void setType(QLineEdit::EchoMode type = QLineEdit::Password); void startWaiting(); void stopWaiting(); void setCapsState(bool capsState); void clearText(); protected: void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE; bool eventFilter(QObject *obj, QEvent *event); private: void updatePixmap(); void refreshTranslate(); signals: void clicked(const QString &); void focusOut(); private slots: void clicked_cb(); void onGlobalKeyRelease(const QString &key); private: QLineEdit *m_edit; QLabel *m_capsIcon; QPushButton *m_iconButton; QPushButton *m_modeButton; QTimer *m_timer; QPixmap m_waitingPixmap; QString m_iconText; //文字作为图标 QIcon m_icon; Configuration *m_configuration; }; #endif // ICONEDIT_H ukui-greeter/ukui-greeter/greeter/pagelistview.cpp0000644000175000017500000002352114204635151021366 0ustar fengfeng/* pagelistview.cpp * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "pagelistview.h" #include #include #include #include #include #include #include #include #include #include "globalv.h" #include "userentry.h" PageListView::PageListView(QWidget *parent) : QWidget(parent), m_layout(nullptr), m_lastItem(-1) { } void PageListView::keyReleaseEvent(QKeyEvent *event) { switch(event->key()) { case Qt::Key_Return: { UserEntry *entry = m_itemList[m_curItem]; onEntrySelected(entry->userName()); break; } case Qt::Key_PageUp: pageUp(); break; case Qt::Key_PageDown: pageDown(); break; case Qt::Key_Left: preItem(); break; case Qt::Key_Right: nextItem(); break; case Qt::Key_Home: goHome(); break; case Qt::Key_End: goEnd(); break; default: break; } QWidget::keyReleaseEvent(event); } void PageListView::resizeEvent(QResizeEvent *) { for(int i = 0; i < m_itemList.size(); i++) if(m_itemList[i]) m_itemList[i]->setFixedSize(130 * scale + 20, 130 * scale + 75); } void PageListView::setModel(QSharedPointer model) { if(model.isNull()) return; if(!m_model.isNull()) { } m_model = model; connect(m_model.data(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(onUserInserted(QModelIndex,int,int))); connect(m_model.data(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(onUserRemoved(QModelIndex,int,int))); connect(m_model.data(), SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector)), this, SLOT(onUserChanged(QModelIndex,QModelIndex))); // m_itemList = QVector(m_model.data()->rowCount(), NULL); for(int i = 0; i < m_model->rowCount(); i++) { UserEntry *entry = createEntry(i); entry->hide(); m_itemList.append(entry); } m_lastend = m_curItem = -1; m_itemCount = m_model.data()->rowCount(); /* 读取上一次登录的用户 */ QSettings config(configFile, QSettings::IniFormat); QString lastUser = config.value("lastLoginUser").toString(); qDebug() << "lastLoginUser: " << lastUser; for(int i = 0; i < m_model->rowCount(); i++) { QString user = m_model->index(i, 0).data(QLightDM::UsersModel::NameRole).toString(); if(lastUser == user) { m_curItem = i; break; } } if(m_curItem < 0) { m_curItem = 0; } //当前要显示的用户是哪几个 int np = qCeil((m_curItem + 1) * 1.0 / 5); if(m_itemCount <= np * 5){ m_end = m_itemCount -1; } else { m_end = np * 5 - 1; } drawPage(); } void PageListView::drawPage() { if(m_layout != nullptr) delete(m_layout); m_layout = new QHBoxLayout(this); m_layout->setSpacing(10); int begin = m_end - (m_itemCount > 5 ? 4 : m_itemCount - 1); if(m_lastend > 0) { int lastbegin = m_lastend - (m_itemCount > 5 ? 4 : m_itemCount - 1); lastbegin = lastbegin > 0 ? lastbegin : 0; for(int i = lastbegin; i <= m_lastend; i++) { m_layout->removeWidget(m_itemList[i]); m_itemList[i]->hide(); } } // if(m_itemCount < 5) // m_layout->addStretch(); for(int i = begin; i <= m_end; i++) { //如果entry不存在则创建,添加到layout中 if(m_itemList[i] == NULL) { m_itemList[i] = createEntry(i); } else if(m_itemList[i]->userName() != m_model->index(i, 0).data().toString()) { m_itemList.insert(i, createEntry(i)); } m_itemList[i]->show(); m_layout->insertWidget(i - begin, m_itemList[i], 0); // m_layout->addStretch(); } if(m_itemList.size() > 0) { // m_itemList[m_curItem]->setFocus(); moveFocus(); } emit pageChanged(); } UserEntry* PageListView::createEntry(int i) { QModelIndex index = m_model->index(i, 0); UserEntry *entry = new UserEntry(index.data(Qt::DisplayRole).toString(), index.data(QLightDM::UsersModel::ImagePathRole).toString(), index.data(QLightDM::UsersModel::LoggedInRole).toBool(), this); connect(entry, SIGNAL(clicked(QString)), this, SLOT(onEntrySelected(QString))); return entry; } void PageListView::pageUp() { if(m_itemCount <= 5 || m_end == 4) //位于首页 { m_curItem = 0; // m_itemList[m_curItem]->setFocus(); moveFocus(); return; } m_lastend = m_end; m_end -= (m_end-5 >= 4 ? 5 : (m_end-4)); m_curItem = m_end - 4; drawPage(); } void PageListView::pageDown() { if(m_itemCount <= 5 || m_end == m_itemCount - 1) //位于尾页 { m_curItem = m_end; // m_itemList[m_curItem]->setFocus(); moveFocus(); return; } m_lastend = m_end; m_end += (m_itemCount-1-m_end >= 5 ? 5 : (m_itemCount-1-m_end)); m_curItem = m_end; drawPage(); } void PageListView::goHome() { if(m_itemCount <= 5 || m_end == 4) { m_curItem = 0; // m_itemList[m_curItem]->setFocus(); moveFocus(); return; } m_lastend = m_end; m_end = 4; m_curItem = 0; drawPage(); } void PageListView::goEnd() { if(m_itemCount <= 5 || m_end == m_itemCount - 1) //位于尾页 { m_curItem = m_end; // m_itemList[m_curItem]->setFocus(); moveFocus(); return; } m_lastend = m_end; m_end = m_itemCount - 1; m_curItem = m_end; drawPage(); } void PageListView::preItem() { int begin = m_itemCount > 5 ? m_end - 4 : 0; if(m_curItem - begin > 0) { --m_curItem; // m_itemList[m_curItem]->setFocus(); moveFocus(); } else if(m_curItem > 0) { --m_curItem; m_lastend = m_end; --m_end; drawPage(); } } void PageListView::nextItem() { if(m_curItem < m_end) { ++m_curItem; // m_itemList[m_curItem]->setFocus(); moveFocus(); } else if(m_curItem < m_itemCount - 1) { ++m_curItem; m_lastend = m_end; ++m_end; drawPage(); } } void PageListView::onEntrySelected(const QString &name) { for(int i = 0; i < m_itemList.size(); i++){ if(m_itemList[i]!=NULL && m_itemList[i]->userName() == name){ m_curItem = i; } } moveFocus(); emit selectedChanged(m_model.data()->index(m_curItem, 0)); } bool PageListView::hasPrev() { if(m_end > 4) return true; else return false; } bool PageListView::hasNext() { if(m_end < m_itemCount -1) return true; else return false; } /** * @brief PageListView::onUserInserted * @param parent * @param begin * @param end * 有用户增加 */ void PageListView::onUserInserted(const QModelIndex& parent, int begin, int end) { Q_UNUSED(parent) for(int i = begin; i <= end; i++) { qDebug() << "User added:" << m_model->index(i, 0).data().toString(); m_itemList.insert(i, createEntry(i)); } m_itemCount = m_model->rowCount(); m_curItem = begin; if(m_end >= begin || m_itemCount <= 5) { m_lastend = m_end; if(m_itemCount <= 5) m_end = m_itemCount - 1; drawPage(); } else { pageDown(); } } /** * @brief PageListView::onUserRemoved * @param parent * @param begin * @param end * 有用户删除 */ void PageListView::onUserRemoved(const QModelIndex & parent, int begin, int end) { Q_UNUSED(parent) for(int i = begin; i<=end; i++) { if(m_itemList[i]) delete(m_itemList[i]); m_itemList.remove(i); } if(m_end == m_itemCount-1) { m_end -= (end-begin+1); m_lastend = m_end; } if(m_curItem >= m_end) { m_curItem = m_end; } m_itemCount = m_model->rowCount(); drawPage(); } /** * @brief PageListView::onUserChanged * @param topLeft * @param bottomRight * 用户信息发生变化 */ void PageListView::onUserChanged(const QModelIndex & topLeft, const QModelIndex& bottomRight) { int begin = topLeft.row(); int end = bottomRight.row(); for(int i = begin; i <= end; i++) { if(m_itemList[i]) { QModelIndex index = m_model->index(i, 0); m_itemList[i]->setUserName(index.data().toString()); m_itemList[i]->setFace(index.data(QLightDM::UsersModel::ImagePathRole).toString()); m_itemList[i]->setLogin(index.data(QLightDM::UsersModel::LoggedInRole).toBool()); qDebug() << "user " << index.data().toString() <<"changed"; } } } void PageListView::moveFocus() { if(m_lastItem >= 0 && m_lastItem < m_itemCount && m_itemList[m_lastItem] != NULL) m_itemList[m_lastItem]->setSelected(false); m_itemList[m_curItem]->setSelected(); m_lastItem = m_curItem; } ukui-greeter/ukui-greeter/greeter/rootWindowBackground.cpp0000644000175000017500000000611014204635151023031 0ustar fengfeng/* rootWindowBackground.cpp * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "rootWindowBackground.h" Pixmap pix; Display *dpy = NULL; Window root = NULL; Screen *scn = NULL; void setRootWindowBackground(bool type,unsigned int color,char *filename) { Imlib_Image img; if (!dpy){ dpy = XOpenDisplay(NULL); if(!dpy) return; } int width = 0,height = 0; width = QApplication::desktop()->geometry().width()*qApp->devicePixelRatio(); height = QApplication::desktop()->geometry().height()*qApp->devicePixelRatio(); if(!scn) scn = DefaultScreenOfDisplay(dpy); if(!root) root = DefaultRootWindow(dpy); pix = XCreatePixmap(dpy, root, width, height, DefaultDepthOfScreen(scn)); imlib_context_set_display(dpy); imlib_context_set_visual(DefaultVisualOfScreen(scn)); imlib_context_set_colormap(DefaultColormapOfScreen(scn)); imlib_context_set_drawable(pix); if(type == 0){ img = imlib_load_image(filename); if (!img) { fprintf(stderr, "%s:Unable to load image\n", filename); return ; } imlib_context_set_image(img); }else if(type == 1){ img = imlib_create_image(width, height); imlib_context_set_image(img); int blue = color & 0xFF; int green = color >> 8 & 0xFF; int red = color >> 16 & 0xFF; qDebug()<<"red = "< #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "globalv.h" #include "mainwindow.h" #include "display-switch/displayswitch.h" #include "xeventmonitor.h" #include "configuration.h" #include #include #include #include #include void outputMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg) { Q_UNUSED(context) QDateTime dateTime = QDateTime::currentDateTime(); QByteArray time = QString("[%1] ").arg(dateTime.toString("MM-dd hh:mm:ss.zzz")).toLocal8Bit(); QByteArray localMsg = msg.toLocal8Bit(); switch(type) { case QtDebugMsg: fprintf(stderr, "%s Debug: %s:%u: %s\n", time.constData(), context.file, context.line, localMsg.constData()); break; #if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) case QtInfoMsg: fprintf(stderr, "%s Info: %s:%u: %s\n", time.constData(), context.file, context.line, localMsg.constData()); break; #endif case QtWarningMsg: fprintf(stderr, "%s Warnning: %s:%u: %s\n", time.constData(), context.file, context.line, localMsg.constData()); break; case QtCriticalMsg: fprintf(stderr, "%s Critical: %s:%u: %s\n", time.constData(), context.file, context.line, localMsg.constData()); break; case QtFatalMsg: fprintf(stderr, "%s Fatal: %s:%u: %s\n", time.constData(), context.file, context.line, localMsg.constData()); abort(); } } void waitMonitorsReady() { int n; QScreen *screen = QApplication::primaryScreen(); while(n <= 0 && screen->geometry().x() <= 0 && screen->geometry().y() <= 0 && screen->geometry().width() <= 0 && screen->geometry().height() <= 0){ XRRGetMonitors(QX11Info::display(), QX11Info::appRootWindow(), false, &n); if(n == -1) qWarning() << "get monitors failed"; else if(n > 0){ qDebug() << "found " << n << " monitors"; } //启动xrandr,打开视频输出, 自动设置最佳分辨率 QProcess enableMonitors; enableMonitors.start("xrandr --auto"); enableMonitors.waitForFinished(-1); } } void x11_get_screen_size(int *width,int *height) { Display* display; display = XOpenDisplay(NULL); if (display == NULL) { fprintf(stderr, "Cannot connect to X server %s/n", "simey:0"); exit (-1); } int screen_num; screen_num = DefaultScreen(display); *width = DisplayWidth(display, screen_num); *height = DisplayHeight(display, screen_num); XCloseDisplay(display); } void setXresources(int dpi) { Display *dpy; QString str = QString("Xft.dpi:\t%1\n") .arg(dpi); dpy = XOpenDisplay (NULL); XChangeProperty(dpy, RootWindow (dpy, 0), XA_RESOURCE_MANAGER, XA_STRING, 8, PropModeReplace, (unsigned char *) str.toLatin1().data(), str.length()); XCloseDisplay (dpy); } /* 判断文件是否存在 */ bool isFileExist(QString XresourcesFile) { QFileInfo fileInfo(XresourcesFile); if(fileInfo.isFile()){ qDebug()<<"file is true"; return true; } qDebug()<<"file is false"; return false; } /* 写配置文件并设置DPI和鼠标大小*/ void WriteXresourcesFile(QString XresourcesFile,int dpi) { QFile file(XresourcesFile); QString content = QString("Xft.dpi:%1\n").arg(dpi); file.open(QIODevice::ReadWrite | QIODevice::Text); QByteArray str = content.toLatin1().data(); file.write(str); file.close(); } /* 配置新装系统、新建用户第一次登陆时,4K缩放功能*/ void Set4KScreenScale() { Display *dpy; int w,h; x11_get_screen_size (&w, &h); bool res; QString homePath = getenv("HOME"); QString XresourcesFile = homePath+"/.config/xresources"; res = isFileExist(XresourcesFile); if(!res){ if(h >= 2000 || (w>2560 && h> 1500)){ WriteXresourcesFile(XresourcesFile,192); }else{ WriteXresourcesFile(XresourcesFile,96); } } if(h >= 2000 || (w>2560 && h> 1500)){ setXresources(192); }else{ setXresources(96); } } int main(int argc, char *argv[]) { qputenv("QT_QPA_PLATFORMTHEME",QByteArray("ukui")); QDateTime dateTime = QDateTime::currentDateTime(); QByteArray time = QString("[%1] ").arg(dateTime.toString("MM-dd hh:mm:ss.zzz")).toLocal8Bit(); fprintf(stderr, "%s Debug: %s\n", time.constData(),"--------------------------------------------------------开始,main函数开始执行"); qInstallMessageHandler(outputMessage); //打印信息处理 Set4KScreenScale(); //设置支持缩放属性 #if(QT_VERSION>=QT_VERSION_CHECK(5,6,0)) QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); #endif QApplication a(argc, argv); QResource::registerResource("image.qrc"); QTextCodec *codec = QTextCodec::codecForName("UTF-8"); QTextCodec::setCodecForLocale(codec); Configuration *m_configuration = Configuration::instance(); //加载翻译文件 m_configuration->m_trans = new QTranslator(); m_configuration->m_trans->load(QM_DIR + QString("%1.qm").arg(QLocale::system().name())); qApp->installTranslator( m_configuration->m_trans); //加载qss文件 QFile qss(":/ukui-greeter.qss"); qss.open(QFile::ReadOnly); a.setStyleSheet(qss.readAll()); MainWindow w; w.show(); w.activateWindow(); qss.close(); return a.exec(); } ukui-greeter/ukui-greeter/greeter/sessionwindow.cpp0000644000175000017500000000661514204635151021603 0ustar fengfeng/* sessionwindow.cpp * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "sessionwindow.h" #include #include #include #include #include #include #include "globalv.h" #define ICON_SIZE 18 #define PROMPT_LEFT 150 #define LISTWIDGET_SAPCE 3 #define ITEM_WIDTH 300 #define ITEM_HEIGHT 40 #define DISPLAY_ROWS 3 //只显示3行,可以通过上下键移动焦点 #define LISTWIDGET_HEIGHT (DISPLAY_ROWS * (LISTWIDGET_SAPCE * 2 + ITEM_HEIGHT)) SessionWindow::SessionWindow(QAbstractItemModel *model, QWidget *parent) : QMenu(parent), m_sessionsModel(model) { initUI(); setSessionModel(model); connect(this, &QMenu::triggered, this, &SessionWindow::onSessionMenuTrigged); setWindowFlags(Qt::FramelessWindowHint); setAttribute(Qt::WA_TranslucentBackground); this->installEventFilter(this); } void SessionWindow::initUI() { if (objectName().isEmpty()) setObjectName(QStringLiteral("SessionWnd")); } void SessionWindow::setSessionModel(QAbstractItemModel *model) { if(model == nullptr) { return ; } m_sessionsModel = model; addSessionLabels(); } void SessionWindow::addSessionLabels() { for(int i = 0; i < m_sessionsModel->rowCount(); i++) { QString sessionName = m_sessionsModel->index(i, 0).data().toString(); QString sessionKey = m_sessionsModel->index(i, 0).data(Qt::UserRole).toString(); QAction *action = new QAction(QIcon(getSessionIcon(sessionKey)), sessionName, this); action->setData(sessionKey); addAction(action); adjustSize(); } } void SessionWindow::setCurrentSession(const QString &session) { for(auto action : this->actions()) { if(action->data().toString()== session) this->setActiveAction(action); } } void SessionWindow::onSessionMenuTrigged(QAction *action) { QString sessionkey = action->data().toString(); emit sessionChanged(sessionkey); } QString SessionWindow::getSessionIcon(const QString &session) { QString sessionPrefix = session.left(session.indexOf('-')); if(session == "ukui-wayland") sessionPrefix = "ukui_wayland"; QString sessionIcon = IMAGE_DIR + QString("badges/%1_badge.svg").arg(sessionPrefix.toLower()); QFile iconFile(sessionIcon); if(!iconFile.exists()){ sessionIcon = IMAGE_DIR + QString("badges/unknown_badge.svg"); } return sessionIcon; } bool SessionWindow::eventFilter(QObject *obj, QEvent *event) { //失去焦点时隐藏窗口 if(event->type() == 23) { QWidget *par = (QWidget*)this->parent(); par->update(); close(); } return false; } ukui-greeter/ukui-greeter/greeter/greeterwindow.cpp0000644000175000017500000007325714204635151021563 0ustar fengfeng/* greeterwindow.cpp * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "greeterwindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "globalv.h" #include "loginwindow.h" #include "usersview.h" #include "usersmodel.h" #include "powerwindow.h" #include "common/configuration.h" #include "mainwindow.h" #include "virtualkeyboard.h" #include "languagewidget.h" #include "language.h" #include #include #include #include #include float scale; int fontSize; GreeterWindow::GreeterWindow(QWidget *parent) : QWidget(parent), m_userWnd(nullptr), m_loginWnd(nullptr), m_sessionWnd(nullptr), m_powerWnd(nullptr), m_sessionLB(nullptr), m_virtualKeyboard(nullptr), m_greeter(new GreeterWrapper()), m_usersModel(new UsersModel(m_greeter->hideUsersHint())), m_sessionsModel(new QLightDM::SessionsModel(QLightDM::SessionsModel::LocalSessions)), m_configuration(Configuration::instance()), m_sessionHasChanged(false) { scale = 1.0; if(m_greeter->hasGuestAccountHint()){ //允许游客登录 qDebug() << "allow guest"; m_usersModel->setShowGuest(true); } if(m_greeter->showManualLoginHint()) { //允许手动输入用户名 qDebug() << "allow manual login"; m_usersModel->setShowManualLogin(true); } m_greeter->setSession(m_greeter->defaultSessionHint()); connect(m_greeter, SIGNAL(autologinTimerExpired()),this, SLOT(timedAutologin())); connect(m_greeter, SIGNAL(authenticationComplete()),this, SLOT(onAuthenticationComplete1())); installEventFilter(this); } bool GreeterWindow::eventFilter(QObject *obj, QEvent *event) { if(event->type() == QEvent::MouseButtonPress && obj == this){ if(m_powerWnd && m_powerWnd->isVisible()){ m_powerWnd->close(); switchWnd(1); } } return false; } void GreeterWindow::initUI() { installEventFilter(this); widgetTime = new QWidget(this); QVBoxLayout *widgetlayout = new QVBoxLayout(widgetTime); lblDate = new QLabel(this); lblTime = new QLabel(this); //电源对话框打开按钮 m_powerLB = new QPushButton(this); //虚拟键盘启动按钮 m_keyboardLB = new QPushButton(this); //切换用户按钮 m_btnSwitchUser = new QPushButton(this); timer = new QTimer(this); connect(timer, &QTimer::timeout, this, [&]{ lblTime->setText(local.toString(QDateTime::currentDateTime(),"hh:mm")); lblDate->setText(local.toString(QDate::currentDate(),"yyyy/MM/dd ddd")); }); timer->start(1000); //QtConcurrent::run([=](){ local = QLocale::system().language(); QDateTime dateTime = QDateTime::currentDateTime(); QString strFormat = "dd.MM.yyyy, ddd MMMM d yy, hh:mm:ss.zzz, h:m:s ap"; QString strDateTime = local.toString(dateTime, strFormat); widgetlayout->addWidget(lblTime); widgetlayout->addWidget(lblDate); lblTime->setText(local.toString(QDateTime::currentDateTime(),"hh:mm")); lblTime->setStyleSheet("QLabel{color:white; font-size: 50px;}"); lblDate->setText(local.toString(QDate::currentDate(),"yyyy/MM/dd ddd")); lblDate->setStyleSheet("QLabel{color:white; font-size: 16px;}"); widgetTime->adjustSize(); m_powerLB->setObjectName(QStringLiteral("powerButton")); m_powerLB->setIcon(QPixmap(":/images/power.png")); m_powerLB->setIconSize(QSize(30, 30)); m_powerLB->setFocusPolicy(Qt::NoFocus); m_powerLB->setFixedSize(48, 48); m_powerLB->setCursor(Qt::PointingHandCursor); m_powerLB->installEventFilter(this); connect(m_powerLB, &QPushButton::clicked, this, &GreeterWindow::showPowerWnd); m_keyboardLB->setObjectName(QStringLiteral("keyboardButton")); m_keyboardLB->setIcon(QPixmap(":/images/keyboard.png")); m_keyboardLB->setIconSize(QSize(30, 30)); m_keyboardLB->setFocusPolicy(Qt::NoFocus); m_keyboardLB->setFixedSize(48, 48); m_keyboardLB->setCursor(Qt::PointingHandCursor); m_keyboardLB->installEventFilter(this); if(m_usersModel->rowCount() < 2){ m_btnSwitchUser->hide(); } connect(m_keyboardLB, &QPushButton::clicked, this, &GreeterWindow::showVirtualKeyboard); m_btnSwitchUser->setObjectName(QStringLiteral("btnSwitchUser")); m_btnSwitchUser->setIcon(QIcon(":/images/switchUser.png")); m_btnSwitchUser->setIconSize(QSize(24, 24)); m_btnSwitchUser->setFocusPolicy(Qt::NoFocus); m_btnSwitchUser->setFixedSize(48, 48); m_btnSwitchUser->setCursor(Qt::PointingHandCursor); bool isHideUser = m_configuration->getValue("hide-switchuser-button").toBool(); if(isHideUser) { m_btnSwitchUser->hide(); } m_btnSwitchUser->installEventFilter(this); connect(m_btnSwitchUser, &QPushButton::clicked, this, [&]{ if(m_powerWnd && m_powerWnd->isVisible()) m_powerWnd->close(); if(scrollArea->isVisible()) switchWnd(1); else switchWnd(0); }); //}); //桌面环境选择按钮 if(m_sessionsModel->rowCount() > 1) { m_sessionLB = new QPushButton(this); m_sessionLB->setObjectName((QStringLiteral("sessionButton"))); m_sessionLB->setIconSize(QSize(24, 24)); m_sessionLB->setFocusPolicy(Qt::NoFocus); m_sessionLB->setFixedSize(48, 48); m_sessionLB->setCursor(Qt::PointingHandCursor); m_sessionLB->installEventFilter(this); m_sessionLB->setIcon(QIcon(IMAGE_DIR + QString("badges/unknown_badge.svg"))); connect(m_sessionLB, &QPushButton::clicked, this, &GreeterWindow::showSessionWnd); } //用户列表 scrollArea = new QScrollArea(this); scrollArea->setAttribute(Qt::WA_TranslucentBackground); scrollArea->viewport()->setAttribute(Qt::WA_TranslucentBackground); scrollArea->setStyleSheet("QScrollArea {background-color:transparent;}"); scrollArea->viewport()->setStyleSheet("background-color:transparent;"); scrollArea->verticalScrollBar()->setProperty("drawScrollBarGroove", false); scrollArea->verticalScrollBar()->setContextMenuPolicy(Qt::NoContextMenu); scrollContents = new QWidget(scrollArea); scrollArea->setWidget(scrollContents); m_userWnd = new UsersView(scrollContents); connect(m_userWnd, &UsersView::currentUserChanged, this, &GreeterWindow::onCurrentUserChanged); connect(m_userWnd, &UsersView::userSelected, this, &GreeterWindow::onUserSelected); scrollArea->hide(); //登录窗口 m_loginWnd = new LoginWindow(m_greeter, this); m_loginWnd->hide(); connect(m_loginWnd, &LoginWindow::userChangedByManual, this, &GreeterWindow::onUserChangedByManual); connect(m_userWnd, &UsersView::userNotFound, m_loginWnd, &LoginWindow::setUserNotInView); m_userWnd->setModel(m_usersModel); setFocusProxy(m_loginWnd); // QtConcurrent::run([=](){ //显示lightdm传过来的被选中的用户且自动进入认证界面 -- SwitchToUser() QString selectedUser = m_greeter->selectUserHint(); if(!selectedUser.isEmpty()) { qDebug() << "SelectUserHint: " << selectedUser; m_userWnd->setCurrentUser(selectedUser, true); } // SwitchToGuest()且自动进入认证界面 else if(m_greeter->selectGuestHint()) { qDebug() << "SelectGuest"; m_userWnd->setCurrentUser("*guest", true); } //如果没有可显示出来的用户,则开启手动输入用户名登录 else if(m_usersModel->rowCount() == 0) { m_usersModel->setShowManualLogin(true); QString userName = m_usersModel->index(0, 0).data(QLightDM::UsersModel::NameRole).toString(); m_userWnd->setCurrentUser(userName, true); } //如果只有一个用户,直接进入认证界面 else if(m_usersModel->rowCount() == 1) { QString userName = m_usersModel->index(0, 0).data(QLightDM::UsersModel::NameRole).toString(); m_userWnd->setCurrentUser(userName, true); } else { QString lastLoginUser = Configuration::instance()->getLastLoginUser(); if(m_userWnd->getIsUser(lastLoginUser)) { if( m_userWnd->getLoginStaus(lastLoginUser)){ m_userWnd->setCurrentUser(lastLoginUser); switchWnd(0); } else{ m_userWnd->setCurrentUser(lastLoginUser); } }else{ QString userName = m_usersModel->index(0, 0).data(QLightDM::UsersModel::NameRole).toString(); m_userWnd->setCurrentUser(userName); } } setWindowOpacity(0.5); //}); } void GreeterWindow::setRootWindow() { m_greeter->setrootWindow(); } void GreeterWindow::switchWnd(int index) { if(m_userWnd) scrollArea->hide(); if(m_loginWnd) m_loginWnd->hide(); repaint(); switch (index) { case 0: if(m_userWnd) scrollArea->show(); break; case 1: if(m_loginWnd) m_loginWnd->show(); break; default: break; } } QString GreeterWindow::guessBackground() { QString selectedUser = m_greeter->selectUserHint(); if(!selectedUser.isEmpty()) { return m_configuration->getDefaultBackgroundName(); } else if(m_greeter->selectGuestHint()) { return m_configuration->getDefaultBackgroundName(); } else if(m_usersModel->rowCount() == 0) { return m_configuration->getDefaultBackgroundName(); } else { QString userName; if(m_usersModel->rowCount() == 1) userName = m_usersModel->index(0, 0).data(QLightDM::UsersModel::NameRole).toString(); else userName = Configuration::instance()->getLastLoginUser(); struct passwd *user = NULL; user = getpwnam(userName.toLatin1().data()); if(user){ QString backgroundPath = getAccountBackground(user->pw_uid); if(backgroundPath.isEmpty()) backgroundPath = m_configuration->getDefaultBackgroundName(); QString userConfigurePath = m_greeter->getEnsureShareDir(userName) + "/ukui-greeter.conf"; QFile backgroundFile(userConfigurePath); if(backgroundFile.exists()){ QSettings settings(userConfigurePath,QSettings::IniFormat); settings.beginGroup("greeter"); if(settings.contains("backgroundPath")){ QString filepath = settings.value("backgroundPath").toString(); if(!filepath.isEmpty() && isPicture(filepath)){ return filepath; } } } return backgroundPath; } return m_configuration->getDefaultBackgroundName(); } } /** * @brief GreeterWindow::resizeEvent * @param event * GreeterWindow的大小发生改变 */ void GreeterWindow::resizeEvent(QResizeEvent *event) { QSize size = event->size(); //重新计算缩放比例 scale = QString::number(size.width() / 1920.0, 'f', 1).toFloat(); if(scale > 1) scale = 1; fontSize = scale > 0.5 ? 10 : 8; qDebug() << "GreeterWindow resize to " << size; qDebug() << "scale: " << scale; //对距离进行缩放 widgetTime->move((width()-widgetTime->geometry().width())/2, 59*scale); //距离顶部59*scale的距离 if(scrollArea){ QRect userRect(0.05*width(), 0.2*height(), width()*0.9, height()*0.6); scrollArea->setGeometry(userRect); } if(scrollContents){ scrollContents->setFixedWidth(scrollArea->width()-20); } if(m_userWnd){ m_userWnd->resize(m_userWnd->getSize()); if(m_userWnd->height() + 2*30*scale < scrollArea->height()){ m_userWnd->move((scrollArea->width()-m_userWnd->width())/2,(scrollArea->height() - m_userWnd->height())/2); }else{ m_userWnd->move((scrollArea->width()-m_userWnd->width())/2,30*scale); } scrollContents->setFixedHeight(m_userWnd->height() + m_userWnd->y() ); } if(m_loginWnd){ //m_loginWnd->setFixedHeight(height()); m_loginWnd->setChildrenGeometry(); m_loginWnd->setGeometry((width()-m_loginWnd->geometry().width())/2,height()/3, \ m_loginWnd->width(), height()*2/3); } if(m_powerWnd){ m_powerWnd->move((width()-m_powerWnd->width())/2,(height() - m_powerWnd->height())/2); } //电源按钮位置,根据x,y的值进行计算,初始值为到右下角的距离 int x = 39; int y = 66; if(m_powerLB){ x+=m_powerLB->width(); m_powerLB->move(this->width() - x,height() - y); } //虚拟键盘按钮位置 if(m_keyboardLB){ x += (m_keyboardLB->width() + 10); //10为间隔 m_keyboardLB->move(this->width() - x, height() - y); } //切换用户按钮位置 if(m_btnSwitchUser){ x += (m_btnSwitchUser->width() + 10); //10为间隔 m_btnSwitchUser->move(this->width() - x, height() - y); } //桌面环境选择按钮位置 if(m_sessionLB) { x += (m_sessionLB->width() + 10); //10为间隔 m_sessionLB->move(this->width() - x, height() - y); } //虚拟键盘位置 setVirkeyboardPos(); } void GreeterWindow::setVirkeyboardPos() { if(m_virtualKeyboard) { //设置屏幕键盘的高度为窗口高度的三分之一 m_virtualKeyboard->setGeometry(0, height() - height()/3, width(), height()/3); } } void GreeterWindow::keyReleaseEvent(QKeyEvent *e) { switch(e->key()) { case Qt::Key_K: if(e->modifiers() & Qt::ControlModifier) m_keyboardLB->click(); break; case Qt::Key_P: if(e->modifiers() & Qt::ControlModifier) m_powerLB->click(); break; case Qt::Key_Escape: if(m_powerWnd && !m_powerWnd->isHidden()){ setWindowVisible(); } break; } QWidget::keyReleaseEvent(e); } QString GreeterWindow::getAccountBackground(uid_t uid) { QDBusInterface iface("org.freedesktop.Accounts", "/org/freedesktop/Accounts", "org.freedesktop.Accounts",QDBusConnection::systemBus()); QDBusReply userPath = iface.call("FindUserById", (qint64)uid); if(!userPath.isValid()) qWarning() << "Get UserPath error:" << userPath.error(); else { QDBusInterface userIface("org.freedesktop.Accounts", userPath.value().path(), "org.freedesktop.DBus.Properties", QDBusConnection::systemBus()); QDBusReply backgroundReply = userIface.call("Get", "org.freedesktop.Accounts.User", "BackgroundFile"); if(backgroundReply.isValid()) return backgroundReply.value().variant().toString(); } return ""; } void GreeterWindow::setBackground(const QModelIndex &index) { QString backgroundPath; QString accountBackground; //记录来自accountsservice的背景图片 backgroundPath = index.data(QLightDM::UsersModel::BackgroundPathRole).toString(); accountBackground = backgroundPath; if(backgroundPath.isEmpty()) { uid_t uid = index.data(QLightDM::UsersModel::UidRole).toUInt(); backgroundPath = getAccountBackground(uid); accountBackground = backgroundPath; if(backgroundPath.isEmpty()) backgroundPath = m_configuration->getDefaultBackgroundName(); } //登录后绘制桌面背景而不是登录背景 m_greeter->setrootWindowBackground(rootWinPicture,0,backgroundPath); //读取/var/lib/lightdm-date/用户名/ukui-greeter.conf, //判断是否设置了该用户的登陆界面的背景图片. QString userConfigurePath = m_greeter->getEnsureShareDir(index.data(QLightDM::UsersModel::NameRole).toString()) + "/ukui-greeter.conf"; QFile backgroundFile(userConfigurePath); if(backgroundFile.exists()){ QSettings settings(userConfigurePath,QSettings::IniFormat); settings.beginGroup("greeter"); if(settings.contains("backgroundPath")){ QString filepath = settings.value("backgroundPath").toString(); if(!filepath.isEmpty() && isPicture(filepath)){ backgroundPath = filepath; } } if(accountBackground.isEmpty() && settings.contains("color")){ QString drawBackgroundColor = settings.value("color").toString(); if(drawBackgroundColor.length() == 7 && drawBackgroundColor.startsWith("#")){ drawBackgroundColor = drawBackgroundColor.remove(0,1); bool ok; int color = drawBackgroundColor.toUInt(&ok,16); m_greeter->setrootWindowBackground(rootWinColor,color,NULL); } } settings.endGroup(); }else{ QSettings settingsbak(userConfigurePath,QSettings::IniFormat); settingsbak.beginGroup("greeter"); settingsbak.setValue("backgroundPath","/usr/share/backgrounds/warty-final-ubuntukylin.jpg"); settingsbak.endGroup(); settingsbak.sync(); QFile file(userConfigurePath); file.setPermissions(QFile::WriteOther | QFile::ReadOther); } QSharedPointer background(new Background); background->type = BACKGROUND_IMAGE; background->image = backgroundPath; static_cast(parentWidget())->setBackground(background); } void GreeterWindow::onUserSelected(const QModelIndex &index) { qDebug() << index.data(QLightDM::UsersModel::NameRole).toString() << "selected"; m_loginWnd->setUserIndex(index); switchWnd(1); } void GreeterWindow::updateLanguage(QString userName) { QString language; QString formatsLocale; QDBusInterface iface("org.freedesktop.Accounts", "/org/freedesktop/Accounts", "org.freedesktop.Accounts",QDBusConnection::systemBus()); QDBusReply userPath = iface.call("FindUserByName", userName); if(!userPath.isValid()) qWarning() << "Get UserPath error:" << userPath.error(); else { QDBusInterface userIface("org.freedesktop.Accounts", userPath.value().path(), "org.freedesktop.DBus.Properties", QDBusConnection::systemBus()); QDBusReply languageReply = userIface.call("Get", "org.freedesktop.Accounts.User", "Language"); if(!languageReply.isValid()) qWarning() << "Get User's language error" << languageReply.error(); else { language = languageReply.value().variant().toString(); if(!language.isEmpty()){ onLanguageChanged(language); } } QDBusReply fomatsLocalReply = userIface.call("Get", "org.freedesktop.Accounts.User", "FormatsLocale"); if(!fomatsLocalReply.isValid()) qWarning() << "Get User's language error" << languageReply.error(); else { formatsLocale = fomatsLocalReply.value().variant().toString(); if(!formatsLocale.isEmpty()){ if(formatsLocale.startsWith("zh")){ local = QLocale::Chinese; QString date = local.toString(QDate::currentDate(),"yyyy/MM/dd ddd"); lblDate->setText(date); } else{ local = QLocale::English; QString date = local.toString(QDate::currentDate(),"yyyy/MM/dd ddd"); lblDate->setText(date); } } } } } void GreeterWindow::updateSession(QString userName) { QDBusInterface iface("org.freedesktop.Accounts", "/org/freedesktop/Accounts", "org.freedesktop.Accounts",QDBusConnection::systemBus()); QDBusReply userPath = iface.call("FindUserByName", userName); if(!userPath.isValid()) qWarning() << "Get UserPath error:" << userPath.error(); else { QDBusInterface userIface("org.freedesktop.Accounts", userPath.value().path(), "org.freedesktop.DBus.Properties", QDBusConnection::systemBus()); QDBusReply sessionReply = userIface.call("Get", "org.freedesktop.Accounts.User", "XSession"); if(!sessionReply.isValid()) qWarning() << "Get User's session error" << sessionReply.error(); else { QString session = sessionReply.value().variant().toString(); onSessionChanged(session); } } } void GreeterWindow::onCurrentUserChanged(const QModelIndex &index) { for(int i = 0;iuserlist.count();i++) { UserEntry *entry = m_userWnd->userlist.at(i); if(entry->userIndex().data(QLightDM::UsersModel::NameRole).toString() == "*login") entry->setUserName(tr("Login")); } setBackground(index); if(m_greeter->getdrawBackgroundIsStarted()){ if(future.isStarted()&& future.isRunning()){ future.waitForFinished(); }else{ future = QtConcurrent::run([=](){ m_greeter->setrootWindow(); }); } }else{ future = QtConcurrent::run([=](){ m_greeter->setrootWindow(); }); } QString realName = index.data(QLightDM::UsersModel::NameRole).toString(); bool islogin = index.data(QLightDM::UsersModel::LoggedInRole).toBool(); if(islogin || realName == "*login" || realName == "*guest"){ if(m_sessionLB){ m_sessionLB->hide(); } } else{ if(m_sessionLB){ m_sessionLB->show(); } } if(realName == "*guest" || realName == "*login") return; updateLanguage(realName); if(!m_sessionHasChanged && m_sessionsModel->rowCount() > 1) { QString session = index.data(QLightDM::UsersModel::SessionRole).toString(); onSessionChanged(session); } update(); } void GreeterWindow::onUserChangedByManual(const QString &userName) { m_userWnd->setCurrentUser(userName, true); updateLanguage(userName); updateSession(userName); } void GreeterWindow::setWindowVisible() { if(m_powerWnd && m_powerWnd->isVisible()) m_powerWnd->close(); switchWnd(1); update(); } /** * @brief GreeterWindow::showPowerWnd * 显示电源对话框 */ void GreeterWindow::showPowerWnd() { //如果已经打开了电源对话框则关闭 if(m_powerWnd && !m_powerWnd->isHidden()){ m_powerWnd->close(); switchWnd(1); update(); return; } scrollArea->hide(); m_loginWnd->hide(); m_powerWnd = new PowerManager(this); m_powerWnd->adjustSize(); // m_powerWnd->setGeometry((width()-ITEM_WIDTH*5)/2, // (height()-ITEM_HEIGHT)/2, // ITEM_WIDTH*5,ITEM_HEIGHT); m_powerWnd->setFixedSize(m_powerWnd->windowSize()); m_powerWnd->move((width()-m_powerWnd->width())/2,(height() - m_powerWnd->height())/2); // connect(m_powerWnd,SIGNAL(switchToUser()) // ,this,SLOT(setWindowVisible())); connect(m_powerWnd, &PowerManager::switchToUser, this, [this]() { if(m_powerWnd && m_powerWnd->isVisible()) m_powerWnd->close(); switchWnd(0); }); m_powerWnd->setObjectName(QStringLiteral("powerWnd")); m_powerWnd->show(); update(); } /** * @brief GreeterWindow::showVirtualKeyboard * 显示或隐藏虚拟键盘 */ void GreeterWindow::showVirtualKeyboard() { if(!m_virtualKeyboard) { m_virtualKeyboard = new VirtualKeyboard(this); connect(m_virtualKeyboard, &VirtualKeyboard::aboutToClose, m_virtualKeyboard, &VirtualKeyboard::close); } m_virtualKeyboard->setVisible(!m_virtualKeyboard->isVisible()); setVirkeyboardPos(); } /** * @brief GreeterWindow::timedAutologin * 处理延时自动登录 */ void GreeterWindow::timedAutologin() { if(m_greeter->isAuthenticated()) { if(!m_greeter->authenticationUser().isEmpty()) m_greeter->startSession(); else if(m_greeter->autologinGuestHint()) m_greeter->startSession(); else if(!m_greeter->autologinUserHint().isEmpty()) m_greeter->authenticate(m_greeter->autologinUserHint()); } else m_greeter->authenticateAutologin(); } void GreeterWindow::setWindowPos(QWidget *widget, Qt::Alignment align) { QRect rect; rect.setSize(widget->size()); if(align & Qt::AlignHCenter) { rect.setLeft((width() - widget->width())/2); } if(align & Qt::AlignVCenter) { rect.setTop((height() - widget->height())/2); } if(align & Qt::AlignBottom) { rect.setTop(height() - widget->height()); } widget->setGeometry(rect); } void GreeterWindow::onLanguageChanged(QString languageCode) { if(languageCode == "") return ; if(m_greeter->lang() == languageCode) { return; } qApp->removeTranslator(m_configuration->m_trans); delete m_configuration->m_trans; m_configuration->m_trans = new QTranslator(); QString qmFile; if(languageCode.startsWith("zh")){ qmFile = QM_DIR + QString("%1.qm").arg("zh_CN"); setenv("LANGUAGE","zh_CN",1); setlocale(LC_ALL,"zh_CN.utf8"); } else{ qmFile = QM_DIR + QString("%1.qm").arg(languageCode); setenv("LANGUAGE",languageCode.toLatin1().data(),1); setlocale(LC_ALL,""); } m_configuration->m_trans->load(qmFile); qApp->installTranslator(m_configuration->m_trans); m_greeter->setLang(languageCode); } void GreeterWindow::showSessionWnd() { if(!m_sessionWnd){ m_sessionWnd = new SessionWindow(m_sessionsModel, this); #if (QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)) m_sessionWnd->setStyleSheet("QMenu::item{padding: 2px 10px 2px 10px;}"); #else m_sessionWnd->setStyleSheet("QMenu::item{padding: 2px 10px 2px 30px;}"); #endif connect(m_sessionWnd, &SessionWindow::sessionChanged, \ this, &GreeterWindow::onSessionChanged); } m_sessionHasChanged = true; if(m_sessionWnd->isVisible()){ m_sessionWnd->close(); m_sessionWnd = nullptr; setFocus(); } else{ m_sessionWnd->move(m_sessionLB->x(),m_sessionLB->y()-m_sessionWnd->height() - 3); m_sessionWnd->show(); m_sessionWnd->setFocus(); } update(); } void GreeterWindow::onSessionChanged(const QString &session) { qDebug() << "session changed: " << session; QString sessionTmp(session); if(session.isEmpty() || !sessionIsValid(session)) { //session不存在 QString defaultSession = m_greeter->defaultSessionHint(); //默认session存在 if(defaultSession != session && sessionIsValid(defaultSession)) { sessionTmp = defaultSession; } //列表中的第一个session else if(m_sessionsModel && m_sessionsModel->rowCount() > 0) { sessionTmp = m_sessionsModel->index(0, 0) .data(QLightDM::SessionsModel::KeyRole).toString(); } else { sessionTmp = ""; } } QString sessionPrefix = sessionTmp.left(sessionTmp.indexOf('-')); if(sessionTmp == "ukui-wayland") sessionPrefix = "ukui_wayland"; QString sessionIcon = IMAGE_DIR + QString("badges/%1_badge.svg") .arg(sessionPrefix.toLower()); QFile iconFile(sessionIcon); if(!iconFile.exists()){ sessionIcon = IMAGE_DIR + QString("badges/unknown_badge.svg"); } qDebug() << sessionIcon; if(m_sessionLB) m_sessionLB->setIcon(QIcon(sessionIcon)); m_greeter->setSession(sessionTmp); if(m_userWnd && !m_userWnd->isHidden()) { m_userWnd->setFocus(); } if(m_sessionWnd){ m_sessionWnd->close(); m_sessionWnd = nullptr; } } bool GreeterWindow::sessionIsValid(const QString &session) { for(int i = 0; i < m_sessionsModel->rowCount(); i++) { QString sessionKey = m_sessionsModel->index(i, 0) .data(QLightDM::SessionsModel::KeyRole).toString(); if(sessionKey == session) return true; } return false; } void GreeterWindow::onAuthenticationComplete1() { for(int i = 0;iuserlist.count();i++) { UserEntry *entry = m_userWnd->userlist.at(i); if(entry->userIndex().data(QLightDM::UsersModel::NameRole).toString() == "*login") entry->setUserName(tr("Login")); } } ukui-greeter/ukui-greeter/greeter/language.h0000644000175000017500000000231114204635151020105 0ustar fengfeng/** * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 LANGUAGE_H #define LANGUAGE_H #include struct Language { /// 语言码,例如zh_CN, en, en_US QString code; /// 语言码对应的名称,中文, 英语 QString name; /// 地区 QString territory; }; typedef QVector LanguagesVector; /// 获取语言列表 LanguagesVector &getLanguages(); /// 根据LANG获取语言 Language getLanguage(const QString &lang); #endif // LANGUAGE_H ukui-greeter/ukui-greeter/greeter/screenmodel.h0000644000175000017500000000256714204635151020637 0ustar fengfeng/* screenmodel.h * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 SCREENMODEL_H #define SCREENMODEL_H #include #include #include class QDesktopWidget; class ScreenModel : public QAbstractListModel { Q_OBJECT public: ScreenModel(QObject *parent=0); int rowCount(const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role = Qt::UserRole) const; private slots: void onScreenResized(int screen); void onScreenCountChanged(int newCount); void loadScreens(); private: QVector m_screen; QDesktopWidget *_desktop; }; #endif // SCREENMODEL_H ukui-greeter/ukui-greeter/display-switch/0000755000175000017500000000000014204635151017463 5ustar fengfengukui-greeter/ukui-greeter/display-switch/displayservice.cpp0000644000175000017500000001220014204635151023210 0ustar fengfeng/* displayservice.cpp * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "displayservice.h" #include #include #include #include DisplayService::DisplayService(QObject *parent) : QObject(parent) { } bool DisplayService::switchDisplayMode(DisplayMode targetMode) { //获取显示器列表 getMonitors(); //如果当前只有一个显示器就什么也不做 if(monitors.keys().size() < 2) { qDebug() << "only one monitor, doNothing"; return false; } QProcess subProcess; //模式切换 try { switch(targetMode){ case DISPLAY_MODE_ORI: { QString XRANDR_ORIONLY = "xrandr --output " + monitorNames[0] + " --auto"; for(int i = 1; i < monitorNames.size(); i++) XRANDR_ORIONLY = XRANDR_ORIONLY + " --output " + monitorNames[i] + " --off"; qDebug() << "XRANDR_ORIONLY: " << XRANDR_ORIONLY; subProcess.start(XRANDR_ORIONLY, QIODevice::NotOpen); break; } case DISPLAY_MODE_CLONE: { //查找最佳克隆分辨率 QString BEST_CLONE_MODE; bool find = false; for(auto & mode_0 : monitors[monitorNames[0]]) { for(auto & mode_1 : monitors[monitorNames[1]]) { if(mode_0 == mode_1) { BEST_CLONE_MODE = mode_0; find = true; break; } } if(find) break; } QString XRANDR_CLONE = "xrandr --output " + monitorNames[0] + " --mode " + BEST_CLONE_MODE; for(int i = 1; i < monitorNames.size(); i++) XRANDR_CLONE = XRANDR_CLONE + " --output " + monitorNames[i] + " --mode " + BEST_CLONE_MODE + " --same-as " + monitorNames[0]; qDebug() << "XRANDR_CLONE: " << XRANDR_CLONE; subProcess.start(XRANDR_CLONE, QIODevice::NotOpen); break; } case DISPLAY_MODE_EXTEND: { QString XRANDR_EXTEND = "xrandr --output " + monitorNames[0] + " --auto"; for(int i = 1; i < monitorNames.size(); i++) XRANDR_EXTEND = XRANDR_EXTEND + " --output " + monitorNames[i] + " --right-of " + monitorNames[i-1] + " --auto"; qDebug() << "XRANDR_EXTEND: " << XRANDR_EXTEND; subProcess.start(XRANDR_EXTEND, QIODevice::NotOpen); break; } case DISPLAY_MODE_ONLY_OUT: { QString XRANDR_OUTONLY = "xrandr --output " + monitorNames[0] + " --off --output " + monitorNames[1] + " --auto"; qDebug() << "XRANDR_OUTONLY: " << XRANDR_OUTONLY; subProcess.start(XRANDR_OUTONLY, QIODevice::NotOpen); break; } } subProcess.waitForFinished(); return true; } catch(const QException &e) { qWarning() << e.what(); return false; } } QString DisplayService::getFirstDisplayXrandrCmd() { //获取显示器列表 getMonitors(); //组装第一个设备名显示命令 QString firstXrandrCmd = "xrandr --output " + monitorNames[0] + " --auto"; return firstXrandrCmd; } void DisplayService::getMonitors() { QProcess subProcess; subProcess.setProgram(QStandardPaths::findExecutable("xrandr")); subProcess.setArguments({"-q"}); subProcess.start(QIODevice::ReadOnly); subProcess.waitForFinished(); QString outputs = subProcess.readAll(); QStringList lines = outputs.split('\n'); QString name; QVector modes; bool find = false; QString lastName; monitorNames.clear(); monitors.clear(); for(auto & line : lines) { if(line.indexOf(" connected") != -1) { //找显示器名称 name = line.left(line.indexOf(' ')); monitorNames.push_back(name); if(find) //又找到了一个显示器,将上一个显示器的信息存到map monitors[lastName] = modes; find = true; lastName = name; modes.clear(); } else { if(line.startsWith(' ')) { //获取分辨率 QString mode = line.trimmed().split(' ').at(0); modes.push_back(mode); } } } monitors[name] = modes; //将最后一个显示器的信息存储到map qDebug() << "find motinors: " << monitorNames; } ukui-greeter/ukui-greeter/display-switch/displayservice.h0000644000175000017500000000265714204635151022674 0ustar fengfeng/* displayservice.h * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 DISPLAYSERVICE_H #define DISPLAYSERVICE_H #include #include #include enum DisplayMode{ DISPLAY_MODE_ORI = 1, DISPLAY_MODE_CLONE = 2, DISPLAY_MODE_EXTEND = 3, DISPLAY_MODE_ONLY_OUT = 4 }; class DisplayService : public QObject { Q_OBJECT public: explicit DisplayService(QObject *parent = nullptr); bool switchDisplayMode(DisplayMode targetMode); QString getFirstDisplayXrandrCmd();//获取第一个设备名称,并组装命令 private: void getMonitors(); QMap> monitors; QStringList monitorNames; }; #endif // DISPLAYSERVICE_H ukui-greeter/ukui-greeter/display-switch/display-switch.pri0000644000175000017500000000042514204635151023144 0ustar fengfengQt += core gui widgets DEFINES += QT_DEPRECATED_WARNINGS FORMS += HEADERS += \ $$PWD/displayservice.h \ $$PWD/displayswitch.h \ $$PWD/ui_mainwindow.h SOURCES += \ $$PWD/displayservice.cpp \ $$PWD/displayswitch.cpp RESOURCES += \ $$PWD/ds-res.qrc ukui-greeter/ukui-greeter/display-switch/ui_mainwindow.h0000644000175000017500000001337714204635151022520 0ustar fengfeng/* ui_mainwindow.h * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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. **/ /******************************************************************************** ** Form generated from reading UI file 'mainwindow.ui' ** ** Created by: Qt User Interface Compiler version 5.9.4 ** ** WARNING! All changes made in this file will be lost when recompiling UI file! ********************************************************************************/ #ifndef UI_MAINWINDOW_H #define UI_MAINWINDOW_H #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE class Ui_MainWindow { public: QWidget *centralWidget; QPushButton *mode_1; QPushButton *mode_2; QPushButton *mode_3; QPushButton *mode_4; QLabel *bg_mode_1; QLabel *lb_mode_1; QLabel *split_1; QLabel *split_2; QLabel *split_3; QLabel *lb_mode_2; QLabel *lb_mode_3; QLabel *lb_mode_4; QLabel *bg_mode_2; QLabel *bg_mode_3; QLabel *bg_mode_4; void setupUi(QMainWindow *MainWindow) { if (MainWindow->objectName().isEmpty()) MainWindow->setObjectName(QStringLiteral("MainWindow")); MainWindow->resize(744, 126); centralWidget = new QWidget(MainWindow); centralWidget->setObjectName(QStringLiteral("centralWidget")); mode_1 = new QPushButton(centralWidget); mode_1->setObjectName(QStringLiteral("mode")); mode_1->setGeometry(QRect(5, 5, 180, 116)); mode_2 = new QPushButton(centralWidget); mode_2->setObjectName(QStringLiteral("mode")); mode_2->setGeometry(QRect(190, 5, 180, 116)); mode_3 = new QPushButton(centralWidget); mode_3->setObjectName(QStringLiteral("mode")); mode_3->setGeometry(QRect(375, 5, 180, 116)); mode_4 = new QPushButton(centralWidget); mode_4->setObjectName(QStringLiteral("mode")); mode_4->setGeometry(QRect(560, 5, 180, 116)); bg_mode_1 = new QLabel(centralWidget); bg_mode_1->setObjectName(QStringLiteral("bg_mode_1")); bg_mode_1->setGeometry(QRect(63, 43, 64, 64)); lb_mode_1 = new QLabel(centralWidget); lb_mode_1->setObjectName(QStringLiteral("lb_mode")); lb_mode_1->setGeometry(QRect(63, 18, 64, 20)); lb_mode_1->setAlignment(Qt::AlignCenter); split_1 = new QLabel(centralWidget); split_1->setObjectName(QStringLiteral("split")); split_1->setGeometry(QRect(187, 18, 1, 90)); split_2 = new QLabel(centralWidget); split_2->setObjectName(QStringLiteral("split")); split_2->setGeometry(QRect(372, 18, 1, 90)); split_3 = new QLabel(centralWidget); split_3->setObjectName(QStringLiteral("split")); split_3->setGeometry(QRect(557, 18, 1, 90)); lb_mode_2 = new QLabel(centralWidget); lb_mode_2->setObjectName(QStringLiteral("lb_mode")); lb_mode_2->setGeometry(QRect(248, 18, 64, 20)); lb_mode_2->setAlignment(Qt::AlignCenter); lb_mode_3 = new QLabel(centralWidget); lb_mode_3->setObjectName(QStringLiteral("lb_mode")); lb_mode_3->setGeometry(QRect(433, 18, 64, 20)); lb_mode_3->setAlignment(Qt::AlignCenter); lb_mode_4 = new QLabel(centralWidget); lb_mode_4->setObjectName(QStringLiteral("lb_mode")); lb_mode_4->setGeometry(QRect(618, 18, 64, 20)); lb_mode_4->setAlignment(Qt::AlignCenter); bg_mode_2 = new QLabel(centralWidget); bg_mode_2->setObjectName(QStringLiteral("bg_mode_2")); bg_mode_2->setGeometry(QRect(248, 43, 64, 64)); bg_mode_3 = new QLabel(centralWidget); bg_mode_3->setObjectName(QStringLiteral("bg_mode_3")); bg_mode_3->setGeometry(QRect(433, 43, 64, 64)); bg_mode_4 = new QLabel(centralWidget); bg_mode_4->setObjectName(QStringLiteral("bg_mode_4")); bg_mode_4->setGeometry(QRect(618, 43, 64, 64)); MainWindow->setCentralWidget(centralWidget); retranslateUi(MainWindow); QMetaObject::connectSlotsByName(MainWindow); } // setupUi void retranslateUi(QMainWindow *MainWindow) { MainWindow->setWindowTitle(QApplication::translate("MainWindow", "MainWindow", Q_NULLPTR)); mode_1->setText(QString()); mode_2->setText(QString()); mode_3->setText(QString()); mode_4->setText(QString()); bg_mode_1->setText(QString()); lb_mode_1->setText(QString()); split_1->setText(QString()); split_2->setText(QString()); split_3->setText(QString()); lb_mode_2->setText(QString()); lb_mode_3->setText(QString()); lb_mode_4->setText(QString()); bg_mode_2->setText(QString()); bg_mode_3->setText(QString()); bg_mode_4->setText(QString()); } // retranslateUi }; namespace Ui { class MainWindow: public Ui_MainWindow {}; } // namespace Ui QT_END_NAMESPACE #endif // UI_MAINWINDOW_H ukui-greeter/ukui-greeter/display-switch/displayswitch.h0000644000175000017500000000247514204635151022533 0ustar fengfeng/* displayswitch.h * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 DISPLAYSWITCH_H #define DISPLAYSWITCH_H #include namespace Ui { class MainWindow; } class DisplaySwitch : public QMainWindow { Q_OBJECT public: explicit DisplaySwitch(QWidget *parent = nullptr); void initUi(); signals: public slots: void onDisplaySwitchSelect(); void onDisplaySwitchConfirm(); void onPositionChanged(const QRect&); void onGlobalKeyRelease(const QString &key); private: Ui::MainWindow *ui; int currentMode; bool selecting; }; #endif // DISPLAYSWITCH_H ukui-greeter/ukui-greeter/display-switch/ds-res.qrc0000644000175000017500000000134514204635151021372 0ustar fengfeng res/bg.png res/bg2.png res/bg_f5.png res/btn1.png res/btn2.png res/btn3.png res/btn4.png res/bt_off.png res/bt_on.png res/hover.png res/hover2.png res/hover3.png res/split.png res/touchpad-off.png res/touchpad-on.png res/wifi_off.png res/wifi-off_.png res/wifi_on.png res/wifi-on_.png ukui-greeter/ukui-greeter/display-switch/res/0000755000175000017500000000000014204635151020254 5ustar fengfengukui-greeter/ukui-greeter/display-switch/res/wifi-off_.png0000644000175000017500000001634514204635151022640 0ustar fengfengPNG  IHDRttEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp <IDATx]YleNv$ξinI4$ݩ!r @p@\P$s$. Oi!"M.Ič؉4}r<cL<3~idv>όz+0h0 `3L|`0 &>g0 `3L|`0 &>g0 `+[l1>|A<7,O?хƈ4hvA-믿nm3G"< o1_{ 6l( {/.^8>m۶sss[yVc\tA1y䑬^xž_!AG}߾}zKDmmm-k833ӯmnnG}K__0>$>H/|d1߿^{8 cǎb՚_!V\7b~עZZZZ]!x/gϞ#~D9M&S"5t}VNW^yf&;5vww'|r[d2H{ϖ kGmõSGb0~@lN y|*kZʪ^|oQPU'c@mg( s"P{!ޟ{dc+n!k?x"}ԩxJŅ ZUCGL|,}"//˨{^[[21ǎk^ zp±`}Kjn{%:p{bKߡzpW~|||bϝ XW o^2ؘ v&vޑWDZ:$ dz}txXPe7xL|?n.@k`b1Q84zE+MN3 qo=/|?$jSୁF`嗲6ly +Ԫ~@H/g{ xfee|䆃>?7͉YgaD:UO|AC-4{8Hi׭[WBAK@CBaaa M&}&x~Ν^xxrHMZ8H{'6ā A+oDV@҄jI#] OA D>Uj曍 Eb/O?vVUU?a, @dx…zn!/ R3߉>doz7ߨ6_@^we8bnO2-g0 `3L|`0 &>g0 [`3L|`0 &>g0 REG;t: n=szs< 3$^׀)))Ѩ5 1xcZF >^ k~rrrfjj73111t:|hhȍBI_V'$$kydX,kA@Ǥ 0=>>>p8q ͢v;&Dp}rr.))IA:NC L,4k ;X\###n1 RMWXaɉKLLԓuE?VMAM"e&\NzW/p A*1iii"3Iw2zJ ׆T!n$i5V\i!"iܹ`GH[HoSdf% {E0*5RWZe!)o6QQ$o޼Xmzz~ӦMV&LGAAA|OOxcc~UOJ|֭KϷ XH zZywMWTT$ёb]=#@P<1NsFm ,i7nܘ@z|r8Y97ң=ċ?d]6&/_UxU?իͥI0Z_)F"rlll)x.$ 'HWOZ'5w=25N =cӐ FKFnܸ1EjMeee2]y#~pH>a8޽;ESR'a$&&Zd==ΥR \v V12Μ93dXćD,//_>)$` =dP MK R,uF܊+LkK$D*4pW_qćߺuk ܓR}P]n߾=FnmaY,ZtH a2ӧO)M+7oNزeKT+SN'IwX ,d/J=Hx0_&@YuuuJaaa:kWWWp5 pʕFl"ZGꆔpeOlΝ;B}vHVfDjŶwɾnYd"Bτ+OLu$~f;фA)V ,,fLAlRyyyX?뭭} 4V].RNF3+GVj5_۵kW\biH\b" Ba+Ih`Р,1eff__n">\q=P&-b;2Ν;g# 8*-r0!(Da>N=rmK(3d< ׋/ Be 7FFF0жPa1O*K.*,:B+ґ 䑘ȱk; _:&bx3L\A"!:r_CRK a'>J{Pݕ BqDX!X6T )yڈL-W/!EdÆ ɡFtpgy0dx\t6U: u@XSSSZDǽ&MԆ;7B] gSX)r2J`r8n ܊[:H#UIT:}PΝq-MVTT$jsҥ9VVRkeee5kra'R\DdMVU)@FW{+$¾ = [O?h]]]?$p~IIIBNNEGpPqu$uuu9GNˀFwuuuz0~{p \^_@ XO8+v8|ڵUV2AOxHĻyڵkcbcۻٓL+X(Zޒ~~EXzjӶmHHHЩ>\:6 LQ! jdddI{&Irh"a#>.qK|ww?'V;vnܸ׫& 3,:O'b hIH:n+Fx28E;k"~b.H^1~JtS~ ݏhzcTS™V±Lfԟʃ=o?vXBݻ3 7BM= :rBU!{Sj?~/ܩ HY@KIII<:c  &Ƙ #&ޫ| 9ҽ={vD u=[=p9tʮKp!us˓ZJF]ȳ!Mjܤ'f.<vr|"#|X |Ar=UK|1Q\\lAJj(C ;6h;h oH Ѐ`t̙j }5RHF1cCr$\1Dn=Z ¦&I'E IOQr#>#*s\.Blv:6Fdc%>hkkC}@8RHݻwggg:=G|c/t9$0N_Tk)PMpܻwo\iGWDcgg j3؃7w}{BnTL|" 3(SRsF_Lt)G.ԸT(ZI]R1eٳ'#TÀC)Z;@^?.&V\Wc6u~Be)Q bP՚!Rk~͛p@ ?L 2%O*~6շ7 @:.ƍ  U\E%)?x~Rt\+׎{J+tbD[O>=oͲJf<,E"[nM 4 nErrY#U[[/O*K(lΠC zu4tP{D.ʰHIt6TSL|;v9&@ HrrrȧA{9B0"Qj ".)vfA+<ڵ+}Mpy0hHXe{{3ju+UJ1j}B,vlAAL/t=>M4`TJwǗx <7555blڴ)"eÆ I$pJzC;ңbP BdG;G{)x!ZUU1 ЙQSJwY :]*F+m1UƍMMMvntAHy@ė&AFZʴdoQ,͛Hh~el<.Ȫz#Vmԏn۶-/^*ܰjÖ>25;w@AR i؃jF.{0*UWW'HY(UNNVhlãVp۷/Ȫ;8wx^/52EY~,RmLj&#iWOOwzd5b*fYPv?B^!|LP ߬>jgo[37Z; FFFw=(rLA%AIX w+V\~P|ԕ/EUPBzrk5yoeHRڀr?05Zù _FPA#k׮"F*.Ll怣qo,..yZ1^K'lֆ"$<i}R  ]k֬1#?(׊ks&B1zrjjjr*,//O\\g٩ƦQH ]%ɓWUU 鿻&o(KD2ؑ#GEzop.8:<D+="ZZ (KىE$hlK&~)));'(-q؆)VF(/@P ؠ8H2˗/?%%J(^n獍s9e!B@ÈO`+T`w<0iRdNz&,.p%##6"_ )--MDY J ҊQ:BMqWM0qa{˳,eB|#&SVW ֯_o3hBzܷR*4| ib w/{f{ c`,w/ߛD'2˜Bs'_v }^jժŘ0_(((@X 116a&,eKȈ;ė~/A; C L|Y]/(2g=/G̪;UYh1"w AP"RglѩıpuԔca&,sD cr2_8sN5˜|gJϟ??(ʃ10#.DCCCni v;!aʕ+L|ŠotSSS ،-fIWȫ.Iځ ҁJ-HȲD΍НyrrrVW[[0{oLf8Z(cazz?vL|USvg2 `3L|`0 &>g0 `3L|`0 &>g0 `3 `3L|`0 &>g0 `3ˎ`*2[.IENDB`ukui-greeter/ukui-greeter/display-switch/res/wifi_on.png0000644000175000017500000000716114204635151022421 0ustar fengfengPNG  IHDR=+=tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp v- !IDATxUmmozmSPĖZS!A &~4G4P11DD@hQ+UB{[*) ^fBzz왹gf$oM{93{3299yԈ\0%L )S`JL S)0%`JL )S`J0%)0%`JL S0%L )S`JL S(ø}ߵ7k"9K]\#skk^h&S뿮mvn aȗs^csגеԵ뵌WMa]VZҷZ:utXM70jtn+ :SI#{\n9Np:6ƪ<ϵ9榢)E)㮍עFɮծ嘲u{tmGY0e=Q>89LqA0e P|N6fכ\Ι9 QiH#y>S|ָE)&-De%;\lY{41 s*?nYNI׿DIs\0{%,G4Zy12ʕ>:q"LY=3TBW􉮕aƱ|ÜD_} c̉)+@c\w|7ZȺ/{-Ko e֐Rc8,5B}w+8\yص>ceLYؕ=w [hO Y>MLȆSPxkng >)se33yY)Ӭ(SJl{± S\w$^s-u{\pBMU,߲!s*#q;_e\:vΠAG,+BgS[-_3c/hMm>iH-57</{ ;sVE >a8s3:Yt 'T-T+g5?2=?eY У'l 6Jhx[Dh|J;I7鋥k{3OjMmCwwi];1}^R}Z?-bQAkе7uk[=X,[F)Zb3hٖNoc3R&)Fj6z4^_kٖ30e=9}M~sJ Ԋ/lX,h.(cVêL9lCN\T wQV ǮO7qNv S/EƭS=5l7goF4U;΢,=Q0hR:V|8-e}!%g\ vn%.\S[$S-hv>z|{)u R[ˠR s%o3)V'Z="<6 sy{-KoXIwN5va|'{vXeik0(Pu6 m[Rk}N4Tq.CS/fcUs"Ύ;{)Q)-=XQv_7l3`P G;%޵R.wܚm\:LYZvq_?ǽ%Ĕe)KCk`6|MVΊHL .w|-1e}0%~a7vq)1eYYPvc\JLYڠjQlt4]t+3ypLY2ڬYx5y(8LY ڙWO_|zݗ-1C kh-_%0Zeni)o8;m֪Z+~D'e 2L )S`JL S)0%L )S`J0%)0%`JL S`J0%L )S`JL S)0%rM [IENDB`ukui-greeter/ukui-greeter/display-switch/res/split.png0000644000175000017500000000176114204635151022122 0ustar fengfengPNG  IHDRZ>tEXtSoftwareAdobe ImageReadyqe<qiTXtXML:com.adobe.xmp kIDATxb{.(-0JIENDB`ukui-greeter/ukui-greeter/display-switch/res/btn1.png0000644000175000017500000000452114204635151021630 0ustar fengfengPNG  IHDR@@iq pHYs  tEXtSoftwareAdobe ImageReadyqe<IDATx[[g>s{IB(jEX!R)/J'hECB P*R.$҇^6<ꅕJeرcw9s2ⲟ=#@3\=xqbzw.|b4tyt>b" &'^634̎_V7Es00 PXf+StMh @*zxÚv*,#^oql&2;Xov4" I`z4g00THKTtP*"E=_W&m8\[3>>Y3S{S_,",7P6ip(+wЀa!ExYDS?r-JE</sF8yٿ4N6u$I<BIƂ9)_CS+ еH V$TV%A~1w S [5'ՖKG$CVp"qL}rkvII$Dikd!w*|ǡW>-o~ %`a"+&ڇkVuU@}.I&5VocJj>EPPZxBkA!"E"-بIds;ٝ$3ۙMM=0\M]V_B2㟭Yfh-|fffСC;:ڵkjSbf*5]3mZcC= ml`ˌSkȬ˰{o$VCyӺ~xgPa]Ag0~OY;J}wT@Qs fm޳Ʒ/b54C5wBYoh5H- ~Ր?> T4D}S Gc8ټ~Vchy(ƚ+/ LRRhJ {⭵XI`{0fCi/ PpLF\!\ d~U6"A99|,(? MP 1{U>" }Rpe|š/^ Z@@#{?\&CG ? t1]A{2T7&ݥ3hLj9y#e2Ěg}@@&D1/ wMi<&A@7>wf5D E(ھ5 GP J…J#e:,h!be ]?s S/E1uJ%C`O3BD0VuF$Ȉ'Jh]*w Ɗy)R{0k(7M^=$FS]Y Xرpcw᧓7+ ֖4(^<7OP<јq@S$\W@jhȺdǀ?r,2fVLuMjqdR}&%AF͌WP_CTCܚ; NӐHԋr%YCGԎX\WD 2T̕JPTT(W3c=c-8NbPmj&୵}-*uT%xBI\y8e=@L8x+`ggpO4"ᣅ +Ie_-%ඔdcddESYx& <˗ r|:r0exߠF12fdːyKL *Rc,ɤۥ)Ӫ>w 5 @qx<1M~ؙ^X].ⱈ$+(,_pA~v&W*.%F4daNe / ablNpG (SP'Fn(R\" \}$x$=A8P5ӧ?;Yb[ w8E&2<^sev7CaM8exu1MbhǑzSjܾAuxi_ ?lGP Yg\NC@Ew1A ؗ!2AvOG~ W7 \%)z:\);6"%DOvu̬e{8H;[?pO2 )X#ۼgju~nߞ4ͿeWVB˵;-,V\k-F&H@$ܕS=lGRFVn{ԛqOoGg}6H55=S@)W\#-bNOOƫkX `dIENDB`ukui-greeter/ukui-greeter/display-switch/res/bt_on.png0000644000175000017500000000503314204635151022064 0ustar fengfengPNG  IHDR=+=tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp M5IDATx٫eTiVDYAAP!DE!)IQEXzBP7MMIEMO3?3ysy3199$-b7H R)R)R)AJ %@J %@J %H H H R)R)R)AJ %@J2mY0M,|PMy,CA6wk5y>{ȃ ) RB"DJ(@T@JPHrHd_m} RY{"')@l{Gnm %{ci )bKȲ1݅\;_*_=}ՠ+@̏˾yi sMy GőMcj;[HY? HmE"%bP7땅&E.tO3ڰ2s~8`=#7G־ŴKb.=y,q*=rJ ]2٣Mo~kGFqfWZ82M|v yB94 r-eT h?YWp^P{TTtz7rdy,!5ɑ;=KπFX9#r}LCܒ 5fIDATxoU/JjFQ+ZĠF(FH4$PXK)Rz̙73^s??0}~zg][077 п'RR % %RR % %R % %RR % %RR %RR % %RR %RR % %RR % %R % %RR % %Rb)i@ʔXa)S*- L-[8Q=jDRj -vXF\b"M<#Uf1q^ !ec Mxb"A+2_q όc.6>$Rrm65]VNH 厞_WkkO[kgJ8yNkӷz+}iMoZH&U͝}/Xb(B-~}TUʷB m_>np]39rWPLv4{ csUOr  rn7RNho{>:^=rH>9XBEFʒ>JG,C^a m;)/Ƭ?c33`,ޏy}gL9Yc)gew sRs=Tڳ$H+Bqf:Ϭ}[(&'x\q҉uqeO;ͷ܀u^vyRʈ+!nم\)yѱgC$7Rro] 4u> *)-?~5})cѧ6ڟJI9Rv]k\Δ1-p1wQLyˡbUS ^O+lx JJQ6A@C++pz֑&6VOcw2o\?|*){YgqOWtB&gCGpsRG4uSKBF3bWv)ҧ[z?ML;)'s-rr{F{Që?ɐ1JٖO6Ԝ槡FH Іu[_btPF9Y!eSTO3o"egt`&} ҆M ]>1!@oJl1#4j8F*2T e h9S+43)S@?ATP_=)S@@3l=7j@p!)h2 -4@Ð24ͣ)S)h4!~T e hP=IR^Rskc[ R@B%u:** #"aE~EA@@F$Ng˻:}];3+3z&w29\%++Zh{{~0+7~ttA綶PD g'Tϣ["?ا}c_W|Nרw|N^~* 9ܟWm̿s8*RrG}Sʵu5~ZFz  F)<$;װ@N2~2#@jKʯxI8 H b=njV˄)dJR'(  WBlL@@t pX 1b 4X*`Dy'3FV@tR j'H':A#NAHE@OR#'#@" |- &0( -W|2dj!\'2$>b1$@, A'b(7!i Yf$8Pn|Őh&+_Ȥ-2&&yR I%cŐa"tu9! LNk\ɡ 9D؊[N>,0ح8_O7vvv~\__u.A>yz#gt9af7ЎO1C_| w"EN3s!5K!M$4E/Jx郯a^y?͵zS Uk__ ʋ}x:[7_,+iC ^xf޿ 1g}ռ:mTzu['e/.-ȕ,AWi ? ᔾd䍆($}bEНc1Wid Ex\{v~칍>T6q}BI!;VI{&:^6;MF(е! X݈ĈGI壓(SZrBʈ:;G>m1`SoR._Ws ":ԖɚdN֖c1'6["@GGkϻ)E9g:&:BZ[#b5RPU=/؈O8?W ɴՖ1 @0S im()Ho-P^fL#rQ XONBt4#$ﳌ`b(JX06bȊDB^ao1Ћݍ{" *# ŐZ;dh%#p+y@ nB:?b P_Ƥ.T nbm= -(0B]߳J,)Rr'AL)dìMDnO>4 IDATxTWiR7 D)GQ!D71Ⱥ&k4q]f7$D Q JH/R*0. o; S~o} O 2T HA'>A O$|  HA'>A O$|  H7l6[OL;GFFn _4_V.믿vVUUU<{lsZz``ୱ>uﭷJ.cϏޱ>+QQQ ^+--sssF2%% xW^{uq93lEEE>9DNNNfc}^^^Gc}_[;::Z'rΝ O;_… !c=/b`;wo~ÇrsRe|ч___5ښWNKơ."""XF=~ڪiv[!X ŋ$^{p/s+f*[hzi=|{Ʊ|z|9\~ V] xrIyyy$ ^/D2cǎ|z(v7F_5 9 wlvvvkkkk>g_knn=~x6-^ _̨-355 6@8bؾ}{膁G D_~ٖl> \K6DK!pdebFacc9OYYY`BԨL6o޼/!!!c=Xe?1HO8cs1Dqqȏy.;;[$z9@RMMMc㽆hiis 8vH_;ھ`RHa.//ˉ(o݈D"ϢI"M[&hlذa ŋXeX:_$S.z,ޭxqD3ss Y2SΈbbbjyqqbo~e蟶Va4IyDA-A O$|  HA'>A O$|  HA'>A O OJ/"@di|^SSS_{㖖X {DHYq+ϟeuuue@QII sϨ@C<>z`OOO_www_{{{74fl( JEnoofff>[OOoj pU剖9 ;֎q֭UUU]999xJ=;;;m me:k, "i]!6h yyy%%%MM111;`.\Xd6t UVVARRRedddKҢEw3PWWW:t9I6! 6444DGGCCh(--!OcC>{ٲe`tp͍@rrr^|||) K.Sf rJnDDDq~~~' _Yfml<0et` 5ddd;w\Cu$| `Ŋzo+ŲTHOꫴ˗/? !лNv $kOOO=uT&jHbu7n,)#3Boܸwe5E٪(< xc|ǣHc\ulqBBBFhhhFNNN _ؼyuPP'zBz4olllŮ־;088!u \NNN1Y hWDNOO}TtttTP7224 sL%3g$?~{/Du݋-wPQQQ G{[ܹ .2VFT'ܠO]QaaaѡC,^>r[vDu;A5n*.**jzq +++ Z̝;wzEGG_"qb'UVڵk ~4[@?Sݻw[JJJš~,,,5}}}yd X#?k$|<; z|C/&KAbbbyxxx۱3M===炉d) #""" 3\J`S.]`{dQl{W^͇\"0[d |y`LU)))s]:y[>e:cس lݫp0񱁎A `l Ο?_C瓐M6ʦq̙֌д۷oJMc@l6 s9$ rQajt-==~ 0k'og1Q&]p$|=q2///7׿nOC~?Ϲ:2e'$$]%:t3TUUR sqqτ+++9gϞ䢢.`sYzU MMMg/]Tʊ>NNNǏ_`q={U^NLLY8YfΜ)gQ38:Ry@-QQQ[u[XXLvVX[[[ ;;LՏ;zɖUXXX駟^t:::j_-/` xpppC!|:Cv>xD?ׄa!!!!K0;p%=޽>텏KO<џL9$T￟ jO䓟,Ng\r6uvvQ3=ƋΝ;yyy W\tիW۷IݙPwLy"T8p 6L8[u˂2piƍ`T]L?_ ¯'2,,DPBs.44t΢O8w}|ddd s4mzDŽݻw -ZbٖPG⒍'ࠖrZ=r||rgI ?-~> _#GbbW^1 t%Gg"p$1)ԩSL~v8-۷oℇ=G 𛦕15 [zݞ={19Í[l݆.!69Lʂcǎr`SRRr21 q|\=)8|rS=$Qc5Ljk׮ig?+NItttг€XCʕ+2I%.]|N< 9'lmm{4kמfPCpWׯ/\'~b.^z|GGǤ+_7;tЋ5g&# ݌+p,A_ oTv)Ra4vΝoJA-NqAUfff>HIIibzX8j;::c.A]./A/$ D^zec%fcuV^ZZ}0թG0E=l %A78Mhh l6ہrqbQQQ8o{31 z{{9XYY2tCFFF֭[%/ր9`TM7o̎,vH]x6S 1]$|y֑#G| z /lŭ~L5??aPIЋH^?'[F}FEE t2q&9..>&RSST+Y^^^U=$|!/NV F2ݻ'L-J 'bccה;84"UGGGĒAٳg4k۸뒘B{3g*)))3iwZaK街L$*L(#Q!%@O ++g6"<<tNs= _HT:8o޼y|x I=A'07OddÚ>{͛cй ;8::ZA%# =m3\Gэ7N:U*Μ9'NX ~8oHssscǎ䎷wp26mT5ywpS&n13E hD3RLׇ;O1?1Kj޽G<8:E Nl˥KB&CGGG~۶m+Wdwu)T#I7pxmhΣxYJGOp̘^~(^3f- w"@Y*G bg;&K^}Օ/HܼTjjj߿+JH#vz^2s6666\l%OJJ-H.nl6ۂbVJjMg;{ @lXWd-_\/!!,$| e7|8634*jkkW& L ~Q2j  ǜ;hH sNAAAeuuu/^Wp0l-%%%p4TUU>f`ooЈ=>CMMMN]]]*,,,0Pʩ`Nu֝YWF8jËYL'RSS RRRcbbj&>Wt͚5F ,# okk I1iTT&5loQ*. yV@9HK6࠾aÆE?Ǎ*0خ]_rqAHqḺ0p9U~脄iHMs~:y [W~ySCrOsO{BF`"i>3z0,wssgZI x}eee՛oyr- k ܊8+J%􂂂~@=}߳R'b pplϐɹW2hrcM "'\x…{,KHoP Ni˖-Qߧ[i >|KCCC;:::KKK+pɥ~hp%FEE;vH_LqqVqD:~ia.S`p:>c\%&@ VX1}p `}}}srrr˗srr:{Uv^):gO}u<ɉˎ+++UV:}g b}1D?$PRR6Iboiu5|וgX6dffR02T̂Y01Ա , ˤ%,8Dw^72,,3IEKKKeooo6bX60 _$~gv=E_! j/lv'H227&VUUUIP嫩))((E-͛bff&^6Cj/R`?kwy/PEE ܛRPCUEEE]T$|񻺺 $|^Pe?&$'O,g2 I"IVVV{ZZZ.bmT$|m?F+TXI=PX2Z]yygggs=w'J___'b/\PC5KyRRRtuuY,) ]̙T$|իͶD/߿DOϞݻņ2zzzZ2}KNNܷojPpP)SXSSS]YYy8-''***4))j/qV/· AK3uPB)N">4O O$|  HA'>A O$|  HA'>A O$|O$|  HA'>A O$| Ĕ TV}IENDB`ukui-greeter/ukui-greeter/display-switch/res/bg.png0000644000175000017500000000342214204635151021353 0ustar fengfengPNG  IHDR~stEXtSoftwareAdobe ImageReadyqe<!iTXtXML:com.adobe.xmp p,IDATxKNP\_;&Bl9ntuX:Ul 4!qۋ 4m't2{9\\\^=̪2V1vj,VBMMooRz_d< EAm6trvzR~'/+`W)j\s49Cމ:br˔S'RXas䠮3(ϵ2KΕټ(c̉d@\bQ..@@@ \ \ \ p p.p...F \ \ \ p p.p..@@@ \ \ \ p p.p..@@@ \ \ \ \ p @'q4e9Wf'W\(re@\9E6%,J(OQm@\y\\qzC /tYΕQ<LR !e9WrF/t="o="B@@@ MVQsix:i6mӶ*zXaJ*b6j%˴M,*cΕA{EyS*պY9JXlmz/rɵ2BN?}ifO'g'lT IENDB`ukui-greeter/ukui-greeter/display-switch/res/hover2.png0000644000175000017500000000226714204635151022176 0ustar fengfengPNG  IHDRtGtEXtSoftwareAdobe ImageReadyqe<iiTXtXML:com.adobe.xmp ѯ IDATxA 0̿E$`Z Y``́90s`s`́90`́90s`s`́90́90s`́9s`́90́90s`́90s`́90s90s`́90s`́90s`0s`́90sp1  qiIENDB`ukui-greeter/ukui-greeter/display-switch/res/touchpad-off.png0000644000175000017500000001337014204635151023345 0ustar fengfengPNG  IHDRttEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp "IDATx TTG n# g5K0q͜$7dD6H6.g19'Fp1 8V]ݍbfBj0!O'B P|B(>!O?k,Ȉ]v&yzK8uJSHի/w^z\Gwϧ;p---bDHJ?6qf#!5ACF*kz1#G W:ǎk5cr֯_/0sLkbbb7")))]ŎOo ݝ\΁ݥ]PPcRl̙s7|S9o޼xH:bOX.[,VӦM3{.ӓR6, 7oެ*// J+}٪yժUE +{ٳg/+Aq+DT<裏& UIU3fx K6o\rFз8h'M9آ6r*apΗǔdeeiCUW׽hѢ?l۶-0Y%]ttt'|}-kK-ƍPlWTTTJQ0TzyyP?zh#Zow}4uɖqPI!B01dV Bֺ*ZW_YYYn-ĶjT.+?Z[%G}J kq^4 ilCH"E2+Y!Iw.=EUO0ZZѣHѫܸq0NJ\= 68lǧNjV1sLEx A/pL&M@LޱY c.MMM EcӸCmE\v3,iQ F':lpP'a޽{@Y@|> G<:VWl[kA !Nj0mOyR<)Uy|;>;y &D[d[jjj 0X/j@=KqS )Zm̍е7._\hѻw][_8[lSb%>\:sL*G;q:4.@ϥ*qJw@'mݺr^Zt-o!zHV8&!!!w)]CSiWTTTXVVvN] ̘1o9t5Go ,BB/ٯZܰ.wT̎^z5o 7>B &W#Đt9v7uÆ FsBSJ^]gϞ|nz1Ѫ.p}2Z]TnYR9֨t)iӦaaa,n1wlqDz/ 3/X?y*3.O3lHם* @ eRҟc ioߞ0v@OvOk1ŋ_Kwl}`cP 1hbN- a;w^z)]HA/j EN>}zέ-cʕ ڸdǢݝc85WRRRrTFY9I|=z'Z=ǬK C!/HKZ:.B\lll be֭;!0= Y׬YS/2T 2رcG!JNNNB~0vP 4rP]b='-˼W +Q_o7RJѳa-S]|}Xj„ 1jM`1= X*'bua5"^#9*DKݵuD'`eΝ`:UJ836r o3;;W^#ɣRcq9QKgT(/8wʰ#B/QWݵ2trwi(l9t5W;oOS ˃~ū OguP|B(>!O'B P|B(>,B P|B(>!O'B P|B(>!O'B P|B P|B(>!M_ի,_nh4-LSSDz}޽<@"=.xg/,55uSO='Oo*Ol-A>--"x_Dd٥{pNw[KW_^?wz,-0殉ugK!p n~+B^}]r#~QB)B6:cQ1qa_xh }!gh\zӡߊ=D@V%ɢ~t߲ ˺:7RD'4=I"cֿ^KtS8E ?u᭑ElaKyB>is\9/n$ o6I![۽4AԾ 2}j年ss?Ъ xdkAL/CESO Ɖ};Dwu "7,?4w+^_VMEkeuz]ڮ3K=cZ`yE/_{ce5"Je4]<殺es/{cȁ.j/2c|8] } ߡMu6Ϛpa˒Ӎ4]sgwwxgTpsm.-fݰ' ùiw'B%{ hiKbK ˂>K1kߥ}˚yP]ފKچkq~?@8RƕK_s=%S@K8?N(61OcD76L:g(2YB 09r6ğ?䥹Y8qB۞3g vi P|B(>!O'Bɤ4"]2BYQ|?aȑfweEɓ'Wn@(0m4"]F=A(.QAT~eAtlvu+W*bbbq%KU'O?5,((|NjKn//}Qw=,<<\$%%E VFllAh}($77T4_EEE^!O'B P|B(>!O'B B P|B(>!O'B P|B(>!O'B P|B P|B(>! 0ĀK?IENDB`ukui-greeter/ukui-greeter/display-switch/res/bg_f5.png0000644000175000017500000000733514204635151021754 0ustar fengfengPNG  IHDRڧtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp 66 IDATx[hTwq]Zo-1dVPml^"4j_"HkR̓AdEAd/EDa_N8-}>[[]]=g}BϓG+'g@^{hmGzsuc#Ha Q39?h3Q;/P&>_/> ~xĜ8qǏ+=_@7o˗/u/_|@\? ? ?? ? ? ? ? ?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 6? ? ? ? ? ?? ? ? ? ? ?? ? ? ? ? ? ? ? ?p3fw޺7mT^vY1Æ fϞ='.|d/^~#nݺgƍ mȶm} >.yҥKҗ}x\.gKСCsm!se˖.o\tuѢE; SL?777/3fL̙3_WWW?߽yfg_N>d5y_|q! ?455nL?;ui0gҤI#gYW\?oŊ]ׯ_jk3Xf{ȑ#^>~ʖ-[Cg]-ӧ!7n.tZ;w6ZG<1Ȟw}_@)[8իӧWe|-} c\[[[OmUBSUUULD8o"mmmhrU*_H; eƟO['LsNX:vتok=S5ΝkE~~4ӦMYyl?SO>Y~N5Yt?,XP7n=WǏ_~X_ \kii9 CiP}֭Bqs1QFX_2HAc͚5UhG&WYYYnݺM0hƍ㊘J [? c~t~w9q2.{SN6lؿ7Ŭ?q&kO>ڵӿl?œ;vhx뭷~7ͯzKޣq$_SUUUmqπb|d^םտ+ ӊcn,lH㿷;XSzS"qi^P r |ܹs7niFޚ=C{?~!.#6πD.\ ;{سgϒ8s $+=;\.W+? C2\Hf͚ ca_WZUn=7[{t8le )Gb;/pB,tQ`tb @~_dSWzOݍS\q.UAWXֿvZg, 9u/O;gϾ?h'7n\ע'fgϞzƣi~?N ơ~ ƥz1؍KXX)xN|]}7oFr~eǎ{tAw|Sf?rr/ًIo+CqH2}l_lի~XlgE8_= Ye &IDATx[lLǩ^*D:u0xNvH! <4O 9G nf sMR#_e)3|gͬuk%zy-Ɵh@|@|@|@|@|@|@|@|@|ć2a„oʝ;w~ٴirN<nÇs\~:]y?;;0#:SN#qƜtC̙3W^mqnz#ysM;vl 4({̘1nQ 8СCf)v[cׯꬑ4{ζٶm[%K˖-/--=I4iҏO>m2eJ$0`<`D~.\0:hGqnzjpo~?<Ϟ= 6444jݻ555MZqFs^ڣM,d .ٳ:%JJJ_|7no^UUUjo3r+j;#wE#G$Қ/^dffj#\hQޮwv4SN뻕tj/ĉ8AvJsr֭G<ٱc_Q8TZ鲲];roaÆ[hY)4^vTGK߿6hiORə}EW^V̜9SRG¦ZEEEre~ʕڹNvݝƉY8i:\3Qï}}u}GRΔKNj7 F3'ѨɓF:uڎ߾#!UMlJfԨQ~}.X ߤ?6+ qw&c^E{~*1:GvO1?6:M?n?gΜ\i WF@;5Aֲ.mmmo4V G3nIMM4777粲5UDU5ʖ?5P1UYuFmuN#~UwsXč6 بm'ZvFkJA$K܋/Ve[qc"'j1be(j51")e͚5cH *ž}*_bKfy[9k,o clxFäԪ,C(GZSί[\\|Nt՚|^ҥKε6Ρ}-C3îi>MQ)? FOEus/^kssssYѵz#nEnc;wmۿY"> |I㻓~ؑ# ~gRď=:qD&ʫbh '-[٣zIn_oP)ʫ=UGSʡjwK$)))ŦT/&hRIs:!4'V4\Ol#I2U^Lm>?U\$ZvfѢR'\^^ER*zw훟ib-W_OG5t{Mj%&<ۍA"֭[ &'Oټy1g 3߻w9@i&Y5vcS6l:͢/hTbV| NuLʔw5t4$Yo'to"}uN)<)Y׮][sڵ@ (e(k׮*˖Z\&YI3G޸qci:^kyN<{lM Gvv𖖖˗/?!@>/K˥]zY[[[/rbȐ!^iӦat;w.lq[I21m@|@|@|@|@|@|@|@|@|@|@|@|@|@|@|@|@|@|@|@|@|)z96|yߊ|OE 瑜>|ӷoߏ5444"~qС?y Qa^[x0՟3YSQQشiďANg_>j.T};{o>T@h-!O `0 E tt=@2ƀj W@YoF|ϛ3M0kyn2KǤ6&ؘZlAO&Tnь M\ Ƃ,{*GSEm.Z-&8\&:GMr-bvONt@ +Ti׏X}VZC衇ѓ8?tcDhN-4D$M~~rvR;!I/Rܮ I ԉ?&av'%|S$/ߧquuvt:2J;дs6( 4lNS[Є6g=`,Q=,Ĥ@f~-DhuBܮ6vh$ۈZO,2='uV5\9~ܹR%EZ'N6(l![͠E-1 ۴d)t5=M֭X[ـ~<Пv'??qoËGO[~0M\h2[aj>NJT aD"Y&mZHwQ&qj]ñ&Z ad"Rb- p+ 5M+`0.fA/cJb}:i8He:eTב\LFhM Qk-ԠS&)̞OqQYi}R8W֣'nCwG=w?NA'+Ei^. HJ/ dG &8+mn嚐zQs۴%9l-nZ`]MӰqs3EźfL, t,Ĕ6['@ jܦhkhK]hIi(Sm-[w>7iN^35)!0m\^Z\s z{l1e9k5瞽FdW]J?_YbxAH)QF NL4OkM-F4A[S۸?$o&%[6)1)Z~ 㩨ƉSsH n|irqDup+ 4Q4t=F3};&m1n:φ{ʿ[{>{Q<a|PM? VtunDRખ.okq*,N˹FkҢ j'j }JLN6[[奄uExzn<7cf1`01Q"Dmf&T3T=lD7.MU&B]@tGrյoǺH\' cEf[_2gD%Yi7F@N6>?a@ Q|Z$E!&mV8iBI-oAVmwNS agp؟7xBxbx6.%gcn%z[ nt#HV9` m3DPC ^k:YZ~R3s1!L3 :a1A-й T40ȥ@a-~bYGִ-E~ DV(&/: |K+Am Go0X ]/ 5erApQXh{RD8-vQl1MRۡ0Px:Dq[k,E1 ÍY[Y"Y7?4Ԧe-p"qY 45Bk <-0<;ḞpsExf0S^4t,P p/ d#D06.',VDck4Y{,{}:q_dx2Zi;͵&e8r4Bd`e39IKB4V4VcklDq (SÕ ~ [b0 `,H|OO{b`0 q`&zU `0 "85A̎ `0ņ)jX9Wb0 `0(, `0KY˶;f0 `,V[9]`0 `,ZdxU `0'|mXbX̶`0 >B)ɴl,ۊa`0 c ⴪HGX&)i`0 c!BJj1JNA;A. `0E/h紮n٩&c0 `,&A @Ct E %vb `0$V /<T*~{v1΂~aR;g>21444>ɴm"Kql!+m>ٳ'lϞ=ذa~W~RswCk!gٓ]Ij;SvRJ?ug|\sM _9rk'|= B[n%?w`xx7pqlذz;wk_nދ7"s%lm FBK|/ɘlƿ(n͛D?yu#|O>dnM۷7WZSNEÆ g}麎=zv'OF8z(* ;6qap__Z'iƮ]p!ho>[.%8Icpnk>`4qwR  ԩSD|oƞ={pw|7n믿޴]ZM/\d3Ffm#]&YbEoΝ;OF;w9ܹ3>?a<ӸtFUW]o|Qgs˸gEPupq@oo/~rGu]-E]vEDˣ_xzOqёбL ͤ]&r!?կF#?3 *>bӦM;|۷o-97qs{Ç#?~ݻ7;7nSO=tn/[oV"? ?eFݽ{wdqՍ.|站%Iܹ3e]f, T*<K-O7g< 0/\neU8ֹ2!Rfd\ ?Ɯ@p~KY$#4]g}HjT{<#|>je[.`ʕR^Ę\&;`L;vH я;î]&l縏_(3ޔ:7kLx\ła<#hUf+BrO&'}#Y^y;vlJQwߤ?p|{ߋ^VYk;i&|L޽{r-T?>yl߾ַׇ`Ϟ=gdV. EvյI+_J[kΝ;K/C=rqƟT6x^L\-^A,uۇ~>h??رc`ׇ[ovJ޾/K5#,o&oСC뮻0<c={FUP*p7%v;sPOpX0>~8ne:t(5IѣGAD-8cG#Gկ~ICpմ~mK7}/d|g?|;/_%obݺu.pA9rd΢3{{{ߏGysO4kV~۷ooww_צ}>O=ԄƎ;"=.H)ذa4뮻- t1d*ƍUkږf,]|L+~u8Ҷ+u<696{ߔQlٲ)ڟG>8i(\ticZ [nC=-[L[uwq㭷Booot~:> {-[o} ޽{E'xؼysg?c`` lL5}ڣ>B6؄ 6n܈rE !l۷oo9)%n6< "l۶o8#}݇~B\\}Sj87ZQ>6㕺P Bvr)IGpq"V\Uz.سgn:Lcؽ{Ӓ )`nZܹQ5{p1>}|>Jm6\{>RO:ߏn :ӧOc۶m( طow* mۆ{.ZZq䪫ߎҗhwΘ} /`ӦM8fxǢeΟ֭[~ܹvك'xo<#~Q qwVZݻwcΝQ}k'Y=z~1cA "hM !RŠ]wy#mۖ"emضm[Sż|6ɗ{E>G__w6mڄG/n;}݇{o`0W *c.sḽ0`0 XAn;1 `0Fַr21LW0 `0Gб r21`0 `,va<, `0 ('S `0 E]ֵ7bb`0 q f1`0 hD$YӨ1` `0 B- s$4f0 `00n9ٝ`0 DpCDd1`0 d0a`0 ,J1f0 `\ Nj_9f0 `0nJN0Jf0 `,6QLS>PR&n"BXFmC  0:tˈ4 F*hhhE hs"Є(SФA "hEP*D(J "/?T*s90#ߐc3]e> PSQ,g*t aH@@BH{S%<4'&a7ODڐ[kVDi( A2 (& ~J9RP*Rf}Qh@U9 b! nW0[xbx~!/yK(t@HtD!@Pڗ(zh*25) 0/ jhm!*̈ڌ 5i6/_PpWC7!%?%{ rn(hmlOI!ZPqARY0@dy x:X ϭeΒg"/V  dd;"(91q@?t`?ˡV50ȺQHRhͤvj.0aA"I@#Y/4DJ=kZC+0W(t`\SĎ;.lj 4O)dD Rz_Ep *6)藄&y-/9\Ư`m|Gw p6/qsu" 5Bhim"Aaܘ棤FlmY6*D\]@Dg7·uP!A@E_*4%9=D~N1XO q_A s9)8Y(>oU$~篼}AH[ \ճiok#(.= apArEd%RBCn!`^hIZr@HP0aJpTPATw Z'"5_) aY7YM4_;@ IDATf-~#՞&7Ǟ_8{fƌBȈ5B|8ƚ\"?a2^54v:0m7Eoc 4,<vqay\iLf( Jq ^& б,Bm.S9.dNrVCzƹ|v a]^E^2`ipu&Y4iZCiۄN Y!*al&g&f qXL`x E)^3`8vDf75+)>gn[H@J1b>α*:M9&t7r֏Ȭ7^WVĮW#O##x>^/pa\\2l (@R.!$#bR@6٤v:A۔BldTDi4L:lMh"!BծI i:A{e8$@J RkN-k 1S9/gٌ̰D"D‡fI<ѡ.}V|uѲp',_4?Rઘoe597'˄ɲ):"PVGA \9Qm:c U3G\}8@ I**Qcq2SvWEP R?˅~ c~s1!*5ʅOnE~ij&V=]#~r~P(a߀3 8%ΈT:Fr^pams HdSi8Ӏ|,t&p[uJAIcv捁: a*&;xW |IKP3q!~ 4'na_qAb;|3 Ɏ3dĭ05!lo֬kƑ※]w=~f~ –L%Uf mh sq!L $)Cs"lq޺<(<^IK"V6[3WԞ&H4jIzfZl`OGM ;0JrZh9 Hۙ mP|/k )@$N1)CJpڅhJ Z x6ACbS _w϶T 7Qm8juƿM[}Aɸʜ u}per kv(!$:UTt4=,DsjAq@͂hQ^)Zi[j'7N(K bgΜBe< )= <߇SĐKAktvwq+ynIp͹a1L<$PBHTIGY&͙m"L0:g%֦I6?U@2WgNp@y&0|7k-QF:wm}Ρ4CQӮ.wj1τ{Ag'?10wNXۋիX<tufHx =N\.x!öRʦ^.D]d&X3W-Qʮܲ |ߔ6P:m I4ES}P"tKMyƅ[{:p;P\M硔2S?+ 8 4@C 3kr*Ù&fPgyOM<@RyX8 lJK &ȯj ®xo7,[^sѵ,|ƃ H)P(@e~1] aSJDnuDii!n;|,AE,@x0^:C&CgA!I*ѽL{BWېDQ݇] Uϛ7K=.!$ϤYDhk[]I +!"|R֏+8X`AΚa'N n:Dž9HHi7d>!=lN- tI5TVqNTĥdM8=?)PE~a m( ټ~igq]S򏿏z~HQ}^k.Śa^w9#!B6!yZ!C+;CdPB]{ p:>YE(NZZaZN @H: >n$pXY? s? \:?jёQ85## CZҹwW? B%8b($NXt HjDh+{hwؤZSR6%D(¤f8!luPZւWbbή "\]xEs)쁭IRYӠ Lbq*lnj[u"TŠJs@" J`OJL5/Ȯ,c_t8 }v];pꝄQBX \z٥]{jZlt|OG_MZ?b 5EMeqz2|Dڵ(%&n*URB8= r"DF؛*PC=!d+{ 5Rר5J5Z P tQU^5! |i 7wʫ˯ҶVb)q5u䖅Dk7^AyUVjaЦrVVε"LOlfOTMZ)j7 (`2Oḫ *6,B`q%3MKDZBaxQStN&4!h 2Sĕ+WKqOBخAK!N"~YڱwO@+yC {l:NWJtXuXz=zzWaŊluY Л1MKfD1aR1x2}kJ@) 4Q hBΏ lxrP{,87Uz"g3K{uvr#]B ~Zᖡ!o667ϾǹĚLY᪫ FAL #^.CC"˳#3 SPZ 'I4ΞWu( жږLuO!Ucy>쀕8i+:Sh2q}iFm:k"q@fxDly# ؜աvƛ4/EӼ6?J8Ҷy|q~j}_p#ߏ>ktoEpKf;B:35 yG.v{S:!GK3e;:ꁆO4T@DFCAWF k3e_*QJyJun|\cisV PU(ې\BҨ9LSwVWv$"*A$66ӄ[;:G[1m`P* \k֩UN"4ԉc" Hڵ-2{rMe{97T!Ex1]<2̹qTcu)>+qwxXzF)tz8mnTgiM,b97%Jq @(a՚FjPj=O'Td@N AT@N@r"@i<0K w6ڔ<yzeiM]J*\!K@Йcxxx^K"`ZGnū=+e.ΞxgD%؟msu$3VeO 4J#1ULmc!Lq@vAsM.~I{l#i,m)]ZZgKuJ #TGAVstug9:9DktL d.b3"[|@^Hԃ:NA#md8Zȕb]хѢF3yl"׉:7m!FN /5? )F\uS r^{vj7b6jFaC)ol-Ji;جJ6Q{4FXom-DhQHV+1k%RL5./uEMrжB=es&1rЋ1/$FF8wnlJL>6oW^ 0C7}' te+9s-)TI..!rebxiB$:]qhJ r݊B=P0myjbEatԈRETV/i+Bƫ"ʕz#a\m^\uTl4"3HT5)*j,tւgprqi(o.IY0JkhS+~Ξ,F$fbv6Kt1{G;tspc%̥K[#Kbns/(N166:O xx(V;CX[8{FG|GANȭܠ'.,KJq BD PCjan Bk?5:Lid2B-X4^4>2g)צT;Vb! q^\TaަR&}UhϤtdڦQ&y?rSRᰢg+(X27iFÊHV'Ut\sR /<GV(//)!! < !CMZC JH{遄%֯ ,bphdAb7-?8.TL d&ڝ.y&I$LjJK0 $״~_DSRLBC(¸cֹ3J]Bv.%)j-PJ'8@6ԅRrQRǥiDfWR1L$\U"htuX<0(@etvٟ~z rJ{1|_D58;j&+$H&ICL,ajz Q0w_Q|חa؅n -x02deIDAT4^Ld5x9 f8#Ps3:K.A^)7F* k0nWb!6MABD DI1LtfPT$_w"nyX!VVb֋cpv4JP ^!H_CHi *s=l!>o7cj|uHU=͠ V0VXZ}QS40iaR]{ *:KRN0W/ruȌAe!3xQa9>'?y}Sqy|BJo-z?=#>sX_bCl,ȕ(TMl؀ q;nv{kVvCǯ:|A3X+_GjNN I1 R(* >>qxϝAqM4Vs{w*XÕX c,ʡ z Pkh/D%ja<[I/!pԥ:SZۄZAJCЎa,qmY3nQrO _ڦ(sڟEP04| \e?ʕ2y}\'}R H)!7c8sfίy|qPձzyV|x#"ykPUW|ƀO]Yq{?,ː lkW`>%! M4̡|v Y ʿu>P18xvǎjJ }hM庱C=10pg<3 69lXtF[XpΗ1^^qRV@Xãx7!)%&,pJSTQM8oB)x"と  P * .Rn5Ջ˻۩8湚S9c7addǎ[oRjZ +W] u8{v=dY55eD/J *(z~xߺJTN_q~|Zw_Ǜ>g!NhZ+vUqt4.}G\00p?xGPVQ(t!T# m>BI[kD.㣣 7I`CV֬ףCյn|ndIb/[$s oƇnoG[o^z?aE ]3T\! `XF D֗ȿ|C&#Г#bdf5&Tky[w_:p%PX֍WẹuXYgc)֚ shc!NHJ>}zq87xJcc˺QDg|˻u *6`8bqbr  X <PDg%^߄nje0W }@`pp)}(NBX !H^Չ.t:eӳ ]]Wc%`dl*2J2BA @\Gq_':3ylč]C/7\~(,Qf rAv--V:}"7|?ġC8s8CNTk5xƒX,clXՉ\.e(,COO/<)@ШV(jT й荸K04<{pw3[Ugh14t?╟©02rZJ: AM~ FJAw2tMjj]aPUQQB=+zyzJh" ԩ8y~1_DMYg=w\%"7 !IAG Y8o/v?b42G1^,bu(u>CVGVG^G.M@&^Wx°)<G&fgг L|˺;!jgsoammteOQՐdpy,[ ~CRUK*ΟŹscGq@6b (V!"\G=P>j:HC#0< ,Vt3X֕K!Pq bя}ky%. 1wZX`ddã#b``W^y%ͳ 6]܂LË@<2RĹscj|g@B+pݵ7`يJW^y/"FGG1<<FVG(hM:!xYU~V\a1<\I}!94=s? +VKU[Jui-‡IkPs\.a(\*gG^ĹsΟEZ R&,z-b p3gF @cu9tXy>/'} k׮eC|(eH&_6~-|ub $,ŋe⥗pW߇R Z 0ZͽOڏ% 7W_ 12ZS1p,ϏRCJ MU< (.!ɢT޽Guw.2Z!@BF.C]¤Ntg'ͥ3L0yǙLN_w3~i'$:nl dc@HHBJ+ڳ{8{9ژf%}FͣͲw^}Q*++ZEkUD#+ê:L(f8ة%0'&I$ff`XJ&Mq/,3|>2C AA۶CeE C<6 X6cDQQ3cLQp;LB(tlˊrs[oi9miyC3|>C"1@?Y\L Ŧ;kݿ?iν}k\6J|v۶1id0@gmCśI*XHĩyȑ#iU#/e~sXhni!ڼ5kD-|>XZZܹ>Μ9 t)t~g¢s3295K~FGǘ$HӄCt0a*VU[,$B S|+_444MM(=\G~`Jv_=)4jv<ʉeY޽ݻwH$ŋ} B і쿁kعellQǸqcx r2׆!v A&¼$n 8ȑ#zsnk .T@P*VDX +ټs:1#NgsӾZvB! ׮]%_x%;C1؎"XWY2}c3LMOrQ hu&:ZZZZ͛gx*399E8\I{{;]LfD"N,6w`Mc}od1!m`q)Eb~1/"cjb2i&&͓O>I[znw u݄ۗrճP]a(Gݚ7~hjj`vv iN9յlFKK =P_W!34)!qH2T֬mzB 8g~,w={hU֪'ftwwc6 ?"145muֳZB*q1Ri$ _ Lct6c|&ٗy◾ȱcV͝ˍova^AQ T^7'L288Eΐ7c{7TWW G/,I܄~xWO6RSS)Z""w`0Hkk+b1.\ӯ255KUu [;q ' $&&㸛i,‘Jͩqsnݺџh .s=-t(Qn-W>sOܸH$Bgg'q.\K jVyKp TTIO~Hll?k%֪HXf |>M"`hhwO姹o5lfp =سg'NUUU0'ۇmj.?\<#[\lr{${Yw0??ϥKt줣^~Ď8q&Z""ezzzmq.oMFbۘqy9x CL4:wtV̱< ;zUVkNvI:fppg~?'N|(ZA(hÇ3pa>Ο}dp!Bn؝=67({d"n-Wwq.@[n:^JMMMIDkUDdYuuc\B(]7xˁ- ;#[8b\^^?@Zo߮!Z""*t#`68Wps=) EᗒaLHB{5ST2N]QB.l Zy7Eʃ֪֪8mg+'XJF7=H\[""""RF2z`PL'qso w!""""w|'S4Re&?NDDDD3DB-} ")@``eCq7r ^`,""""R6\[+ApCYesXTDDDD)|4&-B .0 ÎBDDDD87` i"MN<"׳ ê!r.W5͔d^Wn0D,""""eù+!&=mr(H9:&7|XxgeCr<Hqܐ[\H.C9Doљ:)Dl0Sh=dS8TGIq.IoCCv@{] &DDDDq.{2-E5+=EDDD"qQJeVrI-EDDD)bJ2ww8tϺ@מӝ;^bP/'A|4|gkfŭfoΰ|lir(.ͳg5}S }n+Nѥ}0,""""8~P}pg_!p}TבjSߤf=tF߷°P\Wv[ZLO}TݺWc񙿄0?ȻV?? /XDDDD#f}oz)7 ׮&Tş=ɛ\,->,""""wԪO6VC00wBIENDB`ukui-greeter/ukui-greeter/display-switch/res/wifi_off.png0000644000175000017500000000707314204635151022561 0ustar fengfengPNG  IHDR=+=tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp p IDATxo·;!CJj NMK*?w"_hiBm UPCV>h:vm̫ڵb쮽{y#[zwݙsΜY__ ǢDS@E j J@%D (Q JD Q@(% JD (Q J@%@(% JD Q@%D (Q JD  iLpm_h_G[+Y۾e юD=h!;-hnB[~fG[{apxj}M=jS!h߷Ss0EaI6IE;媣zݐaԐy"늆+>(kwE{\UϢ}?>b|ta.h7EG,'?6vM>8ouEu(546Z9IJXvѰr+a#I5굩}:vIܨݔ{G{Ę9XBԫԌ_Dϖ#SJ?Zz hFm>P|5}Z9B ruzөhE')p4 _vb\oqF<}޷ω(K@>O;aQr~_VzB+{<|7c6sa){8͹^C7KfcQD7ŐbZtu&q**Q&r* W{h5t*&yƾg }(|3 }(yJ1ee"_(ʪ)_p/%5dNml>;2ͯbORhMe῎.6Um~=ѐ-`eSƼ{ish#N<8UMO=?Ieꛟ }6!҇ ghKokڽݝ~p,qj%!=!<OW܋_%gk] g44?:Fk{qzM ȯ<4{dh3 ?`{='?o{)tJuWA,PR8m̾atSl.sP5H`o%_c!ni%Nm#MSޝr ~Zw3ɀ jx},+ZżÉ m~R\N\u(yфc5 f7( uz)O+xM֟tʚyQ_a+Ajhh,U溦B5%Kf7-Te!}nRwQf4&։}ޗJjQ2:T.oYŴac~K vURRNh/]e LM(v|ᰱPn\ygrzJz.ramtE%n?[ӄَi>Y6LNV>{ewwWyC%^ݣIT'K-*Z8Yo~}8K?)/8PmjFG=٠{%쳡-CG-} u%QyP\عl4cQYz¼dQ6NxYzS*}^G*z~){(q/d/ ՜z6 {SKFvgWaD>d %H~ӡvRI QDٽ'{21'j&E9Ŭ) Mo)՚Y:C^"4Qz3D-nW!ۿ(ogJ: |1{쥂Z gMy'Bk3?SB^R4Q -KwYndH[]k<3 Ű[R+t>M/L39ꈲӌ!Tsұ!BJۨfd7QR:Z}r*UJi§,-0&D{iK[4[w/m :oaC4] lm8QΙPH{{mQՍe}ɼ+[""9TAP˟iJDY+9 qDEA⊼[ NU#f (hs8@ꥥn17Ck kh.[)lM2d'r+5 UлC,"ħD (Q JD Q@(%D (Q J@%@(% JD Q J@%D (Q JD Q@(%@[l*÷ dSIENDB`ukui-greeter/ukui-greeter/display-switch/res/hover.png0000644000175000017500000000230014204635151022100 0ustar fengfengPNG  IHDRtGtEXtSoftwareAdobe ImageReadyqe<qiTXtXML:com.adobe.xmp 6IDATxA 01 @@+r6_́90s`́9s`́90́90s`́9s`́90s90s`́90s`́90s90s`́90s`́90s`0s`́90s`́90s`s`́90`.F~9IENDB`ukui-greeter/ukui-greeter/display-switch/res/hover3.png0000644000175000017500000000135314204635151022172 0ustar fengfengPNG  IHDRz5t(#IDATxA[Q|~.Q5j1j5FUDDDDD+sf7ͪzsz0Mo.) Kpy7I>w LiX읝|ݥ4XZ,M'0ԠX&5yHsWZ&„s%L*& 0Akg&9 0&0M9C8aFфI avB%av¤0%-007]Iav(~!0i%-&/yh aF?N! & ݏ +- a_Y~q ¤0B%ayG&L 䅅p¤0WtpEa"L8)/yh¤0/!ܳ0gs!L4Q$̝0i! 6¼#D; 6¼#D&9] :sIa~!0&63WZ&?p¤0'¤aRmp?p?œB8aFyhDpzwS'L enᄉ00}@8aR0CiQyq;pD L^RG07Dpj#'L fIa= s$L*1&Uyv>LuLPWS^ i(B`iSc^_ >4XZ,M[V IENDB`ukui-greeter/ukui-greeter/display-switch/res/btn2.png0000644000175000017500000000373514204635151021637 0ustar fengfengPNG  IHDR@@iq pHYs  tEXtSoftwareAdobe ImageReadyqe<jIDATx[;lEgwwů9#4P HtTT4)RDGA@"iM@-EA(E8Nck3?3nsw،t۽y9p/.ӏӧO=;;[o_YY{7n̔O;"4 Ce !1yOX9y?zmBIL0h=QὨh?-!Y ,\ uLJ[./_f;HNv/p!G!\k. ZIG ]O `xe^wH0LÀ+XYj&2D#!ŷYa6g>x<ȜO?~ܫ]bkgZ2-}9`*X0(րP|J^}rVE |D_QW]9 %@&8c> \ M2%ӇFdFg_aS4ulM$[T 6bFUGI8RD&wS=`))f7RեPg(Y{'$B_Q HC80/D@,G]HP|6 &'?J>U`nO!g~GфJԯ:.|QPCFB(@$4"ҁ`R.(M|Z~qxFEHCrcm۬zyەJҫ W_Nlů. 2[3-+3֪k!)]877 d/yu?+I q8YIENDB`ukui-greeter/ukui-greeter/display-switch/displayswitch.cpp0000644000175000017500000000634614204635151023067 0ustar fengfeng/* displayswitch.cpp * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "displayswitch.h" #include "ui_mainwindow.h" #include #include #include #include "displayservice.h" DisplaySwitch::DisplaySwitch(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow()), currentMode(0), selecting(false) { ui->setupUi(this); this->setAttribute(Qt::WA_TranslucentBackground); setWindowFlags(Qt::FramelessWindowHint | Qt::ToolTip); initUi(); } void DisplaySwitch::initUi() { ui->centralWidget->setAutoFillBackground(true); QPalette pc; QPixmap img_bg = QPixmap(":res/bg.png"); pc.setBrush(QPalette::Window, QBrush(img_bg)); ui->centralWidget->setPalette(pc); ui->lb_mode_1->setText("仅计算机"); ui->lb_mode_2->setText("复制"); ui->lb_mode_3->setText("扩展"); ui->lb_mode_4->setText("仅投影仪"); ui->mode_1->setFocusPolicy(Qt::NoFocus); ui->mode_2->setFocusPolicy(Qt::NoFocus); ui->mode_3->setFocusPolicy(Qt::NoFocus); ui->mode_4->setFocusPolicy(Qt::NoFocus); QDesktopWidget *dw = QApplication::desktop(); QRect rect = dw->screen(dw->primaryScreen())->geometry(); setGeometry(rect.left() + (rect.width() - width()) / 2, rect.top() + (rect.height() - height()) / 2, width(), height()); } void DisplaySwitch::onDisplaySwitchSelect(/*const QRect &rect*/) { show(); ui->mode_1->hide(); ui->mode_2->hide(); ui->mode_3->hide(); ui->mode_4->hide(); currentMode++; if(currentMode > 4) currentMode = 1; switch(currentMode) { case 1: ui->mode_1->show(); break; case 2: ui->mode_2->show(); break; case 3: ui->mode_3->show(); break; case 4: ui->mode_4->show(); break; } } void DisplaySwitch::onDisplaySwitchConfirm() { if(!isHidden()) { hide(); DisplayService().switchDisplayMode(DisplayMode(currentMode)); currentMode--; } } void DisplaySwitch::onPositionChanged(const QRect &rect) { move(rect.left() + (rect.width() - width()) / 2, rect.top() + (rect.height() - height()) / 2); } void DisplaySwitch::onGlobalKeyRelease(const QString &key) { if(key == "Super_L+p" || key == "Super_L+F3") { selecting = true; onDisplaySwitchSelect(); } else if(key == "Super_L") { if(selecting) { onDisplaySwitchConfirm(); selecting = false; } } } ukui-greeter/ukui-greeter/display-switch/mainwindow.ui0000644000175000017500000001140214204635151022174 0ustar fengfeng MainWindow 0 0 744 126 MainWindow 5 5 180 116 190 5 180 116 375 5 180 116 560 5 180 116 63 43 64 64 63 18 64 20 Qt::AlignCenter 187 18 1 90 372 18 1 90 557 18 1 90 248 18 64 20 Qt::AlignCenter 433 18 64 20 Qt::AlignCenter 618 18 64 20 Qt::AlignCenter 248 43 64 64 433 43 64 64 618 43 64 64 ukui-greeter/ukui-greeter/ukui-greeter.qss0000644000175000017500000001557114204635151017670 0ustar fengfeng/******************* 按钮 *********************/ QPushButton{ text-align:center; color: rgb(255, 255, 255, 255); border: none; border-radius: 4px; outline: none; } QPushButton::hover{ background-color: rgb(255, 255, 255, 20%); } QPushButton::pressed { background-color: rgba(0, 0, 0, 30%); } /***************** 弹出菜单 QMenu ****************/ QMenu{ background-color: rgb(255,255,255,20%); color: white; border-radius: 4px; width: 260px; padding: 5px 5px 5px 5px; } QMenu::item { border-radius: 4px; height:36px; width:230; font-size:16px; padding: 2px 10px 2px 10px; } QMenu::item:selected { background-color:rgb(255,255,255,15%); } QMenu::item:pressed { background-color: rgb(255,255,255,15%); } QMenu#SessionWnd::item{ border-radius: 4px; height:36px; width:260px; padding-left:10px; } QMenu#SessionWnd::icon{ padding-left:15px; } /***************** 文本Label ********************/ QLabel{ color: white; font-size: 16px; } /********************** 下拉选项 *************************/ QComboBox{ background: rgba(255, 255, 255, 20); border: 1px solid rgb(255, 255, 255, 30); font-size:18px; color: white; combobox-popup: 0; /* 配合setMaxVisibleItems设置下拉框显示的条数,超过的滚动显示 */ } QComboBox::down-arrow{ image:url(:/images/combobox_down.png); } QComboBox::drop-down{ width: 30px; border: none; } QComboBox QListView{ border: 1px solid #5187bd; /* background: #4682B4;*/ background: rgba(255, 255, 255, 30%); font-size: 18px; color: white; } QComboBox QListView::item{ background: rgba(255, 255, 255, 0); } QComboBox QListView::item:hover{ background: rgba(255, 255, 255, 20%); } QComboBox QListView::item:selected{ background-color: rgba(255, 255, 255, 30%); } /************Arrow******************/ #prevArrow{ background:transparent; image:url(:/images/prev-40.png); } #prevArrow::hover{ image:url(:/images/prev.png); } #nextArrow{ background:transparent; image:url(:/images/next-40.png); } #nextArrow::hover{ image:url(:/images/next.png); } /*********************** 列表 **************************/ QListWidget{ background:transparent; border: 0px; } /******************** 主界面顶部按钮 *********************/ #powerButton, #keyboardButton, #sessionButton, #languageButton,#btnSwitchUser { border-radius: 4px; } #languageButton{ font-size: 18px; } /********************** 弹出对话框 **********************/ #fakeDialog{ background: rgba(12,66,120,0); border: none; border-radius: 10px; } #closeButton{ background: url(:/images/dialog_close.png); background-repeat:no-repeat } #closeButton::hover{ border-radius: 4px; background: url(:/images/dialog_close_highlight.png); background-repeat:no-repeat; font-size: 16px; } /********************** 认证界面 **********************/ /* 返回按钮 */ #backButton{ background: url(:/images/arrow_left.png); } #backButton::hover{ background: url(:/images/arrow_left_prelight.png); } #userWidget{ /* background: rgba(255, 0, 255, 30%);*/ } /* 头像 */ #faceLabel{ border: 2px solid white; border-radius: 75; } #login_nameLabel{ font-size: 24px; /* background: rgba(255, 255, 255, 30%);*/ } #isLoginLabel{ } /* 密码输入框 */ QLineEdit { background: #FFFFFF; border: 1px solid #FFFFFF; border-radius: 4px; font-size: 14px; lineedit-password-character:9679; } QLineEdit::hover { border: 1px solid #FFFFFF; } QLineEdit::focus{ background: #FFFFFF; border: #FFFFFF; border-radius: 4px; } /* 大写提示 */ #capsIconLabel{ background: url(:/images/capslock.png); background-repeat: no-repeat; background-position: center; max-width: 22px; max-height: 22px; min-width: 22px; min-height: 22px; } /* echo mode */ #echoModeButton { background: transparent; image: url(:/images/hide-password.png); max-width: 22px; max-height: 22px; margin-right: 5px; } #echoModeButton::checked { image: url(:/images/show-password.png); } /* 登录按钮 */ #loginButton{ /* image: url(:/images/login-button.png);*/ min-width: 26px; max-width: 26px; min-height: 26px; max-height: 26px; icon-size: 26px; background:#3D6BE5; border-radius:13px; } /* PAM message提示*/ #messageLabel { font-size: 16px; color: white; } /*登录和重试按钮*/ #messageButton{ font-size: 16px; color: white; } /********************** 电源对话框 **********************/ QListWidget{ background:transparent; border: 0px; } QListWidget::item:hover{ background:rgb(255, 255, 255, 20%); border-radius: 4px; border: none; } /************ session选择对话框和language选择对话框 ************/ QListWidget::item:hover#sessionsList, QListWidget::item:hover#languageList{ background:rgb(255, 255, 255, 20%); border: none; } QListWidget::item:selected#sessionsList, QListWidget::item:selected#languageList{ background:rgb(255, 255, 255, 35%); border: none; } QListWidget#languageList, #sessionsList{ border: 1px solid rgba(255, 255, 255, 20%); } QListWidget::item#languageList{ height:34px; } #sessionLabel { font-size: 18px; text-align: left center; color: rgb(255, 255, 255, 200); margin-left: 8px; } #languageLabel { font-size: 16px; margin-left: 8px; color: rgb(255, 255, 255, 200); } #lblLanguagePrompt, #lblSessionPrompt{ font-size: 30px; color: rgb(255, 255, 255, 200); } /********************** 生物识别切换按钮 **************************/ #biometricButton, #passwordButton, #otherDeviceButton, #retryButton, #OKButton { background: rgba(255, 255, 255, 0); font-size: 16px; color: white; } #biometricButton::hover, #passwordButton::hover, #otherDeviceButton::hover, #retryButton::hover, #OKButton::hover { background: rgba(255, 255, 255, 20%); } #biometricButton::pressed, #passwordButton::pressed, #otherDeviceButton::pressed, #retryButton::pressed, #OKButton::pressed { background: rgba(0, 0, 0, 20%); } /********************** 生物识别设备选择界面 ************************/ #lblBioetricDevicesPrompt { font-size: 30px; } #lblDeviceType, #lblDeviceName, #cmbDeviceType, #cmbDeviceName{ font-size: 18px; } /************************ 显示模式切换 *****************************/ QLabel#bg_mode_1{ background-image:url(:res/btn1.png); } QLabel#bg_mode_2{ background-image:url(:res/btn2.png); } QLabel#bg_mode_3{ background-image:url(:res/btn3.png); } QLabel#bg_mode_4{ background-image:url(:res/btn4.png); } QLabel#lb_mode{ color:black; font-family:'droid-fallback' } QLabel#split{ background-image:url(:res/split.png); border:0px; } QPushButton#mode{ background-image:url(:res/hover.png); border:0px; } ukui-greeter/ukui-greeter/VirtualKeyboard/0000755000175000017500000000000014204635151017626 5ustar fengfengukui-greeter/ukui-greeter/VirtualKeyboard/VirtualKeyboard.pri0000644000175000017500000000064214204635151023453 0ustar fengfengSOURCES += \ $$PWD/src/keyboardwidget.cpp \ $$PWD/src/x11keyboard.cpp \ $$PWD/src/cursormonitor.cpp \ $$PWD/src/virtualkeyboard.cpp HEADERS += \ $$PWD/src/keyboardwidget.h \ $$PWD/src/x11keyboard.h \ $$PWD/src/cursormonitor.h \ $$PWD/src/virtualkeyboard.h FORMS += \ $$PWD/src/keyboardwidget.ui RESOURCES += \ $$PWD/src/keyboard.qrc ukui-greeter/ukui-greeter/VirtualKeyboard/CMakeLists.txt0000644000175000017500000000103314204635151022363 0ustar fengfengfind_package(Qt5 COMPONENTS Core Widgets REQUIRED) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) qt5_add_resources(VirtualKeyboard_SRC src/keyboard.qrc) set(VirtualKeyboard_SRC ${VirtualKeyboard_SRC} src/cursormonitor.cpp src/keyboardwidget.cpp src/virtualkeyboard.cpp src/x11keyboard.cpp src/keyboard.qrc) include_directories( ${Qt5Core_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS} ) add_library(VirtualKeyboard STATIC ${VirtualKeyboard_SRC}) target_link_libraries(VirtualKeyboard Qt5::Core Qt5::Widgets) ukui-greeter/ukui-greeter/VirtualKeyboard/VirtualKeyboard.pro0000644000175000017500000000200314204635151023452 0ustar fengfeng#------------------------------------------------- # # Project created by QtCreator 2018-10-12T16:43:42 # #------------------------------------------------- QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = VirtualKeyboard TEMPLATE = app # The following define makes your compiler emit warnings if you use # any feature of Qt which has been marked as deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the # deprecated API in order to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS # You can also make your code fail to compile if you use deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 CONFIG += link_pkgconfig debug PKGCONFIG += xtst x11 include(VirtualKeyboard.pri) SOURCES += \ $$PWD/src/main.cpp ukui-greeter/ukui-greeter/VirtualKeyboard/src/0000755000175000017500000000000014204635151020415 5ustar fengfengukui-greeter/ukui-greeter/VirtualKeyboard/src/x11keyboard.cpp0000644000175000017500000001237414204635151023262 0ustar fengfeng/** * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "x11keyboard.h" #include #include #include #include struct CharMap { QChar name; KeySym code; }; struct CharMap XSpecialSymbolMap[] { {' ', XK_space}, {',', XK_comma}, {'.', XK_period}, {'\'', XK_quoteright}, {'@', XK_at}, {'#', XK_numbersign}, {'$', XK_dollar}, {'%', XK_percent}, {'&', XK_ampersand}, {'*', XK_asterisk}, {'(', XK_parenleft}, {')', XK_parenright}, {'-', XK_minus}, {'+', XK_plus}, {'!', XK_exclam}, {'"', XK_quotedbl}, {'<', XK_less}, {'>', XK_greater}, {':', XK_colon}, {';', XK_semicolon}, {'/', XK_slash}, {'?', XK_question}, {'=', XK_equal}, {'.', XK_kana_middledot}, {'~', XK_asciitilde}, {'`', XK_grave}, {'|', XK_bar}, {'^', XK_asciicircum}, {'{', XK_braceleft}, {'}', XK_braceright}, {'[', XK_bracketleft}, {']', XK_bracketright}, {'_', XK_underscore}, {'\\', XK_backslash}, }; QMap funckeyMap = { {FuncKey::SPACE, XK_space}, {FuncKey::BACKSPACE, XK_BackSpace}, {FuncKey::ENTER, XK_Return}, {FuncKey::HOME, XK_Home}, {FuncKey::END, XK_End}, {FuncKey::PGUP, XK_Page_Up}, {FuncKey::PGDN, XK_Page_Down}, {FuncKey::INSERT, XK_Insert}, {FuncKey::DELETE, XK_Delete}, {FuncKey::UP, XK_Up}, {FuncKey::DOWN, XK_Down}, {FuncKey::LEFT, XK_Left}, {FuncKey::RIGHT, XK_Right} }; QMap modifierMap = { {Modifier::CTRL, XK_Control_L}, {Modifier::ALT, XK_Alt_L}, {Modifier::SUPER, XK_Super_L}, {Modifier::SHIFT, XK_Shift_L} }; QVector shiftKeyVec = {'~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '{', '}', '|', ':', '"', '>', '?'}; static Display *display = XOpenDisplay(0); bool isShift = false; bool isLetter = false; unsigned int keyCodeOfChar(QChar c) { QString text(c); KeySym keysym = XStringToKeysym(text.toLocal8Bit().data()); if(keysym == NoSymbol) { int symbolCount = sizeof(XSpecialSymbolMap) / sizeof(struct CharMap); for(int i = 0; i < symbolCount; i++) { if(XSpecialSymbolMap[i].name == c) { keysym = XSpecialSymbolMap[i].code; break; } } } qDebug() << "keysym: " << keysym; isShift = shiftKeyVec.contains(c) || (c >= 'A' && c <= 'Z'); isLetter = c.isLetter(); KeyCode code = XKeysymToKeycode(display, keysym); return code; } X11Keyboard::X11Keyboard(QObject *parent) : QObject(parent) { } X11Keyboard::~X11Keyboard() { XCloseDisplay(display); } void X11Keyboard::addModifier(Modifier::MOD mod) { modList.push_back(mod); } void X11Keyboard::removeModifier(Modifier::MOD mod) { modList.removeOne(mod); } bool X11Keyboard::hasModifier(Modifier::MOD mod) { return modList.contains(mod); } QList X11Keyboard::getAllModifier() { return modList; } void X11Keyboard::clearModifier() { modList.clear(); } void X11Keyboard::onKeyPressed(QChar c) { unsigned int keyCode = keyCodeOfChar(c); sendKey(keyCode); } void X11Keyboard::onKeyPressed(FuncKey::FUNCKEY key) { KeyCode keyCode; KeySym keysym = funckeyMap[key]; if(keysym != NoSymbol) keyCode = XKeysymToKeycode(display, keysym); sendKey(keyCode); } void X11Keyboard::sendKey(unsigned int keyCode) { Window focusWindow; int revert; XGetInputFocus(display, &focusWindow, &revert); for(auto mod : modList){ KeyCode keyCode = XKeysymToKeycode(display, modifierMap[mod]); XTestFakeKeyEvent(display, keyCode, True, 2); } //如果使用了修饰键(如ctrl、alt)且字符键是字母,则不起用shift键,否则快捷键不起作用 if(!modList.isEmpty() && isLetter) isShift = false; if(isShift) XTestFakeKeyEvent(display, XKeysymToKeycode(display, XK_Shift_L), True, 2); XTestFakeKeyEvent(display, keyCode, True, CurrentTime); XTestFakeKeyEvent(display, keyCode, False, CurrentTime); if(isShift) XTestFakeKeyEvent(display, XKeysymToKeycode(display, XK_Shift_L), False, 2); for(auto mod : modList){ KeyCode keyCode = XKeysymToKeycode(display, modifierMap[mod]); XTestFakeKeyEvent(display, keyCode, False, 2); } XFlush(display); } ukui-greeter/ukui-greeter/VirtualKeyboard/src/x11keyboard.h0000644000175000017500000000555614204635151022733 0ustar fengfeng/** * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 X11KEYBOARD_H #define X11KEYBOARD_H #include #include class Modifier : public QObject { Q_OBJECT public: Modifier(){} enum MOD{ UNKNOWN = -1, CTRL, SHIFT, ALT, SUPER }; Q_ENUM(MOD) static QString getModifierName(int mod) { QMetaEnum metaEnum = QMetaEnum::fromType(); const char* modName = metaEnum.valueToKey(mod); QString result = QString(modName).toLower(); return result; } static MOD getModifier(const QString &modName) { QMetaEnum metaEnum = QMetaEnum::fromType(); MOD mod = (MOD)metaEnum.keyToValue(modName.toUpper().toLocal8Bit().data()); return mod; } }; class FuncKey : public QObject { Q_OBJECT public: FuncKey(){} enum FUNCKEY { UNKNOWN = -1, SPACE = 0, BACKSPACE, ENTER, HOME, END, PGUP, PGDN, INSERT, DELETE, UP, DOWN, LEFT, RIGHT }; Q_ENUM(FUNCKEY) static QString getKeyName(int key) { QMetaEnum metaEnum = QMetaEnum::fromType(); const char* keyName = metaEnum.valueToKey(key); QString result = QString(keyName).toLower(); return result; } static FUNCKEY getKey(const QString &keyName) { QMetaEnum metaEnum = QMetaEnum::fromType(); FUNCKEY key = (FUNCKEY)metaEnum.keyToValue(keyName.toUpper().toLocal8Bit().data()); return key; } }; class X11Keyboard : public QObject { Q_OBJECT public: explicit X11Keyboard(QObject *parent = nullptr); ~X11Keyboard(); void addModifier(Modifier::MOD mod); void removeModifier(Modifier::MOD mod); bool hasModifier(Modifier::MOD mod); QList getAllModifier(); void clearModifier(); public Q_SLOTS: void onKeyPressed(QChar c); void onKeyPressed(FuncKey::FUNCKEY key); private: void sendKey(unsigned int keyCode); private: QList modList; }; #endif // X11KEYBOARD_H ukui-greeter/ukui-greeter/VirtualKeyboard/src/keyboard.qss0000644000175000017500000000133414204635151022746 0ustar fengfengQPushButton { border: none; font: 24px; color: white; background: #35322f; border-radius: 5px; } QPushButton::pressed { color: gray; background: #2a2826; } #btn_backspace, #btn_enter, #btn_shift_l, #btn_shift_r, #btn_ctrl_l, #btn_ctrl_r, #btn_alt_l, #btn_alt_r, #btn_super { font: 16px; background: #1e1b18 } #btn_backspace::pressed, #btn_enter::pressed, #btn_shift_l::pressed, #btn_shift_r::pressed, #btn_ctrl_l::pressed, #btn_ctrl_r::pressed, #btn_alt_l::pressed, #btn_alt_r::pressed, #btn_super::pressed { background: #181613; color: gray; } #btn_letter, #btn_symbol, #btn_number, #btn_insert, #btn_delete, #btn_home, #btn_end, #btn_pgup, #btn_pgdn, #btn_close { font: 16px; } ukui-greeter/ukui-greeter/VirtualKeyboard/src/keyboardwidget.cpp0000644000175000017500000004040314204635151024126 0ustar fengfeng/** * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "keyboardwidget.h" #include "ui_keyboardwidget.h" #include #include #include #define SYMBOL_KEY_COUNT 29 #define SYMBOL_PAGE_COUNT 2 #define BUTTON_BG "QPushButton{background:#1E1B18}" #define BUTTON_BG_PRESSED "QPushButton{background: #181613;}" #define BUTTON_BG_HL "QPushButton{background:#80c342}" #define BUTTON_BG_HL_PRESSED "QPushButton{background:#486E25}" QChar symbols[SYMBOL_PAGE_COUNT][SYMBOL_KEY_COUNT] = { {'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/'}, {'!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '`', '-', '=', '[', ']', '\\', '|', '{', '}', '~','<', '>', ':', ';', '\'', '"', '-', '+', '?'}}; KeyboardWidget::KeyboardWidget(QWidget *parent) : QWidget(parent), ui(new Ui::KeyboardWidget), capsLock(false), isShift(false), page(0) { vKeyboard = new X11Keyboard(this); connect(this, SIGNAL(keyPressed(QChar)), vKeyboard, SLOT(onKeyPressed(QChar))); connect(this, SIGNAL(keyPressed(FuncKey::FUNCKEY)), vKeyboard, SLOT(onKeyPressed(FuncKey::FUNCKEY))); ui->setupUi(this); bindSingal(); setDefaultIcon(); } KeyboardWidget::~KeyboardWidget() { delete ui; } void KeyboardWidget::resizeEvent(QResizeEvent */*event*/) { int w = width(); int h = height(); int mainLeftMargin = ui->hl_main->contentsMargins().left(); int mainRightMargin = ui->hl_main->contentsMargins().right(); int mainTopMargin = ui->hl_main->contentsMargins().left(); int mainBottomMargin = ui->hl_main->contentsMargins().right(); int mainSpacing = ui->hl_main->spacing(); int itemSpacing = ui->hl_1->spacing(); int btnWidthCount = w - 11 * itemSpacing - mainSpacing- mainLeftMargin - mainRightMargin; int btnHeightCount = h - 3 * itemSpacing - mainTopMargin - mainBottomMargin; double btnWidth = btnWidthCount / 12; double btnHeight = btnHeightCount / 4; for(int i = 0; i <= 28; i++) { QString btnObjName = "btn_" + QString::number(i); QPushButton *btn = ui->page_letter->findChild(btnObjName); btn->setFixedSize(btnWidth, btnHeight); } ui->btn_ctrl_l->setFixedSize(btnWidth * 1.3, btnHeight); ui->btn_ctrl_r->setFixedSize(btnWidth * 1.3, btnHeight); ui->btn_alt_l->setFixedSize(btnWidth, btnHeight); ui->btn_alt_r->setFixedSize(btnWidth, btnHeight); ui->btn_super->setFixedSize(btnWidth, btnHeight); ui->btn_shift_l->setFixedSize(btnWidth, btnHeight); ui->btn_shift_r->setFixedHeight(btnHeight); ui->spacer_2->changeSize(btnWidth / 2, 20); for(int i = 1; i <= 9; i++) { QString btnObjName = "btn_num_" + QString::number(i); QPushButton *btn = ui->page_number->findChild(btnObjName); btn->setFixedWidth(btnWidth); } ui->btn_backspace_num->setFixedSize(btnWidth,btnHeight); ui->btn_insert->setFixedWidth(btnWidth); ui->btn_delete->setFixedWidth(btnWidth); ui->btn_home->setFixedWidth(btnWidth); ui->btn_end->setFixedWidth(btnWidth); ui->btn_pgup->setFixedWidth(btnWidth); ui->btn_pgdn->setFixedWidth(btnWidth); ui->btn_up->setFixedSize(btnWidth,btnHeight); ui->btn_down->setFixedSize(btnWidth,btnHeight); ui->btn_left->setFixedSize(btnWidth,btnHeight); ui->btn_right->setFixedSize(btnWidth,btnHeight); ui->btn_close->setFixedHeight(btnHeight); ui->btn_letter->setFixedHeight(btnHeight); ui->btn_symbol->setFixedHeight(btnHeight); ui->btn_number->setFixedHeight(btnHeight); setIconSize(); } float hScale = 0.6; float wScale = hScale; #define SET_ICON_SIZE_SCALE(btn) \ ui->btn_##btn->setIconSize(QSize(ui->btn_##btn->width() * hScale, ui->btn_##btn->height() * wScale)); #define SET_ICON_SIZE(btn) \ ui->btn_##btn->setIconSize(QSize(ui->btn_##btn->width(), ui->btn_##btn->height())); #define SET_SHIFT_ICON_SIZE_SCALE(btn) \ ui->btn_##btn->setIconSize(QSize(ui->btn_##btn->height() * 0.4, ui->btn_##btn->height() * 0.4)); #define SET_ENTER_ICON_SIZE_SCALE(btn) \ ui->btn_##btn->setIconSize(QSize(ui->btn_##btn->width() * 0.4, ui->btn_##btn->height() * 0.4)); void KeyboardWidget::setIconSize() { SET_ICON_SIZE_SCALE(backspace); SET_ICON_SIZE_SCALE(backspace_num); SET_ENTER_ICON_SIZE_SCALE(enter); SET_ICON_SIZE_SCALE(enter_num); SET_ICON_SIZE_SCALE(close); SET_ICON_SIZE_SCALE(super); SET_SHIFT_ICON_SIZE_SCALE(shift_l); SET_SHIFT_ICON_SIZE_SCALE(shift_r); SET_ICON_SIZE(up); SET_ICON_SIZE(down); SET_ICON_SIZE(left); SET_ICON_SIZE(right); } void KeyboardWidget::bindSingal() { for(auto obj : ui->page_letter->children()) { if(obj->metaObject()->className() == QString("QPushButton")) { QPushButton *btn = static_cast(obj); btn->setFocusPolicy(Qt::NoFocus); connect(btn, &QPushButton::clicked, this, &KeyboardWidget::onButtonClicked); connect(btn, &QPushButton::pressed, this, &KeyboardWidget::onButtonPressed); connect(btn, &QPushButton::released, this, &KeyboardWidget::onButtonReleased); } } for(auto obj : ui->page_number->children()) { if(obj->metaObject()->className() == QString("QPushButton")) { QPushButton *btn = static_cast(obj); btn->setFocusPolicy(Qt::NoFocus); connect(btn, &QPushButton::clicked, this, &KeyboardWidget::onButtonClicked); connect(btn, &QPushButton::pressed, this, &KeyboardWidget::onButtonPressed); connect(btn, &QPushButton::released, this, &KeyboardWidget::onButtonReleased); } } ui->btn_close->setFocusPolicy(Qt::NoFocus); ui->btn_letter->setFocusPolicy(Qt::NoFocus); ui->btn_symbol->setFocusPolicy(Qt::NoFocus); ui->btn_number->setFocusPolicy(Qt::NoFocus); connect(ui->btn_letter, &QPushButton::clicked, this, [&] { ui->stackedWidget->setCurrentWidget(ui->page_letter); page = 0; switchPage(); }); connect(ui->btn_symbol, &QPushButton::clicked, this, [&] { ui->stackedWidget->setCurrentWidget(ui->page_letter); page = 1; switchPage(); }); connect(ui->btn_number, &QPushButton::clicked, this, [&] { ui->stackedWidget->setCurrentWidget(ui->page_number); }); connect(ui->btn_close, &QPushButton::clicked, this, &KeyboardWidget::aboutToClose); connect(ui->btn_close, &QPushButton::pressed, this, &KeyboardWidget::onButtonPressed); connect(ui->btn_close, &QPushButton::released, this, &KeyboardWidget::onButtonReleased); } void KeyboardWidget::setDefaultIcon() { ui->btn_backspace->setIcon(QIcon(":/images/images/backspace.svg")); ui->btn_backspace_num->setIcon(QIcon(":/images/images/backspace.svg")); ui->btn_enter->setIcon(QIcon(":/images/images/enter.svg")); ui->btn_enter_num->setIcon(QIcon(":/images/images/enter.svg")); ui->btn_shift_l->setIcon(QIcon(":/images/images/capslock.svg")); ui->btn_shift_r->setIcon(QIcon(":/images/images/capslock.svg")); ui->btn_close->setIcon(QIcon(":/images/images/close.svg")); //ui->btn_super->setIcon(QIcon(":/images/images/super.svg")); ui->btn_super->setText("Super"); ui->btn_up->setIcon(QIcon(":/images/images/up.svg")); ui->btn_down->setIcon(QIcon(":/images/images/down.svg")); ui->btn_left->setIcon(QIcon(":/images/images/left.svg")); ui->btn_right->setIcon(QIcon(":/images/images/right.svg")); } QString KeyboardWidget::getKeyName(QPushButton *btn) { QString objName = btn->objectName(); int lastUnderline = objName.lastIndexOf('_'); int start = strlen("btn_"); int keyLength = lastUnderline - start; QString keyName = objName.mid(start, keyLength); return keyName; } void KeyboardWidget::changeFuncKeyStyle(QPushButton *btn, bool isPressed) { QString modName = getKeyName(btn); Modifier::MOD mod = Modifier::getModifier(modName); if(vKeyboard->hasModifier(mod)) { if(isPressed) btn->setStyleSheet(BUTTON_BG_HL_PRESSED); else btn->setStyleSheet(BUTTON_BG_HL); } else { if(isPressed) btn->setStyleSheet(BUTTON_BG_PRESSED); else btn->setStyleSheet(BUTTON_BG); } } void KeyboardWidget::changeShitKeyStyle(QPushButton *btn, bool isPressed) { if(page == 0){ if(isShift) { if(capsLock){ if(isPressed) { btn->setStyleSheet(BUTTON_BG_HL_PRESSED); btn->setIcon(QIcon(":/images/images/capslock_click.svg")); } else { btn->setStyleSheet(BUTTON_BG_HL); btn->setIcon(QIcon(":/images/images/capslock.svg")); } } else { if(isPressed) btn->setIcon(QIcon(":/images/images/capslock_hl_click.svg")); else btn->setIcon(QIcon(":/images/images/capslock_hl.svg")); } } else { if(isPressed) btn->setIcon(QIcon(":/images/images/capslock_click.svg")); else btn->setIcon(QIcon(":/images/images/capslock.svg")); } } } void KeyboardWidget::changeDirectKeyStyle(QPushButton *btn, bool isPressed) { QString keyName = getKeyName(btn); FuncKey::FUNCKEY key = FuncKey::getKey(keyName); if(key == FuncKey::UNKNOWN) return; QString iconName = QString(":/images/images/%1.svg").arg(keyName); QString iconNamePressed = QString(":/images/images/%1_click.svg").arg(keyName); if(isPressed) btn->setIcon(QIcon(iconNamePressed)); else btn->setIcon(QIcon(iconName)); } /** * @brief 修改按键样式 * @param obj 按键 * @param isPressed 按下或者松开 */ void KeyboardWidget::changeKeyStyle(QPushButton *btn, bool isPressed) { if(btn == ui->btn_ctrl_l || btn == ui->btn_ctrl_r || btn == ui->btn_alt_l || btn == ui->btn_alt_r || btn == ui->btn_super) { changeFuncKeyStyle(btn, isPressed); } if(btn == ui->btn_shift_l) changeShitKeyStyle(ui->btn_shift_l, isPressed); if(btn == ui->btn_shift_r) changeShitKeyStyle(ui->btn_shift_r, isPressed); changeDirectKeyStyle(btn, isPressed); } void KeyboardWidget::onButtonPressed() { QPushButton *btn = static_cast(sender()); changeKeyStyle(btn, true); } void KeyboardWidget::onButtonReleased() { QPushButton *btn = static_cast(sender()); changeKeyStyle(btn, false); } void KeyboardWidget::onButtonClicked() { QObject *obj = sender(); if(obj->metaObject()->className() != QString("QPushButton")) return; QPushButton *btn = static_cast(obj); QString keyName = getKeyName(btn); qDebug() << "keyName: " << keyName; Modifier::MOD mod = Modifier::getModifier(keyName); FuncKey::FUNCKEY funcKey = FuncKey::getKey(keyName); if(keyName == "shift") { if(page == 0) { isShift = !isShift; if(isShift) { //第一次被按下 capsLock = false; shiftLastClicked = QTime::currentTime(); ui->btn_shift_l->setIcon(QIcon(":/images/images/capslock_hl.svg")); ui->btn_shift_r->setIcon(QIcon(":/images/images/capslock_hl.svg")); } else { int doubleClickInterval = QApplication::doubleClickInterval(); if(shiftLastClicked.msecsTo(QTime::currentTime()) <= doubleClickInterval) { //shift键双击,锁定大写 capsLock = true; isShift = true; ui->btn_shift_l->setIcon(QIcon(":/images/images/capslock.svg")); ui->btn_shift_r->setIcon(QIcon(":/images/images/capslock.svg")); ui->btn_shift_l->setStyleSheet("QPushButton{background:#80c342}"); ui->btn_shift_r->setStyleSheet("QPushButton{background:#80c342}"); } else { ui->btn_shift_l->setIcon(QIcon(":/images/images/capslock.svg")); ui->btn_shift_r->setIcon(QIcon(":/images/images/capslock.svg")); ui->btn_shift_l->setStyleSheet("QPushButton{background:#1e1b18}"); ui->btn_shift_r->setStyleSheet("QPushButton{background:#1e1b18}"); } } toggleCase(); } else { page = page % (SYMBOL_PAGE_COUNT - 1) + 1; switchPage(); } } else if(mod != Modifier::UNKNOWN) { if(vKeyboard->hasModifier(mod)) { vKeyboard->removeModifier(mod); btn->setStyleSheet(BUTTON_BG); btn->setStyleSheet(BUTTON_BG); } else { vKeyboard->addModifier(mod); btn->setStyleSheet(BUTTON_BG_HL); btn->setStyleSheet(BUTTON_BG_HL); } } else if(funcKey != FuncKey::UNKNOWN) { Q_EMIT keyPressed(funcKey); } else { //字符键 QChar c; QString text = btn->text(); qDebug() << "clicked button text: " << text; if(text == "&&") c = '&'; else if(text.length() == 1) c = text.at(0); Q_EMIT keyPressed(c); //如果shift键被单击,按一个键后就恢复为小写 if(isShift && !capsLock) { isShift = false; toggleCase(); changeShitKeyStyle(ui->btn_shift_l, false); changeShitKeyStyle(ui->btn_shift_r, false); } clearModifier(); } } void KeyboardWidget::clearModifier() { for(auto mod : vKeyboard->getAllModifier()) { QString modName = Modifier::getModifierName(mod); if(mod == Modifier::SUPER) { QString objName = QString("btn_%1").arg(modName); QPushButton *btn = ui->page_letter->findChild(objName); btn->setStyleSheet(BUTTON_BG); } else { QString objName = QString("btn_%1_l").arg(modName); QPushButton *btn = ui->page_letter->findChild(objName); btn->setStyleSheet(BUTTON_BG); objName = QString("btn_%1_r").arg(modName); btn = ui->page_letter->findChild(objName); btn->setStyleSheet(BUTTON_BG); } } vKeyboard->clearModifier(); } void KeyboardWidget::toggleCase() { for(int i = 0; i < 26; i++) { QString objName = "btn_" + QString::number(i); QPushButton *btn = findChild(objName); QChar ch; if(isShift) { //切换到大写 ch = symbols[0][i].toUpper(); } else { ch = symbols[0][i]; } btn->setText(ch); } } void KeyboardWidget::switchPage() { if(page == 0) { ui->btn_shift_l->setText(""); ui->btn_shift_r->setText(""); ui->btn_shift_l->setIcon(QIcon(":/images/images/capslock.svg")); ui->btn_shift_r->setIcon(QIcon(":/images/images/capslock.svg")); } else { ui->btn_shift_l->setText(""); ui->btn_shift_r->setText(""); ui->btn_shift_l->setIcon(QIcon(":/images/images/capslock.svg")); ui->btn_shift_r->setIcon(QIcon(":/images/images/capslock.svg")); } for(int i = 0; i < SYMBOL_KEY_COUNT; i++) { QString btnObjName = "btn_" + QString::number(i); QPushButton *btn = ui->page_letter->findChild(btnObjName); QChar c = symbols[page][i]; if(c == '&') btn->setText("&&"); else btn->setText(c); } } ukui-greeter/ukui-greeter/VirtualKeyboard/src/virtualkeyboard.cpp0000644000175000017500000000522714204635151024336 0ustar fengfeng/** * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "virtualkeyboard.h" #include #include #include #include VirtualKeyboard::VirtualKeyboard(QWidget *parent) : QWidget(parent) { Q_INIT_RESOURCE(keyboard); setAutoFillBackground(true); QPalette plt; plt.setBrush(QPalette::Background, Qt::black); setPalette(plt); setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::WindowDoesNotAcceptFocus); keyboardWidget = new KeyboardWidget(this); QHBoxLayout *hl_keyboard = new QHBoxLayout(this); QSpacerItem *spacer = new QSpacerItem(40, 20); hl_keyboard->addSpacerItem(spacer); hl_keyboard->addWidget(keyboardWidget); QSpacerItem *spacer2 = new QSpacerItem(40, 20); hl_keyboard->addSpacerItem(spacer2); QFile qssFile(":/qss/keyboard.qss"); qssFile.open(QIODevice::ReadOnly); setStyleSheet(qssFile.readAll()); qssFile.close(); QDesktopWidget *desktop = QApplication::desktop(); cursorMonitor = new CursorMonitor(this); //在多显示器情况下,监视鼠标指针的位置和主显示器变化信号 connect(cursorMonitor, &CursorMonitor::cursorPosChanged, this, [&](const QPoint &pos){ adjustGeometry(desktop->screenNumber(pos)); }); connect(desktop, &QDesktopWidget::primaryScreenChanged, this, [&]{ adjustGeometry(desktop->primaryScreen()); }); connect(keyboardWidget, &KeyboardWidget::aboutToClose, this, &VirtualKeyboard::aboutToClose); adjustGeometry(desktop->primaryScreen()); } void VirtualKeyboard::adjustGeometry(int screen) { QDesktopWidget *desktop = QApplication::desktop(); QWidget *activateScreen = desktop->screen(screen); setGeometry(0, activateScreen->height() - activateScreen->height() / 3, activateScreen->width(), activateScreen->height() / 3); } ukui-greeter/ukui-greeter/VirtualKeyboard/src/keyboard.qrc0000644000175000017500000000172414204635151022730 0ustar fengfeng keyboard.qss images/backspace_click.svg images/backspace.svg images/capslock_click.svg images/capslock_hl.svg images/capslock.svg images/down_click.svg images/down.svg images/enter_click.svg images/enter.svg images/left_click.svg images/left.svg images/right_click.svg images/right.svg images/super_click.svg images/super.svg images/up.svg images/close_click.svg images/close.svg images/capslock_hl_click.svg images/up_click.svg ukui-greeter/ukui-greeter/VirtualKeyboard/src/cursormonitor.h0000644000175000017500000000210114204635151023505 0ustar fengfeng/** * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 CURSORMONITOR_H #define CURSORMONITOR_H #include #include class CursorMonitor : public QThread { Q_OBJECT public: explicit CursorMonitor(QObject *parent = nullptr); void run(); Q_SIGNALS: void cursorPosChanged(const QPoint& pos); }; #endif // CURSORMONITOR_H ukui-greeter/ukui-greeter/VirtualKeyboard/src/main.cpp0000644000175000017500000000224214204635151022045 0ustar fengfeng/** * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "virtualkeyboard.h" #include int main(int argc, char *argv[]) { // qputenv("QT_IM_MODULE", QByteArray("qtvirtualkeyboard")); QApplication a(argc, argv); VirtualKeyboard *keyboard = new VirtualKeyboard; QObject::connect(keyboard, &VirtualKeyboard::aboutToClose, &a, &QApplication::quit); keyboard->resize(400,300); keyboard->show(); return a.exec(); } ukui-greeter/ukui-greeter/VirtualKeyboard/src/cursormonitor.cpp0000644000175000017500000000400014204635151024040 0ustar fengfeng/** * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 "cursormonitor.h" #include #include CursorMonitor::CursorMonitor(QObject *parent) : QThread(parent) { } void CursorMonitor::run() { Display *display; XEvent xevent; Window window; display = XOpenDisplay(NULL); window = DefaultRootWindow(display); XAllowEvents(display, AsyncBoth, CurrentTime); XGrabPointer(display, window, 1, PointerMotionMask | ButtonPressMask | ButtonReleaseMask , GrabModeAsync, GrabModeAsync, None, None, CurrentTime); while(1) { XNextEvent(display, &xevent); switch (xevent.type) { case MotionNotify: //printf("Mouse move : [%d, %d]\n", xevent.xmotion.x_root, xevent.xmotion.y_root); Q_EMIT cursorPosChanged(QPoint(xevent.xmotion.x_root, xevent.xmotion.y_root)); break; case ButtonPress: // printf("Button pressed : %s\n", key_name[xevent.xbutton.button - 1]); break; case ButtonRelease: // printf("Button released : %s\n", key_name[xevent.xbutton.button - 1]); break; } } } ukui-greeter/ukui-greeter/VirtualKeyboard/src/keyboardwidget.h0000644000175000017500000000423414204635151023575 0ustar fengfeng/** * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 KEYBOARDWIDGET_H #define KEYBOARDWIDGET_H #include #include #include #include "x11keyboard.h" namespace Ui { class KeyboardWidget; } class QPushButton; class KeyboardWidget : public QWidget { Q_OBJECT public: explicit KeyboardWidget(QWidget *parent = 0); ~KeyboardWidget(); protected: void resizeEvent(QResizeEvent *event); private: void bindSingal(); void toggleCase(); void switchPage(); void setDefaultIcon(); void setIconSize(); void changeKeyStyle(QPushButton *btn, bool isPressed); void changeFuncKeyStyle(QPushButton *btn, bool isPressed); void changeShitKeyStyle(QPushButton *btn, bool isPressed); void changeDirectKeyStyle(QPushButton *btn, bool isPressed); void clearModifier(); QString getKeyName(QPushButton *btn); private Q_SLOTS: void onButtonClicked(); void onButtonPressed(); void onButtonReleased(); Q_SIGNALS: void aboutToClose(); void keyPressed(QChar c); void keyPressed(FuncKey::FUNCKEY key); private: Ui::KeyboardWidget *ui; bool capsLock; //是否大写锁定 bool isShift; QTime shiftLastClicked; //shift键上次被点击的时间 int page; //当前是第几页的键盘 X11Keyboard *vKeyboard; }; #endif // KEYBOARDWIDGET_H ukui-greeter/ukui-greeter/VirtualKeyboard/src/keyboardwidget.ui0000644000175000017500000012344514204635151023771 0ustar fengfeng KeyboardWidget Qt::NonModal 0 0 1132 293 0 0 0 0 KeyboardWidget 0 QLayout::SetMinAndMaxSize 0 0 0 0 20 QLayout::SetNoConstraint 10 10 10 10 1 0 QLayout::SetNoConstraint 0 0 0 0 5 QLayout::SetNoConstraint Qt::Vertical 20 40 10 QLayout::SetNoConstraint 0 0 q 0 0 w 0 0 e 0 0 r 0 0 t 0 0 y 0 0 u 0 0 i 0 0 o 0 0 p 0 0 10 QLayout::SetNoConstraint Qt::Horizontal QSizePolicy::Maximum 40 20 0 0 a 0 0 s 0 0 d 0 0 f 0 0 g 0 0 h 0 0 j 0 0 k 0 0 l 0 0 10 QLayout::SetNoConstraint 0 0 0 0 z 0 0 x 0 0 c 0 0 v 0 0 b 0 0 n 0 0 m 0 0 , 0 0 . 0 0 10 QLayout::SetNoConstraint 0 0 Ctrl 0 0 0 0 Alt 0 0 0 0 Alt 0 0 / 0 0 Ctrl Qt::Vertical 20 40 QLayout::SetNoConstraint Qt::Horizontal 40 20 10 QLayout::SetNoConstraint QLayout::SetNoConstraint 10 0 0 9 0 0 5 0 0 8 0 0 4 0 0 2 0 0 7 0 0 1 0 0 6 0 0 3 0 0 0 0 0 0 0 Qt::Horizontal 40 20 QLayout::SetNoConstraint 10 0 0 Delete 0 0 End 0 0 Insert 0 0 PgUp 0 0 0 0 0 0 Home 0 0 0 0 0 0 PgDn Qt::Horizontal 40 20 5 QLayout::SetNoConstraint 0 0 0 0 Qt::Vertical 20 40 0 0 100 16777215 0 0 100 16777215 Abc 0 0 100 16777215 @ 0 0 100 16777215 123 Qt::Vertical 20 40 ukui-greeter/ukui-greeter/VirtualKeyboard/src/images/0000755000175000017500000000000014204635151021662 5ustar fengfengukui-greeter/ukui-greeter/VirtualKeyboard/src/images/capslock.svg0000644000175000017500000000115514204635151024204 0ustar fengfeng ukui-greeter/ukui-greeter/VirtualKeyboard/src/images/super.svg0000644000175000017500000000221314204635151023537 0ustar fengfeng画板 11ukui-greeter/ukui-greeter/VirtualKeyboard/src/images/enter_click.svg0000644000175000017500000000143214204635151024665 0ustar fengfeng ukui-greeter/ukui-greeter/VirtualKeyboard/src/images/capslock_click.svg0000644000175000017500000000112714204635151025350 0ustar fengfeng ukui-greeter/ukui-greeter/VirtualKeyboard/src/images/close.svg0000644000175000017500000000226514204635151023515 0ustar fengfeng ukui-greeter/ukui-greeter/VirtualKeyboard/src/images/up_click.svg0000644000175000017500000000071414204635151024176 0ustar fengfeng ukui-greeter/ukui-greeter/VirtualKeyboard/src/images/right_click.svg0000644000175000017500000000071414204635151024667 0ustar fengfeng ukui-greeter/ukui-greeter/VirtualKeyboard/src/images/capslock_hl_click.svg0000644000175000017500000000106014204635151026027 0ustar fengfeng ukui-greeter/ukui-greeter/VirtualKeyboard/src/images/left.svg0000644000175000017500000000066714204635151023346 0ustar fengfeng 画板 19 ukui-greeter/ukui-greeter/VirtualKeyboard/src/images/down_click.svg0000644000175000017500000000071414204635151024521 0ustar fengfeng ukui-greeter/ukui-greeter/VirtualKeyboard/src/images/up.svg0000644000175000017500000000072214204635151023030 0ustar fengfeng 画板 13 ukui-greeter/ukui-greeter/VirtualKeyboard/src/images/left_click.svg0000644000175000017500000000074114204635151024504 0ustar fengfeng 画板 20 ukui-greeter/ukui-greeter/VirtualKeyboard/src/images/backspace.svg0000644000175000017500000000150514204635151024320 0ustar fengfeng ukui-greeter/ukui-greeter/VirtualKeyboard/src/images/right.svg0000644000175000017500000000064214204635151023522 0ustar fengfeng ukui-greeter/ukui-greeter/VirtualKeyboard/src/images/capslock_hl.svg0000644000175000017500000000106014204635151024662 0ustar fengfeng ukui-greeter/ukui-greeter/VirtualKeyboard/src/images/backspace_click.svg0000644000175000017500000000152314204635151025465 0ustar fengfeng ukui-greeter/ukui-greeter/VirtualKeyboard/src/images/close_click.svg0000644000175000017500000000235514204635151024662 0ustar fengfeng ukui-greeter/ukui-greeter/VirtualKeyboard/src/images/down.svg0000644000175000017500000000072214204635151023353 0ustar fengfeng 画板 15 ukui-greeter/ukui-greeter/VirtualKeyboard/src/images/super_click.svg0000644000175000017500000000222714204635151024711 0ustar fengfeng画板 12ukui-greeter/ukui-greeter/VirtualKeyboard/src/images/enter.svg0000644000175000017500000000147414204635151023526 0ustar fengfeng ukui-greeter/ukui-greeter/VirtualKeyboard/src/virtualkeyboard.h0000644000175000017500000000241614204635151024000 0ustar fengfeng/** * Copyright (C) 2018 Tianjin KYLIN Information Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, 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 VIRTUALKEYBOARD_H #define VIRTUALKEYBOARD_H #include #include #include "keyboardwidget.h" #include "cursormonitor.h" class VirtualKeyboard : public QWidget { Q_OBJECT public: explicit VirtualKeyboard(QWidget *parent = 0); private: void adjustGeometry(int screen); Q_SIGNALS: void aboutToClose(); private: KeyboardWidget *keyboardWidget; CursorMonitor *cursorMonitor; bool isApplication; }; #endif // VIRTUALKEYBOARD_H ukui-greeter/ukui-greeter/VirtualKeyboard/README.md0000644000175000017500000000004114204635151021100 0ustar fengfengA simple virtual keyboard in X11 ukui-greeter/ukui-greeter/qss.qrc0000644000175000017500000000013714204635151016035 0ustar fengfeng ukui-greeter.qss ukui-greeter/ukui-greeter/ukui-greeter.desktop0000644000175000017500000000017714204635151020527 0ustar fengfeng[Desktop Entry] Name=UKUI Greeter Comment=UKUI Greeter Exec=ukui-greeter Type=Application X-Ubuntu-Gettext-Domain=ukui-greeter ukui-greeter/ukui-greeter/translations/0000755000175000017500000000000014204635501017237 5ustar fengfengukui-greeter/ukui-greeter/translations/bo.ts0000644000175000017500000001661114204635151020215 0ustar fengfeng BiometricAuthWidget Current device: Identify failed, Please retry. BiometricDevicesWidget Please select the biometric device Device type: Device name: OK DeviceType FingerPrint FingerVein Iris Face VoicePrint FakeDialog cancel GreeterWrapper failed to start session. KeyboardWidget KeyboardWidget LoginWindow Login logged in login Password: Username Incorrect user name, please input again Incorrect password, please input again Retry Biometric Authentication Password Authentication Other Devices MainWindow MainWindow PowerManager Switch User Suspend Restart Power Off UsersModel Guest Guest Session Login ukui-greeter/ukui-greeter/translations/fr.ts0000644000175000017500000001300614204635501020216 0ustar fengfeng BiometricAuthWidget Current device: Identify failed, Please retry. BiometricDevicesWidget Please select the biometric device Device type: Device name: OK DeviceType FingerPrint FingerVein Iris Face VoicePrint FakeDialog cancel GreeterWindow Login S'identifier GreeterWrapper failed to start session. impossible de démarrer la session. IconEdit Password: Mot de passe: Username KeyboardWidget KeyboardWidget LoginWindow Login S'identifier logged in connecté login s'identifier Password: Mot de passe: Incorrect password, please input again Mot de passe incorrect, veuillez saisir à nouveau Retry Biometric Authentication Password Authentication Other Devices Username Authentication failure, Please try again Please enter your password or enroll your fingerprint Too many unsuccessful attempts,please enter password. MainWindow MainWindow Fenêtre principale PowerManager Restart Switch User Power Off Sleep Suspend UsersModel Guest Session Session d'invité Login S'identifier Guest ukui-greeter/ukui-greeter/translations/pt.ts0000644000175000017500000001266614204635501020245 0ustar fengfeng BiometricAuthWidget Current device: Identify failed, Please retry. BiometricDevicesWidget Please select the biometric device Device type: Device name: OK DeviceType FingerPrint FingerVein Iris Face VoicePrint FakeDialog cancel GreeterWindow Login Entrar GreeterWrapper failed to start session. falhou ao iniciar a sessão. IconEdit Password: Senha: Username KeyboardWidget KeyboardWidget LoginWindow Login Entrar logged in logado login entrar Password: Senha: Incorrect password, please input again Senha incorreta, por favor introduza novamente Retry Biometric Authentication Password Authentication Other Devices Username Authentication failure, Please try again Please enter your password or enroll your fingerprint Too many unsuccessful attempts,please enter password. MainWindow MainWindow Janela principal PowerManager Restart Switch User Power Off Sleep Suspend UsersModel Guest Session Sessão Convidada Login Entrar Guest ukui-greeter/ukui-greeter/translations/zh_CN.ts0000644000175000017500000002616414204635501020621 0ustar fengfeng BiometricAuthWidget Current device: 当前设备: Identify failed, Please retry. 识别失败,请重试 BiometricDevicesWidget Please select the biometric device 请选择生物设备 Device type: 设备类型: Device name: 设备型号: OK 确定 DeviceType FingerPrint 指纹 FingerVein 指静脉 Iris 虹膜 Face 人脸 VoicePrint 声纹 FakeDialog cancel 取消 GreeterWindow Power dialog 电源对话框 On-screen keyboard, providing virtual keyboard function 屏幕键盘,提供虚拟键盘功能 Set the desktop environment for the selected user to log in.If the user is logged in, it will take effect after logging in again 设置选中用户登录后的桌面环境,如果用户已经登录,则会在重新登录后生效 Set the language of the selected user after logging in. If the user is logged in, it will take effect after logging in again. 设置选中用户登录后的语言,如果用户已登录,则在重新登录后生效 Login 登录 GreeterWrapper failed to start session. 启动会话失败 IconEdit Password: 密码: Username 用户名 KeyboardWidget KeyboardWidget LoginWindow logged in 已登录 login 登录 Incorrect user name, please input again 用户名不正确,请重新输入 Biometric Authentication 生物识别认证 Password Authentication 密码认证 Other Devices 其他设备 Retry 重试 Please enter your password or enroll your fingerprint 请输入密码或者录入指纹 Password: 密码: Authentication failure, Please try again 认证失败,请重试 Please try again in %1 minutes. 请%1分钟后再试 Please try again in %1 seconds. 请%1秒后再试 Account locked permanently. 账号已被永久锁定 Password cannot be empty 密码不能为空 Too many unsuccessful attempts,please enter password. 指纹验证失败达最大次数,请使用密码解锁 Incorrect password, please input again 密码错误,请重新输入 Login 登录 Username 用户名 MainWindow MainWindow PowerManager Switch User 切换用户 Suspend 睡眠 Sleep 休眠 Restart 重启 Power Off 关机 UsersModel Guest Session 游客登录 Guest 游客登录 Login 登录 ukui-greeter/ukui-greeter/translations/ru.ts0000644000175000017500000002275614204635501020251 0ustar fengfeng BiometricAuthWidget Current device: Identify failed, Please retry. BiometricDevicesWidget Please select the biometric device Device type: Device name: OK DeviceType FingerPrint FingerVein Iris Face VoicePrint FakeDialog cancel GreeterWindow Login вход GreeterWrapper failed to start session. не начала сессии. IconEdit Password: пароль: Username KeyboardWidget KeyboardWidget LoginWindow Login вход logged in войти в систему login вход Please enter your password or enroll your fingerprint Password: пароль: Username Too many unsuccessful attempts,please enter password. Authentication failure, Please try again Retry Biometric Authentication Password Authentication Other Devices Incorrect password, please input again неверный пароль, пожалуйста, введите снова MainWindow MainWindow PowerManager Switch User Suspend Sleep Restart Power Off UsersModel Guest Guest Session гость. Login вход ukui-greeter/ukui-greeter/translations/tr.ts0000644000175000017500000002301414204635501020234 0ustar fengfeng BiometricAuthWidget Current device: Mevcut aygıt: Identify failed, Please retry. Tanımlama başarısız, Lütfen tekrar deneyin. BiometricDevicesWidget Please select the biometric device Lütfen biyometrik cihazı seçin Device type: Atgıt türü: Device name: Aygıt Adı: OK Tamam DeviceType FingerPrint Parmak izi FingerVein Damar izi Iris Göz izi Face Yüz tanıma VoicePrint Ses izi FakeDialog cancel İptal GreeterWindow Login Giriş GreeterWrapper failed to start session. Oturum başlatılamadı. IconEdit Password: Parola: Username Kullanıcı Adı KeyboardWidget KeyboardWidget LoginWindow logged in Giriş login Giriş Incorrect user name, please input again Yanlış kullanıcı adı, lütfen tekrar girin Biometric Authentication Biyometrik Kimlik Doğrulama Password Authentication Şifre Doğrulama Other Devices Diğer Aygıtlar Retry Yeniden Dene Please enter your password or enroll your fingerprint Password: Parola: Authentication failure, Please try again Too many unsuccessful attempts,please enter password. Incorrect password, please input again Parola yanlış, lütfen tekrar deneyin Login Giriş Username Kullanıcı Adı MainWindow MainWindow Ana Ekran PowerManager Switch User Kullanıcı Değiştir Suspend Askıya Al Sleep Uyku Modu Restart Yeniden Başlat Power Off Bilgisayarı Kapat UsersModel Guest Session Misafir Oturumu Guest Misafir Login Giriş ukui-greeter/ukui-greeter/translations/es.ts0000644000175000017500000001275714204635501020232 0ustar fengfeng BiometricAuthWidget Current device: Identify failed, Please retry. BiometricDevicesWidget Please select the biometric device Device type: Device name: OK DeviceType FingerPrint FingerVein Iris Face VoicePrint FakeDialog cancel GreeterWindow Login Iniciar sesión GreeterWrapper failed to start session. Error al iniciar sesión. IconEdit Password: Contraseña: Username KeyboardWidget KeyboardWidget LoginWindow Login Iniciar sesión logged in conectado login iniciar sesión Password: Contraseña: Incorrect password, please input again Contraseña incorrecta, por favor ingrese nuevamente Retry Biometric Authentication Password Authentication Other Devices Username Authentication failure, Please try again Please enter your password or enroll your fingerprint Too many unsuccessful attempts,please enter password. MainWindow MainWindow Ventana principal PowerManager Restart Switch User Power Off Sleep Suspend UsersModel Guest Session Sesión de invitado Login Iniciar sesión Guest ukui-greeter/ukui-greeter/man/0000755000175000017500000000000014204635151015272 5ustar fengfengukui-greeter/ukui-greeter/man/ukui-greeter.80000644000175000017500000000106414204635151017774 0ustar fengfeng.\" Man Page for ukui-greeter .TH UKUI-GREETER 8 "March 05, 2018" .SH "NAME" ukui-greeter \- LightDM greeter for the UKUI desktop .SH "SYNOPSIS" .B ukui-greeter .SH "DESCRIPTION" .B UKUI Greeter is a LightDM greeter for the UKUI desktop. It is run by the LightDM daemon if configured in lightdm.conf. .SH "BUGS" .SS Should you encounter any bugs, they may be reported at: http://github.com/ukui/ukui-greeter/issues .SH "AUTHORS" .SS This Manual Page has been written for the UKUI Desktop Environment by: yanghao (2018) .SH SEE ALSO .B lightdm ukui-greeter/ukui-greeter/images/0000755000175000017500000000000014204635151015764 5ustar fengfengukui-greeter/ukui-greeter/images/next.png0000644000175000017500000000101614204635151017446 0ustar fengfengPNG  IHDR4hBIDATHMHTQb(.DBr#( BBq/:\Z)D(HELV ?0/o=̝sw`9xs?0:)XX ::8Ŭ {`M[/fu7=ኛp-7iq>bVτ^PܬqmHmbw]k/f՝؋EǍŢbGy1qa?k^nTˌ[Kmb%/fu )Hbŭ QBhplF]}Z=m8yF`K]yKA@VSNr-/쨫+Az@1[nO,ԡJb dV{alL>ЕԓT}EZ> Zm}z~NSnsò A_qlHw@ifBilA&pŃJX{6B% {xb]!\,IIENDB`ukui-greeter/ukui-greeter/images/next-40.png0000644000175000017500000000115014204635151017666 0ustar fengfengPNG  IHDR4hB pHYs+IDATH]o QQ_! z!\ps(~X"hB$4Hh+Ab=3sf[3;O'{ރcoObKqnI3I0I߁ǡj{_J2$V1Z,"Xmo$'Km<[KНk[ I+DB5-V%ut;IPiI0I_鶫t$[ _l,.m{<%u`/s#X.%aU1rwd$X. LٞJIc{<%ـnv $Q fHܓ+ f{I˒V!~7gz;f$p$mFl}.}xemXiZ ~~~-I\nyM˲ECadnݪ0+}n0nI@Ò7@0ۇˡJ=BoW$*K6 6GuA0'Cf{p#C46mlf q-X:d޺ el߱= ֹe%IENDB`ukui-greeter/ukui-greeter/images/scrollbar_down_clicked.png0000644000175000017500000000017714204635151023167 0ustar fengfengPNG  IHDR;֕JFIDAT(c 2p(05DRYfl`ՈK3604ӌFBj$F3^0D5 NIENDB`ukui-greeter/ukui-greeter/images/scrollbar_down_hover.png0000644000175000017500000000017614204635151022713 0ustar fengfengPNG  IHDR;֕JEIDAT(c-\HscrkDǪX5Ҍ 4#kfь Q ٪IENDB`ukui-greeter/ukui-greeter/images/prev.png0000644000175000017500000000076614204635151017457 0ustar fengfengPNG  IHDR4hBIDATH?(EaA('&"R*R @Q+%R2QDJ2AFJ&E!'ϩ=bVLҡ;b9]e08UpڊI7j$S3 ׊e [1I`)&n/_K2+VLR  ;8>7叏BAIENDB`ukui-greeter/ukui-greeter/images/hide-password.png0000644000175000017500000000051714204635151021246 0ustar fengfengPNG  IHDRĴl;IDAT8ұJQ߷X|@|-TLl%4V2[[JHa'hHk%#R.; cƌMDD\۶^{"b>" 3kf ؃m[9|FUݼpKUi{NmAU=c{|Uqf.V 1ՎqQU3s 3X:.[U=XjU R|yf'`xUkQ݌G{+"6~+bϏ>G`GՎ?(-IENDB`ukui-greeter/ukui-greeter/images/password-login.png0000644000175000017500000000027414204635151021445 0ustar fengfengPNG  IHDRw=IDATHA 0 'VO(N~1>4$6)?@ j=3PYq%8SVz%Kr;uEzE߯yC]g{ 0wkjkr/mcU;IENDB`ukui-greeter/ukui-greeter/images/cof.png0000644000175000017500000000335114204635151017243 0ustar fengfengPNG  IHDRBBI]c pHYs   cHRMz%u0`:o_FoIDATxڴY_HTY58w&t!}1 + K.Fw !""HCHIRPU(}ιsΌ}As~;};@[^v$9 K֥_ro^˻שH|_6+=kT)Xxi7ž$ǀ˴ }Q@e>Pb#2?PY$קJ$Ia}"@ ח !&+MV%X\"ID1\mJCyK!NL:b#ßм*QL '6DdR܂giS}# <wkvRUô7hڕ0B0ݐS!=p5CczgNB1J veB6(rEعbt)dD v12} Y5#?(GF0߲j B?$ BOorG 7vX½(CXUG'$ޏaI]' +ڀ=\[0뤑FdpNW‹ ҽ@]0PW^r! 8Iȑ)sS͗a,;491Q++ 2Zq=KY4KҬh$ AdUR%G*i)Bh9&9I`$j{idU 0l;Q)SDp6hp#M'>!wufhiO*tXŋ{R=g&|-t)TLBi'P4!.?UB~;˻;4M* J G$e>z1ҬϐAF(u7H!]=.Cj+6; ׮GGPG4.T{7s-[|hR/̢B??As # wLIly\UkI҂F[ȋ+=%#"zP!I("eoZ 'kMz🕤4SBG&тjmODsg)]>9@̈S0@gFjo >:?ճ%ROVU Խ ip\_ E.ZMz, )S%wa;(P+h!"Ź^쐶 aDIv%}%d^qq;j@䣒azzM>Bv'шe&MсF~Cm{=\.BB8Q ~3S*73qԖD[Z5pX7Њ_Ga#g',?;qmp]؆AD=wf[I>N>wnD/}+*Pт}D7z+3zэNC BMvu"41aQJߴIIENDB`ukui-greeter/ukui-greeter/images/show-password.png0000644000175000017500000000110614204635151021310 0ustar fengfengPNG  IHDRĴl; IDAT8ˋq#a( Q.)?@԰U{\"epye1&e!. Pr f=Nӹ|y~K]Zֈh4Zj Ef"bBD\?z]l~l׸V;xTIhF<Wф \F<8TG1X4h6Eb̓0'U?3_nC\́~ޣRx ډYE~38x<]q3Vefoŷ#b"F|O2/GمKW籪X?-⫑* "b/7Ef.̥ލ؇򽃲3QHJX/J.X.sqg3TujjeDOf +2 .a&VgAߐ4=؁~ ba8_Âk}#+Pf满j/ gIENDB`ukui-greeter/ukui-greeter/images/login-button.png0000644000175000017500000000103214204635151021107 0ustar fengfengPNG  IHDRJLIDATH?LQ?m*t"2i\k8H;1`h\& {FJ ʟjHUPk~w޻~ݽ~R7"t n7/--<ܨjnL9SVDkm z4+@! feM JpG.^8ck_ Y)sx|o6>hvM}m KivFm aP9$!yVŠ]94(- l<9nCii 3gg"qhMFW D?:0D &yJ{ k=HҊw-^UBzMU6խ-H>m,TL&p?4;<Wue Q x7t\~ ?_;0fvN5IENDB`ukui-greeter/ukui-greeter/images/dialog_close_highlight.png0000644000175000017500000000204414204635151023145 0ustar fengfengPNG  IHDRw=tEXtSoftwareAdobe ImageReadyqe<!iTXtXML:com.adobe.xmp IDATxb1 #BA\V2-F-@  *|meIENDB`ukui-greeter/ukui-greeter/images/power.png0000644000175000017500000000076514204635151017636 0ustar fengfengPNG  IHDR;0IDATHAJAEkO f&Bv+/ 1Ⅲ'KN4+u#&?&M3ӍCV)( *{fK6/L6g<(.ylPEѣGԎ-?Yi3s#>z#c ]hxU%ǡ.8<6NyTp@Oc}g\0pY-'|%QKi&&R"-@)5Sx] bZǗa'eD*"+"2*ij}GQTɼY) k6qDz36qD6 |ø["x6@=@.)̐KۡZty-@7XA@=t%\יXO g5v1k,-BoMYeG}io6)j1or)JIENDB`ukui-greeter/ukui-greeter/images/suspend.png0000644000175000017500000000146114204635151020155 0ustar fengfengPNG  IHDR00W pHYs  ~IDAThYq0 { #lPFH'(otǛ &(6H7;r*$w%dY1kD"Oxݨ ` ]o4OH"+(B7@čx-@/ b|s-*OBF˄a> b+[ 3]·p,v}d>+:>E XA -`P 8KReSnvi>jg^Q yҧC1]&w#iP {,y4Ɣs+pG+*{QpBno"h.`9gY{O?"y]~"!.ܥODڌU/}9`P ^(읦F˄{c["!kMHi<|OJ&V%+h_E <-Hb{]ر_*KI vQ7,wvň% Y$gQhq#c̯J ZR8Lw6_Ry1Uy~xpvӻlY1^eKv ZQ+G3+ e3׍0KɮOxcYG+:vѽX3;سң)={fL+ ൒&-1T[Zp.y=x4cG-ֲ}.-b2o(og}OIENDB`ukui-greeter/ukui-greeter/images/arrow_left_active.png0000644000175000017500000000103714204635151022172 0ustar fengfengPNG  IHDR DPLTE~"%"(#)&,&-'.(/+1,2/5MROTin)ZtRNSACEZVSIDATxEb0 P 23s^-Pkkz1{ƴF}+Ί:#i1 hI\N1#[`#KB47ӑAB&jb~v.T^O{~V?` &qA`u@"HW9wS\|zA|MA8 "yCB8E/Y yɦP0. 3r.}E0iԱr[tZ~'F \IENDB`ukui-greeter/ukui-greeter/images/hibernate.png0000644000175000017500000000160614204635151020436 0ustar fengfengPNG  IHDR00W pHYs  ~8IDAThYq0 s#hx@ A@v&77@)xAJLrH97C/ؕ,)mF~@ ש@gvXXDf|]A]!b[l%"=! " px^ k v3mk  ݦYd2fvA|.I9=@9OawZzsHoÃ\ͫKH J/QgYHiU|q[UuK8. #|&"'zi)Oe-(3*Ƚz[Oljfr!*v *ާ:ތo{ Ldm6t['ߟ b0t2:bx2)tB8?+2 Ј}WG79DsP*{x18MMEgZT4VIPbf`+-Ie4Z4Xh)PAX*$:(l=R^'g5x rP9|p5ET;fs*C~fqMDLDn$ 'wa͸Oa*\<$8uoE$Wt!EχȚI7#o(=J~)7J{:玩F UzI(pgL7 ?~-h"vlm͜WQMIENDB`ukui-greeter/ukui-greeter/images/dialog_close_press.png0000644000175000017500000000216414204635151022335 0ustar fengfengPNG  IHDRw=tEXtSoftwareAdobe ImageReadyqe<!iTXtXML:com.adobe.xmp 2IDATxb1c1  Ƞ@301Z0,\r߿A>|˫Ǐr߾}[' `-41;;$% 999~/`F~}=,ȒJ$,IJEf9̱ E _,E%x-i@`ֻwaP%?~@( "aa<<$eR؁jZ:L1=*IENDB`ukui-greeter/ukui-greeter/images/is_logined.png0000644000175000017500000000122214204635151020603 0ustar fengfengPNG  IHDRw=YIDATH=lRQ bbL&~$C?v(VqlXWM:դvpTQ;ؔXc1"CľU gz`U!0T Uʮ4`T Dͣ2Qi/5)S_EpQӡ/:0Kx#u&^m^wvKTq3Z]Ft `f.704O^QC"uտIݭ.0p'ŷT*ղ.Y4xjpn4IbnJ$ũX3Y&G y1x/ҿe)UB3L| ؾfG|8t8!~sq9Μ[d%Vlk'G-H6*p`e,e^ < tt"9`^; nGylMlf{Q'2VHT^}'ȐZ:;{Z-}ҝ0o!@oڋ&7B7Z}):f~ )MCv/3IY'o$C/CKarEU굑ikk+z*17-6XguͶLo ~f]%y}诶ujޙˁ$%{AѼ5QϮ݂[ HZaSZ[msQIKv*>Q3 輎vr ` wBw ͘Gu񼻵;/3bi׼5k(=:.RF̓sjz<͛ǮAHs_嬤Fa]yZqM8j:t] Pzҙjv},PWvd5{6,ݴMLProf#&27.=^mlPWV[}̤ UwP5lmY_XS="wR>ΤQcv1nwfRܶEvTROYLG&:]MU4V쥱^i,vw0ױ '=4@U*/juYIENDB`ukui-greeter/ukui-greeter/images/switchUser.png0000644000175000017500000000065014204635151020633 0ustar fengfengPNG  IHDR;0oIDATH햽N0ؚR+e` 12P) OإOQ1vb):fBNJ }qe\槢HVs'@L(|^г]Í&+Zg(̵TtP141.4(kK#SLĸԠa^ClIlR떼-+bu):wx{:rhRc^=Z07NpMsfTcFxcsӮ&st>9=M`Je+Ʊs?]w!M0]+qcv Wyhsɝ;dރZZwDDQW^IENDB`ukui-greeter/ukui-greeter/images/scrollbar_down.png0000644000175000017500000000017614204635151021510 0ustar fengfengPNG  IHDR;֕JEIDAT(c\HM(05#)D׈,U36jĥiFրU#!x5/ AK.IENDB`ukui-greeter/ukui-greeter/images/combobox_down.png0000644000175000017500000000026514204635151021334 0ustar fengfengPNG  IHDR |IDATuα AwۂbVdX-rl &# 7ǟr_0H `VbRCr#yIENDB`ukui-greeter/ukui-greeter/images/avatar.png0000644000175000017500000000204614204635151017752 0ustar fengfengPNG  IHDR::J(IDAThYKTQ=a RlZ)EAm °u;mD~`آڕnlѮVPDJEF6]3/g\9{{J(X#Ah@%_z@ fx `=#u6œ8''HnQk\s2f43rShw醏RmQ1zH2I 1By",o5Q ` >8QqlKPQ&PG0F uc5= rXt:6a2=Bq"IcU;m=yWN-In]!P9qǬI%kZVV#ZI6yMLݎ8b\4.0|@Q;f:[DJt60"} %P(V&udJIENDB`ukui-greeter/ukui-greeter/images/hibernate_highlight.png0000644000175000017500000000510714204635151022465 0ustar fengfengPNG  IHDRtKtEXtSoftwareAdobe ImageReadyqe<!iTXtXML:com.adobe.xmp &IDATx}UX-,K,""IJ"TX VITQEhKDDbQ,!DDMBY};S5{;3os̲l6{k AAAAAAAAAAAAAAAAAAAAAAAAAAAaq-?A-]EbA}_rY6=̾N=KdbIl %f,~8on7i{rn AN-ϜĴE' ͦ#!ǩAC]3ƦWH<R퀠,J̲g J<`q9)W$n i x˲V1%%nI-B)WXA^yzۼy'$M!hx|m΢7_Ccչ='7 kM/u.V$&i#6ee릊ދQ= ^ S^*qbeb$]E>=ZL/;qh$3Ill^UkXceiO*))C;^ÕoѶ62hg5ON^U E^'S3΄CZhJ䬖X17Ox#-XIzģE>3.ۊfp s$9mbD]YV9ɠL{SZش2hIk}E&CAO{{Aqoz z]:d#h8AQP !hMMlIC&*,y\2~vHG#=Y߯rG?iC4j{]()C&Ʈ*ē%9CG/4_1!E#0k+߶ BM_r+,#ܠ]ۮ"jMH|LSulc)gҶ YP2JklZ,sUFIv2h`&:SfvEWxZvA;^&nZZ3ASN|!Eۧrě7'$Ata?+] (t x>ugmeۖ8] l򲨊sq '&߶q&l[#Yf,C a[>onj+~g=UЯXRM'AE(1|U_˿gnK4B;i6ӑ6eN:>g]a #;,k?Uk.zV?Cw$u c!< 1kK~B-*P  z(*^6]8g%>gYwdrt߻{ ZOy=*cr-hF~Q~nڠ%9E_XnkӎkCn-泿nJPH'q |P#&v)+),LjxVт~ڮBI9`ouE'ݕ2IENDB`ukui-greeter/ukui-greeter/images/dialog_close.png0000644000175000017500000000202614204635151021116 0ustar fengfengPNG  IHDRw=tEXtSoftwareAdobe ImageReadyqe<!iTXtXML:com.adobe.xmp OIDATxb?- 3Y·Xr$[n ц0# Em VS?Z&ӛ@u D$"a Ԋ=x MdajnԂ fIENDB`ukui-greeter/ukui-greeter/images/scrollbar_up_clicked.png0000644000175000017500000000016314204635151022637 0ustar fengfengPNG  IHDR;֕J:IDAT(c 2pr5DkiXl54]3F4>)4 ý΄IENDB`ukui-greeter/ukui-greeter/images/arrow_right.png0000644000175000017500000000035114204635151021020 0ustar fengfengPNG  IHDR"":G IDATxֵBA-bqwipwwع7>O1QPC!:O(r1sS-7T\TU-.f6Sc>!F# 2#G{ĤS .b .,Ybxk"~k^ #x}PC5D!Mf IENDB`ukui-greeter/ukui-greeter/images/shutdown.png0000644000175000017500000000220514204635151020344 0ustar fengfengPNG  IHDR::J(LIDAThZMh]E=תmZ?HhK[(n,ZAugn YIƭ[qEc) .jkB]ԦҀ.zwyށ;NftAe$w~|| Uk+̟6$˖j'U1?<!I\!9>GH"YX 9Ҡ~=Bv(*<:@GYO?MBm%yƩ{V CrNI#ڈjV'IvxՋ86TpeZ; -+tn1BYk\FQ %jo8m%Qt r1\1.cijΉ'ng6>RVZwlWIbB(ka(Hj2\ }f] קBB-L-꼑UXŧBBo`@| xwq˲lBhDG俍("cq3b =m9_̇mLKY X[}C#: ?T X!}\|Yw ڋiEkx/fY :\Zn#kWjDRr;$tZ~=4Ǡ* r?ɿFB_+ {0wR,iQ!˲MFj" xB'\AuD G:HaQJOw2RGu.WK穯srfZiŭ&;P+$r8j2͓ 8Q!4Y /* [ |"m$5[Fc NK %_-ƗADQgdФZ*eGrQXBfg"eB˙][zYڠ3|y5S$46P(|X/j)fF.=XT#YBc]z4760]T/hWGc;Z}[M;{ H"Q[>C޵kfvfuR巕h WjqhATʛ^AIENDB`ukui-greeter/ukui-greeter/images/restart_highlight.png0000644000175000017500000000456214204635151022214 0ustar fengfengPNG  IHDRtKtEXtSoftwareAdobe ImageReadyqe<!iTXtXML:com.adobe.xmp /IDATx]UUm IfTDdADDRDAD! >D}=XYjVDDE"!R{0}}Kt۩ĭMվes_0WKmw]~O?Ut%3֜ª_|n> ?'վөھX%U\: h#Ym/7`$h.U7g)vWۗi%4 hx^L6_i@Ц ;sܑ::?ji4~I AW8Re|&ȨM^y?9jl>U# n$m'rAc:E#s<:=ǘ]4? HվfJcF+j#l}z~N H'_6sԩ7_ ԟ|꽐9FQ$ʁ/q 6Cke|L'{t2ƪ.OT.1*E=K.ljcn./IXD|̉nL{(2jDNI6{~).(mo#̏Jc)| E6E>rHgxF;r%W9g;ucAAAAAPAAP (@P (@P)AAAAPAAP (@P (@P (@P (  (  (  ցIENDB`ukui-greeter/ukui-greeter/images/login-button-pressed.png0000644000175000017500000000104214204635151022553 0ustar fengfengPNG  IHDRJLIDATHka? AB:@ɟ !D%ВJ]3. TtRt? .vH,I -AA]"<x/|{{߻sJrlM`4e!p1NT>eZ%@;mko2Z-yW1m 2 R.És6ztsiV{zd{&O^}bDwV@0j<vI@g~e%~G+6-N5Us$y45 Mco_%҅  :~eQA.#_?5g uvQC@} p"}C+046B:u'*Mz-|gՁz4'XOF&a%=[QLAm~ZHI\f K"p4a:+~TIENDB`ukui-greeter/ukui-greeter/images/bio-login.png0000644000175000017500000000026714204635151020356 0ustar fengfengPNG  IHDRw=~IDATHA@@{}m_AɄ沝Ld5sdt'GzUS$%yBa dOi/( 1j-Jy}pARe` 0tIENDB`ukui-greeter/ukui-greeter/images/scrollbar_up.png0000644000175000017500000000016314204635151021161 0ustar fengfengPNG  IHDR;֕J:IDAT(c\XMr5D Ӡl54]3F4>)4rNGIENDB`ukui-greeter/ukui-greeter/images/arrow_left.png0000644000175000017500000000114614204635151020640 0ustar fengfengPNG  IHDR szz-IDATx5r\A63^A#fH0 96C$'-fi4y]-) VqgЅT?38eRHM;@JO^!4a-z'q RE$^})%VP !0((H'%DM %H [& Mhy W z 1Dg}(' a ~p96C`&`?"<04'0>7Ά9 iÂ3${qlnCe8RU:e;UƄٚu B ! }ٷfa8ף;%u2㸠u%IC8WFZƼ=/V"c1IENDB`ukui-greeter/ukui-greeter/images/waiting.png0000644000175000017500000000064114204635151020135 0ustar fengfengPNG  IHDR((mhIDATXѕ E)(,b v`t~H`P xwN~+B$H@d9rgcs !&8C =6X{)pdhJύ盀07_|1-s5.?598N{bq XV@`21J/@ Hjx~kBId͘mKsɻ3a*fi{|m}.M{{&8R3!)Un. 5-CmƎ(||zyu~]%xܘs9e]35x cͳOPVHn]IENDB`ukui-greeter/ukui-greeter/images/default_face.png0000644000175000017500000002657314204635151021111 0ustar fengfengPNG  IHDRx pHYs%%IR$ IDATxOl׹sL0vb% T\v"H\)"R,Ra RVmz+!7)TfU4VFj+ed N`Ǯ\s xΟG&ywyR)%KJ]vqB6u!D{LfEuݒhZY(JŘ@HH)Kv I+OJIA$ KJ ғ{J bY%aE) "P̟F򦊰Rku$@ Ô呌R`YPJ{~$@ ɾ '|>VJJF."S̓~ l &2z Xl#_//΁t2p  "-t`-H:9_ P@Hg(cUi؀RADH)Mgmn$FX2?J, "g^gCh^3Mx1"EȘ~sv@tJg'e~ Q$ f6sH4X%o*$h FF"`SfpNnG!@UlfAD 9?LGޢ6 ZA0 ٟBt>@@ BN  Pf_A[0D )e?0ˀ /@W@8H@@X 礔}fw?~*sH<%0{YV3e|C H<&?1bgH<`KG NsfǑ8~)GTF v'EƗl3K;a8tO]kM5. p2c?خhfn$cAr$bAb$R !(L+LXFJ{CޙWJdZA` '%K9%` v;)@$N @h P4J @Jll` HRz?<$̜z?<+/ Y$ 2w17H)M)fČ~06& F&f4`Ą &fl @ @LH"ƛ rQ0$Ċc"Rza^3J9u$yM5S5h @ 4# h{d]N]8+/k`cH@k_@`u`@'4ABP'hs9?8vv{8"X @'  *H!wH* (c^Kx$ײ>x>sCP /@JϋXp)d `wYzl3m"w Hk1 A~\ !7 g&,@Je,zMlJP 9A#z} &0=9fGH'Hf61{'4 !Ev)e?;M51[&Rn!דU%^208vZp)uS6wARJɦ?@:LlW T''@"z}M`7B@"|E`6`6.؋XKg_;H)3>{&R^9?p\.kL&a۶m{]...N-,,LݺukjjjEXSB\UJ:' ]\ 95,--Mܽ{O?K/tGJo\&G3~SN冇z'=cg/w/~W}v) . HʬRjv200Xp)@t?z3O<đ[+_|'$^ (QKǏ nt6z/ą~ RGt1Ȳ>OǏc=vؖ~T27.;w]ԜRjƥT`^˸˂K"7󍌌fCՙ yZtc9ҿ^G/gΜyep lO.vگrOMMM~׮]vs HU[?KR>?\wQtX1cgB0:L,^ s/㡗yh X`[Y*cuѽ8:K.w 1 J]u gǾ8Ⱥ6&+ߠ_:tWCpҊRjҦ j]JO+c 1SM5 G8o8:+TȲ. ۅ$1Κ*lc?GIkb^lyZp @Mz?^zn>p1/@JK?N稟 }`Rj`^kծH`=&?s,qM LM^ /=g;K[jL泗~ Mgg䞥V *qi?l7ݥM\H%Fo=Ħ?9s8ji5J p?63=zNCX- &/077>?n߾ǾCXmR)Qؑ¿.甥X.@&aWN^L[`L-]R&\rrppɳ{}+qJo%V6McsL X+*@ba3 >VbUD*ւRz{'Jq9獁X&I,9섉b&X{`wX?ev=Cca񶶶&*\b-[>VQ(SJ8s?B@Vm&"kXý˹H){y/D lLL\\AѣGavP{%F70O拇pOrqh L 9h'X"&톇bP ,y{+$k{4F<?Too/Qi,lFT Ez$0ʀ?O󄇚Gbm$ ٘? K?Ow{j3`T6z===&8"U@?6=GDs[Npl"pr%ԃ{h9ؾ};m^Q8΀VpB&Gu^CZ&QG]W4H);x/0)R)(eVǼJ/(KJ 1iR[Y`t5J(`fPMml6j p .9uǺ4| pNA hI?vi867+5| @˯h(F7PN AةW7RκwEf^$#@J] pzpq]&fo @7w\:½M(inGu^&ᴩk 7},;}'* >J=ᅥ fp#zݻw/3{4vS@0fggqGM#Hk)ew 駟 5q#m&@)e;X]]okkaB^Xe˖ <2q6{󄇊7࡚1|fpW_} J7࡚1j`XW>cFQ !D;󣝝CL,JQJUX9/}2(=U>^<{kl @J.䎀߶m!&ݻd^~ IW?611q^@*j>)h@)fjN ƍ<{ӫ%Ν;.6bL߰`@f\x5q@0* R {vΞ=Vcl*lDP8!@6VA9s e0І^]^а #PE?Ct$#8Ag6x B@\rrppɳ{%@)K~>W^y~s8q@G-'=zCnۏ>过>>}^}R sرWf1z.>@%P2G}Ӣ'\R+8<ڵkr?aX555]v:q<8 PYF V>bHܳ458(ĺ=4y_f?{ } ]/p?K/t9q*xp<ëǏ8pu~.]? }* Jb]ٟTPMA_Kл';a?R/ ~,I}x/А_Ju" hC؁4e-4$ ]5qǜ4m-SZwc2Xh])c 4O=h=z9l-^ H}FKhh1cK{_ k1rc DW GWSJ}&d 1^`Iyz&@ &###>n߾GƸ1z1tHs`gttг>[d[^^Z]xdrr,]=F [h/(w۶mΜ9GD`I/HЩSr?ضmۡ޽{Ϝ9ӧ, 4: !z ϟ?22r,a ٳg" n!DKW?{p[[[Oӡݸq¹s%Vȓ?244t]柘@^:$tUѣ<G:;;\,ݶ믿>}Z$+t20<<ԓO>Loo![ ty~~W_}g} k /ܣ7>ӇOB޽{yvv~zNZ /<;r]۷oe2\wwwNF{ԭ[Ѥ e@ D@H " @$ D@H " @$ &9A'K ,1eI'EY @TZK)x¡c?TJx# aX7*%l k1 t3FGGr]۷o յYD܊|>Yk/޺ukjjjEXR)%B]lsԩS?CLl4q/}v)& ]f 6Ol6֭[w2)pO.]"SJMJAZΟ?;BЇJ_zL4ҢR`AǏ|?Fi!K_;xL<VPJ]N$axxʕ+'WWW8:w@w y+B~fq?ro={c>-}ի+34ѬRjVKYFq|}$D/|k1LɌRjN["w ? MGwEgwB 7"2?==3cC@K~ryh^裏^P:(*$uWUFͺvگrO@ ZSSSߵkׯV hnٛG @$ÆNukNL@ f#ݣ쥿l D'J)TNҗna]fW1WLR+p~@w ֊T )Ȅ>r!mA)u>r!NVC' MP֭[w>owttTȅD%~mZXf4@ &} }8R3o,>zx7>w\י`Ԍ% ^ lÏ] 1&)[G]r ٽ{t{M'VWW)(wlr)Z%<c ,;L6T1zM+{6y f8, } lo '`]M`۷s68XO500@68l0brS qM /i XpoP t,n%)j:7CSeвBb@P 5-V8W~WZR)`H)Ƒ R_>*R+CV. @Z-'JyZ-a8U?B !IDATHD$17vDP[$17@)k]ܖEU,bmd U$J&uf@i40h @Wu@P rԢ",8*,w*%ƒp$HDz\\m.6!-ƝP9E'T#4mXYliXӴ xl>?]ThX3d0=x"򶿕$QT["-IL CԖo%IUU6XL, @M=+*U%#MPQO" v3'&T%Ok!~򻜞ػUF@ =o&EF%5TyP?;nݺ5b.b@6:F>coC*iVĥR*O.Bv* ںu}zspGGǿ1UJY!8w! X1105&JT? } |߄T̚TJ{_H@VWWzB tb-[> +(}i/qqƅ)"Y \J0Ν;nc߂,$+ev!ޛ~#}P\x 1zjZ׳&ؚIuGdhΞ=Vc߀ĥk*%RA!DW?@xO܊RjҦ  ! @}*/_>o߾mmm=` _Stƥ vծ.X{wF?aA.$?U:W4ƩSr'N8I y:7>}zO q24N?{0K@|tƍΝ;.7@Ov !,}yGںuNh;w|>R߸z'6b20<: HZ@!E$8)e"Ň{VhDHJ7]n>D$,@diHb B& ME && )h@V +~H}cF3 56o 1"HR @R3_H1Ac~ "H'`v RvsB́BW0;WB́f4LJ/z)#, /L -B`) x &?KX Ls[b`?-8H刟H ̘jKlW4O ̔HaN $+` w chR?七Al`69Ql`69q!C kH<@5@x GO!qR@9B)N ;=F9)e@A*u9F_$0]BLc`S G6 M~!i'DzSoxHŲ!ļY e,:?`ל`?5biK3<@ L"0 eti?#@Uf`?$6&l$pXF> fN Vʛ'~v.$h`?BDQ Х~4-c (6!$LY"y'#2$ !@h1 4 A.lCbH(O@'<ѥ;&𳾏Đ 5fy PH RGUi ULU ^xF/J*1ͅ:%8`,zRd8N˭yTɂH m)8KJ1' i'RjчH S0C(_| f^L] P@-+eAg$Y*6@7  $ txh/>"ʬtdy!j0 ;LBɦB@d,qhA*J%g{1$@LA;ՂM)C'{u$@Rt=P#tObD(\t?DizY KAXNJ]v J D8@^d({r_ZO`!jH{{IENDB`ukui-greeter/ukui-greeter/images/badges/0000755000175000017500000000000014204635151017211 5ustar fengfengukui-greeter/ukui-greeter/images/badges/kde_badge.svg0000644000175000017500000000177014204635151021624 0ustar fengfengkde_badgeukui-greeter/ukui-greeter/images/badges/plasma_badge.svg0000644000175000017500000000235714204635151022340 0ustar fengfeng 画板 1 ukui-greeter/ukui-greeter/images/badges/xubuntu_badge.svg0000644000175000017500000000147314204635151022573 0ustar fengfengxubuntu_badgeukui-greeter/ukui-greeter/images/badges/ubuntu_badge.svg0000644000175000017500000000222514204635151022377 0ustar fengfengubuntu_badgeukui-greeter/ukui-greeter/images/badges/ukui_wayland_badge.svg0000644000175000017500000000124514204635151023552 0ustar fengfengukui on waylandukui-greeter/ukui-greeter/images/badges/lubuntu_badge.svg0000644000175000017500000000135014204635151022551 0ustar fengfenglubuntu_badgeukui-greeter/ukui-greeter/images/badges/unknown_badge.svg0000644000175000017500000000070314204635151022553 0ustar fengfeng 画板 1 ukui-greeter/ukui-greeter/images/badges/xfce_badge.svg0000644000175000017500000000112314204635151021776 0ustar fengfengxfce_badgeukui-greeter/ukui-greeter/images/badges/ukui_badge.svg0000644000175000017500000000072614204635151022036 0ustar fengfengukui_badgeukui-greeter/ukui-greeter/images/badges/mate_badge.svg0000644000175000017500000000072514204635151022006 0ustar fengfengmate_badgeukui-greeter/ukui-greeter/images/badges/gnome_badge.svg0000644000175000017500000000200714204635151022160 0ustar fengfenggnome_badgeukui-greeter/ukui-greeter/images/badges/pantheon_badge.svg0000644000175000017500000000077014204635151022674 0ustar fengfengpantheon_badgeukui-greeter/ukui-greeter/images/badges/budgie_badge.svg0000644000175000017500000000065314204635151022317 0ustar fengfengbudgie_badgeukui-greeter/ukui-greeter/images/scrollbar_up_hover.png0000644000175000017500000000016314204635151022364 0ustar fengfengPNG  IHDR;֕J:IDAT(c-\Xsh543ft8 @׌bh # OW$WIENDB`ukui-greeter/ukui-greeter/images/arrow_left_prelight.png0000644000175000017500000000221214204635151022531 0ustar fengfengPNG  IHDR DPLTEގ3928~~~~~)0KQÓY^~~~UZ~SY~}QVRW~~۾Ϫع~~=CHNchbgٻڿ~!""(#)&,5;6<8>'YDF m+* @`A ڒ@*IPlX$*餿P+gP 9갭ec@񺔀vS!JAƈrЈ֩w}xc-CRkz4 !j-+O.CZJ-j&= %2$%1x´4hl }]RߏI6= F8r`QI~l:K$4~^u@d 4, TKDBw`R7 #Z.p# Ge/895k KB!dKoo=>bmubG8p7 x q  =#l%rdZEw`~ נCPecX(YcNacA-$եH2b&Td6TsI,I/q@I4ԥxz!nDNo=ǟ?cg}nJ&&P*3׿$8.D4^;ȿFT)=s9;vIENDB`ukui-greeter/ukui-greeter/images/prev-40.png0000644000175000017500000000113014204635151017662 0ustar fengfengPNG  IHDR4hB pHYs+ IDATHIkQD"A1 E\4Qw?VIADA589.NWwUi>η^Ag:0Mp?f{/p)EIr5Q`]3$Xл{mXd%zI0ۧ)~ӦWzcKI֮VJ~zaU$W f{ 8AЛuQҗd5v>E 0{h-W)Y ZKoWlBm]I`hzs/IL1vyv9:<G,glIu`5Ga1Kgp! & $C;IGa1+{`ֻEUKNOr8`| lPY;؄3Y8zdHzFk{ЫD2V}('^k`8RR`Q)depdXcEl*`me}2'>U޼/sY}+T+Mܸ}tzIENDB`ukui-greeter/ukui-greeter/images/keyboard.png0000644000175000017500000000025314204635151020272 0ustar fengfengPNG  IHDR;0rIDATHK \WFPUC͈)Y#",~۪W4Gݚ (O꼪a~& {gSتUȮn~U-c%vKae ]0(hIENDB`ukui-greeter/ukui-greeter/images/login-button-hover.png0000644000175000017500000000104414204635151022233 0ustar fengfengPNG  IHDRJLIDATH?hSQ !@l]J?)4c fp02XRS](TC %f"nb*XQ儓yr|%wodv!:K@ .~61sXO"4U๩8j} &gC=|AGpޚzA dCʪW*as3qmzdK6c,]=b +e'yQ( wZ@ b4଄ TX6;*;+twy20tpw&< sQdRcT^i '|@d?{ cXPR~KǯST}QC@G m !D[nEy=[ȾNhh:XA2m^ 2{|Jzz92݂$2Hj d_ rF N.*D,eAIENDB`ukui-greeter/ukui-greeter/images/background-ubuntu.png0000644000175000017500000173362014204635151022145 0ustar fengfengPNG  IHDR8C IDATxY8$*s;ub w0[C6bnh-d?y㜽qFc(b 9cxjt y]ep(QUވΔLx;%njd ۂB\8&l!SpJ־fr+~ ʵi([&vCŵ89ab58_:ap2up]3f]xց9p` 1)3b,o$:aG t^B|β!!~;?'d ,eRozbN&p[o9wVo[8~rK}!\kTodzQx"Gy.wY h#jo 9oFer(La[ɺr=A8 8~HY[,etA8eŒt6OYέ8fAS< ͻ ejoMYpfP6O)v!`nw7֟Q۸WڞGQ/8s͞s ĽF{.~Kdׂ[9xдJXs\%t'(_NRSb/~v}!? &jf !$u} [t|oxoxv(haM?>z/"r%`iU2;:ܦ<'#7N҄6]2H&P?۠I.#S$imυ_Iq٧+C qDVK[G E q $eLSmxrmm)":T{'` Ym&P@.Jچ}aɓN>Jnܗi8C]ҩ.Guo=7#D'd; R2>|N\Iu7eӉ׀}W_!w^ ?aT \҆ 6\T prޖ>۬i}Nq[ V淀]o%qGpڸ_֓{C/f{~ xu`%} X/ccP''nˀ t܃^h_>lqךztq9Pȼzc^Js{4ؾyetrxgL#^"wϓ=yBumckF_Mu! hHr(3V(2JCy:%y6dcn6$ 2ޅݑu}w k#ٲa&:!vg7x 7s.E@6ګĞU޴"ly`F:\]`[SoNcl 6*X7!'Ztݵ%aE;\•2&ΨV\@}%; {WK )/{1q5o>o'6hohwݻ+l_G۴ID4_ޅm.g! 3 Z&\jE\U0䤁oAz[ИPy G^{JZ&D 1mbf4{߬M!"d[C9;[Ň+|r2>xvL&:W'p5z>|ԉJ|A=(o L""'=Tf}0ѹ귷Gt!iKmRѝ Qd@o:p.'k@l3dg6sXчdW?6_܆D$BDIBP;E'BZMBNRϧstʜ5{3r.>0֭Ifd Xjʳ<m{aeݥCbgxsl/YbODI/keu)%ĻU93:掯9I=+ٔ)6^~!~˯yX`@ %}f~u|s#진O^}MLnF:[$GydR!.ۑ/8l&i &p_m!@r92L tRL]ʧs2t5ǐT[%'uJoEG֮rEG0GK.'Q&WW_hO'Kz'"jD;=:2orER )9QS tk#/mrɥg>Q.1B0ex҈Y d='Ç4&e& Z"RBA$Vژr갠ޗ-dQ6 vH71nc͘'>pԃђjA.Lm6c:\|p o 6]>-MB $_i( +虚5;57 \#7m$J8-`,``~9Y~ >"w&q7=2mL>ZcK:wvLjv]]ckN[g ^W}C\dCш])G-M.36Y3' t[Ճ( qÜGiQ8K2+;m<0S?k woO8@ni)oD\~E"LlQm }<7qkB.rj7=wwNW7$tD#`Ga FO:۟^Hwޮ+F3æzI&ٶgC ֝W%i͜2YOfGo ,z:M2>Aa=:L_An!qUϙռ8 ]y/DhjsyHR$c-Kl_V_ӷC꒛k͗I7IjTuqrpN Lw%*/'|5u;"]dweXF_C`Gʱh|N- Nf3EuPxhi\6eeza5IEV׻Γ[aIC(>͈\♫&P$H5'~A<$/L"EQGQC'}e$NJRXc-{X L~Ո~Ck&L8HX ԫU`<tw&vA1/9˽PR"xyCbJ:30#+^*$XA;=ESEM1LJG2 +22o q `L__|ݯ;nb/2 ~A J &/L2[*~vgP BnlKJI$0$BHA]$ˏLN!9~P{:a&r9JykNWdeMvR_"[ξp{}L]^/'# |'H:/$I`7xH3諿L|Gh!}2 b-z- ϒY'nįL0DEAB;(dQπ :fHUoLOUYBI?x*qSWcӭ<$)|6AjH$yjà 7&1"~Ewm+2tTanӕd_n%\gn\O_I?/Gy.wv{U=$2!}*: &E^$Nla$4mWoiБծ-yE|'EL%:^Vp39 IDATB㺼z!$~ O؁(r˥Oqjҗ.Et Cv!}΃Np^Z%KrvΎ&J(?ϚBgnfsF`4"8nQ'ΒMg (!+e=;~??Af$$w];|^Ў,T[|ގ>Q!{dk55 ||'ՔvΟ(`g>{鈔?z5j7~bMd~{Ɣ,“e~ e`6]jc.Q]Obq[>U>l'ׅǽ>R'cCsu_mXE>X܃#l#g@<2>ÚFVB*ңQ%Rϡܩ›6>i' :wpsυvB?mYAD v܁e_8OTiC;!AQ=WoMZNg 2".?Ş))9%AZ={-_QfLHHQ|us^kdzفRC 򐿏Hr)ylOz;xK[:'CqW1.;deyQ_%klؠ CS(΄,ی;&FN"5IP#oN b$}\>i Љxt#2!} <RDϳ;FC~Z$ dk aIpeUW>?}$Ć.>$$07~ǖH>S&uvJ,:9PRX_;NoDihCrg! 9+7'm~qsGD΋ᑠ3 튞H\ZџCn;3[' $a#Uzwgk<~p̵܊t {zOuzf@S,>!~K9M:t˺|]Q#oMVzq5dR^/}iqzQCIr"bv†l)չttDLl WWޥ$p+'@aIVH kimFlsܨE Fer!4{;2Gf۸󐿏,5lOmuR0erB}?>S٘\&7瑬pׂ-苞QN'~ cȌ~YQc& fu)N#%9zIbBEx;1ț6fRGMfQh<0Q< Uڢ3-Sĥ&'CP2yuin 7s]H/:5m^]lYK3к !YȢʵo!w2=@I`$^K%2. %ȽaLI-Y<ݝ*۸f l& @5ƣ/N7ݖKr5r_OS(}&l1(ANj$ qx;c2|qBq67V2GV#CѶ . -y$f L.#g. #~kPpE聝F g+Z "2اo̲6Nα:Y'+GT2\F #~I4y=q N( ;D& 6/&H1IcRɰ!9%{?c'D$K cw.aCGKӍ&iX~=`~$ _ {q-=0Ĺu-3>#|S4ƟFD)(&DxH*dttcĉ៞V yq%X웥ˢS",'U1n˨$i2z2>HkF}=yۄKAېAw4D||ϓZ'a\W˿}xqt}25Y+(Ww;|PA ?[gcgUlH`ߩ#0.y:e:XM&GMx}(C>}8:@^ g';f6|#զo/ak"v+_YN1Jҙ`呾xbk {a@]&UrRs('\GU[͛H^W ԘA9 ;R}XG3SNIӡ$I`SR8!?"M?$F RP Ô-ג3 X"lwhɆ}$[n`ڴD.Sr^}˷ ҥD{kfQx  z" ܓl6WO~7B@EEm5*,Uu10ǻ_gZY6<_$.EVň 4ev@omIuys@9 PF~@sF|t*Lp3fsǡE=h$I#cճ AϦOYC%IQ#bHN7^"Ncbݮ65Z-rn[J?Rҍ0 ̿Bjv\ )_>?]|GTe1v\xH]>"C))bb][f@z^g}Tmf 2؊JIP(Ud;ޖn\S[p6<g)0每]Qd4H`m{߯%7!_afMzdܥda.EVذR:m+NLN} R ~ 1=%Tb!2Y':{1%M2՛Ad|ǩv!EIqRR{ Tǿ9{4l溗(L VqײA@- j7[.H 5 \S`4b`ܼj6À/4gMJc#M-шm.J te~kdo#Yti;hn?D]Q:aՏ $\IbVuZRU'gHPzorfL3 eLl)$QE0cZF<88v% ¹hDGXg=[NXyѱ?ם 8"6 I(31k}\O"+ԏ(nO+ϧ0Lv2c|BaZp5HlW03b98-u0(e|u[uN'UtƗK WVfK3 HP6PLZ϶d JyE{\/E~E,F|# 1m vlSb.;lo=*}3_a__%y%*sg3NQLKE6mlA!LeSI}.#K̃IOSBv>mJUfMN̲gqr8\?I$!k-:$诌eGDou2:$$m~$A<6uE=cm#TO5aR\^o?9<* oc/4bTYX>C"z?JL"RL:6dv:jBf(^c0I6X!0)0@Gj|V!橛kKUC!6;cy㧮a[6"?4uf~ǂk>h=mT >KMl&w_5ߦJ|,8sM>rz<xџIO9 > ?DrӤGug3?>pWw$')`CK0Y/2R71dM9r%}>b]Pr#I|dI1'EmP;33FCFm1p6Dxd\%vxwG-̞j }ޙg@6pfڦ|dmPEVq%fu'vGege+HýQ ‰i\ Ldz۞ ZyyluX=b9EluyνQ̲gR9|͖!=&{s*+t_?N*I Rd/ 9#!I^ 0<;u5zZerի夶KpQgIwjL榀tł~0J SȋNrȭUo1p ;{a;LdVߒR~dyixY2? [9i2_Y)S8>N3]-ץOW"#k' \R4UnAuF6/lv^~]5î,2ƒ;a>}GLͿm;E$#69J9ÚS"0L[1ދ~Θ%94g֘JR@^bؿvrX CL!\E(3K=3cv1 rOW! ʛ(2[JY/yfWo̚K_ս`/٭9YlMI%9o`X(EIFGCIBP2Loa7&)EܢSmл`c蚻丩QE%';RYLjO ic:NЎW]]OsD>C fz^_7}PWUZTb6t:|3;z5&;1Ou|A:8ww}Yn[acy#{z 6 |ಔDNpW˔S6'3\y0߀U-O&B'X?h]c%KM%˽@g89> ത}9!31\>[!9CXK)$]e2ܜu /`qpOGjnL/ۨ/84sKrF0О4% !z!2/UvZS. 96*[O)H\LkL9X3IWsBse IDATY c:Cz j(܉G6J>/MvjD+SOHo6giԾ]SOe `:y4kɫŴ͉dI;`uiVDʗ6𜕵pGA-@R;.ͧFaI\Qjt˿`Λ}1(q$8)f#%˞- ijmpvBf̲֪+^;r"GA Ѹ׌#6Gg`},#`!ROogXZL~?q-L3izl534x$q KDPB@\`?;,=&D6 AU%pGv0> R,}2A[z9[[\OxCTꙉ] F׫q;E_; ƣ>`D'Z"X}*;ugMK_erMshb0n-s}Ÿb <|TlZK rXu@ת#4^}S&8l'( <]Kܯ"0ӟrQN~ƉQԄP|rqe R>\3 x] ֨>1G@dz͵HDP_"/&ф^r26U 5~$QE̾$E_ݗA;JsT\4 H/iz2)INRiøFϰ၂䱭!6;DK8_TW$f&Yc ՙM$xe 7Jz:qD6rߘM},K'no@6Xx] ܒW"%9Yy6vGA:T]sM_vjZJ{`^tδ}_w7v{[c| ڃHU+{6&#{ug'$+"u\0-Xm=ݮ"UlRN'/,{%'Me Ibv")C:fڄ)DG406"=Ih ~%1f,cCndHAqgi_$ Q-3z(nb2^a#D07~.`+'.Wυ۵UJi!z\HY&@ ǏYg +.7^h(n(> o?˲J:"IyBK0߱֩դoue<:?Jvx~"6E`B#l3&˵/nFaUnx¹hI`–irN&ڢ"qK/6p6xƉ.ϵ^mh@)s?zIK*rh޳.A qO *Z=Xph[$tZ !(I.>TE6cyz9?uCTO/my(RK0L!mV_/{kG+-?hgj s.|8ֲqҬױc]_ o6\h]9~ W%ż7$l = /1},ic %B+d+QAHi$/O Gߗ=/WsL&MZ lߕ5tGYtVt\$@KaRf ⑚؂)65M95'1`ŧK:K} Xe"LZ.VX郕tCΙOu!xĿ$ &Ù!vEԡ'<)Lxb> lXX's%yu>d0 JWy֜,{vwm s΃žK(.栏Lt؂4' VFOH&L&}ωKB8N)05怫OgJI\2nL/]BuV dV_ϪՈ=̧g׵fڐ``CNBn3 B$xBzp9ǃ ES%lŭu\sRu*TʎFf H)P9 Xc_0!_|m ء}m|g ^\~,iWG%ر>s$yv* -2{c /22A*WuM 33; '&%gRH[Vy'dYBo𬳔 Д77F}XN2Jur. Tσ(-錤m-bOb8JfWh?[gk"OgKNHسoo$7%5%/V䷂y}<30O@GuxFOw~ օ}j9b\}+6'Ol-C~MV);4e: |SlyC7hz}rӥ?RV-/m9JKOv 񍃺~21   zØxd}\m]tx(5ވ]f`u*pp%q4`Mt21Е&SA2s~ZLVEJ"9LNE6gpb-OGu2HUe=eu:])Dv[iy|gsݫพG^;FPcΰaٶ-_e,@w| #`U'wPzBZ `blo1FwvCŞ7!h$ḭ!4G9|kC֤%"7OЦ !{!_*0)0U;o7Fm(ԸDr _?Y·?*bewuʾT(4N:[S$9s959Yh8t;#6#2]M]VuЗ#~K ل _]FZOx+}ժ< \1h=̆|o 0~uʱ,Fo6jPs`tX oMY6aem~B1Q;%$, c+5 GID2s>6H5,瀌cScJA˿?hRDLZ5%TRZIKLc5 ou1=h|!JþHŀHߗIE`XPu'CG[ۘhlyվP~bB/-k;R+Ǘ-V]S32V _&;FIvt|.-US4e g~`y&Wd۸ |0vBAc}#ӈӟ-zWJ;76݅>^ͤ!ywؙ+Ov5Rr,$rq|gۘj@hDE ~2ZlM+Jo7YaEy$@B[!lV,9:Zh4)zUIG31OjF)c {{ց)8kl$*{{&}"0b!I$El+,m{B>z4~|]}~O/"muuL!2q1@06챔s +qCweP8nd\K`U+9>G?M(b3!M'ʹ<Ξ>|bue>jJWM"K=Vّ)>8;,xeͷKUn2dۨ*?` $S%D.kq[dlmnJn+U{ m9a69a{_k3;$8q#C3.7gL`i$JGs,̆\$HGIgM7<:\%X 3y#[S;Y6F iX8-} WP0 S(k7`q9}:.*g,RPhnj.azʔ4}F?_xIOR>{dbΰav7:;{d|8`0'-z65Jel پ~IO~!v[G@7zC[,&ߘ-췺̪4?o ܟ%L/zV H$kvq!lx-*n>a 4?=d'&{ٯm@IuɆtV 49mkM$v΁}sξ,Jܓ O#rI[^O>iz )qZTbmKm0,>>]cɢ6:R9;MSȍ\nXx'y\p`=;8g_I?,ݹ  ̴$$l/]I૕g/b܅Lhv2'g? l_L,? i>31GƨRBNWk6"Y,DLAYZyR|N%Wya $4 ̇v#\w}=Q+$YL*fiS',I⍩7^W+vU鏎icfpU| t$f߮51z_U\SKz^!Mf* c$[AeT[ me˥ >n9'1[h#z2?r}Ɂبs=Oϕ3IW >[G<)G8,9SrD0_rjП7@XuĻ+W-D ֓OA{Yf35m 3mlWV6 35! D)^Fdx :[!C?IPڎh4-m󚥒^[ s=o7l `isTdס}꤫\(@Ǐ`542ST4&nA'9h畹7%ys:P~܇l4-T>C$>~UGzܨl~6㗋^ Y^tӀ2o5Q~c|J=X1$o߮Ȇb#KvnVp0zVy$aȐV$V|/>:!Pq;Q;ZS \n;o'6;tif?*Bp#Pm䶉oB"h1n~a}R==>{ES`4$egw<BbyMUz~c8 7 Q0S'Չa"J LD?p_6 CVlNy 5̦0غQ)&ONܜ 9S h20v~MUCZJ$}+Bp QN}GGKC{q|x;h4&W[aj@rޝ-ҬMmWgT(`v 0኉@[=vL+ EbsdɑMvKI;0 J&lJ^(zcp[8F%gqw2ԏǍO~>Obb A01̍'k["yNBmË'?q'QXnޝű/!U,ysĔx&{jv[|!b?,"{rj )Y ^Ծ}Dq.?1v2gmy/uQUl hUDOw,/,N2u*v·2؄:!Xj `a8GKվyd-GZK=R V}!X@[reixLTq" l&fP,;1{K<+1 &6n?$?!e;s )HFvmǠh"=N5~'>pIcp.sLx,쏤0"ĴXj6,WcElnБiBYS[K)1~2 % dk))X.Gy[HT=;`T~]W 3%L$B WAm =-:q(: ;XO%zEׅ.X=`ׯe}rsں^uQq{$IFEm>F|Yϵ,̠alsG h;#u%#8U{9ӁU:qěSy oj=!1h?j"Pxbn%$b [2[Tg^sRvnjd*ωA㿗Qݞ o[Qɩ>H>sZ훯M~,MNPwz^,] pIcZeZzfaoZ2[}h2 3kI)I |L؎$3TmC$p=w&;~B>8Օڛ>E'/KܒꤒEFnD/^0v0i61} B+J IDAT Cy1(8Q>'T)}mnG.|19dlV r"G s; ~ z^9&`+Iq }^αUrS .(s 66V; !+QEDit׊e]I#lE~?UN ]+^GyЉvw V+٫|x86)]4-0x2eU.)Oq[ʐ\ԆQ@y]d ?dXie K<5sB)a\]iS$J%vv4O|9{*\j+*"bRdK=&m7[cP_+GB# N &J'X8)Ռ^w9RU!! &R!T\XpSIJSq6j c.R?p,UO "Y Q[b+HQ߽K*(/ـٗ_Etfu}D;Z]Oz]Y5F[eI+9i CÝ bvؤ*x`]rGvHz5w".5Ayq y ANrNr d'H[2#o%}fET ď8v r7|"kApB'O+a)dYv^>˟&]x*xUc`%Nq<)7&(L7>Oz >͉nKZ#5AZNp[ogC]>;g();C'=.A00 c.)Qi Xi9Ev"l e8R1JtZmʄ]Cx]>>mO |{>gBbKXxdZyW7ėVwdJC ֲڌ%TP^mrp0kI)i?Dlu6؜j=Em%Ob?!@pml+iE7~):zz*(Q;&nƆ{s G :vk0Sok\WRJc1sEuXtBi\̘]b,j7YA1y6v j4-`lUov| 9_Bۥ 0C^k2&\poEOpy^Mx dj$\HsӤ Pĭ<$kP\+rY4WY:"Lؤ?9*bM(80>0ytv7N?h32qv'8y[:x"i̲ChTE𱧊!18hGb(ʅtw:qʏqk4O1Su|Ŵ)>4-JsZwO.'R:n;ZҹY}]/?-aP0Dx3I'Uxa_(c6d>esm],J]#6Wgxݷc]8v:z -%)eOr@r=2546IۦXzyQD O!Qj 睤({OGvsG֟8}K%HZL;:n_S jHPOJLofKu{t:OK4&dKw^,hU o .8I3̖|S82{%CDZuqĔvf8^ecVnd41-` g|v I+hrla2#!Z@dEks9AŞl; wQdd=]@Mq_?@\J ЧLBb/DA^MVr8l:i06 sIc+ ?B=$z{ [rLRLE305m6 `mq үp$ >N'eI-D:rlk'Wk SUoS b]ͦDlcarG4Y84YbchzzEo 3Ƕ/yfӿ/[QA$~S*;B\h, ChЅD'[j'xYlh葜 TSl)y>dlɶ'J \ @P uQy Zʒx?cx]}lzcXqY8FGwzŽ].col,Iv$w')9FG/N Q!b@Jڅ]S{A:^ zHz1e|`3giRnZFJ~t(C5crk"Md_hz gH!'`./BO/3~rˆ>mW c| d*JĝYg-3^\ih㊾OfIEh] bY~U23u )ԹCD_4(tjV` %}8ߦ? qpLdTe*Rd+XCiW ?bwfmD?ڱ%꼙®,;!2v3Rv>i2LcLw]yc[DY$OX?[2~</ŁCv9?-: c?e[u`skMJ_ֳS2{T vAk; =oNQE::6V57 aI=C;4yH9x{)3s%̩}k@o乁Ƕ yvb~ZGQxm}5=[Zu{'qϮU!#m|Md?]N'Sg6.=g$3K+gNUn%p^=ӹUIZ3H?hচi[!J"C"ޔ-\nkmCk V{cƫI?g4 lR23+&Bk5"&Ublj19msdaV!E"co?q`溨P7a)A{l GwX=ƚuQ_+0c$]{l%W6)jCS'Ɔ eƹ4?g[ kt)obn@D.;_=D5k3x`!:k5Y7:jmD|`S 90myw;5{8`󯠊K̘@= SjcT7rGgٖׯ#zehwp‰_"eTU5V>Lh=^u< e˳TBδCD aW<$DfWSb|R,1 rM—Xv[M͛"D6qy2mp'{'o&>?w U=XSҶyL[["_S:^O!^#8ONW"P@RYǰՃ0uQ}S9C)S*1І> p^_ \vz]{C|FMsTGzW$o0kl rV:(01 >rK_ռU|0|_V2@tԱ~1&L҈B#u){7" ̓x'[CPx}JYm|rm.E8=),“|ֳW5^{Ϸ1me7U 1xz}N<,EnWk+smWT)?~,yԛ%1 Iٖe(*l Ornţ-c]z$<ĀXO!ʚۀ_8==qrXq[7qKb+noUllqj;k@;qag`X zҿ>cLx'SV 󢪑^l`ߴՑZ7ڏE8` 6ъhsϾGtg˦'gٖW]ew.Ku+aB0gjΣ# 4ޮ-Z!DlER.,avK[NR0`F;N*؋VE>܍ &sOn)$b]kƫ|ץ4=ay}J~}bPP+K1}2)X<{U:lxhW?Rg*|֝l`>L;o$Gڙ6"yCI?q0I l;Y]^%vڬ_-YvU;֢'>IAP;me6'D%}23DT]XaL6ĥdt [SSO# F %Tc[Ɲ?҉a|IkmX0ic_T9/KOYRqF큯7 DdH &zA3[gdc40:(.S՘NIi|J8YM[loQp^69 H*^>-%gKu^XjgRp|sK~Շ9zttׂ'#78jd330+W_)/ AͶk v>KDd'Ԅp6Ōuf}[$pŝpÑ 6]"k\m υ5~L6Vd[93k]fV\6xԒ¯ChCԏSJu> hUh(W + WV8YSneMܸAJ;Oc5q9VnCNWoxL]g]z>Ḱd]C2b\kKIvV%8͹V X%{UcŤ| ڷ9㌥`ĸvző҂ dWr}M8L!$eXJEHߡ_uqmokB"sT/6?}ϒl_=>mЯҏ֬TOU/r" aϴ?<ļ2zq>R&y`$p\\q[9.l" o=GT/ӢPT; M'!1;ֶ|#t *@)&˽z#>)?ՓOr}ω7y_e!݅°Bx[_V cQ-dqa8CnYp!Y7u,;66$i=kBXKTht"g5JڟLqNH KyK_!ʳNÜmTYl8  >Ĭǃϱ[S)3XJu;ic iBe`/ƽ"?oצ}0cj?8g48=6Ox^:=]ztY6R2._K82gU&l;Ƭ\TCA@9T UW,d$ mCOg?ÃSژ jK5~!TlSH 1wUG5!7 `o+V"2T7ޙ O V೶UnwM8Xs*8/Cg=Ck]2bz̥6[6gzs~RG']=(e*s&Uv^DulgYx9U,JTP(Zv<"\cNcR?\Khj?hCpfUzO%@awݞ Ws/c#o˧'|64]'~;iy#-GłsgF %X0kB(3]e91?|XFɍIKpc3?Ȕlטllq*-n#iT|⯙eZ:`Muby0N`__+Q2:*'Q2f4oև*-uꚥ輷>_r]Y8OqjٜHgZ'VUHgo [Fn3 ܮƺ0TSYOFXեHM' Q\݁l~& uE IDAT#VNu͟}=X{qh/,+GAؕ"fW[p :l4J7Au_|jČWUP<ÎEej}n3XG7{Ć+n-J]ݢ)X=Z*q^ymM/}/T!3[^\[ acTǑ&Oil; )]Q̨﹞sC z-7U9dj@KȘf<?=<+9G>-nt.g2F?aCU;CynXks5P˩I4CT.’@sC}}o#[RD 4ڷ%mk\|ɰ8-ڈ]/S SmN6 ӀS'ن#`,_hlW|R<+5< +E$"oCW&m[F\F UT!HT@ZEv;Py)ՉFpOk'αFX9AXv?Ĭ9v j1l y]d>`/:~[;`[*caI%$&ipe>Ňm1Ա\/XYNU8.gnSؿ un6]2bIŸHQ/kÍOGACbsNqxIYy t ,xŽn7=0K|Nu(<.(`/q 1/5O]C?2,7ݨ+}{0z[NsH u<sEl m@m*"# ю3>{:ئD\PPipq|†G\HF( 6"o鉬u۶D\ ^țXht*R88 @j].l#HПi)s1KlPj-D'B͜৔W[XaKrO,DSh]x?:R;lKT0}/,:?8ߦ9J7*5jzm>cTf\,Q5wձZ'mIt%t>ŨmV /g9skqU9& sѭ=}EdxY{g[zR7KWwI~rZľb`SGGF00aƘ !I:jg;Q%}Va &k@mM*u"$ޡn xNն= X^ᴭZzkzZhۈ9(Qhs~ˇ''.#ccc+/~6.>g|'\>Lm\A,faG-?^*'y`xFax[>ÇrL>@/CQ3dAqmV3l#Ӣ}nmKb_J*/ ƀ׼fƱ~3kp/c)|IzXvϘ&uk;ݷ"tNjM>o&>pm|F9TM&+оD%WV,[;ƫ4 G`A:OA/9zT  y+NO2QT!1;Ψ '}E T)IwbLqB,F)fLWMcL @0@u4Bc=cF }Ч\Hq`nxl!tSHomTfݼlYNfÜoϲou .|P0-u},~D* ?j,>9=ϑ$8 ٳgg+$žp^!@XY=N:>s3 }eW1)?5+o;nvhPz^:|_GIk jYCa^甪eLP]C6 lnfjӹ=VChWDubV ߭D$⫨{Ym|-XIU;G$+f~Y\G-(qn3p8q+ehm~/p&UJҝX?SBѲy.n6 Tk_@0<(+d{pULJSkCiKz>1Sw\ƭuk qAT`mCփ :(fm0.׶?;NbIh (F^()HԸfϯ>Adٶ)g;`]l-Z]6 V]i!A=>AzdC!@8j9;uu @H?"9@> Pqo;pR_5Tck$7Z{}lxuSQGN7f~ Ed5e] n:Xbqu{A3vp8Fulx;u}yeBɫ t]ʋ1`X{m8*&-K3f;w~\,ojR$݉9G+űgǖ  Ȍ4^Hñ~ )Tl Gimu[ǐ J9'Z7?V0ɰ['P:05X$r&,}g/.E^__uA;<WrŽ2&]fu X5gMև೻%UӗbCoR839OPȶb[_O0VGF[qŌel:[M=@sĺ𹭏@16Z#>'4~kQ\ŕpc;]AʔRՏ&6Nc=!@2pks_"Y{YǛ3r^Lwa`" 듋zVϰoqB[x\m1G8yg}nk<ym#b;my} 'j酏Zf=l=('[q}RF2v€l&3 mY|@B7NmUc6 r1`_O uwC|Xgdnx7K}` 2qRݞAˤe|wr7 [U^c`[f65F":CPn!N$)_^94aeMd gv[xܜ;k -gexg--⽮? -fWNF>iKœ[|;BV~?AW3ʤ.0\Uf4@1m* 6-woMi~rl#np= ۢȋiYsM']3S]p`mLwXdd.L*Cz븀6=N|KCU ܝ~G-1]?tԏ7qmGOȮߕ%cc#pLuK UiH~KH.H;AD?8rN>LǹxͽCdGs55rD N_|<{.Q~t'E5 ‹@#h_QG%e<4R L'rq(fݐ~HpLQpĕ<;RIFCgv@r\l֋ڷa#\z;DbfUSEN{coy{و6›7>k緀Pjr&Sɤ4صV;ȹW8R)KmBߣ^&$)1"Jq;P݈yH81y4i(0Mm.pE,L/ܣ^FѽVZ2Yg%]꾨$cxH_e=` [+}v1}HEIaP,Cߪ*ea|P(w:}Br>W ,/ Ufj0,~>E3Lve_?FnXa3_5n'շ$0WHN+Gロ`Tf w41ī #8h[,^+J?E9)av:DO0V>:[hs%l\z>CU]Mh7 رYC{y]gϽ3܌ʖXqwD9qjD94`o?59hخ c{-+U\brloDݑ.WuUn"g33o:[=B-]ot/B8V\./po[T _v`›Гx/b )t4.`8*@{~+і+O>*]ohG C*RC m^voGg7u ̞{c @vRmyjzI~Ϧ%mS2C~aڠtVlm´^Z燭47U?C:pg:D:]"g@'Kz`jJtJѦ16kx[?`2&"!> 6C=wS|e$Y?%OnW%H-P𙋇S#+h`qW^5Mڦ(6p+ĢCG,+z<ec<ˆ,7².ßuC1cQehJEH=ѷ;cޚioNOksyS(ϋ'ԕ?r+E@]C5t f6 #f9B/j[bAC0KpSOlKm56 PP ~wΗ\W!;| 7^3]'x\[W`!X0bT{Q_m[?]Vy$`6{ ?DW,G,ǂ +I(D*HO[4-vDqLɏFaRSIf6RWaYfcIMKh<%o0lq-2Ee&I?3Ykyѧ*'iƥ2xm}YIX(jIJ^ħwvoy005RwJښa{׻[~w+ix%ewkm{j&i+-p=4A WeRz'%? utt#kMDžmJqC\YlT!/b AA~Lp W|U&*e *Fm^&1ņ~<06^L?Ȫ+9-AKjX^\d"q_cI>ư{6Wݎb[ʜs&a(MOʈx(]t65#5ƅc B[wyd&Ko&30B`*1q;#GN,{7˳ <2">,idO%&Ć_ib:`^0m5?s N ]N:RpظN?VbhJ0I;c=i.vÎVTHzLq{ף٢"!ETԣN;T 톝n2s[~KRO+f߼**wf!&s7 (ڜ mD99Vv4\?쌼&tU=ˑ1#Td}M|mD=a(ms,XO6ld @QKVyfw!3YY}"֏T;![|Q~!h GTo\Ծ۳~c_OV^ QkUuMy,wuWŕ9R4!3|'N pEG&[h+phN-q#!ax+›.Xdp62m\eMb(FODP$#ܣ WaG* fѯs2=^\îSߪMKZ|T- 7gm3}ٷb<%vsL߿Af7DeXnؒMw-#~B*H}McY̪a>q0 fI|[Rz(wͪ y;!Vd"1WLt#1#L{FYl6ܦ~>q(l#;)ūl`o OL-b DιnxQ5By;aH~G:}b wu,n[g7t:o2r;x{ /!jTd},Ȕ f"47M󿘰0Nci{]^%GL/գr})b?hҳզb3B+$Z2>( }g'}~ufDM']K-=1~]dq3nűBf۽#^ &lHd5:f C(W5 IDATXێ_ĸ:5A00I {( ?]Bo-߽섖SxR۟AB&Y%u, WRt$tL>Cs\Lx=K(؈Iލy\RquWw])JcɎY-j“r歨ME5ޢ\خڟl*[AQӶ-y\5euyhƒ[/D“'O37n"މ0O&_w*lqLxuǏyF|Վ͛J܈S/D㯶 L,P5\ULW"c_$ Q>g>;Yl (0=#y.l"e􀆍 w :C%уFeC Q7aL P'eN3.Ch%^:n:]ٚ:.)I\lcX6ְ/ڲZLC`wfYS??7Q|!A"vN[ռn & DdAf;|.>Ҡ (3sK*縌Y'}WQ+OCO*M*9[wuuy\mӵkrXNKrHTܹ}l5Tk# U\t GڎmooLxQl?8Uג}_ᴏS 6=;{2Bx1<;eՋb`)dM)0hu ,l_q9~54IV:@)smm8Iߘ>-*RdaW,CN X'Ja[@NJ샅h} CM+HӖE-G4sgW^?|7$~Z YRzձEac}0>~Wa#9^GٷXu|xc(HJ!Wo-r7a{wwsY(5a@ %"[%hdTDf U߯)@7_t.%a1}2*!D<؞R\ۃ:n;5i緛<{k15 NPqd?·om䇅mdt58ɴtiSF'Xv~0rڼ=+խύ\3/b*K?Z hgt2# '2srE6L1DqX5F特e-wo8+bzI!F{\?DP0p*.YƘK\c<;9H@/Tb#l6ᶂCY8lN["h%Nh0=Ơkp @⇛hxj|kmbߖfzc<m,~v`~: ڮ*6EJ?R`sϕyv6pK#"Ts3pIFkthe%,I ߏ4,tĘl1?9zE[e +NTKdZRzu;}.>) Smty30!46@KY}Q׮-?"}eY_AMrlx.۲8 YyߠQIy:Ȥm\0:X/OV|8̉n$8T{CWwd;-sMgi:LfK"b` e))8 p*|Acbx\S9H`<@ `W$ΐOaleg˲p_D>+c 2h-=G&&^jpL(Zim]VgoLo+e9ЎdF2vIYR ᯴#9q9O5w٫_Q3 FVUk{(qwgu{fJ΂ (fu&y6=HXU H.S SZ/b_#շ^5.H C5m=qs~B^)>_ IgL)Í"uiǚĘ̝/'KoJЛ}% ؓ57zbۣ2TcC7)/a/^i|y՞ 떌NX'^ֆQ?]PJR 3khU!T7Q >oBHW5w`y@N0$r/V&`b!ㅍ=`~{77ͱ:<^C "?U~]]fq$YLȖ008YcZ%D㾧T,˦/EcaLk 3帎rJ=t /Kd_:< .Б=MUԿ?Jy9bPF!m6Չym>-2_6 K7uIA~gb? TKauH/cʼh!d9>&{^߂Ni$;"o:R;\M LS\P (Vs$fKZ+6ݬ26c9ed'7%Aʜʰr.V n9vL.fp+q վla=EC]{6ܿbm~mvG=A|e<VmA9^ᰘHVU[mxc˝Jo-߹<7/<npf?W3M$Rax[~ @:PvgBFbH``GIJ9u>Քt2`v~,P)'2'qn|ʧjwm7Wn[K>]}Ɖ,zU:̈́PD#u)MĒ˶ըW:m,'Bd7\pH `OF+m3Ju~.;m.XKLn?Ӥ[|OR>I8PRt6f:,w`^/jOҎCyQF>3}bAѻj'tS֙OE:unIqˆϰmJ8H'bC _w=?uOeG >c]-^j,pm嚴!y\= dO E@HV#adM2 +؃lsoeF[+q-wfq;멾++bۤRi<&F ǨW=9zC\qmǝțŘ> 9A?l eMwaۨJѹbdtiN΋BݎMm]:ROE&r u T(t b#?E^/J< )kIvy߈+8a_Oaꄷgnы{|WBItPÎ-] P(ohrLħ_o~Ѕ_&a`cco^c]sq.ǴAghp8r9eI98ۡXeD_Cn4B2?z㵶33^tKIr&,a0 zYC52Zx| m؃lY"S58^R %-&7?uԪBmG#+]{D9va9#դ1Fx}>T%XNSc<4)\;8Z 8͏~H h66Dӷ]0n$E3'YkED3 q{9 Ř3Lcjm8P򶵭ؤ*y%ԉrSf[NhWՏh}=wV]\/w:^HOC8*H1c|PK>tV͠_zZ^ VEi&=F綉#vtx^vR0 ߭J: iMe,p}$tDw ^B6-(>BLg2Gv(4)EKSG [Kc{!_Ccr&z0.M*@OqW,/å>.(yXgnP.}fLAI(ފr.Ϛ$"㯆nR*g| x-}Cvfr]mkS,1ں Y#fq)U)fds^7ƞT^?@Ȥt -F2'hMĒo/rTyʘ$L?a} ݓ 3{vڳ$J,n-A[ | Uggiߺs@BIMʍ5Dʬ\Mږ`?Ix1;ұ[RM;6hs߅!]L)?W ;0c;$ Խ {dc?pc+%IY1['P7e+ Xxpk|,N!wB1ovr-bأz*WPObxV=FWR5Hn;C;-#n|KtWzxρ1 rQU+?s++x]aLc^OU$oO $}d׳t G:ۊod8fi8(tD)+ص]^vl/;)!r`}0l;^NT`-J ߚm˛6, yBo+`!Βן9:e/ 04n#Nh&fLi-]+w[Lb(+2vtF{Y*%~.|;Fy}@PyXw0!h++ mgvdbC?q~@m T\Cq}(.uK$a 'u#ےy_ztg=xjQ ?'ckZk8y[j|h;skp'iV"1T{9 @0:܌Άs^A[- 1݆`@%"jt.𫒞$LgY95 ]u#@.S -Hn~vk.'eV5=vjFk[mקw,G2>(<9?-7T.MNtAh,0AeK "N*7'U2WpC#ϙo YM^ޅQݜFj2= N[N<ݢ./h7MH~੮^7;aL4*ȅ=H +PSl[6@Y;͒\Ջ.}Pc/fT{6lδվo oc}Oy9{ ׄ5D;h\Sx5Mb@.>W Qbh,׮7wvELq'Dh7r{`C 2+)hk",sg5܃&@۔} bvz{hO0}٘D nZax̡RD?E}aW+58ioZg9:h صu{Iu|rP]NFX;R,N<]+F_"?sW+3LnbC6yBUe E.s% ~-fxo< =c,;>v(K 9H9~w,6B'J I2u2MR~.d&G jw,~44I'DwEIJZV.^zCyjMV*[nOS\2= ̖]'nV`^/"/J;UV7ԯ\ 7q P7@\|XňgG#vfB Z [Ulי[v-|ϭⳤYЗ*X7(;]%ihg;c?8ɰHD@='$.$#~4/Pu#Gق[aF> t#Z IDAT!O4)b ~tk;+^ƗץotMwtCڂ?`xBvLk?u`?DXvvC ^9 hWnn>Ih!-y5?l>irx*,5}m 3} D+`mmsS1sGhyp.-iψ`r:F%V 72 •1bE?q}qŇa]Fzb^@'ԭ3*6BY;a0BgU+1P+Zl+쬿r>ŗRwEj~ۗ7pZ!3)r&Fȹ<|7M#%z 19H4P.# |;5k^ j(@ :_\6βJ^=\oҌݲ)Ū̜~8Un"yDRGTvJwb\Ԧ6DgPee1{UvXư.׏eR%{/$4VVL PFq3ÅMi:26K8?eݶp<œ*mcwU'/羓pڋ'׹ڛʜoy3aW-Ez;#]emCLv2y1d?nжG6u,x>^JcU'xJN@3IoxUQ(ϡLp>#tq31X|&^ Dޗ?7?6$Ip"/'N(UEhiWYhzoQ2)o6㦾*+_,T kzm>v˺w|som'3\^~6B'ZU[cf2 !]Q˾E xcGaL/3חrs GU/{E(O/γvh\7:d |lf#=A沴,~eD bUg5ThEr| IqBK1Y tl1~w%$@?H$"_Ư"o}!֍> av|[yNCa9?-x: ȷ |-,x4s%pLǿWgsK?r1Zڏ].g(V>QT2?AQ+AbiWB&P&OݼF);v-Jrρ~a'/ (L[ !x`:nOAx,=\  ݽM5k@)?&W=3iM|LjGjĵr֮ O>*l>JRBjp١;x٪z۶٪8} qi`8,&'2^OVvJv%zѻvTC?dD2bG>e2K̷a$;C&;%I/M莼-~9~I=׎%YBV ߸SVˋ#>)t,EdcDSh6>sG߭r_yGmezm "# MӷؼXGc|0;9 d}LJ~y+y@o>%A~WMUx!#(8fBC8;{^kEڟѹS* q(&Mxbv }*F*6Af;$@}AzVo|GoXI-wXa-} &}ʉ}(ܓTOsC{/!SC6;s@ @G_QeړړPDǾC`c|pEd+ؠnF#LOJ]LAİq-:щkq%tdX8=!0]1$;vI/Ԗl{b&]m;_O?9p_䘠R1}H|r+guwAFN7`I&Ae9O0S|g̾Mk{*~yBZ'UDs=}ag6 Vv2z\CG=蠺[!EO=G8Co2{qY+GW8nwXYyh].7҅՟?R[Z(4o62H$xJipHuGQiGa~M?i d6y,43~:<9_"_X9sV>wQ~E0R^ FL0%N".L[-y=QIk{,Z"q=p"G4vV'?<~v O,7얙`fW ;zxy<+K!?ڙoIr@JSy7JĥJy NkӮ~g Letd-3=b(Dk1ŕ¥LF敡F{5K=v~qі:؅h3;.3|To60̨m qF>|'࿆ikmQ=,$wGmy XwraS ƫ.LVz a}{QlO F>WY*xGvC&:ֺ7gw3v!7* ur>_?ٌAb7}fMXN>?DN^ŗC`Uo'W\Kq,7,-J%=(<4s3r3H¢ CV<ʧxvӈr~o/lEXj!蒇DVn_j3wyrhK[?H~Co<&^?*v P=EA㠃Xl_"LfoY&(dAWN='ݱg>mmbΈOӫ'>>mߤiA]:4 |pӃ$o[֝'x\\7z֨˷#0h|#G|8`5(hܩ]œ!n6k1]7:Mu-صCIV[V{扷 Lyw?|<~P_6| xٟ9YyM^_[4fs*cœ[}4͍D懁`h.AIu.1.Dy_yi\<z{(Pr dusm$ sI_{1| o#bArm|M9ݴ?)1N>N=y7A#іk Vij<=mEC"_򙜾r .”o)wόAo- ';Ro2nda 19қ-*Z_s!pT\?]TO°c >͝P'ι`eqM[l%3&U1DZ&So* LT!N_&"ߧN>&mO͢9Yϋ+q6S]ELS^/HHy3xEÑF!/^E{mz~'Px^pz9ߨ>Md.2;Ц ?* \/ɳpKc%Zvg7j OVdhc}+L 1[#U}X2Y^!a9fi$A_"GЎe5[k,\뻡r׫>i Q8t52̞L,1䏵^*dvy ߥL> mn+93)DqBc1Kc O3cEۀ烅DρMK&3t- hxo6Be%-7!Usc V"p9#˱}{zF[YM߷/DqsyC`Uo'O]*omVƕ|Yw߷7$}bNHG֞5(dHۦ-JEy<=Nx|9`b0L?{KP*f3wyIDUު~(NO?[X69W"~*M Lt=q8.x[u圃Y[Ea%>r]Xe i FWLK{&>XR]Ǧ _}K StXNJA9Iƞu-stLq\i6$QjG6-,sV+.|vUަm}eUa`;oӒ#Jb_1/J޳6aK,Qߴ'q^Ҧ~TQE"}OQN 6&CQaؠNL .3}c}3=I/+?N@\?K €<)$c|ެ4gL$׎7l_7ƲM}g%ZO|7T2E]{z๫Gʜ[ϕmߛw,wJڇ.ocؑu9g u:}ɐx;4|Ƚ^}<$-L`2,1taCiur ;v+󝲑KN?nP4%Dt[a0=` `R(?'?[yٵ͚,)Nfd} &_kfWim0NR>~14[7sغdG[%yͺ?sywşb6E7,1މ2z ?씶dб>Rh؆$*AS?;@M10G`ُ4 DÂC[ʏ~$5>m`Ѽv~ы"lx5ŷi4ZɛVB-kN.&#{Pc֡:íT>ƿ2IHo2 *g=ЏlħÕyt! b}>rK~Xf{{o/mmano#oγm5„P>ҷ>=/ÜQsW9$նnNEyn\m>kTe*Xg%Vj t-Fʎ/J("~ooUwظu=X\1߉Wq@[V6EY CH7L+LM~j&4I**=SW0 L;v'8{ -;,y=|z"O]}s;<䡡6"9Q]ys cT )CZ<$c;, ab~&0-/hKxy7flT1^DZ1L7U3}W[9=Poe :w :a&EܹzBfUťo2&Z}ʊ;f~DkKȐb!y4xO,o1{_DtS-]d4DA޹=B['ȶ٤Leq=NNSd2]T#?T(C3o4 k6&GZ׀仿lmxƸOsMp|-]7| 38x%6| ,o[O r0yv;WB˘pۂ7C`M3y -u ok4%J޻6N8)ME@Q5| ~jT>BlhYF7nw[|}r{@a Qwak$>ݧJd/v*/z,{雵EvtD:VyG:TZH l>T>ZRo](|ەOv?h8jS)kGLZb{DK=O,pt4|te(l*d뎛GG&$?X0rh& 7T}A;UwȷRg.4 G2t|\!Jvlgu2Eio,7L)); k4;EŒ5>6 *[#QN~H59۱wgh .@H[μ#?Qn͜IeXn4;IR=6wUI{x38zVaȱ՞l[v}HUvS^8}.֒`ۿ-nBÝH>9>V=WƜǎE?s4"'uV!os6'LjrꚁP.VZ.lm}a}bE:Q \̙[a1'x>weI3ʻP~0@[(?=;qPwKf> ox:I u E&Cp5 (=Xt}{wLW#=9.î_rBjp44=|ī?|m,G_5ۧ$u0` 3"x $=' ÃP.x'x(=p,aI!r&]sЄNF.0'<:ɽsC2|$&EVLS򂡻#7o=tPg2dۇ@ފ}`V8^ (V|?hLdoFľeX~u/*O02= V+~@gox:=wDٚTbl󜳧l/p Ry#h-v_0  P륀ƪS=3LMʕ8WtK@|G@2"v9+w˔]n(Ñ2.t?>F(HaĎk{E,!-ڟI o>HN#KtnMke`]AxL5#e~׺8Q~V:ƷųwcfwxzH Bnlk;a^/ V38Mu|cF4uDrA2/bGYyo3ˌ0,͕dveW k(ώE@7cW/1Ez舧$ a3`3sI=ǃa~}oPzQH| 9M0QT$d䝥IX,L3Aoa3PHZ#mIȀ87&>!n`:h3L߁OXԌ +.}~7E-҅O!BV^*NNo6X;j4Iϱg*|nzUS}G\Q}jg]R^ph|)_<`T55̋mIn7!?y1Fk&v/ChtH;I=;*FfIV2]XXPL u.kDv!xWjgƇSi|[ZWG'_KeopޫʷN~]m/ ;&c).}p[p!$\}T=HU Au ~ݔ񯔻M׾_n?G,}nw`΁sR 9n/`Y0<=x>ʟ#@ט`#_>󯄶p_|,LoE7+(96|l}l&#fWS1Oa+[aW:< U~Wbypfcی . {xmlv~!?aY?Gxv߀(Ο u/0<3شejYG']*sAs:%~Ad݊'8q†cI=̆$x ZWBL/O1& ?0j;ؖV;\vң_Vs Lv؅*s(,]>5&VZG?Yu*x8i#fd/c \LŹ/x {4}8'# >EeW=S7;"/̐gyKtu^'4opL/4f '/],xkEƎmUhPW\gvKهU+Z{wugjyIOuKĿT SC뷕Ծe\|vcZ|ăb"$a[BozȠK25^)`?Hɂ'tI ?O`CIuyք"Yhe[H~W!gBwвv-[Ǻk*|3G{݀g\@Iź2 4E{4lLFģJ>6NٸR͋Z]WHG^MGZe =9x\X(ɂsorR*;.a΃&=GuR-'2r(vU '_'Y{M3KCu𓖋,`;C}3ѫz~u(W70a~2SB+8+&Z[uV/XC|w1]r.[[ҹfiW=\S7Fegk=:~C8ܸG~7dS!G=ey޽6+'EHj^6Vb] \5+Xyw=[vQ[hcW/+ܖ'7[/ۇ>`f0 L+7}LN``sJ)E4l>IجYHdwl~qy~ .6xdP)O#efW6EFY| g<򵞁^q cF=4ѽğ7Cm3^ln}7}_kAXKןa;Ƃ ZD  }w.,I6 w\}\oEvvҏ؉ @X>]cvCI' x!h: |s0mtQo3zF_{ Ve N5dx3-B6ՌO -O.QoxsB,Ys{PU6g+!%<ΰ(%W^uLcKecXCE2Ae|}26mθ#]%N a"+{t8oZ-8r|w7YMAa_ A~;, !=`x/$#ӭ":(} xr}~f++?#JT$#:+sBXDeO3Q٘0뽛 _EvR-ᷬ_ȼ>owuaig3J!QDkc8}asr܁3#6V^.pE!~|ogQ>Wiė#{ˤ7˟3H5sh_rOC}c3n-GY^ X_?#@Ap%jG@PO.߽\%-|6f#tؓoXԿeEԄ7SFd4߼- ݓ8cļծ\ )v_&A[SX:=_|y1[9$`^Jk b O|k_eo3C; CX*e\eV_Z]%3LS2ztv"b 6Dw%(OW:u}hx1ܶ>Rü%_>2,mWr?NVGEOC_1Z7Fӣ-hg7|8t oB?5jx}?/ݹ܅?8Kjv:lt2|ې/ Vn7%7kc>\%k#n*Ru0|!p+ȿ>8 =hOҽ߾ܰߗtrw<_iV"?sػuNhm^&=.,KZ#@DQP_mԱ݀oO І ]V H9eKS,(rb;V@oXuAdvc^f8/+rx Z߁eIeLGYl3e2f,Fsvc/kaGL=L0qC’u\EyOZ0nUN5#Crk^,[)`-r*b$kj- HzLdRB֔Pxh!u1$ړo<Θ: Y1B:\@{q|X_ӡCwwa>{6>jݪq'y3o"?::Qq!%{rfa0m&R:zsvخF3fyo9[uf``ߛㅔ%/%s{2>BٺPy i3 e\/ԎU3 \'\>*!}4e~h*[ cOMC*1 IDATlCml?} ڕιJ?h PI aBkYHzJpMK1@3䭅oS8vwrsc8[q.-~A/ɿۋ&io ɾze\Q+tg{/Wu]tE[[tyPG*AU^<ؑ|EW.0[uF24l{㹥~Ro_O^}djL=@>mcELQo{S|8M0lk!6^7Ir"]!oN,&6iK'I^/Q@.p{9[̵rG[ʼ7K7-ڣoiu@J]ސS ^3b :%+Η c7eklsɀ hV,v2 O*x t21?Ck$c0Z_`MB@N6d٘g>K<lm`o>r h^\\qSa?BsI ;PibVY>0ј40eɧ. .A8G8nE +&>3X蔃H)b*t֝Șo/3؀-x)@k" HoYyH4px6k3>elbl=ZX^~C`F[u|!񴴾ɺuA;O4є]C^Xsc[qS@RG ;_FrwO E^mY༵@ \/ (5g%"&j'˂ Yֱ]޸J(0%d -S^0"K;|_fBg!3[*Vy%2L=v>Hn+;l;LWT[|?agڷh9;"5Av2ctKb녇~cc1ZDgT#lIпjȼnb|c}WZ&e>Zf*I۪K<vCf@<Q| s@9B( #ᷣ7ķGi~vSnj ʚ0`˚4ށKC|4mXOćr wh0N+7ƝFi1XgAhp 4w̾F4KWݶj0)k} *7 WOrlϺodA_8}x+"`%cE+ZWjs6dxy@׎};wfxEelI}g"_Qo`PL_^: 6f_N"<{-E&?/>qģ 56 <&K(c`)Xwr.UziVz`r9hM$x@=(mKrRyRMf ')]sˮ]0_.<0=Bm6pV^o?-[\̿,*doU;[O B'@xm~] -i+LO|+r9oڙLQOT硴' P|_%n׮]4\s릳vo&8g![bB`b Qz> il.A$ m[I4E3N: DV|*9R[x-̾zfbôTi d30;8K Vz߱CPwrƸL;zB? ;?BktmI@w@YɩDaz~GtԑEז|:u(e~nʹeؽDńx./9!0ui+2 m^hcKYfX?nϑumm,̥|H`Ii#~mQ/`eFd b|ӳ)3{&"։%"ɷ8|pGa ps[uKȐUo!W&g.If||hGs'3gu ΑUgq\2`ߡÚϝܬ}'aptco?2 ǠқqyH'x"^l@L#=3z'.C2 .C5Fk@C[UOAvgU@I1Yx4g`܃d(΅E^`[j\?VauXh)n oņK^c3Oʚ`9S6Y=,4_raX'X] C&!8dx~EX"Fw_.NG܉[ #۟ o殘Dn35أc?Y>7tshp-tMm4he!qOqyz{Uȓm;t@2>K)$WKI&(kk?r>΍R=E7Z냀_B2x2 k_.Y0ebpI>5yЀ?󉁞_/<0olCI{տ NZdwӂ_pNv3걼_`,v]q12#m*C`E78n>.>Mb@,O/$p[c T`]eӦRPdW=V@ʺvHKᣓ uAaև oW;Z]>'}Nx]., 池9 Nk,wm6Cz mgs-uf1 2 yK&{6Y")/ѽmG$Z6NM1迒4A>G?ؔ8oorE_=9<ʶF0kmz)O`۪Ꮴ0{$mQs`JV/;A[}Kp0]ݜE$;v?Tʲ<蟬]Qv&81 CDgr$si~>K0>vE (|X_٠7&'oc澫s*A.d??a•&=BK]g6'L'>bOM 0PNv`$`sǜ#AcigIں>hyLcw|$vk ML/߉B?>&BN"k?3Я\|نrG  d`Z% qϮAl:#N{,ܑٛfLO,-wg8,+?}XdcZ6]#>Sc+zoH ?Kؤr1Ҕ\;"hd=9|""i'NT-բ'nͱ@{Tm ͒'IK#dgLG+؆cR=~c J[8G&8ChҜv?Q{=4i9L姶Kyq&IA~,N:k& quv ^@81JI.w)cH{'wq7wjx91̫;syytHpT]WK1щmHwqQQ6}qI):Y|SuFW⪶5M{VZL,C)w;_vt5kvɠqlc(^Es8p8lZv23MzyQs"tt&b,Nޥo:3J5RRLGwHU5t|s{̱ؤ ,[ӮILLpa?&ZF[ij<`gɑavk2CwՑm',UI>ღwT bU<f S,;O1ei;.9ǍtDooz3`Gި/.sV=Js/#E3z#q+`*|:Qi&~,vDW%xq -]/A֬w)RPI|Q?Xɭu:&5IdpXȡm0+w0aR&Ե'悙׎M7њq\/W`sd|^R:ј9i!<{Aw'γ>EDCM,ڍ1qЀ5a,B ^b NXΧG2C׽jK?|>&l汲c%SC-30ٝi ;E%2[:]-ٓ_2۸Q?k*+?whwR|;o^acll}u?l_|}%i׷jwOwwҤ?lw&HZ!$?)k;Έ *ؖZL:TҘGBW=E'lw56ʮ6?ѰLW%-kyay<~%=h~ڍz̉?o2=`̍Oge2¯#G0R6sC%ǿ˻HL;K ) (OLg(ڑ/yqo4J!e9ưsWqj&Z rrmOm?uKkcznqL8޼徭_kGl.TQ_uoOύ/iFdݚӻ&}sZjG.69n 7Dx 27!Q"p)dxs#8xǦ #˃@ 'zAg~ha^?+emq -o/_"W֚MpF5$I]. 6q7J9^}4E\%a¥߹ꅸ`wm IDATH}ƿ/$>jWɝ<9mC/z =֮x[V7jL֤6d }zR{?x;<{8D%CV^,rOa7~ t֯^tZlb~k6ڒaxkmA)>oSLV1!a?"ȯOyC/vH)=c;5}\ov;cEz|$(]_r.LZlOeH#7G^*=LcEZoVyu826^LD|`u \ \#p_`Z[WFXT'%E+-_9pwd]Usl3>sF"p){\݄׿(]{Pqӎe6Zh2_Ns;>zלer.YDP[L& I^_/ S%w( 3>GludH?7&jCA⩅MPYOYWdO}v\tgJ|zŵ- XiSHl:JA&yͻT_p~qǒ2ϏB?3/p&|ϲSwTl;rC;)Y4mҬlR#),y~mɧmSivO.Kcwef|KʡX⭢7$t"]w3?I\AIiT2jEb%$apDn(ťnߤvWьYݾow/z+[yؔfkS<ںո6r=_.yE(}:YJ̱$QeoĈR$lkǛI[& &5cNxљ3;qr.H*+J k7m9>+lL/o|)H#NSWwēke]g6<1{6[Ð#xp}E`bKQ17ٸUfh 1;6f6Fl\4>Hؑ$Rzfк_/`6ζSՓ!F{!y/mcr<;>+)dQu޲Iai]_TqRj㒗XߐEQʾO.Lw*J?R!K}3˿ :?(.X3l IGN"r &12wrD HK'[t+]OFĤK _` F;l3*L Cz{Ȥ3:Ig.|;OŘ~gYjwe2 4yٟ'v7:8X CVȉudfֻdq:e{j]HY^}z!h#Ѓ^K*ox]%)prL?H5Jʲ/v4}L(po<6j.6_Yyrslau=ڎe<֏Vmһ;pra#Xs?<(˼埁^ncst3p%ۿ[r.o*sx<zb>uu,vfkk-Ι#hau w3tl5E:=L9Ϛ'ّԙ]8n{Mǰ 4Qgl#m*=ZNeИ)7icCk QP~gx6u@K]>)-(McuBosud28 ;[Te v=frl1lFbInrtS<}7btv50(;_j]tehVDS ?!DL 5*f]~}9=)zQg6Ft{i_ZNBw\6VJ˷]nm7MuV[>ډ|ؼ nI,q~Erl{# +NDA2?8'ɲšMD*TA):S6wEMܑ.gϓg.CY?cr==Si/%_x]۷GyOY y!$9ĎbfhOXP6f F$鷀zC=IFײZGCcNDezgCGp x§wp%?-0pⰇcbx]tOcl6zzNUOڀ֒9S1!~Kʣ'c|J`GgMHft۱-;9n#rʗ%.ϕ$XaG2&|v̞%B[6XƵW"i ||Tn]`ml\Gx-osɐEzD0RG?#a,֩vs?kuI\):Mz70=:nd ȑdv:Ysm{>~#W yq4wr .2Y2fZ-+ys=EސvOy˶W|})\<)}a>hNk|cHCĿc]ۉ^A8.r!bHC9KN[-.^sl9^Y٧7mdcE|V$.:g(?u;a .7=NJ1wږųؚo߸0 CQi;HhNڈKs#( ',W `ʧgǡYSʿ5Ѹ^=/ïv7~Gň~ZO-[׊?mhY:=oQenýc+#]M9y?0)߽gE9ʸ]>(O->{#K;0=-ǡ#$(ecCHB,(ifղ$g CfLD"<An`le靿΍c}| e=gV9 D Ė-u$8l+= f8ײ;U=T7)Kz1:!)dA:үS޵BzH*^drVԉ;nN%`T.mI28zYC?EsO:jcg qL1X,4٬BOWg91Džz} =>/IU$R B$2o-|_|y͇=OyYh o,pJns:sT``f޺wkIGtǷ'^NQ\=7 a@E):r_kLSЖ>& @ow܎^@o&C uÚnLt\nTdEWV g2͛DI|ݣ8Z6 ߶=Fyg /cE~>/WRcx/S&~"(19A'dOu`O,5Ha3;|`rU<ۗ"J\ ЁWr & h\y='!IL𩕙٪ Lwm=vˊo.F7/v]Ty;$N 9_tzފU-,zfOIچJvx.|_(^, OjNuҕ.r4)| i{ @nTUdo9 1!LK,RDŽjTwJQZof_|Q{4cK{Wu¸|R߮qWk'26DXd6qWN8ѯ/^i#S]J,SX).pӨ.$u$ g,>K688wB"bEz# + lT'h,่Śǿ vr'W]2oۣ}qgQ #=9X"pMOۄTfE?tǑ[1[3o*Ob,ģ0"pIԐ{Dn:!3KWBIfՀh`k7RWd+odM'#6VӟI8iQq`ܤ>Q*]CIpyWCf~MkOGK<ʺxYQNa-pSkZ/r2+_0yA^9߅ѸGXvX>Xu혽ԫ'aoE.<.6}ڇ64/i!Hk ^Z='鲺[ 5ў~5LEJ*樫vڟ7'͙\-kq%\}KZ9"YljA,ÔVd\3g#-k/!&B+B]Z6Q3`mY/5><}3%G.||+|,+O9-(abZ׈}U}S֟3ZDܦ׼o}(uYac1<~?>O;SmtEK "ES;GB#,v;c4?蹎~>'>S'*]=Z OglݱMRw|._+o+O[|yrf떈E]F_Ss(+a{r#ko{ /Ǭ eWsm13)d!X9.DH'-$Leݒ< 7]*?ףQ쯺:A)’eZ~bZ&m,-BqyQͲ>f-i „xli|\XAq5UJ(INV%7w4L'0XA'# ~AC;qg'a<+/|BOHpƅTGK\;k.Sm-qh?qO+OB"-?fQQ5۹bx8i:-BIe S7BuDskWrOLb z.)wGCgiEm?eo}H}Cϯ IDAT8yf Vu8.Y$ӹoI,ޛfR +҃Ws1$3\VXI(tEOu=$*|8RXb(l \? z"&&Bs5'V=Wứ\}w; CGݙu|2FXӘ |-&LeyCx_0=R|"b_#T2S|E\/^'"6w8{r:)j{]]E*/7u72|(|щ]Fs6OY:樍=Q/1sE#p|/ηz7?B֥:OLœJ%Qށ`F R]wB,tti"XN._YԘ Y3!YHltB8h}2\Y1td5i*#=vwwAwuߠ/SP ?볪{Y>ŕh d:pU}H=T}qY8bsv,Ja|-@xmsĶN¥FcG)&Wxz4`^-V_"vM_espP\u`|-}$w|{cB?6:'٥6Sc uۇš5]q#Ģtttѭ[I6󉏭] ke]~ea]^er1y]c6^׆^Yiu>/'窏qz_\}1E=\z.){G|EYُ$xԖco62C02eFf9+˵)?x)=a^KHR<ZWvfV,qt47[.=GS-}U]=|-QV"MÒf{ca q;+ӴC ح6kc r_vu )5,++o3.2&b#΁Bm<{]O YO=*^f%WUL]{my/R@k鬌ޏ`?ʺ&8YrvӌG՞,NH3.g`]VO{Y[lC\╷ߝxz(JNo`c@o?tj_ ]Rr{[ ʷ*FMwr`~ ßN,n?~^m4 v/ =u,L q?-ێֵ?Y_s: Y¦ oWLt)5%O0 dw l #~J'/7G| eP;./.Z1ۮlmOL[NݫwgK}Až0r笽'~ p,Clt;eqpEy[cl?FRo F ЎE>5QDI&G \2ByJC[1Ů[hS=m+.bòӳ9#/Mu-y}hGZτ\]فyB7D3]˟[&}l"q(i!^n@¹VGYWp_,Nư_ltNZ]\Lӈ vT}gd,uu}|y\xIr?L_7Uw.AO 9wJ.Ew&~DQD|]N]L$YG0Bp||CB/1<|=ғbzh[xݐ ]B( ^P4O-$1+sOd6oߢ.Bwiv$%L,? rB6`ډV]HaQl䙅id @;8`6A+,{/ uCvX`c9[6>i' ЪYKmr[c7t^Ϫ`ZcːyNU{抶,AIT|(:tq^]tI_xÓ;z'c~YaD$Yn?9al[)krV)+_9˄6 <$I;2!}z̏  ynTW\UҽnubzJ+,;o[X/nwppܡuBOYgF3χϾnO{\\lwvO_/ Oϖ8_>_وE`!;k#":eȻQ޽U=v>Yŕ]=uw*4:n||:˯ 2njtjax]7Ðョ+|BG);O#0[$P JaKDr<ʉO"Xw6ӗ>yIG]-xMUȲݩU\4oKbptuesҙ=ws&&/o%4]mP&S2"u='nUj3IhM]Zdv$ ձx)b =cζ!?&]mw>_ jYwEqhao<Oc}|okTе y|s;8`bH$t4YϤ\a"<΍Qtet c9Rҁc[gsEXZőxTy?g9?qRg}T2 i״ߋv뗈u1u^?+o.{'@upi_Y\E2vi 6o]n@ QVqT4ء{Ɋ<  VfugdeJӆ=mgXLQ'O4~?OÜӋT^nBc:t o F\N#gCݽ/A&@IiGO)M>ϲs1O,z ~}2 ʷ{7U\X =t 4 ްS A#kK3+wQvWۯx"[>=Ra8-md옌'Ey3 / |- M&Ѕd5^7i;|%~\`_>1bF- xd}T##FGkWi{WEy{4?ڑVȁS.aϛ3_QspYsss6ghNN'£eic*k4~)~1x{:C? n# Gofk+crY9aw:Ћ[XkL)p8zqФNQ%Kǵd>Z:!")3YufE+{g7S`+/ ʹ5e7j.]λW+ˁknJ^<;?QڑMmFJW8wpSbrEʿ̏m|xVwp{.K.e>o~n5.eJuf¯̭-i3?u87yladzowt;6t>ٓۑބh7nE5o/st~JEڜ_G@xQc<? {ȴwr5?d4gq;]N?UXuy_nUXIEL̟LqВߙ29[-x?S~hcSa۶Ǩоw3ggOq$v386^J˞o)>of,pp{^vkh5 r(:sg [(#ȑ[%"I>kY {O^ɦ5=06BR YVZxd:Y/8ݻt ]˴G.O'4C<ω*k?ؖ]F=y`7O"NH6닾C=-zVvXҾd@wOp9:&6GHl H9y2$Hp j2\wu\~S~KwK6$̫ YM9- VÚn mgm^ÿcsY?D>&ֳ1%x2=!L ? _fm˯"kcW}{4se"8S;m=^/z㬏 2Maa ]gl&'~֋qmƢybx$<}"pgJ[꣡E9:Y<ĶtE /R{elX盇x~BƸwחtGWH\v^=7|خ4^~9LN8qx頠d\ ؚ !;U bt` ]I ~-w]6te1+p` l<< 8v/]OӲVmwJe\ n3//).%+b wLcg]heiʰ8P`}X 7= p#f8b8f>1F}#lbe/b ЄO6] Fv&ju? yLITB,0eT5M<01l\bJdy&tq7Dfb'|Y8Ԏ$mAԇb.],v1R<\L<)[ʏat:=3_,ec 6tKU7M.4&6}wwnU,keeܵ@{ig˝w83V53=>Y۲n;?N٫_IOq²,aGa')X%&!.Ey`(> ?RČ}c0Ƀm=$;ٗL|I EVkPrB)C686v&Ёs!/J5uJ W;zeEd*^:ZG%7t-r<ѷ)=l7`B#⥷bV;U=cliwa=\E` qHJ'n- bĘ,F"oX`|Su ȕo-oqc\V|ސrt蛺4O͕mMҲ$ 8#2C6K%x^>cçb?lj3/Q#29"Q7 &(r0Vϥx0PsD`sy+^ Glt^', j^m8 YW5B݌s;u{fn}I?UmU20$L k}#Z.ךm̕o=tfsJ{MZŜIəóbNq}`"W =?{M}>A>AF 1/$N]w@z+й/Mwv!iÏpz_~׹inL0 ;Ц*TlNbSgXE;v;P|{vp罾zGh&7R[RMP U!)I /oѯArf %!LrkæNUdtD'ZOy{#MlOa|%V7{2gN"0v6d9ƀ@ + ֻ+;cQFsܐ݆-Sa>)2ܞ iK_Y*֚ɺ< *y&>ߩMCvƟG Dz~UwSv }}o 1_V--_}:j{L?z\2 :ޑ\;]<%EskG09z@1 /g lrٯˌl>i>(uv/B"y(zjB@kZá:[je_4}^Ie#u.ĖMi0bq7 +;V]st+{t.p1YoߋHv7E8]Z }"[tXZcy9CSa拈GiETیq_;%}Z@mZuO3 OZF}%c \D!ylL'=篻H=],D[)P$S9%K KGG>֋]'cQYlf [ljtv,q$V]̟ȳqF WU7jut1A|Yx!v m,&_6DUMy1(~'u7<^ʶj1Ζ>`Ec[)E=Oek'ϗ1QQY&c"v$8v\O~,ΎSõ&DŽZ}JN`JSv 8ꮏ.yё}f[$86G v ร\>˪C-pa+=?pWSwy~/s=wog!%$AkX " IL±I]bR0 qY{6O^2Qo҉ U!3/FwDXT#kUaUxU? jKd߬c%3.;VJ4VN^`d`wR8v}OSvzu~|X /Ua xkob>Yٿ6wpF-דp e09,wN8.5n?Od"gn"jBƪp6LA"S䱖5ңϜ@m޶#8r?hOWb둏~tx q!=~Fٲ[\f$%2I2\kRTGSu">>+[Q >UzY6L';]\vNCl瑯퉸%clzL7͓^:OF?~;~RybO>O-vlJy |b ;&nGwKHܽ{1UFPร\]LE+F/s_G@ )H'*w&#f}$gKQ${ʊF)Ap W~)}WdM1$e{'S$s?c7&D n&M3u Ƅ,c* p<$fs3injxu1;a43ġ@hPyZXUwRmT>ZIWUW, ݶ!rX-[qkH̎nv >T7Goqlc:{?V|bgç9=Սii5u{NГ;U^qc}ubRנG}K}{;0fΛȞHk8uSxvg%ĭ1q[ mYo;w:vوo5#zK_9u lP R(ALO;/1}B0ӷ^N3a xw4d-_2 ޿ٮ5QoP' ۝]h.r=t^wF<{'hj?o6tDml18e\8>~QN+ۆ_Wus/Ozd3iKh/vIeIa5!.@GhOl$̾ 6AXM"зӷIW뉴7[zFjkI7Mmŝap/>WwӦ}sNNy8Kv[9ݜ%n:H]<ӉKfy~.e$q[WYy#Xz> &)_W9[gfrNn6qPm9w S)ߧ(;cNգmi$:Ph?3-PDtng:F}N>>O"B>=,9C{W?`q筞x+*}cy/x;W[oк͟\>' NòO?/6+QYY{) *}۔XW0$9n|{͏щ4{cCQ99$SX:ԜU-ob|KySS'W)uRIV&b܊] k\ x\h9j$~ի^/ `˘M$jO_fIWsuyk)>QϲHsVG[gf;v1n1iW7pWwOeo@+Q;{H@]|;AY6DPxR4$Σ3ÎEKزZ\_F'u'IhAd^߇ܩ}z;~5|zfE՞e}k{jsr .[Smߴ ,T{˳\snn7J=&/ {pWymB/mQo:^f'2ad>GM lbD'V|3m`O4Rl|R|}[k ^w'Ω_\dx"oY|@^T^#q43uǎWE.`"O1KaXpn Vy:Akkȿeà v1[zʋ/q|g -֙Ob[r5J"Gt{:ѯn^^3ZLޓ1)/Kۈ2A NO,x":''ͿeJ?᷆UEECѽ@=CǦ.N|!K7;x"U9WZi^ӿ7Bk;C]cgsrI߱dvaדe<&6uGjBJMQh)fCnK&kC'&_tZkq~W5.zx"_8+rVK'"c57v=v&/SЛHzy,VC_< c"eL?}kGO[]hLfvX̜f˯Dzg|Ns^PzGc?,T=Sʳ?U/+yD_|WVŬwo -/NHbJ$ \^, _<X%?ftMd7v4FgzVٕզ fYuwz*yo =@F@XMLɑɽUtgbwD;(+XC} ]`XnZ',FSvO~q2/|s۽/Y#==`ؽN9'm1`UDՙٯueq-òHj* ]^{w(GȽ)Eq*nKU3Z)yPJkhygp t6ﻭ3ɤpYKy|_wzC gE/(RX QM2sic\ZF⋮%+>ՕMMƨ0>@b.(\=3|oj/H"|;~ڲG =?C<^)ő->;xY]Onڐ-}5uJ'zt)??S~COs/7|uxD^8- LO KI;Oz:.KàI7^~J u(iXO7]y OiUXB{bqFNR,"{daU$'r\ţzۯ^-Y;5^ u1y?<<Ʋ 9 :.l9|Vb~q`B^40 #=|@yc}N/|Rݢbo򩾄PioZn9mLb:DE+_]BIcp#@yC|ɾ L:|;u&NYhXsd`>L ji5U/WdbnʝK4z1֐ٶ6=Ĉ|9X>7 }>]؋>J={#;u9ݬ~ѹTcٳgBMX$g3DKXPdxgJ'y C —*+-uJ1;W"GS:XT[D1Sq8 Nw?tб/[@~ U?)_u_>HGm86wZdT;_xٮrg1owgSyɄt3j_I/Riw=o.j7@cv剚XE;wNj)"+2;E-mF3a{A5rv6uw5b2m0nurS;k-:tN5΅Et|\ⵗQ 2nas]BfIW LwYԷ.B,_ & y96Cgqa֭tʝ6N`czN`1牭`7ْNCf[B}w3:yUNҼĵ#wcf))viΩC|;;Ď`g>^u{AdEYaMn}]w2d_rN7b#fkgtqi!NgX thCK9?,K IDATbMxSG/I&>G(SPjed;{sLu{|wKqGw{hl;1da=p* 00e'h&]~/YhSq_<']\9n&(?W b#b}g/f}$ׇwnU;-{EtnD$V(= XT&gp$ 0)sE#6 W ȝHum|=3% ~b ²;%lûjs_vLt|&wrG!#U& kD7Ѧ+YNNZd+} &x¯l?|}ya7^<s3{eF;^O*`1{(BcRz/h'JV)hUr]TzmOP:ȞDp=1s1 Dl zB1r%"=Y._\6VfouAEm㏢cիZpnǔ,w󱤃ϵї-{x?[rCWMNæmkc!ECRh 1#O|'vl0 ]d%N[Ib28g)KTOz] 'oޑ2 IqM{0:Tj5=ӎ>W"{ϾXf‘/*'ܞ;>'f{ȅ]/Z=V, e{?dv *3{'B‚2Mt#volB8 qΎF9(@%=Фϱ1/h|`*zH/V OQ#5汅ՠ $8<Ů[{MT bNщf'D\B<~,6G$Ɉ(9gfFi~{4d\>sw+$ gS$gޯֈPuy{UuꚁtupOGf ^dzM|Ui}?h?:K0?;YvNvIButwN؄NZK,sIx9l7}G ?Yح&xnA13䋕8;|"1`6daNQFIɋ>*WUl}K]ܞŕ2 V5>*'KwZof^L9"ldL5_ȏvx2=#-Է7.rao{9 C~G8Cп~@`p8SX;N;tkTng'hAUmmxM}C,~7@ 99éϊD2 ,/"Xw-^jˈf+ZߏMaR"\c8@<ֈ h]dy3*ё6 :NtbY㼧sue']w&O~up#:ϳ"!i.)XsT_r!Ӡxc0¦?[%Y߉iïp~?˛ɣ>; WtX:ނ*6S  %|Z"@e}nl !g|?oOvz)a%*.>Gה[0(ZdK^Gsn#ˣ)L&S+JǕ q7x$Hw.nHԧJBeiYB}R6 ˮvK<1e]-=%{MkOoˢ>?W&WGQ{ݬF-ٷ`GWaINXe(1F|a3+v7Iv1zﻶ%.82^Pd/ql`/6w<"`;oκ4=:*3io>kXKDsO?Q%j:3J͉,"H 4ՖXϪE6jji](' K8v 0J^+")ސ%9sRkIw(9[#yyo1I:*6h#h&jß,pm@7[7fė6 McthG2НY/b8K[Lwމ~Cesx򩾃2wid~k=cvw RkpϦ9ǖ԰Y:W=\oD*It]-,߫!k' mS#D_cϋUykt^ZQ:w_aB|5B:ߓ貒PSnc/ӱ~.?ެާrwj5f}>}d'+ޖ1=ٻF%㨁u+zxdž~,ޭgEoGZquYk{6UX򃉲/]+܍-.n!{1+ laS'<~JSiBԏiݸ+O5 1pNN` 8PuHxp#.0zYhqēM>,J$0^âOq|sv>@CҸl*hwВOEE\ՔΎeݔ ICAI,,1G۪iԮbkxgz$!w1hANX gq 6zCX\v \V;b1&2>{n6]\g6B\aG4sh;mڭn3{>~/.IJs|t.ېd=~t|$a(IU }A?<Z!_MP{|L\WN 2]8V؎Me&sᤑ=ؖS-c86Op+ oVNx%5w)!0҉[4TMۂ[0ht !;;4;hwjCrϷsgtm`[{+-gIoǝob3<*uu{/稷4:cQ k ֌sΦt=ٻio7g4heU yI0:)J'C \پ ~۬ v69k`"a̶Li0sL].*UqT2l J}3%F=oHirZrϱE1 . eL \K18:>|hlZwYѕTLt'$u/ +g!ʻ>ivgC;+v>@W61yZ+LRy< մO|b q*b[fKxKZdR.v6t[;&EdX/]jun1c*+C:xK㠒ۅI/ "_+ yxy=-Oא}Q}:M ^mMllwl|~By_µ< O_4u2ew˝~ Ƭ=u?=m#ksU⭁r\Mn*V 0F݃L x5dzawN&y:;9Akqe  yؚO?f d IlRa١!} Z']F$7NeaP}9mElc`&} yֆt+e|g¯V$kVT EØ[ ǫ,Z3O<-w? I|lfپ|Wvil=nsjsHt B)Gݯ OV=ӘHH5ǐ.Z6t|Ж"ΤLV},\k? xvV YKp~v;ee N?O9.:vbVoM+K ۦ}m7[τg{ot/#)s;Ln ٘9Y!xt}f(\Sy!âqZ͕Aސ+ebLO6aؔoQ<5ۆޣFw A#t >{8ReӫIkp.\`K_9eEj[h&lgly ҳŖsx / ,le[ _ NGs5hR 6,=}.ŪI'$0bS9"pS-$J+{Fnp lw6JS*ogp%+d|n?](bcLɠtC6=y^鈃ˆoJ[9%eF9zEja}v w3>+fw[Ó]ds͕]x)ES`}ض< emϟ?՞Fw6ws?V 䕅`L/8/ ݋u%u]<٤?1j`|~j1 ցcErjMVރ 6(,9-y(  ޒ67~YtիV|Јe+9Oꛌ fS>%s{) ؤ vGN׈>+c89'cXY{,Wyh #&z7jOFn[Wg҃~)"ƜڮO|O&{gxMnjl ~F\|% dlE|بf51`&iS3Bjbr ڝf?Uٙ\vO [Έ(OՖ 'i.tVb"DVqCue ծ S}b[-I4G>p*f] "`lŚɭm|>b 4d|DŽֵ?Nl*ܾRǫurN!l&>{qA@fSNTVq*s@"1/wo`\JjwuΪ= vZƽ3Ƈ)]D:RSLQ-ڸ[wo*7diO[[OIڳvۑ){y&?BnR{ :V6Kb[x<>}_Kj`G7|P#t*Zן1t] m{^btN IDATykΙHWpNs@r"['˕rOmi.;X ?^^cvVN^8K؟lrHZڹ0L 29+ 6lAgx<3xh҇17x7Ӡ=_-=h_3{jlEZT+sJj!V>rg*7j;'6b#Od?U/;C&Omj&&"n{A'9/AiOyv~e ;'[򁞝Ke śօT3>ԜƏqsD?H 4rMS]/U3R'uvf)0~>L1FT2}  (Zz+ۻ&Nsc,$n`\Ψ`4JW]@11UAg1.F X\*^sϝBɝ/O6?ap4"fdG`AM䝾$0/ Kr3%W?WL26AL%B;f3;$Wbn<.,JE]%HٹժZb;ο͞{m[yЊrÂY"pv$X.cG3_=cZFL ̥N]91a߾K~_&2=r"[1!z !{7n迵hPC;_2Cۅ` d[Yr^N-fN [ wjgXĄMa)&9qdzL:Й ?Th,icsq?'~m[],Rar qjXbu~adS,{ȟsiH=PEե9ȁΧ0 ^' E/x ^>V9KB6ؖEN#k>Sk飨ˈbwyb;od\ EwU@.fڱ)#E(>ՋrEh!?da vV-}1 N_biK}}ӢL dle+#"X8Kپ(}\1+cmmJBϿ+ֿ,SMX;&Ӿlom=zTX|llRsXK 9%~] _:a g9AesϋHGWGeh+#-I%MYB8] In|i֫9z:0I]);?S\sY Lu*,(e1_T#.0@MEwvTui2O7︧ULxsTz5ih v+tb4 jlWjI z,1VqZX\/ ",xdA>u~#-*?GcVmM[ydD^ Ƣn1Oi5܌`q\N^lTz2S8 H#)2ʙ_F1y93)pVʕ6c.D{<`jjn?wbtC.n :3ܖ/rו4Xt|jK[N90^b̻9+?۟y|/UwۭSbm# jMaOs俤 %w9܁{<)X;wr 5%:^Q >6V7yXfm'l2:lWh*Lj^[Ҫ멜l%HXic;闎g}QLh,ŊBm<Wo: REbw9̺ᷖͭػ޶͕Ї**AL y%coinkgZq5^-^} b,@-qe1bbNXrI&£ex}` W1s{}fyv_&Db[Ե@#WGz+-{yaaai,KWy;9]oh FO+\z&0KVB[]mlmtguz{A.ʗ#dUи\nckafceEu3=ܨʯd~rm*t2@a.ݎgIv&+2 pΝG%]Os{JT2u' Qϵ&'`ƪW=TdlcZkSzV?<ʢ%S=*Ѧހ=1cGg;6{9ٽ}iSa,~b$E m XnUf>AB2zCַC$"wb}xEIXHLc][>xGT֯ͽ"#=){جKc5qK@}Y{|9Tvep\x +umaoq F27n =. h Pᩘ_+`jذh7+ck6R@պYa-ʊ|^Obߘ¹Տt;e?Ŗ԰hhx C+'?+>4n-6[O> +V$NT_2=!X@b Ctߚ>"_F p7G\/Ɨ niG!Eg>NfKPL9εK#A%~a5 ڤϲ_~LOKvu|%TA6!é6V2 ٭>>9&X-pЗ泜Bt{1щ5H˺.G);+5>?LϹSguǛz>hOLߍJc8bL\Bǂh۽NٿK] J-hQ7dW-1 LWW?8kwXM^0wuz9;MЛmw?c+WW)$8*3}dH9-L(u^)c.sc?y7i<.j׋B89Y\qֺjk ]aym#>N\F"v}'4) ~.%4pJpf8#@}lls&&|'cZڸdke'']uZKh>pO?9?X_LkjaXa¶be}&6ؖr{3_ZV)\s67sёH7_8,ql](؇H&FI$tBeڹ>U{s2^Bnbs*fLV6@ˎU3_4lBetbg{X;vް4Zjɸ .[gUCұ{N𖥈n=_Ú޹>j|^rfb]-+4u,1߭N0q￧xm!qZg5Ы2M׿j @X<Fec LgsvEhbma.o CFDZݛŘ\VhYRa%/^BgCĘ8K˱ t-T:kVq+gq[+/O/ #hmF\BW)Db,.~,^Xvzl0>߷ЍWc}:$Of96UVn'ՁI8fkr{y)rD"R?z >jx?ߘBy^JR\*­իpRycsҜ$flMLb8䱼!Xa&ʁեLUP-?aI?8ҦMsߥv*5AbuҦ݆$^+L'aNK|ъ&1M:%2]?_ 4Rr#ZHKV+A|bbp{IߌE>7bvT,ydŤ]r7buitT.1u!^̭ džLq;WdlkLbspF3i~|<;kwSKez ;<61#ʧQIK]233|1"tbp"b)`г+ IDAT09y+JѠ{-+CEcQ1:.s^%l=WL aDrǪjٯ,v'hb\VԶ'CB>xH,3-^Т/l4nmsCݫ8 !DRL $NT58ٙxN~G2",sbI3 *xΖ܃\$7(y μn 4z!G8+Ě׸l[ƓӶB$+brV+-d.Fy7Kako| O1;-'zӼdyWf"1v\'9?B917?8Ϙ4i]tyNZ#D 32ɘ醦ڏ<؅Uב!2O|!^gc챻AԜ|-fm#Cct1p_x:ngz>W}b s.V鿛s_Z>zgF_g/?N ~.y=-MYصa.'9 _ ldWyZ |a|2@Y,:dJi;xɟs'@ΫY 7]_7cٹ\ND屖Qx+d#LjrX}a U VX;n?ES(uDϩ%zar]OnOMs<?io}莾 1I.1&09)ϧ.I}J^+?d< &!&y^-jBrZnbj{{y-a0ݘ8uWuuJ-.wZr1 g HU_{Ah4jGu}\HXccl](-E*Ίsd%uRʔyjA#uz].Ns XscMم>\dua9gϐ 8h^wJ})Ozjw_,O*싶3~J ޔ+p8W*wXq-λ8k;XUQ-Cr/.Aw]ϕBh{ٟK29'G??nT5V ?_ԍ/y_zלu,wr[qG='~ $+6o_A|`5FRfik Y [&lFVζxZتZa(d.Fײոɒ8Զ2Tg G>䡝"PedbH+asl )OBrf:vd܌mƉkǔ hcCٓ-S. >I9s,W,֏1!dgvɹYDZ@t0?sz|>E=|I|]}|g-S~ʙJ|J_UA;,.iqx.\*²]}ڿ{u^?fe=ͤZ޾d,"یMفLnő\}X,r»qu&ogc xȩ+;;Xς=4?ju}4Vm>On{>m.wCPJҬ?P4>8_?E];_Rq,lɻ\᫢Vf~^z/䓘,PuMed q8nki!}ԣ椟Jj4LϏb0(rky9?x7[gc[rq^"3i]ϞW"@0 3}"?t`[E+bpQVZ͖MVYJsR' c} |_k6U_VЗ^|F~[' + 0sM4#VXG[O"vY~]['r}}L)H9*t|&&UO,%I`ŨLsС4ai4D_:3Y|1O8 :/aβO1+҆AU\т/?XN9_?O`7ؘ}z>ڽBrі\iqEa* 0 )w %O ^2|'ӘeLY\;/7.x^h;h OO8u6MΛx7 ߩECۅ}laZe&>11&Xvq}P٤燺vzq Yⷖ+'1֊zN~/ቸ,7Lcgi3J+갊$qt$ԗEnxvOOgEW"8v(2|Ԑ!p1w"v_!lfE&~wE?Pu.;U~8u(̈xg"ؑ< V–O鏟ガb̀-R6[!<ȳ#M:lbn&}Rlևusn_D؎dp3LRI?aFy^݇߉$ Y q _$lL;0k^#/EٖK26OZ2zl+ccŸy峒\+vWrYAj:'~ļjq@ dqbq҇g;syO 'ft$c"fo*] :.>%϶v*> (u& x "nbuYҭ"p' ;VfnSmx쇴xW,Hɹ,Nj~f+,ՠG?B}Q (/5LWT1)<{sqs%bFzHB)MuS1A덳sT׌J<|+^nOB͖Lg?enU\IWJ`?m {n;<טXVĜ|;zEy/?_fz%Z@#+^_4/{n,/ğ&Zt%sv Ɨ&& ;h3qWWG߳Ǥ|!e }lO埆쳉tڑ[ȿz3KOciرi-ow|W0Z.ΉBS\W0r,D8/]ji6^dOV"N 6QZU?'u<%=nG{?-﬍c x6ujm`ɹ_I R@܏xS+h+caefB!bvEknqRɢ8i9i'?{_/u>%sG_B t'wN&[⫰%0:)zI(% /G1[=/O({~rݰ}jyp4Yi[[>M54d>Crc frL[-[NmNߕĦmwn&&70Juw±7Ys*~v-&uöV.=s]|}񳲹~l vPS?"~RvZlstS f{-AYku|!}S(:L[{m|nV]> ?&QBy̽=ـoc5(lC |qŊ^}~Sk)zՍg{n_e4nDs]W0ws11)KW25D#n|uV=]ܗ4E2f$815v6}?R쎬 |)K ْC%j`îhGqWϿ[B Gȹs\ũΊ"左t~B{ :h'q7mg =o޾n"Hs_L|saEʲK'k&m++bY.2[:|sC]/ÿ|wO0}~MXoh:ܒQDvAgxgOZ^0z7HW,C{X $5umG,,dz |BWrBgoҦV+sX,ml``(9} =5mmD3GP=ŖAHnFUj_O:2f1zBgF -Ǩ#/`RLVo]Ź u^o2Ml1뭩Y\8ly=jcn _.9 X('&+z2GQP1^z0 I-\W8lc$N2u|[p ¿=$ƭ,_l :_?D"zs1 [onRGTG27֞~Fr;moo ]qLp$870/b:hy)삽wva`oNg߯G(w3*Fh:铟 ~|Z>>{wǙu{xa[6T5{-}[UlQlO.˦ob݉zw}o5sMNfwjC_]aZto s6x5^l枭/؜cd9/Mk֞v@5]9S(PM% _]ۊ??\)\^նߴZZ+7gJc^V5x3a5 9K\Sqd@m>b0{V%^&n7Y$W\qP!EIֲc1/r-f@M\g&Нi!aeaU3ݵNyxyVLj3Xe>hI/-A]ڞ~O$&?:/1ªܰ8~,LsxV,okQG4OiL>m4~X`&$9Tn 9~y}X#$LZ;`)Xs28j+3,Lx@s}#n`j:!3YmM;]oX`%#ڱKw^<C)ӱ3;!:Z8ƺ~>d0q\ :|iӲiɾѢ8ʱ=9 (6m>! Wӑ_Kg[F^ |ermxʟrc|m)n0$s_pG_mNj7y]?"K 7- Ev΋^L F uS4#`2){'x~sPn軄+!td{n)x阁UWt25^[ɈVh0M`wr5:hG7-UF峲 Y; إc4~@v}7qN&g/Ѡ٠І8,I sN0BfW_fM=rI@E 3?ZŘB+rw K=2?S LZcpr(P#K2w &|ӓ>߭|N8 ;sWS]tJC7ꬠ稷 f>:6ku~%]7+w؝4x V'x_ʦyd[ q;ol%1nV,O>iUqi3%S1pN!V(lmWܿ*St?=e}܎3{c~8ktv+EBN{+li?HV=흨euEʏl=4|_ U!،PE<вϿ!H&bj2-R{0N.\KW {3(8mpw0ί}ŏ0X_/g\Dw=3}3x]_չ;> ?pIX\G7. +jIf8)r{%%qbVӅ킲5+"yOƨ?薨}]j#P| Pne+ )R?y:Bpbjn.j>ƦT8m3cX8]WB.$Aqe6!%8MpLmُ@6!b !cc/4y掋y4C-W l:`ҡ)~ol]f]`W,W/]XT:7?G9[ܚ:~+?,ONGeO]Q垭2YdDA dd,`9*v->sGk&_ya»Ʃ,Fwh7v,]DEI,b޴#hGuNF8?D` G(.#|Ր%$EDyQwCKǪtaxG26߮N+u\y V0QcQK~j^]>_aS2yw"^UF f[x֜oniXE>~(k@;_} z2U6BKq2H֓1ö&zmAٶ9jYxM~1v=Vڇ}YS/LOc>ph ͜]U+nmРL@iӆvA:;D%3,OCwBOyCWl /?SO; ̕L6}-2Ju(nܱ0\oBp7MCΚAtxЭ6]s~kj ?_/3n,tM/(Ҹ@?qO?_y>pWho_m=H$WR )(a'Gx MJ劦e큕lz{]v|fL w!]5fK2 ܖPN +>-2EP+b 6Z|p`|Yk,peȌ]ݰ%4Iba%A۵۠sw3 = @C/ϳ'SO2>Pq `F=Y0[c\Y|f[qBwZŋ++ ';4hP."g8YvԹY;!.fJ=7Jrw~wKۑ?$GxIcMWWֶ5y(Kdtqz7-ԦUĻvG1{0]?Hcmݯ~Ms/ia<)q aa? 1)ldɻ<}u5zc9OeFX?Dަ,7Εռ^/&;&4nk[D?PGe[ {~PN/ys_0Lu/$ ȳ|,9__BC^B;/|ipx a~<\[E;q$?ׯPЋ~0DZSqdד_ U OeG+~; y((\sJhTKn(fQ>7|i.NK8A[oC* 7UgZ:i{~E|{xU':ՊEtv/Gfvܦ?OVVAZc5ID<8&QQ3~O" {< 6cQV}&4t_/bΎ_V *A|Gv,"d3R3&z^ҳeIJX%4e>.%5tvb*~skga`.$&qC kÖY/ ]?|+0/{-Ir)%$8s?lnI[DJ|;"nx:L 1F' 4+mrI!5qo*p0.rǵ3="N8, 8/V܅<Ĝ1d }L%XwL o4_{z|M/>^1\AB>rYZd3h7)}=n3H3bruk!74^\ې˞5O1}Y@L^NQ{f_?G^VV}ԧkMNϙDn1Mo;`-7qFgm9 !=f\ی|;L$O;Ynm|-w6cWRN_3aMG?Okx#McucaA/En5AOr[d___߇/ B{6*wK\[v#V[F&%'ض0$MzŘշ\2.l`>PLX@4x>/d_bm# ;*>d-sg lvs;+/^y/XaD} [H[oBI}mq/806,- ӂ(OD/d[Z-{4,ͧVg\~ obkdg @y؛?o/M ϸƁ5{yfLm[*O,u' m͢dPݎ\azcw~fwv w:<s@jl_nUE{Q< ͐f4P}Om@Lm8wwhwlڑjo5fu -dq({xÄ.z8q|{mK,{[77q &wcB);}w v,RKwqW Ƴ8B1wYs˦9i9h@kW/gQ۰7Gi|io??l\s!x˓mn -h//dWr{m0/&yb1П/\@^ }sҞ!yľy_Z7 \/&+oLbS|Ogүg(&;xޤЧ)uS}ѽ JJF8q^1-q=n9w396[,.}N!ųl$wߒĐn =rx χ_^LۺPtvagRcaF_ OcTR{KrłB0̚^Uo7pgs˸hVeT~aDQxR`8`u, gGE'ae{G0S~]įc@b4 }}׵C$YdU>[AX6o~`wv}j<.G\v|lLF]}(pRYc&e)#ڌ]'ЄY )|v!%g?ܖdĴj5`m8kh$vb/7Yq$פp;R(XBVx BQMsN`B *0:x#4rIx!m]C$mh'R*ŽkQ*ëIPd_y. ;}ᔽ΢Mך>oNb\S>ngFB2ܝnCn{ B^&K$SyxkV]%Oß]]^~v'>砿yWb.ůySΞNWm[rcwlB ,gZb/<6H4INF!1žp/kUD5b5?]KaU_;$=З>[V UͲY0L`,{Qh^&6 :OĈv▻F:ؐbe(:Cǒc"Fĵm&gpP+|kpZe`ZĢ3t\ nhpe+ĈEuz!+<["nQ^?|<ͿrF/8>#2+ _tڼ|ʞ\6aNI'S38=d-λ*\J/L\bSbB+Wf'b2..]gPbpE7<naP V[>dKb\i#g1L(nBV]~f>Θ&ү+q(~<l.v29pU,yfwco!ž3wd?{MZ/s雍eo:{Ɲps㽠ڔ'sjoɹ훎BCgs [=uٳu$} ^^@uߟ& 鲸k&|~(+h[9 !eWyKkr\ ):_$+/X˘YYz5儁lcA9wdq1 zYW2M9xx0CN`fBIb[S_Tm1+ 75 >xkQn#9w} P\v|޻wc'8I\|j;Aoh28ee|Ž[1sWߞ̕{UvSt+ Gjhq|2/ ?_ b1>5g/pֶkxx;0=ގ;yR_{䯮6Nd|[y6-t =϶?4\.^h**CY{ow[h =]ֽeåz#:z[3׶c|m{\ǿjpC1_x}{i7U:c@qݖb=w>G߻䈾&Ajhw~o,Bc(7#Y>%sWٱiޯaWVA+vxqb-X^[kGZ"1no m^a@C W~l;gE 0| B ܋5 84.g0b 0}ZP'?l34|^^,:&[.A_蘭-|GRdE p&6Mej OaNYt A*/܉\xelhvft/+lڐ7ϓSʔ-$d}ѳmU.h4&W[Bw?\FS,V-m}4 [|\{OT9e45 +ݏi籟k)]Y-nιg>ϏJv<]Dݐfߑ+~Zm*HlbVOi쇯{|<$B9;{YzU bO#/^s},5>BWY 6δ:k]O@|a%c&^˳Ga;{ ϐ-y\6kMB3=SXdWiE&3/(sL` sO&D}̇VcUÁCYgq\4q.u_-px}Y}lm:}0O] *Gw,oQlh:<.m|տ(pJ>C+ING%Nx\:\„~~zjOLuV7ζG~Is?c`u?,Õ{}S 3VK5ZH}8Ew=|տ2lzVm;-0d6c^=taT߭s ,m(oBF>umI :c9mEchcpe{3׽"(R%c~/nW!yqa>='ȰlKOA⟀z>m}: 3b xؼPME#?.xQ>OdB ~e휏q/7$[>Et"ҀU^>_?1T>4~:= +Ur^H6N?ΣL')Ԣ#'3} (0P=ϼ?(PFy6bYGhz* /~%0>u|񷧉1~G݄yo\Κ$Kg5 0Ld\3?jSH|6/;`8/XBUԥh'b OpLsTB xO7;Ap潎v>[ƀ#sƀٙ-w}6%AgmKۀFpsWd/R ˜qb)69HQRkgZsЋM?"ILΣC7O_Xp8}yz)/v ⽾/s-Wt}D|P|IsJ7u|dX8Rۑ#=X)]ݼ-]g劥Biei{EV៷FIg#3?^2Hܵr}^R>L %c'jAd;C3ضƵ8?S\]j =VN}1uf"EbqW"&Y]5'{~wjkGnUGr˟]x=Ӄ]39f0}u tWqtio<bxs9fae 8/ [WϾ]V-Q(_'Je=v q Ouض+ȃI/?#蓓l|9,_ҊܗwIoN*~ouM,Msj>]i11aEXV3doTdcLf j;1_/o6t±Ė}ܑ9q@vӳz +(pIOZwB Ny(pɀ#'ڗw}a;K'pfc<~)qdr-ǷbEl3ũ6?s`WLYc޶WF}@eXo{W "}Ok髽F/wnM~skg]rX2|΍gv2|~0zi:G݋Isa_N>ry[IG~gcvSCb|ĖɭVbÁ@\"y~/fP}EHfk|^KDI j4X/,8ľПl5*zGcbIoՄn  ϡ}:A >k,ϡlwMhk5n`"$ђ3OJzpzz^$N0*zۅf.ٕà0|Ɵ@*`+:./5lxZ%8Aխ`:;Fq?;&\)]:Z#.KV? lG3gcz|<\iwO%XXmGd3ǯɺ壷ZmMgǿ}U<׹r.nweͲ< ,.C{:v^C~^tp]s B>] <|!b-K dfڹ _0B ` _l o'/${u$T3u3j3ΉCRdkD_46y^ :qa-/⿵%4}ʶ566}0GJZM6h -Z\b2nzCu,,gvyxng:ɞ6w+ޣg~d+z%3S+&L  չY=P,<\+sqC3 z)VN%GLlԭ(Jb1×ex ζM?/.:eEUnm'x#0xnm.Tŭs8ַ%E*|fth|קoc)dg!d~pUG3N""פ yMݳ}wOn%i tW}]_/%t~̩CwE|n 殛c42tgWl({rvNsFp][ߋmls{|Ɍ2{+k%,5Fl7wh"6#_l#g; 5x't_6jL^(eyn||\^LFC0O˹3XxĝE,五p`aRvn}O17fu„MpJlk'ςٱ]cIjua֖TW퉫N_RPʹy01 P3}sz/-يe|Ta29}u:Qg)k}^4x6|a[LB"#a̛s׳|ۣc[`Hȿ• yLŠ/̰zU L\wn:)8Y ̄~X_Uxnb3IrJR } -IhAM0S,anPvhh%āT:wPOLݲ$Dյ_ٻkGwUGhjRj1d8\$ݮ3j|c+zcOf_?B|)ݱ>v$dOIŞUoۖ&=W8i|֪sN]EȮnyX%\mݗp}19]GNa|.Z3qVCuEb2;>>Ko1D1vUwAa~ukQI^Bэ󡷴rlUvLq!t峫wz:;m3AtU#.ݹKݍ!EBwZ7턽{h$mT/XTl₝Ĭ/gjQXIwWa*M )U]ɬydp*(L"~PwGUR><ZF?4tmzHE1w xgX ۧ2JR~xne=ޕbM5|O-ՙhsɒ^݈Cq#MyWO嶎9Ww˝.N]&yҴ+D0ycm][_ 9 :K lnF>#TlZd [ :;HDpɬc+Ymٸ+~ƥe]_^fLe/RW%Loα(LTU 8D؄yt#򪤧Qҳ㛥_Q ea,&WrxS S6 8i8߸6bLo!Oa)U?e , 3Y?95;S۵9gy+vz<tCyMӢZZ Nkp,t5%ƊX ]Stb׵k*mV/ &mUa-vl*Qօo1), GGEa}~i>*xTUl0ɓli1SבO$wθ^FuxEG9bnVm<ʟ0qd|~ ȑ2X;~]"__;yee:姦*xk%ϽOjr$.o-+W%{;c CKH?nˇGP֨w hv PlB.a%xLjHpk@_wV"^> >Q:ąFWG8* `A76:-?ՐE>f]ó fum(3I!sPќz0ewH>'H u) CVViC9ڐ,JUqbeCw#e*=L>ӗG]u!:ǖGm{ e%ٴ|Sn; .wZ>lcx痕n{I;wӳbNe.Be  0Jw|U^[Wt`~X8ebpL:IzGmTq_ V/*>ЕcjLzg}uZ;4&k؂$Ldz uJ9Xhb# ?X|jio7/71ň[I@-<3Hݮ͉]&0W(ŏ]|3<ڿ_W҉IW|lϡv6R;y#įv%[{=nrpff#~qUI+G֏.9IvG?gwߋky=]99^sQ3ĥP4ITI]btZO;yxtŏ:ަ'a'r2 iO#g Zф,$rX?x]$gÎi@[:yjԢp=VA_T<8]_qۄc71jGrTξc==3۰Sٕ@#˹`e>C'.ycp*WOʛa-ilm8zc)bOE2y^.[WՙLpwvמIos8 6Xha%!MN4 x0@䩆*m2%~u[zwq<ݷHJ;/?Zˬ[M:q L (|nx'Idy/cmI] 75~; Fe? gz}/vE! tcpW]Ll,~HطjC22Z3"$9&u#f=rre#i*d=oU$)#J[gFXr]z,֐7 Id= H!d^H۝2A8İ[&|+$;a Z:xbZ] BmcfmluhKM־X4S*ҶEzQNycxqZ"'rgsm:RfarV~KolsL\"d|dϬ*Z'uz.'x* d:-u7ݲ{?qgR< IDATdˎ:LpeJ>,͛\QǝWvT=<ZW\On`a7,/γ '4HN+DcWk`߇`V~"%%+cLoeb7LR>cQrx)$_M9zJ-]?]hT!_+H^MMohLx^+IJG\k961EfKLVcc[ H7'l15c#Ō~*6Mn.]ǿ;!K[4;ԙ46>2 3`㹞U,,A"ЙΦ O Z`v^+VG>=2[3`sS?E64upI`S%pX$\b*ch%najj[iYvѺfG_~M`Py4t޷.R&hY-DџO2brCZMT14ضOuW/`|&_3?DܶD ]=ZYLeD؜-ڗa!>.<{r5/g;evҽlΝBNS1CJB'7QE0k2LndJ~RnϹ]b:5i&BCS~II856._X{TѶN'YQG֍u,5~ڄ*:lY;*u;x ~`/܉'L=KҎճ̔g`w-/,ۢ}"0, xt`e蔉lF4<^V]>?_ȒYl,elY~Zo& `u滘-2n8ff; hk̕| Q_ ~!jJt6icôUu^c׋pIV6&Ϗ4.)Q=k@]GsēČmLr,> w~4E"]<##?AG}6>1g봏Y>9Ic?u!J$|#(GlW>O+ #e_s{ g\rR,|෫~^WucJTij(\AX-:4 ,xٲږ6kv;ؾ]7drIl\~On̛-K0aןk낗|'Bﬣߑ~pmL7>W`%e%eΚqWr.w/Dlr߷ko+VS佲Њ&0??IXUXk|p\?3jUwm}_]{\t_ všPv,oڊyFzY9KX",3^)o [= (] 1wٿYZп|с\%)K]'f-zPv):I|zH\՞boJ~WŦvHkz޾(hvԸ?!Uۊ&o GdX&A>c}Cc:༟)`|b2`0[]Ȏ1Y_-oظ{{W '&sbn9(h ]nG bޕя7En2!h&vvS ݼ]e X:,~ցI`5 yɳc]Ya4)hܑ&vyT{n{KHWZ cp U{?]*>m5 aXy]K&&BC=jyꃵOvx]$6+^A7UFarz}f :<$v/ؽG<%%?ANa8cC)|ۂ sscNY2v?k#W^|k^V~gYo?r~2q^%_=u7ݕطl~fxM+oe`BĻǂ5@zq ]WCooWWlR',JJ5?bMƚWS ᝲ3ؽU3z> dl3ӛ&h=R`{Lx ͧ#Pwt3htأ48 Kx}G*Q~=H<߲t`,]mēgsNcbOϼ/9o(y.%x'6KwV̕'z,-Q h9&^T1jބv?DsxQCE;1XcڑHѾiԿ~a4uʇ_}鳩],wL6]H˭:Srx]Zi sJ&#ዱ^/bi`<{o+u5a[+Q|=-\ ]=>_s=+w5O|O^wۡ-mΗvl0_(voyyvܓ~r+f-Gk 叺3f.K l|"׷WvfD}~ E:J>Iu>7ck&w/pޱβ +Y0 J^Fnp YBQǯ )xrXׯ^q46oOsUSwfv}i'9">$橣]S7ى+U#iłmvp;y9^SO3ЂmTW):6].b˵13# 6da3~O.n+0M㤾v_VB'  l"SUY|F9 %,Fݑ;&d\{w3y-~-ҷwsk%XB2pVfXQ: _,}~^bzLN:l5X_ s}($xo UG~#&zsVFަ\ س}OO$7䎩t08oߴ)A=e'h㕿Xc>ѽhfz)>%}gr>ۏS/ νw%')buSx 9iY~l;ZvoZ 9|汀PɷSz(Kd&'Ev)JyTBoX#XUTAkkj37/5&&X,Wls# ,Rc~ng'!ΰ_ 1Lc>v'ALוzUo1O'jv]Z8j:Mt+lS |K`;޿?Na;ugzc$qByoޅ! $vDB$z쿁zj(t(:)Е?j3gu^I/aG r& vbWfA#ꅊ.}8P .ڳNJ~kRiTnmN,00?2;{%6fqn8![b!F*SoCLd(( ^F'aDbC $ ,M}{X(5ᵟ%McL0x^D/N+1_TFkpCx2+^;lߩ,oh ^dIR4X^}|h5ocld b bæJ'26: $&_~؟Cmgy@ccm7~yq'$q>QɐW^KyPXOp\ ㎳bGU\$lq֭+JE?~;<oyC"%[zagB^gћy.ӟN1ȩ?%Oy퐚 +HK3>vXɿ#SFcNWkoc͟[_/?:Rzch+u'I" D ;]u,ěܹJUN&\hߥ._5,Dܪn/(WbBx7bqx<^YL GAdMkeJ檋:;@zPVxVؼ=#:Vw [>mt ~o30OJ* 0U;ܿz XFpՔi P8r3+utj2o;wq9 '+9l_"KVKʗ 37. {K΅R";8'Sqp,![[<˷m>֪ѠeVƋcK-i:]\*{>r2 tA(J`A{t{e7oV]ǂOzC:Ă[}Ȼ1(?cʔؤlS<'*G1_NO󻣪E5^DZYZ&Cb†^9?L鐩|c1O:PРd⑹=ݷf.yԂ Bfwrʦfy5}v'/M0N]zncS65h b$MOe#ǵ.=m߼SQCΕQkyv`Q环ƙИq|Nj:Q 1S/{bV/w.咧'7[4]2[8KoJocC.nwa%Ǫ3vlr㗋ٱ}[>|2O}C-YrpJo/Ozv|l>|=Rly¦,E'1E6}.' La`F0.mzM`3s[v봸R”/Ͷӿ 5BX N y2)vJy*;fwM ƞn`9v.~Pxv7k #G,CV_9 K|5Vtñ;JðlFSgV4e62m)g>`s\ѱlj7s3ٮb*aix`Vqǧ9앲&xp53ʐI5OSrKI-m}ҝykYH4ϋ'ؘoY*%OlQ%ktbXyc~2|jc .UB SOTM)̀G9IyL06ﶭ[wJ5@J{ŽhO`Z\7Tf>[0<3/̝:JLo=$H,nO`#k}5K&z ݲFF[}9sQ$X: yڇ1tvDu`k[?(6XО:Aǖ|6u5q 6[~v*U G$ǂ㇛(B!WYtՠx SB<&4m-dzIL/Lꚁe?Oԉ@qb1?hMVzI:)ߌv6tr^sݛxS8K(.GlFG5g'~QlM1X=@#~O&2';CG}}Jx[}BvdoJSʂIh`isEC~F'.E]u|^GDв32_&̑L >"w|2gȳw2iV#esbo.НQ>]p+)6᜽O ܐqVA鋉t)a ;Ʃ1Jg naKCucFl[8gn;GN~c4;o=~gߞ۞㹩Do&r9fow^0<|~I\Peߩ{YLۍϋX#Y̹L/a/&]8h3r3ޔyEk%`Qd";~&@L*e5rL: LgxtC.:8wv+K~FMr>nl4:-Y"X~#09Yb LuAȝ#5|BhѻSCEǕVciNdݟR t\[ z'[Do^c>["kd9 >9d"&MyǧwKf5م r0v] yҡ5Ϸ >*66>= Yc]CZSgfO.|-\CXԨBiG=@U mt+D=12JA|r|22?닺glLa7hxa4sSOdcS_LxHtlgT^aaѧMщڹc+t zc O1n%p;%n6Ɠo;%߰_XoљtqLٙw;/zvҹQAqd\nO~ٿICQv&vh`w P}ug+XMm+Daj(&!Txpc[S[wV<78bJxl22n=9<$uPb Z#K2A[ ߈N_ͨ{Ȗ>mU@# O+ʧN^a~D2K =G :Ʒ*v!1eEšb1QHinjS쿞k߰}b/{R{d 1 IDATl%RSș*_kqp\ֳR=~:ߜm6'=a:*vp>l8{I\elHۮV }7jhw߱؟.gfwŪR]}KegΉV3b{z36Ptתԥ;BV_HJXgrˉgS\kuoAMܻvQ˹̒Y~Fwæ{Rǧc-_cܲ怜Qzfg'3Ǵ(ޟ/=2F|X2'8lt?\lo{qzY2^o~CZ)*;\I;Y\ cT90K]n`QƑMCbdkGmaڳA;Gg#!+6l&%hLd$e iHCfE @l.*3]q!ޮbx7;XQjl$%"G(Ε$˔f|A<:o6XIg}%~K^F ~Kp]ёmNah"7#Ge6Mhzhb/UϽ?h8C$ (>/ <4,@eĀ^ji5LB^H;tx5m:3+vvrY+&zLT i5[b&euwp\vu%Bxq1t l|bT@sF\^T_c4ؚxv0lhid"&  ͆m?v G_F2 X` _;XvQf c-M8#o]Y71!Ԟcd( \Q|:7;/ZY+9-|aoY\5taәr?Y"( \nCq [K~oCO%BC>nN2>j7pH&Uq3txw,ia &׎3A̭lxHੂP5 > j'ԅ}";/Yb8-#ZDv1o&} abd:mbԬGU|`x@"$&Hк;<ZIy$B~qMͽį]t2ZAj{r{Z- X=#!l!ӺxKbz5;maE^+ iv$ʱtk;E7Z:RMC>9~\ׇ6zK:X2ª..%*? 6 ёM{NG..B{n&deȶ#I;k(CH cGEΎZgTW%gɛ~da ؔ>B$eǭ["9榎O7:{E;\Bn`c$c wJbm zg?:m|Im!vi6YMqW6į! Y!k}TJSxgvxiNq@kc?Z$Ui(lj&v IwXBl/'-ZluD{Kž-y k)񚁻c>ĪV Z^@Y-9Frm1#TQIȣ2OR! 좍kCȒqڙ@mjugEt{yK `a'= cThL8q?ԹᲹV~u%FmC@)Otقۺ2z#/^o˻ͮoؑkp1m*Wƈ\s{{9XkIݶVc l!3ێrm7/SS~ΑߤvU1Lz!6>>`s~f<}>dǺJqڮ2u)lR'ݬA/(p 7 51+O}؞T5nC:jGWЩ%s:5rb)GvhM:\,PՄU<]2R.p{s _uؒSR•/SСQBOv|L|+9QӺS}A [xv>S㛭&lmgy/T/Y+ǬXa.*;m vݤ}@?tw ;K!~+$Ѿڹ˒,x ?F^>wmih(W'k8+8:p Y=j5:e}kyb(k O_n?lB86ͳ㿨(] 0e["BX}d=qC-4NozVy½+6ؑ}꿲,L|?~2'W;)7Y5ḞS˞sotn"g ޵ByϿZV;&>'Z W ?cX1 i}' W ecKD65ngSn)C ڷa:Fَ&0ȎG .WN PŋW"O)%̷G~pKBIA *y&yLE~t>~;A53kBdڔ%饏]yOpZսebTcbv @WM%u76]̷%L'_L*۶$NאWYߓ5lܑxV.JSC:ss^Y"(1땞uzL"]#F&b&w XGNKEgAh $Yp OP_@;&u~Sߪkxm*jl3dIފxzȝsh)} ^K>F8㨰N@bM}}c[ vCKxqm6D0.U}; Bxk!_bA_L96%hĴ u.yWIw쬥I1n2 J۝,z5,P-ٱ(d#Mܕ+:r䠕WL&I>I'~s(hmZ&_N"jsp1:BW'yg N>H *˝N1X]fB#13K41A^A8Ay7noVp0h=LN,c!Ԙl9/4r60oY)xZ]ÂG5'_Oˇ!`j Xww!ݪ͊Vi76^H&]9V?.YXs~4OlH@ R_rؒ;y[޿[e~O~S7t=VY[+낌8)"?yǗ]h$tELw ջg>}gP_ 02Wm -^@g4{ҢF~z^2`ݹLS _ֿlw! d \L.|N `,\F[z &QVptpdr52P>`uvzך`F &g#IzKf=-8 IZ 3pXȝ֨J_y3^gzl}$/c喱5)*!1`"X:ƄagyZQ'ˎ_߷u=dzzg~S`c`lZ/>%rHa)ҧy֔ob.gcӥ1Ƹ&b&wEc McS|Rߺ6 MWX;vM-?;f2[Vlw臵K:Jq벞ӖwҋqZs):(a!O8iPD>xoJnz,O0wL2H$2\cOM,+( 8*#E[ Fs#i8^ަm~he%0 Ovr7^#6FM(zls],Y~:&ߍt6ywuVW󛬔|/g#qFqTu-c4# !՛T+Ni?F|a`um$r覱 Hf}ݬ a.y?+!Nr>ZŢC>1>~=M]ПU96n3ޮ k5vb~G[WV:4 1ci"6,w"ݲ֑;%ı no,EtG[2xR /cLMZw_cۮA\V֘瘇]y~}){(<ckg}t|gk-?(t֬Ʊcnۨ<@Ta6u2|k̮xm/ުIq4`kǹCٵ[ Rl]>PTm+:אJ ~lFc H8iB[Yz/RIH' Cݵ0k v* LRtLfC5v-'␲=تu6_f9Yow`(llr5fYc{bhʲ$wbc# 4~Jteeq&:8Yiճb)pV <YT۾x ScŞ=Xoḥ! %j^cηqvO>3=彆͛ӧ#=䱵+'5x7l|6N( kNjCv32n8(_."6 :넡vlޚ#&䱒8S8O۬Pkl7#G@4ߏA޽9,Z'tO;ЏwJ'^Շ~,2eB T᏿64fÿCv~@{dNSl"+ܴƩ͕oƒ0qD+iR!jnWdqc?fSvݠ:)/ܖH?r3<%b49 \.M e|e8`BZ9f'g!_cia,JwJ(tΏv:(qBl9󏽗n/ >Bڨvm^82(w7x32s΃ ִ?5<+lx4е׷ F(&]nPvׂYɱ ~nvٳ4a9#1ꁻ6.眾w|2oGj>'#O_wb5Od5z/8W(T1y2"ee }A.ypd?mܗş-񖌭ص68yR] :̷S8sƸ~].˝mGN6uw~d̅U_ G;k wj(Lke1:aTcaPf+ ,5:m'/^"à@UwO':ŋ<YyI`@,j?>rܷA^>iKlgoPχ;&CJ$I`+}h{6|8Ӹ܄^# Iy գb):ǵi]\T[GH<#ԃBI̐?q}UYk9>| ^qs=^lbxRXdw y B{W,.w B4D=k*fWcʓ Kp'Ug~=< Ti(doɐx\n1U$ԑ%?/[}ru{yR,-N)_s"ӟٙ:rA}@MOd0ڧn,Yz#xEgKri^f.csO礑1kCUԖGDcu^ ]<EoTEL;ZeĘs?lnƳE"H&>8bR-ӵ;p\p?kftul(&;_迤4l3|H؋~vƮ6 IDATheZV=1㲒awJ~alnX:A11z:g Ѹ(Wۄ|0mw^x;!-A:a5Ih;c&xL8 f'mXsv31n:vGzÌ~'1vY|} ?y,ނF|݋1)o?qmT`ǜb{\>i]PPQq M9@0Hd`|x_2 ?I }F :X\L.gn[$pS~\Kt'qddV4?'H:HJǀ "b*_0 SϏfH_uNWﯗr|7Dvܔ$8|Yōʓyrc{weC92|l[8fwsױZ6ˮ_.tEï?X3>)d=Gķc 6K1& /殩uٗ˿ # J"Xx\};0H }Db;Z#gZڒHROK]T~5yY4lWMh0TxV_92TW' 6"oDAKlFK=Ύ8F{xe4RN^lcرY-g$Mcf+ƪhKEkdz{Dv4_D^ -\o3ߕiKSCkk̑>3ȇ:qw>/񌼈Yo$!A}f1xy.M4 - xC! +?VG>Wpǧ H@j)vp]wKxdrtΰL3+&zR&F\aX9בrEtb&"l}c++ouNE o|Ƕ5dg⿀ewM/;c[rn,-\8M?ukӦrޗ>o/9Ƥ>&\$,rחoC#>J7Íս[8 "0g%ݱA62J"Yo_2a26E*>7e!Tk6v"̹A`A"z2xg$+խw2k0RTs?Uİ2#mrU#*Y[Y?x1ˀY韻_m]IPʏ ;h^hc?Ykmvy~aJ ?bnqw1bt:1mql_GxD]#F+GރdN3Y&-=P+6I1xoySk$.dEjqp=Ӊ[Sm87 OVxؒ|ΖNjćZa4^#1x]kCvv}%;(J_c"ҷ:ڮ>Y+qTISLV2_?T 73|;i=-= LtZC ôɓӓc庲S&s?C7b:-K#~~UrYρ}#9B(0ڟaZ~f.M<ԧeS|g-ynI'I^yCoJ|X]/^Hz'1*tϾlAz"ZfW)>Pv6KS &ܰA&OPF9EgT+Sϔӆ,N7/~]Uݝ@D;1Noz|PHx_׽}c#K"N̚'vVe'DZ-qE^Lz-b\=~19xWڋގ`&\ǐ A;6ClX3ŌQƶanDSH2ċmk]d|60v 2On3zCKws=.cD6PUꙆ_3/ݹ+d&-0qCIE$%NhކNcL.)-bJ<¶^&˱ݳITi?I4ӥʼn47[Y2|Gq$Qnحю $$WДA洮n踆Z&_*8ߍc3m]u_RD6>MDMr;jJ۪},9I`6/kWǙ(c&ێ wzSg+c+z9&'&'C85Mn]3Vo]9Xy*qXo}w6 d{ndktEHP&Zm68 *>HU ]n]-6՝.q,o&mٗagt·+&(|[]lrv.)2י?vRHp2+28I=V PAn].3K4\CIpqи}g  FݳP,W_#;:XKik\3qUjv v,*m "׏y7(h-@AN=ie_)]Uv2S}s]d`=.N3_qvzٝiYbƜ%("|O[tQNӭ=Offmvm0\HظXjlے.veOKUq(cAcʎZgB>XZs} w>} Ve <7vky~Rnm6&_ә^w7㇯jNߣ_Ѹӷiz:&<{{Hw-Ee[ n ֵٲKMevi9yij{#g~l>8JLo^l%PV\k%\S0GIzw&OKHb*ZixZ.&>7͝|ń]E۹SXZ~V,/%EG!2uNv` u"vPf@VGFF`{Wwg<K$Wtt#sDi=D4IJ#+۝O3k<8=/Y[ x9=E}920]^6.b;4/X{?hn'CV~}sGWJA-, 7k|3nl.a1gOJ]s0;s <==’R`_|-@fl~IVBeS.bzNDhF;n".XX5 ō3c543H_1Ί2]L}MgefFu1d:\Ix8!SnIhc0=_m qn{G,5nk}ܷG7elӻ폊ǕX6eܯ~sK7tG==l;œy/~ ,\R")|9Sn=O<LwM\\)̎ui+@,*?HM9 έ8Q&h@/RdnE'4@'IN [̇4|gx1~}t7&,3":m^jd6\_&m (9`v$'fHN{Aw ;y?giv_g`c0bw SwOli#g%yxXWd`2Z< tv(lGKl[:Թ{_1i3o'1 CS^cKHߥʠ&2n(͘~Yw.L'ceer3=ə:O"&|Nl:0e] ;:7&'5׶8<ߕy^ggmrGx..V 7uc/|LN}7^C 9bz6w. !J]eCDd7aSl҂!:&Qg׃FHJHYB+PIJzST!\%$ he6̭Z7[N Sa3W <c Ob^l ^IU6-DpZo{}8T Vu&w{-^o~uJ;ʊ{$R:r!g!CsqJ"-r2n?CȮNjhAG\w=˔O0U lNڡIW%u۠=fvkVmF G߁msp,A=X ^CrZ|w>]uNx|:j@H@8f,5O~cM:[2t=lf-)ҙj'q>a0_}^()_{BBll o^B$?Dg63ʌ }E,w45dtv)bV]'d2G%|@3MB.$ eL.2#OgM?βH<XSzb+,w]LAr63nb%NsD]η=A4 ~E~Vٷ w wE?^ж?~fxfu̲:!M0n-/GȀ̏N]aAG rXߝtu43nϋO,>c _KǾKP_.eIB^* Փ"`c|I>CkLG窖F`1GbQ=>n1L}7 -n5~!3 L8FTe?jhَ\(lycAޯʙ8=T<7qdصes8fcW>榎+=le_\a1%_oWF@\0fY}TN̓ }xybq7Ucrn'}3f| 2AmM{o⇅iJIMcxR qu Mʜ&>&wIZE~6%e6*;@EEa[$ܙ&>; VI ~E;! ϛ ONN}j,_B2@>QՁ}Ķb u,4 1ʄ$1qV&|k+ۋ@7 w6K`v#&qۄ6AAKvk>7Wt<[z$ݻYhBY19v A囟9柟ߓUʴ8N?g¡f m:|]zkA*#Paix3ILj#}W(em daX-g!# Rm[bh_5k? Λ=~_JaGdu4]qb6Hsmfm7۔L!.Lq<(c{;֚nߒf uӈZs<;IOꝝy,;-z7=LM\o;wd ~~-{7ϧ~6ovu f:8ncK^=$aP;fbZ3Jo**YHBP|pNqM,ۏ ɎaʃLJ{XGv#cc/6TM'Tχ~)P<|WG={M[`6?WUtQ-&V2-OsdBk8Bv) ѽCznEOXPI>ScaKn'e Zӿ&~.q4iՉ_)/8VY ,DF .cܝ{ m¶Qm15M$*`IV^af2 Z˫E"SL3oi dUeOU/goȯuJt8Џ P^LV>Hu-tE)3p3r\ŋ+'Ka2Vi>#PXiaZŐAR46?2(>Z*)΀rwDG~a{E[$/ϷOߒCL:x/x?k:].!sQ@B IDAT!+cs{r.sm˚=~É+k^?w^jb_`ͯ-_S|L֑`AS: &)xydHN.w +^ݮIK\yUG2ν)g0Cw2":imα>FBݦ )Q3 qO Eؑ[ "iUql\p({Z<%紫E7ӥ2+M꟱y1(\3v7NA.~4t͈1u-ҷ/͉?He8h_:ik)]ƛ_ xnm?16[/ XeL IۜXubK]N}s>SԆ7)0O'sh616~q<.`|L1 ew.:X5iVse[;C 14>?8nn蛻@p-}#hv|09QO{Zvo_<-,l,G2N,eωa%e@㊞ٕ 91h<LO 1^);S*!m?5u3JWG$ط2[Vk_m;-)6 ֝,͒~X?1El#1)h9AFNP黅ɤwݫB1A|>vRL4$Qn^WX-d`[ت|6vm./'VCX&.o2۫h?/; l ;^']uWth"|y#b˹se>g˞7e1 na_Ejm⪃/XFIBoL^}`: [6|W˔Lp3?y[ƕMl ~U>D78~6yu'3>_uߊv),@^^<~^mJuL4&L3Y>_$>oi{\pEs<&ЌsV=1UB< Mi8jbR VJk\6zfŞKY`\ĉթHFNNW^'XZ?B8lXu^̴"3oqq{ŲDagNsoM3׵3טTcYqrɦ=96?-O[c?[^ker}Ͻ"XH](9H)yIl`\ JJX ]@?߷{PBnj<.E'JǑW_G Kox nxDbG:!CVo4`Uw]Xh>B[ Wg P߁w!]gYWDqK[-{vZ=pJ R#ϜcKdhOW!P\KCkW z%UrqH72Ƣ_qݺ[,~~cf;y4>\]x1wG&;xTドU _+sb|Ba;џ=Q]i%*~IRjƵ󒦾n-V/uU2.H+W/餌9IFg{υ_nղż#!čgll232NLJ/U;Ǎo ]\,zsDTbxړ}ˁNPWzWGϵ,YKyм#||WIPY"^;ݷc!o]UG׭2C[XuvK=}g(}ɦrm'mP7m$sؚġ/Sgry_(;\`Sf \ m,⯯8w@ЋAWG_ ^wgUG' ##v3Қ4KF;W?>]Z5,ެDw=S{Ur#=owY"=d6$t~ #ԓvR@c7#_TCѺ=y41UfEdݵoW{ !?vv &ѥ~h:_mI]ĿKoi8zFl{~ hKq򕣎 yy~C_N,- [.u]>SmoT!ӱ3NdȲNpxr*<َ5{]^^?MPUʧqg'}s0al=sHMG/;Q? |YvٻA $ &8߹̄BwKl9&\9Sj3Y)Oׇw^i_(|~ؒ7-ubZfF5a[A+v }@_R^wcҳc=t'iԮuDٕƵ|@7xy,$̇_K1lO[Kߙ<0Pbz`7#a4;Um6) T/LBJ&d=.-F?XL6>Hvq`c~'cG7- W?ư1.ٽru҆ 7Gиtdg3eO&u>77|S;zhh;]&˫y$|w3A&S2)u&VnL=k&a<c )9S h@$_S91h,>~|7H^'ޘwߣPv9\+lt~%]k y<M/&p^UYs5>\CqQ>OFcH"wR!j,u]zn!h|I4)ϖJ7bƣv5-"u(Vϳ~.1GCHRd#8hU&~ZG>Jئh5^azP1b8@r`t>(mm^c^1 h- 1Z>+Bc.m!'gփ<oyX㱉+3>Щߧ,^ڗ?T03ӑP뻛krUly$]ZTz ?\~ET5RO6b .;Ot%Ly0l7'7X%.+x]?=&ౌz&gwL;3< "'y1>!Mt?s|Ƙ>^;#7÷_Y10}1ٻocM%GyQX9멝R(4q;i`a0E8zPhnc?<^'_Q:,W|5>k|bѣDRqv c Tc9wmx_q>'bKWq5'Iv93t_>e>HY';~β&A=֖<)`ш,{]4O;g[L3O߿n`AuҖw6` ANb]]IzӉwC1رnZAۼo[%޳M]LWs֦i3c܅ c3Xfq`طFc%cړ]Ov(Wfn$m]L?oI^K2 8ؤ~6у_Gm**?2{6>+ 6Fq_+FyQ9;h[9/KޮŝF_yTd[@b_nG/k}bMddu3~vƞ{|g8U7FzQj:7zI'rO"v5/@=yXokPmD9O.XcmVabh>ʗjy |}Uc0ҴEz;?abII} 6b [Aj-$ |젝~v'1 3}B;x%>7Ag&$cWVgձWx=3E:{D0w}v}Tֳ+0Vqeޣ95n _|f3KUS(㭺)N8nHm#wM MK._ cY̘ZH4ef#FKW/5R]N D]_߸%qy SFo{8$=Z꺜U5ۚ+>u`^{}kM.y998eϼ5!=\w#oΖiJſ;DpUSzZPqA(.YKb@uOij+Ԙujo8FY9KI_E~KurOuRw}L$Zohqkw¼ AP~E7I\1I!`y|0ZMG&#~ޡTĨgSY'wUS܉wٲXO ٳM=g(q7=X[7ǍLxxl>#r({uC~ȩj.F< @wo2\)G>`@\gp-&[~>ѣ)RNtWOA7SUɧbP~=nGXs3.ω)B!CE._ç~bQz-; >[Kk\Z[]ڡĸ'=ܝ'2z+ wӦRWli8h'!kRw=ɮ_9ّMңnXtr^|(,ƫuuMq CGl^|)VgX{/iN'{ơdmMCyɟI*l x$!+2+vMmʓqATkSؘᘣSX) %]6Y]ɴc,8Œmio#_,dKr7#ѧ|bZҎ0]Lxz_T,)9+m}.x"N>>zÏ4_iS˳F^ s'xヺ[zHWD˨ٚ{sӹe#^{aDAOЖXl;{ ~4|;'7l-T^ UZrۡyT't4FC@9$dGd.> SBXάK}>&OٮYWݬm˽/Qf'/KZ]o31iX$"QW=9 o=r0=<feW Vd‘ic^ x&{ؤi[ĝ'p%0x&m@?z3[8Y>}ؿ[/f;1"ݣnl3M>Wm @vt!2og>W kM@Q{0$<αN|:b#~ٸ޷V>zs! +T3?cE~ac9,K"^kxuU^B]?ǧb"vJw}p/l4IQ#ы31=5A~quzX&7"EgkM,#7 pv'c TkA9^د;U\n*yfp02}ɣeXn;>ua(#g?u9m>l ?#} 57ߙJjo+RNZy]@>8~=\27ep$Ί"%z🹈lCC꼕7z^"X0gsܴ=,ТV/al܁#n9oEG-.̝_Y%cb;Y3ȞmZN2$nb98\SM(½tƸlx8Dt6AʗH˱Gc[_N/\,l|(RggNLz\cs7t%jvbt~\ W߮#i;A}^~{k?^\$ׯMFCWl]gEȬYQ;['OdyCƞ:9JzwCbOj"a=y^ZH]^ش&T&n8xBáiW8m=#眀΋4Q̯+r|t }Cu[N1Z&+&wgs(BIۻ , ] d ĕ/.pCyMr,4|۸izTV6ᘁD/d/zm,iCɕv5nSPYd죆7*6>anm? B ٻ v6N,uߧH(L}+K`:4C~A4t"l`](O y,W} C}qcxǖe8&0?1%cr$sWtlAyow5hkڵ|J5JGz6VKbjoEp\;abՎ0+NKx7eU&ys1ҕO z1&.״oRu.1t X6*FiXSѨDNw7Tÿ;J%C%L?)8zam:5NV)BRڣc3kx9'g9K>0R[ "e+'1;-gOtOmVvXk8.k2~b.o%` [zϕ˶Moh}CUF^Jk>৸4 YqPZlSY/oG $3<}n:Ƭy.Qgd)9mʎ0tgl\9Z2>+HQlo!IoPlW^e_mI d| )(IyPw +9ld!sяoOGasX> Cǹ+% 尡 *Mv?P`㺸μPU8Ԙa&az]";-;hƭTE?Z a5Fz=G3[sGে7ʼX."Qo/Yb`G? N\; Kc9Y)?Dzr@bOVKh7/?)!ώĖ%m۴.M&\0<M`8wx4N9Gݒ5#FِM!!eѸXKNܽ혞ÀX%/u;y=La;06z1Y" kwGl{?Q>,_vu6.}GYϞVf$oK怚wڻM ,,6sV)7sj'|nXch.6c~|oņA!*|;b7ֈ޳ƲolCU beogY3M zI9Э&5":}Yɰ &#`a]؞/y*sV0_i͞`~7]sҹ,Oض:]DWGŒVutYm2<|}Go}ƒ۰*㿂+z^߳C/>xN'QVU4~.!ഞ5az*}z|2TsyN/cَΩ$Džq\~%yTv҄1$a!D0$[4hَI"խ|nGy Dr!t|p07 Жcu}ٕ'77-K␎'/lJ?CV{$t:iPL_IҢ|[Dg4ezz5w ̋C+Zԛ]{u.me}L2{mߝߗ C6vU~ؿ=qбB lcӷ<&ӥqǗ7ף~QwGf%hě(,NcZbɪR*2ImksEhذ P^:LW1n%Z+Zkc$̢%K 'yZcIǴsK8{]U[=;O"cO.jcV̩{:y1n1^ uȲAtM함*O[}~E{X9Ym[*n3kQ載;ۊ14.+47̺uy{Q2m]?nnJw[~x짎̎/nR??[ -R |Lwa`b?flOf QÕ;Rl nYKղ9zH%9~Bh5^strkhN%M>Cuek]/]ѷw)~z3uZ?ipv=eJ%'彽)s^ fe|%wL3aFaL /3::Gwo;v|VVNse?觡 XKx=:z9)"R0!iagN I#3ً͉v)fa[ֲֽG%㺼Pp_-Ըdil)qB<㋝hkC.'D }ѵ,st6?0M \&kȑ٪[7݆vMSj}L!s'񽢖~#Hksx3dy~7[@WXxrꚤsN$~'/ڨ,u7~8Eaw :; ĈilB8d.u>q@?mWۚfkK?c,$3m0%:{&5t4$Q7{.*{0uخZG$P2sV0N F_Y>/E eF%꧶nUI넼uKsA?,f}Je[E7Fk9?qFz}([$"t$Lj Zx:z"_ޑ1In/tRN>ۧbInPkS41MDR'ʢtXVsъ_tKU땱t .VC'><\ht|$3#G6ZgH928)9#=;{/NBun_n ?Z5&|:yD׽Y&ct4? wcn]C>@1mcnۍó^=gQ^* ) ?V&G~jVǿu%Xg>ևȒ77wlrA CO` [ۈ~@ң~.pr^cxntC?B{y艼TW!6/2V1+x~ 4Umnd8RU?'Ae_  gN6zKdO K_s7i%ǂ1v#1(mvrM:z&vώ>p}7e?67hɛ,v_m۶i}\F |ݡJ?}72`{ZYV^wHnV_]6یh֕:xQ(B$<b1ZiAqKX4͗b`VW]ܔ[q6k{}۾6I}JQ쾁=i]DSҝƁ83\qMtDNJ|DK喳V\F&Uյgv./˝fZ< mIuW]yNe2ٳs*ĊI"yy>Jķ3XOG8Q H.zT+%Ͳ =+oK0o7=yq6q:sFaA DI~j`4@y?aqɗcG `oݸh|׀7K~;zO/BvvK~oB ݢGI,6txxNoflcmƣgȧI_ >o%07ѣ ?J |˱8^ґįn;%jvAmL&~ o]%m }"} ==dǰ]|6[l۞[ä5jrCc17OV|,8+{noE97zB]>:NM1V{D›<]y[38^Og򉜹62(Mu320>=͜+GI`L:}`hb{XvuO''xu}fk|,>眚ʃq]@ 3 kcIFtÓcx]+#jWYkc~O Jv]DzF1k}V{KW{R,ٶn_M:+)_ ,`VmB_(LP%m0aH>m Kcʞҥp㤫s,Kqݸr?$#l2ӦwwҢ]<[xoFDG$"C-jD.m{i GP&.*X5@&"n[YuWoi@wk;G@&NAW9W^J.뢕ϽcqlF; $;3::ȡ~y7"N GcEZmk8,5SvN跬hhJS5O/3HY]Rm:-ิCǖ[:ҧ39i:y'?҉=Ye> p;]DN_̢b[HRic$N zɤApB@ӡD.^.lY/ ï_jj>,U\٦.8-a "0dgk+ϼV5/oR68g^*;.#)$m4emW)uL_GRߖXҘ[9ǸZ<# ]{1d^ppQx5n?RH>-;i孒W< !+~>ʺFD]hɈWܵ*cYC$\ӣQdopss£Nթm;?q%ex?|i!h/>(`?m` 8CڬBQn_"sxcS75{}e'"I6 t<+о8񋸈얲 $K|{=6xW_;w~1;U'^6C3/ h亻I4xuog;T>%bBG3 ~4:r^Vc%c:^cy4^;GovǼbi`W/0w2] fW8\+o> q?TkN3z=,JVlϊkV h c)cݺH{X99 N+'<#. r!I|H^GNuOv!G?scy$k R&˲^so5,/-Uct^͸nB ݗCoľ_[XG߲J?44Q;ECVn'2A+]osޱZ\0 y܄ [/$k2Nq͇v'2n/r[0f"W$f$ԦrH6 sC[0Faa(?nzt /}XFN3Rg{qP4f I\!Fhohl|rI O _]~Rb$-+Ln$.g3ÓFyyw?d0a~5o.y^U`TUONbba);K;m㿴 $v?-Ɣ˘EJA` {C>Mg~沋fGK8Z@f|P*8>JmvlM'cYTQ=;O[5*ɳNb|=Y)c~/вCŮV{WKZV;Xͬl<Y#ktq|꘻7^> +q+V8r>9VR K>z[\J'H/n IDATw/\KJFJ/iѐNG7+P/|`G5թa[A g'xzM] ˬ>gzP Kf[凫;J&cyP$mOٽ4u.]_cX^d=pyr_2R?$Y9^ߚGdUUwSV7Bb21 \C$PvP!WLҚU38D ?{7]f1{nȞ6myq1/.ckf}dVA|bߧL+ve~]x\&U˶D2MU[}bǢτ"Qz* 1(v+&ûEO.~{0iYhnc6OB2*m/*B~a,RWJu"x_W_6O0E(w2ڳ/L(λ` + p(0uUQ}GJR<+DDmUK-Aƣ7mf|QǺu]h Z,~ǎX_>tRo|୐'͘:̯/W(4nBYe!sk0b]뭵ԩRv2wEr2yrkNC #F״nB $1Q I?ƸD{P!k's.F+$pƴ&m?x9 YU{?,}MvgE^[y+vEFdӇ>6Sgߨt=:I񷱉j=NgF{ O1m?jjxϭ|n”%`c+w;x G|r.O!>z6+l7|f;J{z>`<,<JV}W8'B3:81<_4FWy= ,t y]<ͮG ό]1P[Ͷy%܊xEx1UsY7|DX&Krv.m&DaCwITe Ri~w݊Qk~ەk$ =km&v?/+L.vplwc"_!T{>\_):Uب=' _QzIZ<}חwúXo)?,f ̶#Z"/.dY;FߟPJ02-lqKJ< |vDZ>1@/C'eK 8z~̹.w0 Y/m.a**QdLehwMep|gTL;2:%#:h#Q1C|ĎopCEHpW9vKǀ.Y̟7{냁~HϬvu[5lX}vjW5[*>0 cy98,< 1Je#(x"NbVJlnZtwőwTTנm. xrԳ7mdR8Ip筰!=t4wK#DP yѿ)1?máp u(_(L ;}/> }HTXl&-xrUQ|{KbExzTɌ-r5S9di9S:#-ԝ:pd(1ۄT;P|kQBc\m] z2:UFӊI?@Y6<Ԙi}V*Fǖ+;ۭx\3z! JʼnˤJ.@.F/9Q=%*]3m%'iݡ.^ر\1̀&ԏAAJ/XoTchJ@b"6\2y|޿#~Z_S7O꽴zM^Twg,|WԳxJ~Sžm_>xO ]}_'\B؎n[@l˸AHz`Ǻl/އY&1yKXMeF;h_'!%u4IbkubI"۰v*ق_S7|ZalU8ucL]r py!b[ӧ` q})+(]NR(+x%xWdZӸ7wg={Ia}$뷗6ߪY2w(*&}qL'~&>E~#ػ{ ֘^vh;Lw\p#C&WzuՅ`DQ¶ٗj&|xO'OfE*ox$ؑhBGx4*Oxd,/KBs跣ynJM$`[|1\':pbh+}7xCno8k(+1my^>sTK* $K|Tp%u5^K3>Y's]ظ;.*xkȩغ?7p1P'Wu%7UNcNcF}]]>_lOD#ˑs(0q>{ jl. tW;g5<_߀I"[#րcP 'pa9JJ{x_sÓ!IcL~#lo6̽DG69+\Iu5x6Qb& ;Nۊgda/%9fY-f72c݉-&~oY^]O@n 7=r+v-1vjo^G2Li0=Cx#'#N'o,nMp,IE|$vmTy:zHb׭SxwgjQwߢ!?/΂+Amі'W ]%?em~0a_]j4K8 8i*v60NaM;l#>cb>9;};|mr(}6sr3cbr㠗_Hv0K"SL\[ġqߊO <;sŋe10 w̶NSP|}Bi0aKhAE1ZmP`+D?GW}&wU.DދCM,_@??e9DgGV,oal< $׉` vK߶l>. Ob*p|qeQQ19K_x ?G;o@t󃑠PcJIEz4wF'#j4hvVz2 l. .i"W3d>j eϽÊK 6! KZc>X} ՙrm8ωR9܅hpY"2Ie$8CUJZ5n7HXfhU:đh?'0(eE; KZ躛}uMj71]8r;m~c bxs˿=5^|ׯb$POB{ۘS]y2~yV.._wOc˴Jwnuaj׬^K0܊nD3Qc} 2B 'v2e͎L|5.MUygawb󰚋w"yOym/7[=h#cg49O#S6WǢq[B& #Ll_W 9+Ch*auQW2GTuV|[={ZvvGfw_^|R޲?nzC(,Jޖf8? GOƽ,9>obJ;n/ =k?$]ּczXhC<@=[29 HJr*=GSJ ld~`~qP*?9W*jh&םXO]*|p6ނDU\Rg-Y*ܱ^miM8_IBk#"ɽ滩xmmƑZ˓ uLk~򒴒V%d\0\{Oүp'p6qf@`W?L7?qoʏu#9>e2<,So9:\я=̱20>lz;fgM۰"󹜓|U'D F|Ɓ}ޮ,>?g?=s#jJr*s@uzyjfa .>-ӗ}7+sw Z /*;9iqoIY\jdW7lʬdT1ՍG(㱫xt>*_ &>]^ O\ X!o,)xv4ge!mN;wGU{π.̆t] }J:K>#I XZ hxW[ aߥ.#\.r]ц,]Sϡ&~{%:xC'"YXJⷑ䦴!>OM%W'm~oE- 0A&N=?4N$>hd._̑^X|#1ҕֻA71ގVY#EaQ]ïh=lwKt᫸mpND+>PR|@֕e .Nf4%Y?Wp_zU l7z;{ɲA;<4?;P9Ń6+/Q%yA}z"oa]'/Y- w(xuݜlt8t> y=&QO{YRc v(E!^[_v|SOߙ?vK>^íCu[m~{<  ~EI;zlUGXGNIJ,GqQGS>gf0v'7wDp`AUI&RI+/=+7Mg8ЯWC$a>cݱl/ˀYBO %hH-8k6④|=v\/ῒMkK=vkq.$-mfG'KDo?6Iujwt y/y;]Mqi}ˮ:1|zt$HxFm2UuL ܤ2]y0pDAvִN9ʥxm,:/1{gGCd! ;6p.N_ PFV>>ޗOvncJebSUYU9KSr^Tk}UUΧVixc8biz,2Fxz1xF& 2;0@"B8|]S\˓w]-%~'חV~ ɘ-ǎg_;7~u+:9OdGRyC/EQݭAW+|#&2Bjw`gДEŽc_vɣ+ Py20SW qσځ:Cէ7Xa8TW*]Q =NjǍ5&ڔ?켱|nZ?KydbuuL_ ˣwD0;^MӃ؆2:c[swߠx̷]5.Q]ÎAE|L{dbv,i&\m3kV IDATlUl,"@u$6]nuɮҭȉ0rJ}@w`"OÃ/[^?}tve7gQ晉kN@sߺo@W|sTq;+r3:mJeL+ٙ-[k1ow31W(kt^ VQq#bYu޼]@#*\]͵qMmx@qlMc(=⫕S:Y8j#Lb1qj縜g:OQj15L״]uh/踓|"U<v䷄!{//&dra_%ϊoG?6=7`Ivtnzf^ޛ-?l͗AT_.Y^Ru}p3I<o˕My;?-;.oLpvޝI?:F島5[p''a{-uPa};7pa16!Gj|k祉imnpW44u? 'ι*J}Jѹ mv{C|^e82g/m,A*þ0N@k\F5g~]cu,o?@4ly%&:Xlr `EE;yiw|o 2$m뉞"k>4#lGg/e4`~TZ>"QfʵF|TPzfSv%1Åbd˪L e#αHb|6 uWm  6]o֜_vkW@A?0n;\st.,Kv/K\x-`s֥eU+/(}Oq׳l.MsPCRae?,-JWbty[-@9:xEm.QyS+=vE{ h⻂~AB_Saї}M &==+Na2BwvYFf z )sWRy[-UEmouU5'yyxNlGq_q嶠K|cK{q [QDjW3]&Vf_<[jXU]V#+0e7^Wߓeϭ}=#]> *aB߲ ;F(.d_Q-#- 6|@|} І_gr'. )s̿[Xh&CLj$Ѵ# hpĵғ@~v+]³Z$[Fwa_-G K}$%a]N2+ӑa Kv\,+fMd-H<<60pYvݾ07yJTf4}7 !m:0x. 8+4n~".BFjuۓOvk!0Ie͆[2 f$f7k@9z1dt9Yrο%yni#FGsE^P~[2*<@Vp|;g_#iޗ=c2 V#C@~sydk]~9-٬ۿ ' ~j1 8eI_p۪g? !~Dd#I{IBv݆BϿgB_d$[A $n뚏{,}>}~ƖKhe~U<ô6O0ϵ<42 a~N~c/58h- PD.*KkLeޚ9oc @;`eBZQuatIA?~lSKR#NWIJ;L0-[>^2܏Y[/{o:Ey .&.GKw-ex7 "e:wсQfTMc+  Ѡ bfpL\YuƩ4(tHXߑd%vp}4bq}L'Q[‰,^_e1%UJsi &`+a 8鮞)z}7vy~*͖>2)[Y?3#23}ohQkN@ۚv]X>~?,ڗS/O]-!$! A:10}OUȏ+oUKc6/N- 6_:#l[7}aZB}^;w]N7pX4V8LV;[}cެtglglX>nf'c::(6sW5Qd՟:%:t8qJBzyʼn] ).b*e YPL0F' ܥxVV;u0}R/l̒Mv|[v}gWPϨ>@فXC;a]}pL9yB&$">v vZOZ:WO:ÌPiOe٧t VQL?gG*5z8GB'K#;&m1@!݉sb _ޙc|l1+}Ď˕~slO~yQoo -lWIucUOGNG~Oɑ#2}&B6b.<7]&_25]"fV|u>gU@m0{+Mv&@C\w){>jARҩTt4^J+*v:y=]}lW^krWtp חy7O^AWNq2Ft,u|m wWu?.'d9oٲ'{!(:yuw*ow}Z7 ^PioOqKfj V#\߷ 4r@ُw m2G"ӺBX,Md d>m%Gs7< ?0ċf?VG0z|ELU3ȳ,,AUz~ڥHDh7m.&L=|{s>D%\KV<]]L&U6b;19$uv"jPY$2ݻ "` Ovw0{mhm34wge!=ƈXN3MEo.XpwJhe"ZE%LcY+&\?Ӝ :5ۺuKZ$?11C=*vMH.4Œ,GHV }Z2>%ir%떹!Wpvd+5 % *zQL`zxvw dI܅㋍Τ[r,dkCJC^{;N/tmABY)~?_@lWu gOm}fgޖl~KwK-[qR^WQ_(~0N/ >*RFD0<fX G, + ml5lh=;.CIܹLvB(bo{k:,i ̯Be)`o7m_ƃaj۹WW҇! /.'m%iNv[>@Fuv !(įMϻUUm돝:^Z[}!mo7~]ɣ}.~C;U֥"k}"u&UU&nyDvE,C8us*ЖcWf.{hc<ŧі^ 7O4l3'V1I i;yr'/ؤ( ltk<ꞚdFI}L8o0%iJR7ÐqgzOoEBp;5գZ/sNnM&y1< oާ>o<yovRLts~""Ӳ "X'PybiBV<51m] +6&3_&Ob瞎Dd~*AEl>cěe].^^T0y]r|}O:u82[b|qٽ_ })tH<`\RgUApqW9jQ3#Vf_$nvD(a>֎ZO# SsΜ]]XYy?c_{֟VN\”J e!\~?R⾴xn=T1HPrW_aY% D 5%lƾ!+u3%X>읥<Ηťp1[VN|bvcGB.gAi/? td3k񒝷^S⧩| A-L*+|ll>hwzchh=1%޵v1Ӊ*+}>bzN6HS {!ddTE;xlf)o˘xc9εrZR|gs|ħ>֜q8|9,Vf-;_5;Y9'>XWo32-;L?'RoQZCvb:O4|^~Q%/{k_+JNJ"8U>1߿6lc:pwo;GCr>6w<ݍtx<ĝwV:,ѐCb`Ζ=<^zf ]ܴePt לe  &1fI(:H@$:]/=&6tG+}`uI"9.XBwP{:+;GȎ3 'HXW48^!1hűᕝ%_G3/2sF5+E:{دqrǴ;^ؙe~ҡD NIV"w`]vFuαBPsI=+eUmUٙ|tcX_b䜰xamV+'sޘ+l?lX9s~.]OHta{)&)d>CD y [hƈ/-,n´ I# OI\zx6B{vJ+ag R^X/-e,{VƾqQ4g68B7fjwbp1)|v4Mg꨼l1^c#]olGHHrוc66~⊑l=$ d%U0TVPtQY=?8Eҡx(EYL>c5je썙I3̰~q"$&[_;uo&ˊSeN~ԉ od< )Rq34?z<6}k_TۀL$d;3-/ɰ)fzAI?l5ޥy.W Kr4d NXޑy-m+V3Q6nٲ脷|k¿N~b 7G"c3#|1F:7Uˊ)漡u;ceva v8DB ,֦orDK<ͥJHt+/ ڰ~j9ash~6?as07N;.Y"$9 4|fDc/&Z-֮͒/Oc; ?AYjJ%î_k]1S?~K%C3Ss'𭻖 >1h/XҜr6#>-6"@ lTfY룿G&W0#& ;wbsIX>^k=~Jm"8)-c/ u4 bCcfe#L9IIõ O[k._ QԦkw+QTFD=YNaW:bXXzc 立,U " Kdv>z7X;Vw7l1=rHv?~mt ugź;rEZ6KtsnylBɪM,ݾ gc}VNM =_[+2EE/J;+/-tC >?Mz2x(̍,4dq.x<3~`r_|Gnc`A;e6Ho%#i=^}S;ֹAڏ-SgV ;nw|)Vڈ۬F0ﱰ_; ڟ #g>9 M]}>[2gkrٱ8;wU/#QPjⷑ9lpjp$3Ͱ|N OjB 7D_PCz!nľ61 3c3m?I?ExZ1yWtޡ\<5wJ;c| [z,jW}9ڐ-^bb<*ݧeI*&xk b*0Vz!*i!}d]à >xW"]\>l>}x<Mau] 5^y=ZeIYv]#{%ݼmŀ"9 tyj!`\HLɌqsZꀨ6?ҕ2,M~RU{|qyoZgϿ'w_vLC ]S.eCt/BG;> }>F:dC|21Pӿl6m'Lvv7k|4)]Va>˄1/$f+*w^[eEА-دoZh0_гD衾#cGe.5hӛO.-[o\ho3IiLDDt"L%WsP'h ҃'kol[- wG3<;,}s| N:DXw]>9x履Џj\Tc'Gk.bn1T|aK8Q/> |`S]EY~w*~O}گve۪DO/L>ט|Kc &î>UygѐvV~eGU1:1Ii+~ tsjj>O's]e1V8GY9BPXk kefjw  RKt)Bz@@<ٟ'@j1,G<|B;XnTB; AY *I?/ڳc{}PcտONdߡWbE0LvZ =}W7SPve#c)<7lUվ^tVƎ"T6yM-Sf4@|p5YBM.vwn)&}r9)L͑i!{LTzQ"(_6Bo-=׷@WJʟ_'`@z0PsMVe=4e։`"|L7+<{;&eg#]8fAM.MauC\anG[nvHJ]r AaV6R/F~h7cuCO'Jm-/;< ="IIr߽EZU#:?vZc4ž"fFZA[^e ln[׈=<wr'*125IXS_P:! pwػ%Iu^#_7tZ2LdGszHr<|*AA,={x-N*e4XʸaŪmYڨkڹQ7|ő](-{צd;c[qVQkv3>8y{m+VrN0"Ԗ+GpL2}l`2z=g1/Tc%(y b)nsm^}3Zw蚭>庾}gŻebG:}fWWu`r%E=K} /%*Jw4-'fe':M5KH$02p!XFKh `.8$j'Җ8mLv%N|ǽ3(K.bHua ;4Xj>fVvߍN8o4c|ʂEK.?kg s3X %/4=q?sz2.ry{jӚۅY'u_3 ع|įϿRNkjgABe56ǏtEc q*ƪ6u_"j5ӐZ7 څ e1㍉08YE96}[=riLXc;̕_h[לпu= wY;ǛX0mceEZ"[_xX]_nS oWlv0McW(}[y;_<39dB} ĀLwTsż ᥋aAjӞW1.IWRdj=6뫋\Z5~gmOroK. >Ƣza{5Z%¾X֜uYhU1}}bnܫqv m]g lɳ1,ut59?K0*YcwST}qsS,[5 X[^RCP(1sLb9w[h^8:39;`#$)>E~ϔN_MC:UjDn;LOof,o*}f1UкXb8SM-}1qqǧKckcb7~ZC&Ε>7n^pym^J>l<]?7b/xeVă9I5_{c:H@70_ k۲DNwHU"g~\Oʂ`FtoO,.}4 ,Pt[ҳn ^%RRKD1TtCd1[vwK^4f-ģrm٫vM3F{g>{Ʀԇ8ڹ]$^]^? Z]T82Ln@G~?sQ sj.!'J4 jOQXl wS'tȾp1ạ 8n_Bƹ,v%pfl%y}6*vxyOGݥ63_@[DܽVB1񛺍Ab'MhɁon `ٲ: PEs],^~0I k1Լ]X h\gVɂ{Y:b t?0HmLnGZ>t3[@Er8smWxx2«Ir@lfۇ.7lRV@+`py >#r Ldݖu ˀ.ٺ2YG+Y \s8Gn_R/>M?n+\aLw&h_;ʵ*߆U']M@?JF׶LWpE#@{sdAJW97/8A\RL0f~ޣZUCEwG5=ys//>k{oY]O]KyҒ\^N,XMZ)E뵒p }Q9::r wqs,pZ=Ʀbv"1-l~1VMvy8)Yb 7GO_gSTrX-Ar"Խ}nX٘&|LHlvʣi|Xw~+B2p+ǢĩA | VLiZĵ:X8T_xw#c-~|s.8?vP<<2x!EfF=~+W6}86Q×ڍv.|My.y G E4Iqs/ ?[5*}'N) u:˄ƪNDr|N|NƇG[iύ-_خKN#@6clPֱ~XMF|#2?u:={P{&oPQoi+: !?w Il3>eH8п/v~*6o/ S>F9[.۫P{sý2W^zTv;:}3W+Ϲ9(&;X(ŗ@Ob/^wgoQ):p]`1@kbo>Vؕ|o@k8Ir/5]C lobL׽>1,M-(gp$kM\p]V_޴Rޟl`#^Fvnh_&g[W5EC w1dl_c.b(ÇL0uO[`.uӰ<&.xql@_{U~Gz2}LWt^=|s%qV6+-|m1~LGEO-8pa-w*f|ێcM[ :C]'A~%4~F䆈umnsDBEXn~ghgkX"az;K 0c+̆4y?$*۱so ;,?כb2(IUo`|T ȂW/ 8CX:F_@EIMbGzv%ۈeXiۺyig.>\uFwAo %ǻlē &TwL`4bBs~tUzτݫt+lۅ`di:ܵWCϓ>+xi/ʷ7~݂ͬCNHyRMyɂ]LoX7/O.;!]ڦUFqlu ho] d?l|!,'ݢ-}`Q|Z.K[B~6{:4C~9ye4f /{B7?|BxeK6jzV; /`V8eI׈# n#9Id_it*VzN ,F.Lņ橏ƽ}Y$f 5]ʒԇ ژ_[w@ w zqw7z0rؕM ]E W n2JC[=i 1m*!,3LEj\@%tVmm:nx-9@.?c3@vDw䳩}Ϫ]M,%|ll jdL #bgHz]9(mgЧec: FӓK pܣ_- EƗ,N/a ^Xt-6썽 d0zg&mPϳa|"1Jes~ۦ^UsNUWշ'r5>?V>_t/|kٮ.;7ڪǣX=̣=zM4qcsPm1ڮ bi2񙝴]a);1<_wqzm1Nn6)>N?N 7'ebXB-˅W#uAZY"r>>i;mC`[O(MM8> yP84bf^'ƋxmszPkN?WmklSc4D-(#}5tTڎʃ8H;)3b1[T:gͩ1CN,׉~c3}.Ll$^ӱx pׯ858 'X~=Plt G׻o!Q)=3ռ?x"[9dvZ|7Cj7p0ꈰ8Xə?\ُ~jʟb4~Q5t7)_K KҽxdNo^?xȦ?0E/xue5׎!üwɥ8`Jr{cIz͡$a5;c ,y0rīKyIpmq{ĨK>>b;q_twڞlA=8btxӇ?.фf`QV)!4^.[YJ L캝6vW-<Ǧyv >RٌiFRadB\;$n<_liαk͖ss\ަ1at <[xD BrñDe>?,G![>E_۾[Mv6[ /ٗoѮ1b,XisBՆƃ1GY9yFm012iyjF,0N@s Xcla"T;y\!!߰h~LWHg8!V9Csb^4}VFVpB|wtv3٠x(I78&? Ono`XpFlϷt_c߿nihj{@{cusw/u}+0v)]Obc;֢Rө7T9?ĔO_INdSs8u8hbiڒ5Ά06.+}|{,vl;c!jCyI(FE-2Xh{2`R5ω c4"2pTYJ# ZzzQ#P,Dd󏪜}&_$*(N@11 8\ Ѥ3Ɨ&7V6T1:1C)[rtg*ىAUXLW=eJl&ƚNG:}v\ŏNX< N^+?vc]59cIdzȧ!p#]5#kDq}Fuq>܉Y^=aEo{uГF=Q<Ȭں\A?/9>kЃOƨQVw]& Qvg&^?.G;^K5Y^'B0Nٗ=>,wt_UkIl2I,}:o(s9F9m)l)87bm/ڈ-Xɛ̫ A.¯}3®tNO0]E YƨOe\)Y.]+ϲ Mx1>(DGG'߲ i)&t6r}3bW:pO_e6 ޥULM:e⾻ |ge eH^e𚽌?mIu&[2jç%<3:JK>p8\TpLkNU_oq~cm#ѕ)sY"myri_7=k(zƯ>\g'赳5²~[MUqz4%䜗XI)ddk~c\ %%7턥 t\ nŠ to:{!8gƻCܶ2{y!X̣Qmj0|f8#쫉imQKyy# 9/YrG {! s+!a:eQݻa;Xt6&pi_Q^lZbf5qD: 79h]ZtJAB׶MpQ;ү}Hud]F>X7m?P}W/w-La#eIle|6&\BDŽc 0l؞.h 5lِ٢^1EHaϙp.}o⯧>č#lOv-xrހƩ_.m:w{`,k{߉1{LƑhb?أΈcEKEbvT_/X/D}:탻@x,G[GprH?xe(C-v^r]V/qm-ݥ?I` 2O m dk $ǫ ST?+߱ymu«X&>*!h{9L> ҕqOÚ$Y zv!arݹ]N:J=яSlhۮ;ڸx*,~JG\$ǛY+.]iQh8N`7?pb Yxkt:w}{bЗ%_ّ[Ӂgnb<&IDގ`M*#XwuP>MgGC1amlӪ6m߃+~Ď K!zY8?ל~7 $rnlا;%ǎ$ e_"ξ48LTW%>1ԻcA$0Y~8ޱKDQe}'`rQXٯgϺႵ`_džƮ30>7m7X˱3oGV ݟs/ Goɼ4m:˭ڎGǝ TRb}uGc3&J%6$D$)wWZH0!rm=F&/b.wl|ڡdw_gFߝ k^  q#oXKiΑx·p6v@^U'?ZEWG>vTemSs_$Is_X[]T浖siA4Y秙|&_ Յ`7//O&uAh_9ܢ<_17mb~yXICCR6񳒶\r53R Ne#;/آ'N=~Y9+} T|ow,ck1k`ʂ8Ћd,)x2E;ؿ 1x~m!I<"OB:$ƼQ)yްsAoniԳ)L\o Y(Mik,Fx=!E 1r߭ u, y^2G{Hn+a\Y'b Wy6<9*_Y$U|_*=?Ttt0s©@nƕ8߂{0]׺x̲k[xqلIͯ`Dr}Tk)GX=͝IJϷ*UخҪPҍ;^,YvUoHrE.ePWƣ|!O ,8'1L!e_{U $-[|5vf|<^w8`Җ/W?FzKO!L/`)}al Nr XU8 xoC_/hbXTM 0]pZs<6ݻQyp8lk}~7ѧR 0{lXheO?~Q70l6C՜I].}wws(-?6ڊFHtd+zGQt,VG& 1^DMd3-+I-9>aN!E=K3Iz@Wυ_;.?*˪] W\o/6Pq^inz7kL•ŋJ:NTMJ8m`cndYE|qWC].,˅DG?$-lzx=N}闺gv^uFU}|O$V* ʘpWh*NNxzKn}ᾎ!w xBcܥtgVeW1E:z(q CڿCclzeEa}jQݠ I^P[e8:8UѲ,@sYg$l,7/p$. ݾVĺ |&ʘrpw1mwst.ޚ\7ם_5]=T:̿CO炫62cΣTS_mj/{㵖O۷ޯBK._Ci몸W',4 ypI''9v(o._M?q?ol`>۟uz|r]v8d\G>W`ޖEf&x)ɣ6<&bN(z0Ype*ַEmm\{D4~H&xa?ҵk"髩fIϠnn0EYk{74;]cf[jQGTՙs{l:w`)_=8~d(fQծc;^mՄc=4W*s~/.8uo0]3:(꯵cB:{s286;} ]ˮ7n!dOm߰;5?,eb_vȖۡyT&棻a}jƁMwHt-sA>qs$Mk=܏M IDAT[(ru&i#\e[4t0նEgӣ$rKd>OSk\JL Dl]w*qz0E1@X+;˞:cgUoy:cS ,*0d_ E az R&+ex UWQR}[WWQ6ИTGm9ԯZ?QHn.ܧsg~Fn}!T,G9.'_/~AA:m|NI9t!X|ڛ}>|97q층o`L(A&%œ þ55oK{͍Zǧ^:9^ᬚ.۔].lF~o[uų)ؓ钹9xyZ):rGF^[|qS(zUS^g +# $|fJG~77OVW4dFA^}"l*8GolVd?/ װmӞJ?u#m>هh/+;W@GS &x\Y uRڬ/e n.xS7šu_m,<~~[':1/q{1\˅ ӯ}uf܊-{=;Bߎ0+rӪͺ\~)j7~AA(M2<ƻ켋?*KIf ׄ T{g19ևo7G6|NG8\wamۆ_9>l85 !?P1(wxqS;1z-+[gb,fƀ-AD$VWLS͑^q#*ygP[8l9 !ټdb%!_}&};;S4{l/8ᴣ-/)md[X6o4DΤu5S e3щ_3,lXs F~;>7זf^l4Y~7ñ{6vufu,qv}8]?uJLb Y{OW51BHk˟?lfd/?Nq<"@M^Ce6iۼK)_93zM x~_$Cx' |k%T~wOC; t Uޛ?kKU'U8SΏ;[167b-Vpu;X Zܕƥ]d ^֟F4\4E}|bRsyS‡Nlv7s]ypMP^qco 9ʙs.blS۷\煮RQ\w(gzx[gB &=]x/l~tʷ7:lK5ubGÑCC3Nϓ%R[]N? n FBTr;Po _e۰|y>{8^ǎ7Ehj-L>8V>@U6/.ݳپ {6-JW}=V?}VzOl{w8Z,sv΂O%=Y.4oɞ:wNI/ggziס:u-.axqUXfQM͓%]S{Q*~?&~a ҍ=jZ(L"dDl[X$fevP2r9v#;v\C@oklt.XRBS=_- ۑXqD{YWqur m/){=@=[LY1]#N\6$M8^ꎈx4 ou< du׈redViQXunYP@//ɶBɦB?P\ap2,ǣeuF涎~Ƌ3qf=4UjJ`4_6Xnsw;~fcTAz`sc! \[5Kt&謌LGħvs1^%>^3 Ъw];ߌXﰛ}'ݍ}RIUϭW#}֎×$?[YkyzCY;u9:!N @>E;ޙ+1;N1bR}=+htֱƫŨގ5gS:R'0M_M0B]TSzH/u26Ho݆[?o`J1ٲӕ"fX>1}^3/1嵫1y0RΎO.{FG0_#>ⳡ]P7{vQ>cc useE">QY%>CSrD( FD؟V Ɠҋ#䓘U>6gJE^.c+|a\ݥ&I2J^%ФwG{s,lYlWWAU` CZRٲ>僿-Y(.Ts>|b)Moc=>Iހ+"FEz*5bQk齁Uu^"3&j@*EM~Zixurot઩7ݍQ n/N'eW)>o֥/h!X 9Mo;sInRs#{6.{y5`^[3Z]7(c;xYxUp>7/K:Us3QyKzttצ=,p.+ۯCLJK9 )qJhaFf]06^]N_aJF2b;%ڡ.^ǭ(%mYܨsפ>Ke&TG']Ýx)oDC ڴگ, ڙȴ_/y}bp^{1$*cYiUo=[=>4=Vżwt\v:\(z2/1۲_[d\xFي\(0s,̇c 'SE=;$ [Z9Ucd߹?GwcoiRϫ+& %-.;@DW Q&hKg,?&۾&]6:MVeyDu(XV ٻs3wswiV]t0j[pzp%QN2qGvzl۩]crcQ V@)N_p:tgqxLxj]'K;; ~7KOQ,k߰Xzys,6AYZѿv?ۮ2H7Kwc&k %\e f` . b߾^?oge Ķ[24uLC:KԢ*P^ҸĻN=/vVto`7}MzU;\c6VAx#|{<[a_b|aJb#2Gf騿cXl\c}Ev Ke}}VK.>}-:'|$B+|a;O&BA߯SѤv%+vOgx;q4!]Pfz9۴ R󩑴uPb^'fNtcd[MtJ\xD18VL4f7d${^֦azy_;7nޞůt6Fq+Ugy /BFXZ;#38v}8t<#9ƜB36,n`yq݃/폘'kטO: Zl18U^] ng{3<]}sZjVuŀ{.?o#j_Zqx^HXQGYUx.gJ\I_ԐoEfqNj:$}l{-k]6N7n^MܓIY,x Mʫ`:65?0\Ⱦ_luJx|.^l>||7L^=>g'rKP_"M͜piO{ç=>g<|NQ)|CAdy_ o؛ؠ~|s` gX^C&߇p&@ i=};d;ۛékM׌|//GGa)>Hc~~NnF<. 3EfBMKE8F1 !i(vi$dvdK,P)36fnB;s[r7+[`ol6N-JOQe5AWk+ό2 DUѩ |;U/P' crl&y^72Hp7m76 Tw\G z\5<乼jp}p;wNj֏h,<j!]ݫXjs9}}:'|w95O=T|+N}wB6i7sփ yRw}Db1\ 9 t}>I;M^zNf7wN(87J#&䢏.,LJO>#*cAs/7PXKa`y!_#7^gmR8ExٮW'|Hd|Fs3؇qـg~ M᷄ݴމyw?P N~n 񅿇4_K_h<ž[{3CρC~Qu7vimTDx$'t./2ƞurC>.U; ¨nd"_ށ)7Xb,p]Wx l?~Wlbi3Pr7ue7xLw'cFS9봓`f;Fx6YT*4)_=O0=|徴HpLuxDB>ן~6q LJX}vVڬ;mIK0-X49x头+*"YsnwǢđ3 ;oṉ<φ`xmBIS(/ZfO~YE ^-}' sPi{|@_wF^zx`OU.ˈ ʷð9ܬdsDL^}2tKVp|P?So[*e73zݴ@]˾,tv4]㓴L6g^5y^0A*mY(DI/\뗘N#yp ~#",4~nj".KQ M~w۶i|RezqMx]MuHX10?8˶}}G~go>p.3^[ԅLLjv ˯7,t,+Ґ .S" ݫ 7:>{Yg_ >3Ϙ5;\{toc2_%&~^û/*.T@-ʨ_kSe\(=i IDATvck2:y v:Y˃d Ī[%>sxr;Ź\M/o.S] Omn!ʖX b.Zγ6s*ui~ŷԛ={^nx7|w^7hOԞWq,*`'ϓ 9?`ex3bCPQ $nܬdPWqKÝGXoO{>|sI{bKcc#k=2{U'#nDUsUrXxhHx0k\{NM}/³5˼;&PL?g%0͸/:zO }.:Ӣ(U~#fo2? oQe σ1U m&e3z>jSQ%X<وQ\_m >@ iܧ[N޳#dqj.w[.x/zh*&ߦ>Oo8n ǧ(IG{^gÉ9O1!ˠI1q.vXfW1,֋3yl6٘oĨO݀:r^׸+)#Wս~(N`.v&_ #k`>0*}C\T$#M`G%%ۦNʾncf5ΐn_mjl?h|Z{uwB>Az{"dsRYc ~t`s{e< O ~$|mxCsZ=qDs,`,my3MK9qY㭴:q <=dxj:nø;d?w) Ф>DPjxHMuxN>U:vZxVGɘU*S._<bm/=b*aCʐj?w]{X8K@$ r̯@`JV=^y}GNd^n N7IE%Swr=Go}a1:Qw!oiJtrnss 6P>9_-|Bj[vl'_oJST-r| &d ȸz>k}@|.ѺOpKsǗMQ5=olEǦmcb#KtxsߐB(n ĎmgwSc v|_;)n7Î_ St{y4ss?Ѐ5e`ګN.3jz3fs`a"/c ZA4Cn8O{K,m \?4?9\ OnD!J\#+;}d\=_.zZ[^ξ{D*U Iu;aU,RwD KĪ;yq;>%ʖn,cqV4.dܥc@{.8(ŃvOXB-E>^a#dJ{Tv<]]XtId}1^ASRKZIT+A -;\toOuW |mCUۮ"BUh'3~Ƨ*Lׄ,*/`W9X zo]gmEclɑV>Zמ]\w_^u?=}Nݔb[> Ml(r~gң[.c&U.Z =+'rUQd}iy;Uxk&LΓ!4 ~/"C;δ[횅;%^WL@ !#>@c墒Ogeg:(.^RލFc*z$/|b{a a?7Z+dz14~ ^~}#8ۼU<ҮoaA׺Ҵ=af6>ض5yUېxXeygWB+c_Z v+&5+Tgw|7~N_yuesIu Pb;iv.N2}0_m:bZ?Y#9_cut/T%!/=\w⣴~KFh_U~g>6Y[{K;qIOs tթ 옝!UG:RBΉ%N g[\hw*'6>pvcʜY{+M0vShrU1Y)!oa-׆i0EX"NA=wcly*Hp/% sg)U'bm[f-h}XÑzFKOWR" vla%בv*NN |0&·c'ާ[xsv_u @>zj-@Vp<3[Fxܖ!gd3E_ yDz{o_RUO9^KVs5;sG蛨|mH7l6ƢoJ}A n7,!V?>1N)c &aR'&8Z;zQUf83l9NX/d}(Lv]#qOrcb=ʑي {ҡH;/*e$ L3!l'q<ﷁPEb9 W4}1)P16Zͯ8"S,}^}aO߬77A1;`=qe6.U|Jjiu$ci[x ;/gg'(q1gh_|@1+W3xE|bJ(ٱB}wҎw,c5)|Q%d},VͱJ-(F]@o[\K%V}/:g!w=j! ) ':ɔ@9wxW]M0DT ` Dy -a( ]_Q@g41a)2mbcosW z֙<(Ylo3{k.n{7T߱u5=.R磩]?}0>ē TY2gx0")r|,ԴjUayd-F]KlމE~$?!Fݲy c%grL 7L?[3|EQ&U*.,c}7ʻVԫ>_%wXVc[yZ2Q-N08N0mʎ߶I67Oq|6yKivd_ lt@߹hA®;H`tequ\gK"򬮻 lw,3@7&82$2_ϐ8,%CLI75_oc8o!'o[/Lk۸mg/>ٴ`!6;>Mn+1\,;>\8=nQ46);Ǯߺs}0nQôk<oQylk@9h3b<֝y^H o&+=[;}}W|`}TT1b{G]HroyyJ ˉr _3NQF+OW=̏ *'ˎY'KtO@b8i5m7/DK8V;=l#<0)`\]x1?fhOn}db6-,V.:Wcs-+=( qD'|G ֲXfh0g\KwWg]$_q`ǐ#'!/aHZ7j S!"OO0]\OۚIޓAvQ] *O(E*b5e'曼v{1}❼]&f(XV81m hu 4w˥s$k%4g;Ljh,47lq\t39]y =|zY32kܸ)RbGJR~'UB+E9yBL,2>WIk, dHMgbL dwSK(u#ZON07pTi0F/\[8δsOp]xqGi2ǎ$|my&Й~8mkǚd.LN|S"gKW.ZS+",03q'9] 5+C΢:vKϟ3vn#09fGPl V{?Ryu[RgR8 ]_*wCa)QЃTJZ%Vb8czc08>cOt֕dlx5v %_>ORdxqUoȘE3ɗF qa5O>8?3{HIx}VX뺞3!QUo$\ w)MDos<U.8qQ&@ xSm+"'g쩿ՅLO3lw#vA0|_?1`1  avk=a:^Vaf|M'Auq%Է~'}x"l<[]˻.+ХR7ʹ"$'+Ek6g^0&3,ĉ$=]!ge@ =%\𩝻㝯\/Kc*b{.?=G抧?+xJM0ErëI\1[\~4i ހͯ6;mkK\G:C%Z懶S{(uf6(^՞į~ͧ@aHWş #n7o^)9TxnM•KYv{:?lV9v%[=iz5nȇ Dz7@@~R[o@Ps=</V19ITċ:U=T&$We9JcTCVxzzBdvɬpU*N~sWa䷥Eg~R|(Z:_ۨpd뫼x hTÆel[r6![4O=w]z ۓ>s<#zTz-7ooWKt[Wu'Iy֜s# ! L`m_1.S[q=q*Ц7A䢅e _lֶtlmk9|ryG fնbyYo"-$t61*P:nFk"dRNl/&_lj-y]sof۾KrgCcU"S/Iw BoftafP$q9YsLw&Y=l|2$G<3TV RM߲Qyko!4{ـ_gB g-4qX&Kg]ݖd>^>UjT08f2+(Z{SϳOYkKALf~Oo#c_>d#љy09E @/u uמ5W;NwS؞?D IDATo5~C1W"7ߎP"*+mq..џCAmgQ=Rpd̰]cnaҧvfU` M,87s }R ]b WNQa9 P dˋ"Hplh_c*Y!/`JMUG#YVJب.OK^Guzrsz?е#c0]o⯽$z>Aۺo%M;tUdjFoa8_1Bn|>O$AajcP[g61sDL:{WNWdNqn"c Pf.+0] Qu\.R~rg_ J-WϪeב3|4N5mUM]?ke55S}.R(U.ڌib[_!Pcݯq朑V˵*pI-Գ\-񹙞6>?yo{JXӀzvUW@ s[E t D!7SVS3"f0˰o*a!"57rroߣni(g\Ԯ_ۖpha>@Fښa2IϠ 8#(V|. $>(ZCS]52IXf ȉþ~ϮikvE}u{l۲/7F϶q ζ?bJy>*Җo\K&ɑecgYOļqU.Y޸UWjW}7`DuHg)Q19X[pOvU>;VDA]&뺃۪:8|I?lhǎadŪ-}OW?b8ۭcǻ:_otƈy}Vg. W Y8)EkϢȻw@OazocG&d{_o!;蛱[9׌YjHaT̾ogZ\OUȂmë1;˶7yrH~L]|PzΝESb*㯮10ϸYTڀPa!0 \jۉg#@Q3xqn'BX:B(V_)Q[-Jf6OS\Xyi}>ƠaGkF: ۉ8}Bv(X _uK{gEZ*km7 _c1,-EY 682}KS+AU{fNjό''!~/ۣ}j< Z S7SS__,F_qğa+nJPÅ_>bpkmg}E|KܨYH&<~/Wb Д9Gadz˟f1V-֫rF?b OME<*A&=+zrw~UMq_sTrTuUlĶ:uB;5vl!˹K,=>LΛ$3͝-v[}d8Ǘ\8>.m>1j3ɛ?uaLTDKK]c]{TGz]'p<۷z/"f@~ڿ~/7 {ZN_i=N{Ф:"B~Z>jIU .9q )b6);'䂬n: T$sq_ކƆ(:Rz㱾?lx(~1.lF|nng/m|Qď.ig4lEئ;O2)^XELOKjI%NÕ,Թ>ky~ykg0G|2g?fע]l ޹ qx%/SƮ=]R]џ#N];_Q@ ^ݙq&k^LYs!^ ䷢.]:UrVx *,V?.xz!`OV'xpU!N }.-̲kN8=e?szϭ{p?GwҟOnz?Zkב'tг7 շԺ\D*.}.sC#;Qrh,WEwu ԋk7'` -oASe!|ͫNo&GCBoez5GG坖ae'v6 "/okVXUa<.[[xk0 gVp v.-"Fm L>и|X*zVdVVu+G/OfBˋԛl%!BB10{b n3dVm5[ŵ~@f3E- j4Կld29Ux\+>UG3ևpuM㰾 |_PNZly>Mm} pWZW-v7qRQV6W&N1#\ `q꧜X\Eޓ=oI?>靾m_ ~+|/]A շכ0eb1%7ğ!ϣ;GsMʓL]F/y'Lt#x;\oSwkɯ_ޣ k=Ee_YJ<-FSߐ|яhp'|1jrkd+KDMawS>rEM]]Vdi`cZ]y)Xl_sEoKϊOϊ5 ^2 6CF~#1Bg96h_EnFn  2yKuzYsJ; CAAӖ-;l$ohcJ\ݰ&8~+ ybT~eA93o(&◴x}b}o".U$~Pkm^׾8&72#trnUpG Q:4ZYi3~p"L-Zd啽@D_ zEigcy֨LVKeewكpT;H z'ьIjUSww>kW{t~w۬Bi;^$[<$diXns*yd`s6(j"/eq `.q*aip`*}`:&r1k̰hw!/u7:0YTVG7xze̦Ox4ůΔ WWڢr ؟=mnv"۰7+S:'n+/T,Vt%UcZGyYk9L$ UI%IvREڶw/z[E8YدSk۴mGU;Utg6Bv&(cEgt4%'{8VwEt kt~;:;?8%hQ}VOr !\Qilg E{͖WD;{6_Ӿ-Xx83bI ^~pO|ž++*Eq8/#sQ^krj4}vT|k[|Wohߟ6&wAW~p#un'<<ھ0tGҁnAC,ύ"kyӟ3f<$ӎ=S~nF?Q 0~fݹP;d~p&6;>j+isg?,n'ikpiiEfYJavoq)z-#6c.E03>wKW%ȬE Myo4‰a8sp_`9-HOf1WGn&*}w^ )?iD"d_P2,l6O#mCAfd_C5[kE-xކv>r27!^:*O6~qlN|1eeYKn,H1WCќ57o$X`:O6X?.{P[{vm𱼪w'ڠy^J-m40m(KKd-j}u\7ulz8:;QC=x;(:e]c/ W{_M=/ a`$\xm7W^ : ' [괾Oxͯ7+~%s~?͒ ' =pt G-c]Z`m`:Sf_PW{}4~0|0 ء7er-M޾6ٽܿS1ŽCo| k/ZlѲ?- OCl2ϰ v*e:5=ӴM%Z J?]ǃ3=wJ?}n ݽK|SbDʭ^UF']K4>?U҄!9MʳL.m'ƒ9v'T.?9 %x3I}j#X_N $w>Fظ}`4i]DBl]g TxRlK{9'pwηcqLƆ\;MZ_|Mp t{RޙWP90z^Gxljo?g#9,v^6lߎM!x埻b(/(\ǰ_~c1ɏKP>hObߠxlc{e"p7 XpWHgP^jE\|BV_ڈݜ%e[Ut:@I?k<#5aɀ^ȃxPn7,|[/)_]/O'q􅘞:ZxTPkk}W3 mݘ?\שc ˯>E<6/۳>e!6 wXxYn;H7{1wtLa~8PTO@g:(0q4J23%зR蛴پ /syY}0#x#Tג{˯/^sklKGz3 r7*nn|GR4ٲNf?p\/83}T1d\1)fRަ($xe_o+YrBZqoV4ھPt<M6bDŦr[Ȭڪ;T)WNv5wb&W*/{'ͫ}j/()7?^OX975lѸ<v3Sϫ+}cm]9o!|I]1C@ }^|\`m3𹺛, %l` |'Zzsh`GfW)*vnI&GI9:F!?]ys=hq1u_Cg@)܎KDfˍzיxKOCXd/ߞ=H(;@g ;81}(i.ȿ͓'#Bbeg.2H2/n6l%}⫅l8M pbDž܎gWG8=fxerȧhڶ$B2?7ȕcK/5]['<U乌ۇ#OG2)kHFߚc_e?4݃lmGߊ|#[yi ݒ1 Ʒ)C5c[I-j11aFwdox`i kr8G=1NJOqw6Q{?sgsFlrVtYS(Sbx&e<$m5_+t[;4A|6LvDΛ1¡AII4VjE16C٠rM=JC?OWp]&?B«S@i+Aa'q}\u+UyUVѧc~]/y"w*غp${Vt.t܉ŅySYgMoc!H_L?I<794n{f}ڒ=kq`f/{>m08._3+v*(02oJg>S>No϶1<֛(`cFkylYCpu)ڶckOdt)SyfFj#R{2v?:_Yći6#3g$y[ézrsx)$G6ˠ=D͎߯O'_%M?W Pi_`|b ^ !B~VI8lM翹#ǫ:B$*ԛ[KOmp1_ٰ $n]Zpl&?_2i8[x6?&ACcdLy,QcQK| ndd[1Nʐ252xlBQQT֒i۔eQ2^=Q;k7ƉcdΠ-hrl^O' ko5bӨ ]jߴ"~c WqmW1~#372޲Xx6'ᬮAD >UXvLC62ݜ7_ߎL'opKvڥzv|(y6T0v;w~H~HblUN=P>r3S`g>ҲdsF{C:ϛu}8an^|3j ]F;ON|a0n2swxϣ- 16p[:Q~cmmfvc{XP|⷇9?t`FNl:o 4cK*8 >^?եWʴ᜼^A^yZ~ߞ=2~O+2'N` :ОTֹ{[lcWGؗU~>; gMa-5u9 eeYEͬ V0<@D:"8߷C<"-7M99`t?V .:]]ͥQwPtPSAɶ:Ub mΓC9H!ҞtP#ozo]g8]#㣕ꁍ{ã-f;L8Y͐0et{q"yL0~W#M}%m9ئT pھEvx;XoZXk/Wm{^۬JHr?R+#@?9W]2/.SkxT ]?u]'"n Nw= d2kHx*,8'T'Hy)ܟ5\y{15dW0C[īzN^)=-N=A ^y ~Z"lo D(L//ϡ[I{⓻!O2BE N&oc ?^bi/,D.L r"rN>G>ߍ7[{P݉<2lY5\Bq%lB׷ʐhv~amGGOQg>mO$d]9̘O+PCGEowx=5.=36F{ 7F"xa \C\!o6Vhr0af~KuEY ƦgebE3B&mTmrMza|jٮΘ\0w}SZ#y`_֛&@vn&s!`حq;!(os8d:ЙSxֻ[Be^l ~5Y܂=˾el kWCWSkWź[Ih'Msڷm~55;{@mBmmհzahUB(oP\l7 ?z*#I[tRªng>G,8j6~ܤG;?23> k<%# nh7yi]-#?A鏄HÎVy*h- _Mbס1.^{,e2iVI㰅Gd1~s)Ѝ)TTX[~r)UvLAZ{sy,VsyX_?W1WdNTyTCB U;d˜~vǦ"L?=xowc(_iUqp].qwIH_wo>ˉXNOBb_HPd+_hv͗y2wk὏19F%!Ej #1,q@Vھ΋іKʁ@c6zێ/0o-^g7 )7>*%*ّXjQ,sVJp=_K>Vdל'=Y:^ 1^#u.i̩؆?aZ 0ǘg-6f]{?-}}.ݳv,@ 1s|hOƆvd\m0ۜXYMKnĞ8V|8x lP}|t[14X&mYE|H-rH ZT" EAܱ2/!/-?!k^kO۝ S{_,fW/ڒÉ穿?+u@6{v2AG|j8?G@WlmrԣH[@Ue>ua4 ɔ oLrz<‹I'{g㺘eU8ԕۡdk*9`ɰN+%bn2G~̄G3Z?¦"SÂN2,O{mڃ\tvYzPL@)@TV᪵=+7S(S^Qg,I>=kxp"^>'O^[͚=Ya{ZJ:¿C|u)X7;Y >0ؚairHM^i__'u:]WN%=ǧң->:@h.}`vL񍭸t^xUH}>}a֩_֎KyC M]q 탷CLaVo;Mp6(v`кnCGuu1\;@b{9Vq w 6:mǂpNc *xAC xׁį϶!=;}-L&y+z!N|ye|ZLBN 6^SӆںtVo_yLvins'S-[éoy0vy5-8P8he23L_?Um qk[ QÓy;W56}a8FӼI]pl1VsLpd2]mO FIV_o+-NNi3ʣ(#Fm._!J[>9bۿ~_zOЍu$FER}q݁ZDdɪ Y%JCdIO˲!4km=:?D!+]ߙ]z[IIܶ?z[R;oe!m*^ :%uD27O1Ʌv<YtƺclN1>dơ>/`\ƿsڟ6Osm9XZ7.Cde~EzX4hÚ=J pJ3; &nZw J(SE/Ӫn-ᰯP}{W$:&ɫXw%, YPb˛pR֨GygW]ˉ&Z@Eml?G_/Woy~@d>XSi{{-b&]kG9WݽQ{fPt5@<:%c~LHHS$?a#o_am[O?(9QHHeA^lA@ >C$tn̟cr{ 2۽2^ُ,m_}tTmVU7"‚rm !Zn@D4C㸱Op>)#x ξ'< lD.oNvhS[)oW>~jnj?Ԓ'I]FGW;8_ek78˂*ój=`'_[쐳:XRջ U3z#~Y8M%FhP}?@4o_~pw~ۧz;ol89cg'sl Co~6CGgJtG!(zFX6/&'r5ˌg[>25k4߀˗yv;6X/!шPFn~+͏KXEL d.g{ڷxJ#aDOT* ;lYJy)- IB |9[-[GIC?4z2.tUqD劼V- y'׻7Z@׿k_IPӜÉHDp_wo#k#Q41btWwpJetw'*Ê<ɐQ%-J2'yD OdX}T*/j ެ]z(ϒxCsL@ 9 ޤ_v7~0 ؠ#/P;$ىNFZRWoKjhVJ'ڴ6͛CQ.@A[Uqeg-;%<7ROt*%ki\:=C.GğB|vQ1 !}*Y};6^-dY^Й[>K'#1x(D:`me=_%u;=䓝H $Yx+Uh>02ӽU wNB<\NdOO<|4cn1_JOpȂ-ϥ3#b-P-b9 # 62tHFkrޕC#iډvv_X})c.ickXx`MvqPS{8qS4a:1$$?(G _9&A5D0άMހ3Os\#6|-iI=^+,xcHd!z?%(DviZb?Y??tY1Ŀu|b>Gh=ѷAono7cM>pllD._T&Z'@SY2D=TJ4C?1Blޮ:D6nj &}׽>ـWIxnm IDAT(ZU}﹆և!XJ㧱'Yܵ%χy6摊_y;A MQɇ/jwtj<^b9\f}лjnC95[cl?|G}O27\Jh]1} ij]~vYmCNVW-lߡB:fyZ5)O7]).S>/4ħsf~ZfIc-ֈe`U<_lMrՑb'l*21仇J>qtD9~Ocb3-P9TIjuP` ݟ7[սtv/p篬/Gםgcc˷!i۸MC&>fB 0Ou6Eay?uݯWi2Gx8"d,D Ecl'eN2؛0'q}&Wxb{j[dGPfF'NOƧĺVW@?9=ɱ-su {h=&m#+{f'mҡu<ΞM\úOQiTʋo \$h>PTD}5靔;##R^6hz&bjO|X+K6|5$mjY6s(XW!(v5],ޑ.sզ"ŧA+|]UD[H0YqF'*zDy:eW: ;ʰ"'1Ux'iןx7q rmw|zb+/ä538/{M3jY<Ɩ3|&f|nL[mb;n^N*bgcb"c ۊUF'|[7EvcrFpjtv ٺhL-Sوoqpn4"9=%?t{RWl=r*f-efj?nT (it?ٓQ133ֆ<+Y,KxuMIAtnWx {9ی1*Ա?"y9C:zR?f~lQ)hJSή_UL In}h!)y~%|Ci^}uH -eoU<1ϧdMztOs l7V L23v9Gw?MNhXQ8WWML085wě`@e^ m3~Oׇ^ku~89ŒB,4_νSy~]wf|oٵ  .l[:դnk,:^m4TE |N{\]^|z LWV{n0< #ο?B_҈Й_GQ}sO(֊3o2}rمF/0/֓f$.mOfOY[j0 b"nbuT|„cIm6\iz63'w `/W۞1DIHޫ>NʆD{5ٓ|t>ΓkD:+T$qo's(?Md'}vJ'8$Y~@ OCI{.Od/jyd\l/~OSʼKW C=in`A"wog-꾎9ByE~L&#i z_95̷Е|N{H1ɧebe >Rybk~z.t!N]yq|ޟa:~Kd~6#{w2RZ~7S }Pqq?6-fkKsDxl7U]c1*ɧq4Վi֜ԃcW ,5lO4y 4s^i՟HQu_HPr[=UzrL BUaѯ]mXzʧ\$,xWVHT6-9>b<Z%ںl.Wv{l8󱓐!&5/7@Oi=ae87z13=xΞ|LvÄ NU"2}6i\;|FcaNIVom2zv¨OJ:P cK=^w">6=oiN;ȟoڕNX@]H!%{bJWY֎sNK !Ĥ3<F+r^stKzr?Kd؟e񆉧Z50VR=\ vɰd`Y}3@5|V82D[?;t~6VzM(m]{&;e3$]GyfGٷVyY(vKʚazpNڀdx3oq!|ں-uT2!FGVs2u5}Fxm4^>?-}!&ߊ~{ =tP|B} )yU!+ERƍچ_ÜFS%:#SVy+T?t&` &Zvs^n#˘JգYl f,5޸Dťv;qGNJVS\ hno?s,+Am9Q[5̡Xr}tlmRfԝuN~΋Wi KfˏktZg5(Ԛs牫=ҊJeu+|x<yںyO$r8=ͫywx=bJ/c_[qاM'L~St<AcCt}߄Ƈ໸h\[a:*lduSgy|j >9I؏zCr[ |qOZ"0ҷuo[ DNNcA:ر{皭 Ӣ997ay{ IDOIFO^ur?vpE!@%Q|t4d 'sh8~\ ոﲎLoGIYkt>NU^#r(;nU_ 0rF93XD:2^bnECK36_"|>TSJZ:+6xr9 %e"+ `QF` &- ɞ 9 \:L[<;4-=/|vzdqe8^aAH_Ud%>9=i,Bq+OE,Km 8RX] 9Clg8e{go Sδ!ܿDϾWLkxv[SR77oB{Ew͖AvhL=9뺀~L_i1;}apFuvԇöQhJ ?ܕU!8 JS V=z le/Ru*| HKimh#5%x;GM}3WUӉ9{f~ztbiēn'J&uUۣ&װ:59FRFc˜+>k d2VWeXMxe+slzw(dc>NTFV*W_7>E Պ̆ǒ Yr Fq_^f:_ed>G#W:Go6̝)n}a (zqi FtdA 1hl}魾|MaݭYpcZsD t]X~X$a u{۳޽~ӾًA[~%<8Vn {Q!zc>(UiF6,.N؝㖧yW=~H3ݠk9g+D<#W=xK"=J_d@ڧc3yBU6V.ms.'TCNU[S?G5/6OcueƁ2̯t{an6}{K̷3|1Hy qoUҘ|~ĒjAB+S?h'džL0Ae>Ҳ,MKM Gʐ|ju~dϤ2:t^ U/g<"~_jxp9?8pCMs![ -PE[z.Y%&}{\K7;I2H#u')] ,K,o5{;|&F } 9'GhA>#ֽ!|,o'&,1E\Z1==Ξ+^˃G}O葝dpʙJ[/ mnl$>n]}s-X9R>۰Cְ \IP=oEfN^[F L:Tr 2-5 skL{>%ݖ&ǦN}re;QSEJ =b S˪v%YTyf{e6K톢Aջwl2zA{A:~>Kɯ>sEW*:bBڝpVh2]wYboJg\'Ne2 ,n َq`U4g."3=U<~㕵'*C^/ߨcd!]zLeⳋ<=GK؃~ĸm{'t>>_lk!DnB; tbO{iaV[kWPVE&{􆨔@j{o@C27jwloߧiϧ=~Eպ3فд/ʮʝ9^"'fU8~!!Q=F}Ϫ,[YA_le{>@jO vU-wT=2Ag?OTGeȣ_#,ldLN9y~"C|,N$Ȓ6氻'`oW./ӹC}-nziҽSYx \; &2֨r5 &S5rrsS%(rLiHU2VF Z'Wd2sY<]#@NLD`Z9X'-_:V[ƼdΧN["WvMO ߈ESs#|eܥD3}ډ#of1Qʶ5I{ʫ6g}Ұ&<ƻw>_͔n6\À×{ߟ޵ŒA X6n'ss^JZҽt]gENؾZ3d7*/Ƿ8G%<¶QաF<+X9U[^9|w?gv<^BQS5Ibn5=ݕ]bF^H:=?sk IDAT^k& (;UxD+$EWTUNd޻@ONYt{C ?^x9ϴ ڠ&WtXMG|+X[X|ƕ+|gX[)YD, d$i;4v3lO &OU,,8# \R˲3:VyHUWe=ItL56^s"k?wkX^08NG )oL~Xh[ ( 5P?!^=eJǒmw#&xv}lG,wr%י¶,ɺ guߤ멐fDGPXa [@oOyfۢ9)ҽ/\60{ZWlt'B/໓M.5 N,;m bd7L'i'\ ϶GނM~T6EvH;n;: mWexit&pï`;8PĆRtJ2#-߯5,DF[lI{!1/6)-n"Zvh$;JmS(]X;.Ƽ(ab 6U"PoeEh=+b$>x)>BX/ o6.7`Ɩwi`'2Uê5w ϡyGfdԴ.nfˑ )I+w3Z|r:yY8_CB_"eO-2YwwpuGVI #Qf0&OM!oDbJ뛹IOM[mE4,y kr2EZd,YVrZ‹˂q=瞜![q&j,V;3D^؛ENIG?Q] f]A$ҿM8hŪną3I?9CUf?m^ƨDgsSIC4v9^ٖ}.Xy;f=/_F_6aO&tuc>\xwI?=ԺqC;dsާ㎍druDHcU|w;ѓ"T*bj"OX}OΧPnܓgaz:fz;(X?Cx{L&F?IӛazwB4ȭڪYOy1 g# t%/ͯ8n**< lx #0 ኬ:tzqFvC:`6ae%l66X@"1s;$ݥ҆{!6UxXz+WR M,`lHMmaYmtdi@v4L y4+KKVFaj~v]Ϛrk8>v[]vGj;We=VJ %0Xw#XOkx&M> ;㶉$k14?Ԗl:2bmp<ٽ$L'LS(nOswm C [\>>/јq'?N.T8;{u!&_igJ9o&|F[ uۿ8lJ$yT{|(DN^ηο$Μ})zsq ֆʑg`d+O #[^{Z ,.dfd2X v)l+U{m9U7V#~{=  㥻}rsQ?P*%Yq<-7pŔ4Kr(j,YgE3[vEh秚\ldF]G2k+qA\.$nښܶh;bm1J0]#$?r<#s!8nn60.RXfR赝&aUi;BϥNʢXrsKh;6\G'l?5wXP|!bp+*KQǯ)ѷU:? kTۍxcZ}n; e][EI?5wv]'wև6iVƨ]J)}> gmIr7 |NXx??q;ۺ2g/ͬo.?-~ y|Q?Kk8?lhƜj-Iuf)@e-[XD:hHhK״#}SGluGn% Xo>4z}vg]8j|}§1`Դ=IU,/8?jo:4j37~[pӿvsI5&qHv *Ɗ &=kcxyMGUM6it-SIxVރ+,+~QȎb[mgÍ2oW!2i_xcl*_IҔ'+A·zWUu6 pv󻢫B`?9]{BK*?Ӷ!rpOƉ tCx8_37}1c=(kα,ny5-7*@vO:Loa]?mj,^~|_=+3מ޴Yalc|U;..acXٯTNN,8}+S-${eeXj >33z֣7Jkv| "*` ȚNNN,,4j^ !+kW?E#wVᒰRJZ,T ![s!,YVw8<͉O褷m`{~12Ԏm$a\1{Q.&P#[G|,ai\џ!ƹ|)"JH]ӣ* \ZnQ*`* ' ħtERy&>E]q$n#S>~:XrvY_$Β6$$_x?Ӗ'mLކqo2/GyR$o"y1d{ 3GfE_L˫gSx}ٛK/Vwӧ5rc0mlı>)=c*1M]cnq~hiuȱچwmbsc; z>(P;(౺?r}NWFr,BWS{+xҴNa; =cOꋇ4[qydzm8]'mz*oK:>M:V2S˛Yj?Y{.x_0Y;O~1v P`0VAR[Lja JD R%NwdFz 9%4n?'XDѺ՞؞BL-qxgj_6(>yWӓ.Z?V#5+$=IFMUP֜-L=j pmښ6J: %6z?A<}Ÿ *|9<ȡƮ*p#ە>UUOمml͋^ujdbebIayHy SESM"j[wوQ%.Q 8ɯauUO',m6t/,5k*pljZYej#J@YtLɑNʙMUIf>+ϓl` @Ft;. u~*|ŁΊV}"V̓ZH~UIo}2,J@k/N՟m{"# ;Fx3Y=6F 7]]T=eBGC.iGpd'XnhxG{vd5g2^.zVF5?8Nshxٱ%Sy+{Bsd9sdlڞD *J٨'{z~uPUO+~s;7(U[,KOzL*{E%f tUWw5> cɺoWPĵLWE|PN[U] TezyqAFO=pxfHQ[PORu:SW/K6e!b>Mb#Et2Iowb+p<9B>-C<7OvX7%Lq'kp+zGxR_ϑ?4Ns*P 'jOy~o~O~'=y_^k3=jn`xHcs 6*t䏮) zȖy/ g:]{z.C 2dl3-rf+vx;΂7iQYSO}1* C@avD H*W60 : V1,C[Hl3<k:áYm*c#Ɔ݄L+Ѿc[ymjR,ʀa,z2l)ۢ=:{|,/i2kTmLϒy,_[YZN; }rw _xLWv.{aJ5\:/s; 0o6PdK׊NȿX[M>ptt$gq`̶w4 Ā~ W=ƓO33_*Wf΢y|_ϓWFؓ0 CY}" چDXca(t KUH:лh=s/`=N ym0n3m Kwb6Űog3ҫXfwc}k4pijӳnI H#̶4aF ]x¹hZI ]͔-3#_q4SP\$irv|% kFgNgae|'NiڪhFQ$Qkm,<}xlRl3]9/]?L "cY 4mYkX COo)?TutKNV|uLT+I{`݋a>y%!Pk5ipG:d$!'P?u kgOӢ1?kط'z?:S/Wn+RION6~z]`CM] ڗu״.P'ۀp6hFhe?t2|i osY:;!Lh: ~M=Oޓc.矦");$bg}} rH-c"~W\qv^ :|4Aqު Uv bmi= #szm../m^k/_6MG"=K)o$߂_+8֡j!od5e"S֦;})PlʰNj1`?(/sՂ7O%m-SeVݽ(]gІ8PWqY-b ~?Me)NǓ S]DOZc AmMs;o8RӱeOO5k,x6:1bExO#!?a 0(JFgMHCuLx<~Y[qϜF~s;wؚD8m_V\ӏ'ߠĈ IDATJ UrwK81?Kvs#sIμOm<K# oLu\ϮD fŁ0 iqcTOCYmU6"9S$=J:6IOW_Jjv oe-V}>|Ֆ|f^5{aYeܔ ~%A)>1| cY>8ÿ̦I&yvDə9Q4lX&eceoV.؏i@lsƅ8 _\?>ۏd5A _n,L=m]3򧥯Q?lx@}WWև\GN~10.wobugڽNsa{M^Y[*U=k!:'?z+$Y!Uwp:=z@읞Iɝ|Q[SI>PcDNz)"‰Ĕ; iaphMl%)'Y'MɼU8h;{frѽZUq,oԏDKo/׉`+j \,:W.y<'φhW'QՀ*QWe:3KK{9VׁsG^EP82_4Q'D*L1c2s1"vo&Fty9"6cST3{N'|NKnL 7`Pu}/8 :7xII B $>ɯȁfxډ"N׋Sխ7ߡ\X~qm{5DG k 5*%pDEXLb[[Z&ؒc i6f>y_'ۤ#e1*/NO<:~%s9VG^?Xt+6c||sM?fpr.⺗"]g Whw}#h6;lqq #4wӇ8Шmyq{^y,K)]u eYK[…n"d |MMK#?u0, H"Lez h.p6h$ aqI$EQt#ڡM@sWizj]U+QL"հ2FQ qW|U ~Uf (,k[55PbȰ ,ӃwȏE _HU>/JR 2yp[Կ#NyK@ي[#~j ȧ_86cgnsb3kݷ|vqDo6`MBxԿځlW~/g fX 9 ӂ/lY>{m4;o6ϒ}P4֩q:>Dey(#=WrZʉB-Gԟ{ϠPA^sw(d_K6{+z/65wugtS)'z}&ek>ݸ4]G֜z6cJ+ioPڋ3a)aFI[  uU롱poMO6Ŭ; cmu&0vRvNoÿ,&tUWBf]9<4tiiѶcH=ԶWm/ڋ| 7LRkw %yvS@?uVdE;ѓO,cAنߣFY#q}Z隌1񣌕4K^߶+6Ka,s8S4H> jf9sL a-Ag)`z6m{A=yҭ/b[e ŮG/~ ھSf6="|C4"{rNZ_H{PKdɸc =wq!vL5ms>x~!0V^+=V?Z6 Zc7[|IUI]Ls8ğYl3`/J1 f!l@2՝RښJ]x4s.8 .R6.IUz<mdժ\tT wL%;۵m\q38i+OؐC,Q$@ͿNG/FȠ]Vg04c{}(&a~O|> t1t%Xȏ+[ VƗ宣ʓ2~'j3F`eҕ5 Wpy;ޫGY9N͛zvH$o`{5 }(9~,N5 ius}qーSRC@8Quz|M{8^k7TZxVem5jbrapY(ceFPcFT*[ç<-l+7D\ג#ަ GUOp/d'[2WeM!Xշ `AN >EO&sXNz9YRQ"7#.QW$OӕE9}5xqFZ; Wp5ݨ#YN)>ucBL: 14I,QJ;+ %C60ѽ kz*>Mאok쯱.^1`Y&l3G| _}^94Nꎛo8^:tؿ7` qq}`vi6ܥ+'c݆GoU%c G%h`WHcNae,x`*Iw޳ٟ5\81J2gX()o~׿ǻI=3 +fj7yd?Wl,Vnl?LoY++ o>\.Iv<7gx68G=[E#.rd-Ə$F1 Q1+xpY{AO2')ȱ Uߪw dV +B.kՃ<ڦ9El-KuY,iWO":lٜZ !6z a|QgpNc;9{ۙSYbUn 0>u'F>&@ii&m.{q(F 3QcO G]3}11cE񱧝N5606ۋǴ~.cz}W[VN_ <{돷4[?F_C.2wC[GPߖE)R<k*0HΓhn 's%|Gř?~҆BMlܻGt7];G>=O1{nzgcuvmrm(0,k|\[nFڱc۫ j[sق_+`b[61 %Q bS\+ig`;;ۙI7K33Հm~WihK{z}0[Շ8IuQU9)`va|#?z`2$C>q}UxОP>a`p!IZ¹Dq9Sq(IIo=GQy"E\[Y>"mi3[#CV{rC Cpd8ٽks.{{uT}fwҰo bvh>Tyu1 {-8ts(M͏ o.<>6p-# iVXNG= &UKm챬+ݹ?خ-p {./.neʦI~|Ə])m<BЙšd&HX_Kl 5t-?1ӲxI*ʊp׀/?K`+L9gPMB)_a/M`W>d+vHB0:!lCK{kY&P%V{׶d)+q/sL`IR٭n%%_ʐ%h(;RlQ'TOY> &AɉtC;em|zM{.ѫ>1MbW{]S9Opc36>X7cvW~CE`[ C?`xLn mN׿.[x[}z_MQTftMy)ј'ݷ D=2&SzgdX{kM{ȵ;=_4?>s>T+*J'@եE'l  YYSXCe)Mԋ)~yCWn,ju \PٗY`#.W3P x5LAxr/xdh:=:a*_F߹S4ݨ\&TLɥSm}׼Ŏ_AŗaD5Θg%T.%#f'o<ϤgX~gP18^mS(6Y,t?XXN@1^nQv9 N{b9i`oY=!ڍ“E83_>I|,P~<@kM?X#sGN-х$u묃>JFvZ}J}~~26rv̚vEx>N ~\ߗ}x~q3ANދ}(lSNk}TCյ?َcbTLn߹µb&KN`bvTɇO^Qlhbr/!v(>R^ EJL'4;ēJN;_e\n%I7\xc4{ٴΩw2 ~ *T#]Y'HKjkp{Q|wgVQN `'`R6희w`MdbުZ^1L+r %XG^Dȱ?4ݝdcuz)҄$9z^?OXɞh~Sp:Lx|T6{ T}xDR>P9$)vB8a5kB1x-vS5'c(H B,>/kjLs9\X@?y /uȷQx 3;_#n#CƃqES(۾GvU'BWn+k#- /sO]'=&&;^bEY\^ d׎\qFpSLMm-K}3G6"N=: &fE[[5-͞Wv_´ fL7B&}Pd0j+d4^(iDWy:"O*o(7g rذe͇6^mJvo>:ǁlanG-D@;%F7g۴^&W>#=n8}RgZ¯ҍɴ Q ?_9 ߡ GӶ֯2`aW>1tqu 4䈟 FuO 41jgܔo$kHN*b'Sni~/::8xߵ|;Y'?SE1ZXӸ|m;si>Q/~ Q;ZӔ[@ctp)'17m=s7,](=7W.L٫Wt'GS8ؾ="3T~e=ǦUaϿqFd2wc68JHTW,U n/dxӤWѵŗ]G;/EL `!חlX=EOnnr_=)_SqMVQHLom@aV1Ĺ 5ÑmTn>{fA+-Ve3b22 ynIv8@~rc\ԣ_~؟My'[75,:&[d(KDme{L>쬓Bmc38Țu:w?b?Juǫy+Vu'Y{=Fө^IOddromg^܉M?ww4$j`ZUX!_i]=l]Pmc>.k.BkM ?eSѳfk=FXRr۟' n:y#n˚:%=k۬>s~Zk'8GaBmm[>u>wsWm1g-5x#xɤ-pHboZsbzظ>GVok#bQUd5ة-v^1pʈ-7}'=ؑW$)GnX￉L/(U( =CN>H%t>NncDwLcOYg RF-w-rt.wmT#ƃ6$\l}w&2S!"[mi[X> }LH6;&DC$EGl$JGIUWn)5NU~ZNmGZ?^ՁEV*[Rةm'yT6$̀ &&,s X;_dS^4W>hk =yNY( 6bڕ9ۡb\wĊaW-`W׀|=Q},vonl,Kvx|||%.[s,ObU*,_]7d30% P3'e60*._+x<iqfcOTq&$n1H.oAK?onovSf]k8B'vn{qJi;}*_(u=k|)[Y  @+EK? nߡelktf*[O1݈KGoF< HG|x{_$/l:~3~w?Q闦YX ,\mbA>T=,4h#!PAe1יXWo'~K!{P<~ vڨMYOR @`$EfLs}q:'}E13ԅ[L?xW9IR=VlH>61B]hep"ltǼL K~uÉ;ǞzFl` y| My$gGli yQMa!7Ƭ-/ꚵ-Kkt.7}{iNJ=иckײۏ#Uu70kv>GX4qp=ou|Oy X=m] sCLjQEl~öazNvnWuzc4Orh.+οjpם%PqOBKy0yGL(?AhtA󋾳j8V5 [rpQo10iLΟ.ߘwm+TLR#*%J$ka9fݣ(n٧#BY'bxm5?H4i݆pчu.O[XCQE&>8HO\ o1~: [t/ ;J=Aj̰ F5q)2Ok3e^DGĆX-T =ầNQy&eLJ'"x-h,:3vιkՔ* [Ƭ$◤# 8< cLOChp._ZѤJ^ p7$ vF!7AjtUDVӿtv}社(7/{jμE]%˥[Ϣ$|pόC@G6R%-t*WB`SueBVJl&G[n9ugmcT•`7~(&wtCOXFgwl[ރV8zx̘gd:U3uo_`-Ƕ(ReQLy@4k5yE/kla7oIqb!bamHb^龊O66H]/6KP\7R)iZNF5=/l-oH'21@hyQvہosgWk[=EkuSwbgUۜd}!@\?֫d8*Z[u~OϮKX ;` p=ev |'ٲGᔈt zTl].ؾxݞ{$WS5uAUÊΥv5,3zlb[(]+.:jay qҽ " 7 I|Cm8šjˬꢐZYciK;-.&fW}r}{) ؓWmgBD /6nXtSyEq6 tT]`[ 1=XL~#wU2g&X[q% ~ ?s\[kd0߉p<ŧj-DWl߶IʇGi&F]2,^m=`Ǥ؏A{Gmv !^i[2F?ۘԧxc?`r(6ߢgs]mqf| ~nFOQU7Zw7Vz%~ݮ\?Gzoqf$CGb;Ne\_#帾/ >'c$WI2{MxR7=_GQ=]r۩?J[^OOAayyx{?1o4N[`J*j[74w#n#1+HG%7Q@#^Ƽ0pMT"Gfpekƭ&{x ^n?SV^ђ~9A [raƫ]߄8I~u\|._LK#7_[Lo@NM~;$3N;:[k%ak{y.Z|Q^}g]S|zNe;[]0"pxퟎl[=nY4A]4LJ*tʤѱ4])N.ձ֦Tg80[pPk;kb*^6:rl =`OKnHֳ4ℵ eEeKXv_zY[jvz-^԰P͙T9$vQսX s1|56FKܶuuF[`?ǽk/"R%N]:ɼbI72_15/C¡kz ;S>}yօlV|bBM<@?T60L*UIVϏpo4r 1y6Iɫ &ˀ8^b}>Gr\|3Z&mPzU"ؚ ^ܻD7=A#%Xl;A﵊~][G0Ol+Cf%Qo㺍H?&#pmP 1O<|2|ZMcm k~ u2_^;fp>m%]\4MK`|!B{}QT,yW'B~)^ D/%U2'#ԣamS 4^*HT`}x/k(RM2<1ҷ7U$ K].(iL6S,Ӝ롪?Kj4uFv"7i>z4nM/ʫzN H)%`2kfC'76c*sm\{2Wz&éRD<.0KN{_ki\VC'f_]|9I3&]7~϶Ol +t4/_xm /Xxm _שy -(]8XO2lU=ZW-tV|xRCTg"VРd%}lFܞƮgldzP5,iO|ؼ׿}| m0s?.vQsX4Oʄ->PܗLhmݙ!Lan V[=lPcBmaOn:G={+#шiF{YUƲodK:L9yA  3YBXofek16ı3V#Flt۴si ?<&^,d*ÏxJYԪ`:eƤav}B3_(>"&Gd@~uz#,e~nL!9/g3e}AYꁔ@VFWOк}˶3KOw6W2#uv+-|u{܈["3\D]|J{L09l' `<(V07WMً=QKE>sŞMNo.64뱁Wv[[slg$۾>ξ;j}9:s#lcJMν|.ymI6kKo}9Gm|o(ٞū^;_3m#[Azzöb:huI<{xߵ `*%8HRjZ/Om_ZE!먕[2ǤcxEq?32xQaY!rVCv }me=Vu2]7/g_ IDAT'W::9/_QFmXt3hgu/yoRH?P`gr0>D $q+ol㻣cK^iSFT~i7Tm,Nf'(NmSV%892֧⁸ԧ?\0`$uy|%ervtn>5?" D -㐐b|Oa1 MSm18`Wty~dbgg.7 l{4J[;~U|m#npX;E">p OQÂ>jCrǬۅ Z]Mz>˿#[д^}AIQ%j7yXu)dZюt[&w]nVnzc; \]wq>%'. L**x9OR\W=/{>_/(>&%)q[o,LɤifUW߅aK"m1g:z1oM6?%P66t奊{&%ŮoJi垲CGb^tOV^65)nG (æ6^!٭0-]~d; K9y0iK,^؆d'SIKؤa ^}O *eM|z#,o ь‡COE }bn[H$ Y?H4{Q51(m!B<`=H08:ӷ=TXNc<3A(op j.ydtK^~wv##uwLq|7 S}{?lz0Υhyej V*O!;Xe)_>Ix[ey,]%/'GQnԣ3nK) 1~U 4Z+S+GpS ݑyƹXUKc\X$ngڰ4T1~4G:n< Sg29TX7<6PR66M -1XKQw[N%- =i?SJ79c36juQ#\Jdþ]݉n|feY2!I_'.z^9ƞL'Ȩ,!zMbF9:ʅ~5߳= {OXZ[(f^g+Nǹ1&->ze 'ɍ?u8m`vbV?&q6@~S=샩ϞWx\|:#o>Nlzg7ae}#WUw?n>#QuU8-D9N@ݯyd,I2:KG{z3<<@ ԁƵ󽱹#.oU>v=g Fbgwmu052Ϛuae&iYSVhaFXW&]%$7;|P=WXx[>|޸> L>!A:GinDs]Wr",9̈\gci\l]ɬOX=\OIc6"?#54a-՛C˞ӗ͜ǵ rwXn(`K{J*S&@i4xT7.uպYKNc#rj=>-¤ 9~7٧lI-qdOpGS3\ F n[{(p7?5ꋚ8~<h+w'XxSDrRٖxsym4V=9F\.=c:/T2 %?d7R:+YIE"V6nӵSҰ{n,0#~I;R8~+Ӈ˵XvF .' kޣ&{_KX^SVf$HE+׺N|Hi{V׵¯wd;`z*w?I(8j޸N3M%&}ӳ*&gx61Y16hlHTi[z S~,1-b\Vc,6u",&eKrY'tm ݈N7\0;~i SR`EVZv?¸7 eEf~a ϊ=j5*)zQa3l!lJ"-/dܿfH]i }o-?!~Ekh1& VB>M ~k\aCX{])7+}ط>iع/rgH73ҡx(n&GBMňLޗQp vb7luBl16j3Zoʓ#hᰌ*=nD6~/P=m zEk^j3oYߟx/i_A7|Y>9 yKFx٫mA>]0q륯>'9.",ƙдFlއn_e|B+81#\=C7jA㙏=ORn97 \^w{U59&:VgVѦlMզ`>&fÎv4VP(ӵN]wuTw3Eoc?Ni_7[B:`Rxr]qfOl70>F%ٞ׹Oޫzrߵb Q.^EIƱ5$4lXX29 +)a :6:K߰QS0995?ժ[軐+ҪeƪgMZZGD% ؐ N'} .+:mQ+:,JYcpotA@[_e"Xr*,k۔a*[߰qmw0|S(|íU۹_n?h2i)[o$e%[ LOO7SG07jLM} łzA$S}萅♘V`%gCMhY;Sk.I];ruN4sj# }ueOD]e"S9U!@!;)8T%c}8LNs`6%:|C$4( KF4;F]"%V=$1A8N¹AJN_.;ىWCkW(^ݧ|5RfX{O+רlw?B~KԨk D*/Ӎ8Z "۪H?Q~НQ^;MhKR#OEJxQ [}}`a^ߍ]gR>f'Os5ઙ-I@E•2bY9TLV܄-^d!^jD ''89.7sVuox%I]7=CELhNP?WvK7QztHSMpb!X4i]lRX+}x%)L% 2A|m0[?W[Gbc7l'dqØT:a7xvR\Ϋ:uiDU/]:ű_XXcK|zrzdXś>y=1Ʀ,lRݺ>3;]%;t?=wv~>_;ψ ?]17=[;cb<䩆ts=eOOo2lo#~p; 4L}t9̕)%Hqg+_Z+4yZ=tpcPp,uz Okz Eet|݇;+63Lgo/*E0.Ok_ma7bIPÖqg_)A6Fg8/UV ׍qS.w/ yx+e>K=ݳл/: '#^dm3!F"q}xh?UaB7O\7?Z왏&1_.mGp;C#19q|>I^K=j3ue'㷔|ĝ(>͑9>$?23?6F/ȿ_Q+DM6QnKljyEy_y|鸋S9[aKoMuk/g8 ry@ёF7SkE]TkݟcgQ`oݘߑwx-ӽ)L YxYޑqT TM6D(!E1ٙ}+S]uŝrUP[Dzߵz ŘU_mkg}Q⯣I\mH6W$NrU&9&ܱ߱Zi%hoMF>3K_2X.Kʘ)&,f+IGQt܌E~`9;|x^ST&WΆ_9!"l|$OooGiK`e3֢zt3Ȱox :넱%xbc!i#$|6 oբ溹|Wr{Cqm}׌{);?_ XpwH}lZ'z9v_gu>d2_xz狏El#OD\ ru&ia5?Iu,6^P<ܿt^fxa,b֗c<\"XG=;(ޖ_ }zbtŚ;LDp`X1˰KR~ OczI9ռ){^U,lcu4>']&U|MߪxSoyB % ;>K1}q7\Iej1{x`S`7@ \0JIv].HX)e/-u-Ǘ&-8ePz<O"ˆofS{MD >&‰;aD濵E}Jlɉ\mBYvEq!;.JJYqB>g'"-<_817IoҺAimf2KN_Xr9x2մcPY[fu0F(3,/ Z2 o gO@ bSD 3cT?1ỉ);:d|+)5mc*k]?Y! <_ػ4ᴦp)p OɜO-P Kv"IDzPO 꺋eQ0~V֌y(ev]ڿ!1ϚM{S4eVMFJS&oPꍶuRiFP.wV5I)?֟m6(';x98}WthPu^>&NC϶PVVҼ6ke7zs<Ӝd!4g)Ogt{~>+{e/{ű?ŴlSϱx:1|'6a)cvo6jbsb|qɷ]@6>:cu:Gg-_nヾ MarxCm;w<`,6}sR(ƨo߾1vCھ\=UCɚƗVݧ׷mW}+.o+Mpz}4[㼼grdbrRJMܒ0O .ŷF83{y\L3:<5$_wTD8]3=G?G*p\&\g Uۘ@3!uur;y6ɨ]hs%x†ge1dd_ɺ2?-}L`UXN&.mQ c:I7NJK3q߰S8y !t+&/6Apºl%~7vSbuLsa#VW-8imcxb+S:|3osok6!q<O&v<&1!6eɔ5N{qWMX6\_6W08ߓ;cI6r>dr5f/_HԲtAY;T/')=QW,9ٹK?iRv@MpÐ,=oa~e;J<{-ٯ:O¦x֧:A5pIMwgkvIklXzВ {^ zkV^¥`J6 +ެӽ_L"Vƛ}W!H*UR,}?(Q v"l{LjTēDz1_k)I\Op߉VJWx'aVMz Fx2+);,W}ٽdJNXS9j sf!$uIz±sq 6[7/YO&jQ/ͥݛ|_I+?~}j4ɩBqwt8X\#WMY l .#Mvm0|k'S;O_#4 i6)',J>O+:ޤr%n{諦<{HiK;֛lϿDGt8|!"X咫,)cd7W?L0e},SQ>aeg#ab`Kަi,fYyE/!v꘱>1N>c3#Tl;]!7\] |1Oځzdb /.m齫F%7ckP٣Oԅ}FJPCKtbku?*ir._vEL wVLuoIق^!Hj|""G(LΠኋ&dSqG( U:qmnΉ5uGy6$$l-˺5ښ![&;6uIDSoV)\%BB>U*<_:X8PDi9uApm%fy1T~6:>TOeYkW#Mvg:iI"ox=r=^(_1[31}<o(O(4+*^NnӘcqzFͷM|YF$7&Y{j`ҷ;{xlr7lFuʷ"ή^"7P:^|88A([޺ׇ$gleE>oO~O 5?2jdmWUCÄv%?b {:V XnDw2;帬٠pɘ;˱*̋;|Vq)nLT{IKdq$ܴ6Aq6a >2ߑX.E$i7&[ԯL)ۓMQ)_PDDkOI*z\dW깂#ܮmsGߔc*D2a5O,uqr\$\vZ7błj 2qb!q0h,L{c]8Ns]u6ӽ>!4ǬWe4> ;@oHtu䑱Cvd%)Vx>ick=:؆|:rRz Wd.1f!T~܅uu<6b};]ۃ\Q~D ]׏Fq jM߹|:Zy*RPԼ1֞Ewv]oқm8|77\g?ָn̒9\5WFUٺYhu`Ԃ&-||$aF֟a?iNy,cyzk7x)Hqj'2݆CDO<+-$X*;]u4|)fۙD):+g]F (c\W}S ޶@޲gYiǂVU/Ep %P|!VXwgyl\aGSe9cy?k+\mߎx[H/*yuul+cHy}3;A .`b3(tl UXnP;e-t8di'3&Tc^Qk+/h7JHƸۆKOY;{48:=ck_΂3ZgXhJc$emD;U^e[t<,t~!V]_d".t`[1w5FBNWT^eՉh:I)cpVƕH$l 里x$P:@6D53/gp avg`f _2]~}\秈'hVWG~E?.,}4KS;$}/K(k&!x/ܫQ?gwaјe<.ql|U?٪tn~/3eO7އ0>s{qberٜyšf2MP")?aesS33yè܏Kj3?fd7zx=u翌ӧÆӵ>_>ޭVh_g~rnxκ?~D/IW<~8ڀv?)&vǖ qs[ǔqE3URLmaϦo,I-^鐊# Ow7_D_㤌ofo%o7NX﷽}(##||쩾$S]^S&ڞ`Mt%ݜD)*d+K|ml@씯{{ql@*|tjSro;lxDnwWi;f]B[n| 8oYx,mSĆ!%pj%ζ N}LؓWJ31%<'N0s]+>ަ}]y]:qׯt(Ɠ̾\ۂ*ӡ`!&J(cZ VGbm_uY32_1ާ|WtEt=iGߢڣ7†Q[^O~3rMe.cڐjR\tnqbo(+ۦq07glyb6iʕQuhNt< #Ą=]gʒnTV&bcn-hcAkq"Vq0RWZyzE,T{>Pg}--t٘ݳKT0GYgvYݫum8_>NW~Bq?W:W|z9;S ݴf(N\I`SyA':̑'WGGA[-27W/q7caYnG<6q^Iˠ?tm+> e=o5Io gL$P OlWUbz4,# Q-V˻Tu/-F_I+nW0M&9aH0^{SK O@/~k b ):{`A=N`4B,}G,q;:ӌrZu.9kNqm5& ޱ~}& aG΃sj#k, c~Yڭ50S4>QOw?I1_ޝHU(v-o{+gjo0vaD`X-<ًgIދo8! \O[~~Zg[ћ2 wxZLُ;=Tmjx`Q`S{(P H+,̶'Q:pw/A#2 bxT}e?/$>Y˿%}W3o)^OŴͥxF({4k℈^YD4+BL!) O>3! |N>1ǥCm~5/WYg8*فH&t@ F~-7j ۰o>,##@t%[$^!4"/k&xR|3{C-~t7!&;$F=oT|={VEBBc7y6ǺI|Vxes}Ab~3bc';(<xډz[#z`,1+{Uz\u}NI|]o<.+ex)yԟFqZϊ R77ƦlO[Ů hƒU|KY$sxxav&5<@ysQ61 N ۏ z4u&߾{c'Q$󞸟 R@?NS*^*߸OӚ|gugB QNx>ۡeϣ e RCyƨWyk|?m [IzpP==Vn*R|r r}']y1L4xbB86|#J- %o^AO ٽUOb>с P=(6!Q_LYřs=Y IDATuXBf܁SV}>?Iml7rrӝ o(~qtFn]ޗjW 2x~HN2|qӌxgh@|TG]Co`L\c7}40fxl[''emXW60_ RWhЮs+{ۻ|o(oH6vWY??GMn&K0s!oc|3 {/= k!?Xݹǝi6{28'O``>Q5ܟF2WQrFW+ڈc, GZlsdz.h n{R3ok'kH ㍒d˵x7 NoS-nD/u'` c=Xh\MYC}+jh|'ӤtgܧcJ~0߱w̷ߎӁ!q{ ͌KDsYnaRqք6h!yÍ䫙G=@#=i t;8n&WßfItn.J;ba8Ӿt?.C5b<&S#oTgl8F`@W:qè^ ΟY/Ïr66m ܎-3N)'+$w7y%m ۾_ ibk:,/W'gmWBy 鯦bSZL0;Y?k1[_%Tc_̕pBp汪ھJڰ;gWĹ"}w-[X,.LkyZJRZe ,חʳBk=t¯2\{17ֽ]uWv=SX/W*ٱ[%q-ıKAoNkqzOwҧ_'ی"v ?pW;zdZ%%ÿ.U45&Kщ)խ|Pg=UXq,C$KW-FM\&I6 p~SV!VcSux76.} 8fd 5ɖ}SNеL<.'ȧ8%ZLn%l2uɉD_*6Q]MPkoSu۠'fX{p- CFp$ʦ[^=o;g̲X| Gث}_k}6K|A{k,"A|Xѫv8If`O/K6%'YI0^蝷hqD;l_!:6z9g 7#%֥>Wmݡ|¯hO=B;}̾ ygөd}O:N0jlm|9šMCb EkmmO]ԮN!{g.+띫aNRM,2zFXa\=<@vpocS;8]]APvW;4 Od1 ;fSLK̮z"Wr Hѓz؆#/+n!37Nnsa@O|l]Qu$p@D/ QtlxVn=zކ_ yqSo aS˗Qg =aݏ?~Dߺs$XVE&M> %n\/7PTadvXbw2*i*3=.3P\@|YP?tz^c]s?}r?WȺ=U5OҊw\];9w-);'>*Ave%vNo&^,y:gY؏O_G{ q1wm3`HV!&Qڲ9+.`f8Nhd+L`7>[rcq;}ЯqcOsM~5'Ix~N7ci6>WFeY֞p\[ >3&^7Z{2}8<7OFVEN-m|hGab2jSujk3GN}p,$A6@jC~ VqUBʚ*RixH#!W_uzt;": |#JSY׉A~zd=~bʎKb-3 1S\alDnaWB[[+x?? cm)0af"9^{Ec`u$t j/(0brT ՝Q@s4uUAyW;d]>uTcU~.$ݏ٘'~\JƎu 5jve4oӯ=\>5Uz"N{7{Rnɮ]֙4LN8@mj 9_<ҲجNEo$W@S]AjBםH`.,ـӲa9=?qR^ƿZlMz'y޻ѿbf'Ǟ@dZJ65(ypʔH8 e> cvLS{lDg7"3E I_J (2=yDeKQTqϻ,;ȧ8/Cr&kwJ~$19E<1(fEKI X-nS~ɢf?y3.Ql8NӺmKz9^ۻ>v6\tHd}:'l둘br]"l]Quu5\W':8};X<'u}wo Nȴh)4=GhfSb̒,Rn}\ 9uO1mlBdM ?KR[I|Gu/%}pW5UMeiLLZ}39IF0/s.矅)O|~?OKOw6iVȐS@ף`< Q2fαH]|vLA23Ę.QK~oߞf}q:0clȏ$->j[>0f_-򘲵T_?4̿&0Otܑ:qeBY{W$+/=%aBQU5"$UP*ȇ<7 z~Xֳ`-_·wY]kwkg\uÚЩe ^l^>3Pk;g#[3mɽz9X'-\ ,HGC]b5/axx*3ҝl53|*ZSy_,ZM1ְpaluskUn!oֈusɽX'Sqaru&:(D̿o煲\i ) |36.h@6+ tX4J 1 ;@>jSG8(4X>/.4ft[AWA'>mEI) _(>KDz M}Eᯆf4?ez1&7eӖ{%;`D6(|G*phnvø Z1C~vaf6ibՀ+e ka?guo`a î|3釸)OSF![_bi5BulŸRן=z b sܟO5ca,6twyLӧkm\v٠BXcow}?d[fܚq:  H2o$or|2i -g -k;'ZOx{do4Z cqcշZKz!8[(a742{0 7KtXS;+ Uv$(U+ [u,>I>SO/nF>&<5t2bkmW$D4_67}-oTB]F/!ނy)}Wk?*zp.>&EzoVAӤm44L*gg_4Ǜ;p򨜲ehem [d%mGl/5Ts:ԹзR `Dxujf2}4նw/LAWo@Jc$z~deWځ+(Ƒ6ڻhXu Aod+7?+me'+@od؀S,t [Qz=zS+9z Smļ=',z#׷G7Ҍm3~{q1 طun^u27^'\[ȸ/[f} `}"[6eۜ_'oAq8 BJi8k7'-8dr#U֟a&nMBP}!sU0 ދwi4"Hn_.\C,2(ݴ M {hC].N*}i *{WȜoO [^/kރ,b!wl˾{y^'n<' ?DƕtN@I#1jHoncK.[}된4Y:[Qy6zݖowdmo複_A?Xv}ױ?bʏ G])5nmlewa=zQeظ/Qj;#9HKucgQ2&YZmUҷi][9CqZ;aPyTi{ FN⣂-gkah'љb?n IDATpZW`_?`Aùz|en_w |vs~>C>$K_`KcW6Dʾ3|52KZfw0\8@RԽ:/_]I3F!Q_-r\g@WafB6/[Lgg#_}OH<VGGGEè1޳֟ȾGg^<5J̀>旅-ӷ΢ v_]Ιn{qNv6mVwygk=|:Uv_4)[zXڱbkjN%)_^m6$!j/B˧XE 6ݯ ]cOϖ꘸vzt$<6n*}X&ÞqmuTM8.bJM2=~Q5ݨlp9fZWyG^Rv؂MC定̓W2;%nn{2C=F иcӑ|Jͳk&NMAzW7|7cgcķvvmqa?z>꾕6 {Zh?ahjlSk_2xD{k} G~14ďxr&dL >*bkV!"̻v$uOwd@Ojlc 1|u"d~{< .%螢CCOn,cgwFP8vĦ{-Kv$@S/F[fg?Sߐ$ |u>cwڭfuvsb'm16cN`O/ 7#Y4"_LC/4nwml[WݶC8?`RI O׺~Lp,C-VnB ~ SaDe`? RcH%H;|$6 WAƿ{\\L=Nac켌b eHGjoG?l 8:dی|Symu4\v^yC&;{p=@D?s&{\f vwx~I:cwK.5&%`;XOV+&z>o`r4/c\v,u46DIXƈakFVQ1;Ê=,z5I&F Uzٚ gEJ |X4w &tϻK~vҥ EַAXs;_c] WbpY=nDrG\Nҥc^Iq5}ߐe8i5 0H4:|P]8ح:,VMV~.mF/p: I>c;/CEp*8?6ʾ6` 0G1+&InYړx`^Koqcsަɘ׫R߼e pl%k/`PvxYN?'!CzVw]]'MSg\ʰ59ߦPWmDYjř1=r\/-Bİ}Ȥ?X4SǙECsۆHCYʪZ";}=sC3nӎ-KַcMo0ce1/E]’:8y KzY_X7ڀ3U}&K?IOn o8G$:;G}4{}6ܳ *^DU}@i/4⻑d#[n"=^+`}B_z#٫"=f~uڡɫWƱNRٚ}ɒZlnLreD3WdܰkF>}pWƑOֿKZؘ8XTMVo݌fdϬ]5_KhC7yݷkidZI+$To2ws|W1 lŃ|&I];n/KGۏ\n/o[}wpA~pĉ}Jue]|A0EgOʽ~}Ҙ%9Ec'?%?'(>K/ 5iz6;xWyCԔ!;u=%%ٚKۚYo{f'Fu)`sUtw𴒹ޝ9ߵxC+X]6'6 ~mek"_]WT`Z__\$mQd;VzVb$c|;\ڈoNQhj^yS##7(Xߟ7 :P_T|dvYdY[ (x0Kn01L*}rQ-yrb"jd걏_/݇>KY\m.ы,fqʮ[~k7 ҧh Rr TO6;Mκ&^Y%m?1p˽Jaܐ~@IV`;vA 縝Jczf.D{8l>71o~SDQN?؛>4z:bOPeܞγRr=>MKV#+t1#GŹV`o/eھkHhE' ,>qfoj%[jj-Ct1mQ6).~Dry]Kg;|TG΄⁀;;[y9"cEsuGKrǞm8Nݰj~ ?ޖc!F;KU#+Miӟ< ?q^ݱFQ4iӒ $Sl5$( vW?U$-D}R=$ek>}9XRuaԏgft*dm9JP=Z>>/os%c븝nO//?=K?v8d"cu׷'nz7 ~r3?a  Vycxy;݈kF*5 pݷjb%VɘOWx黳RFjTN +  0S3xpڍOqZiW $$դg'(hA:-{w1d }h:'Tϻß>tS? r@uuӖD}ҿLՏ$1(Phg+'E8$VC/V  o.6>gk_*Yb Ma} FM}~X&^qY6asx_~4xOL_7^]]>whpnU6.X/z2y]:]3r\MW/} 0M8e[,&d?qAtrwQAtquU*e#Uts;+Dcylw"0Jn)%ZzO<&E5IJ]>C{d6)u;:X 7lޖTI#[>Dw YqV]x/ڕΣrA(lAa|$DegBRaUmR[ղoN%~-rB:@jgI3x-ï%X՚HFT٘(q|KU 3pkIUziEI%`x`OH/{dvn-y!ʓ슝:ow䞠\G4:V!+XL{ O6Ģ}zIm6;sjruU׳cIPhZ~DXo }I`mKgr^ ?6i ;B^,gEG#[W>ɗ`xX^q6p*>;tƉҩ's)`fx[C_UyZ?#QN3K ւh[XI.4c>j }b$V &1KVTFځIVW^0B&2~#dmkQa-?+J"p\%x{ٯ/*) 1Lůwz|b߃[H= 8~e}gpXkw䛺zq\{`Y3H6DH^W:Ok5:~$O0fW88[A8 DQHjOT Rzq>3t: MN?b XTݵB[& C_,jUzXJ:g]oxyE^ç6IH?}fƷl=׳K_h>f'X^!Y"osNUWyj ]zj2նU50>A8 d%>/y-tr賄g,:Z VK.Ux.Po3^֖q?I5'^#rƱo,Ǝ{1wPky, }:=݈orN[y jB4?s=[olgk?-"LgxǹI~}) "?BM l `OL meAE;qe_8(0Ms Vm&nxc|P=K%[vl֘;#lĘ|ң)_K跣csL.<5.NXit|u`xtg&:d[&kM%~CXcկ|r%^ U8cpұ ^,۪Lo-[oU*IpiWև]Ya $Vh!|F4xM<>UϸDcT6›B_t|w2quwխb?~_]nW|w͔}uYaW, uo@h0j'lTP PQj3fp@ Mȉ`.IjpSWRj_OeHƲ,E vٺIL'A k ?V&k6TL\~cWk; qaZ/u>c^Uei3KXJ'|o[zU.c_u0W]=~,_=?7{zZm`^r7 o`^r6~{{,ΡuhR?m[8/=Q(j&&Qx!"ezNΣt\s6ҿPt9I;Ģyu*?aiz KoAxרO.oЯ9-l&Gw;U|; \m.z[/Xc7 36%'ܓs&h& ~)oz}7WMWWY &<&++Ⱦ~XϽoO}Eo☒WC_C5Xb#/QtvnC 6ƆbuMl`Cуox?[y .3h5*VY" '$Vϯ(bӋą_:@cǒ-i݆4A~Vr~|x}{a!*W];<_YsGy5 “逡o$#8)I(ZIG=eP^$:ǚFQE2u#tY t[`x0ވ IDAT.-ءx]DSk 7Էx o}F~{e/Ph@lWEi1Xm,%3pxǢm1`ݗR$Z?]ږgT'Qc]]r8l:`ߙU+b&_yTm^ `8`[zTMow×GtU:A-+?쁝UfWz(Z UZf*zX©=ߒֿ:.g p W){C$,n3U(9!uP(m ct}@c*afx@9 h ͩ* (K_|p p1i*7"۷298ugWƬ,7^Im8dgrx Je){ز9%m܎S<z-4zڂfg͍_ʾa{Mc8^T_!%2>vxxF3|??u܎F \߇3HFlL;1_l*ɲюN,kwҖ?X(Hzg;wt<1@zH'elgnH4'J K]9cԓz-X_Om~]+Xp̍y]y/c~l&l# $peRTmW lE^ۑ7)l^|5yS ]ׄ4륙B 8uz8sڢ` bhjČe3h/[CQ&cjw>t5TF`mdxc4^~#vYIAD嶬0`x_zsGۇֳц`Ezж v"L?IOۧH'.hYؿZ' uk}cӨ~z6kM].걷_V֮J^g=i7B~3Xd[''I]1kCAu8sȯyXfʓ{ho52>i/}PDlt_L&O0:7[cN)ͼ. 0T_ښ ]36qgdG?,W~)һ(?.&MȻ ׂ6GԣdWLNVU'Uհq\\V{.pzÒ͕_շv0DX<{kny-} Vm6jHg^Zߒ/tMRɤ11Cq˘,zd4N|1gju~O:h6KAdI]0O|vO}c,v8ڻ}?s OգpHtأu.cg|1ZFXZOOԟW/YkJS7mc`ѧ$r PQ{6,&W`?OlQQΩ\j7ɳ<};Ł,z?|(K*-i\wg d뺫i>z%HEV'YMn_{:&sxIGwϠP$++O~f+d `b'vxj:քC+#ڰscrmpW\T}EkAUppϠΔmzl0 ^E tqH>qk)_xS2`'^8z[`cZ?D$<OﶰOIY$N0MPX|*_\غSC7,u />4dJӄΉ;¨MЎChO~T&m,]%eix,`]dCAl0Xm!7/jJ0;D{ռ`P cն6t>D G;/ld \pn@s?w ݻ3VIV:AX|%VT-JgYC/PS_}[LJk޻|#m29wcczeulm6QХ2;/+Ɋl+0# UWS E"bѽ[h:mZ2T3szk{d ֠66cLkۣיS_:r+eoi|q&$k>4ԓ:~Y|_J|M7~ƋqtHoo#\AJ>#ԕ-:uݑu=\;6=]o'؞so!AעJ~gT*+mAR[ߦ8cv |Nlo~@|K,HM,G1TZX{]U"eFZu {R7}a;7L$SeE/X7{.htLJ,tZ:rss,O%vi l ؾ )`7>qq^d=JgNuJh\Vr1S&L`7"PW <#a3~(5)yb1m%mmwO5{HMzkS0>=`PW+-/6V窭lԪ}jV<+TXu&AgM;mo4Z^?n R€gj6~ ߲,kqiE TȤ|7x0f@X9i ׮ϕs7{]]_gba 1*&_f ,j N.%T3>$>ِ&y}7yW$'Vf{v̏u}s׷`;: hDz 3a 7܏klR:2Ͼ%3-|wĵw5j_d.)mȽm'"Jd_䗎^]F甪{O[1nq~q8kW4|r/ÔFNA+-+B1>]LvWģtEα>dG/P U-r{`K3C(,~@kT H`ɂ7tQZCg jN>?4n>[51蘋δ!>gm`Is_9 O8J1[NXC5(έXS#vJ_NxGF1j. ^^AP]߃i V+Z{_WpAѓzǾf5ކ/" Q}*g"bΉYL,@evOދt Eo/)hcpۺ6FzɯWw.ZswO5CXwVPZ !lWmKK]Қ 7z?:p6^5K4H)~?d o^e(`<qC@. \ѣ 7 m&+GaUہ晖 gM/+Yng:{GN+U/n)n) $Ih3,a0=f!5zd;V'=On9J$Qt1-ڦ] .pA'K%ي`",36<ա/SMugT:{J6߂6.9#Wq۷kq?Ik>Hj/y,U h{'>(]LpJxm)tm|O惨>_j]֎llqU)MߥX_i axe|/Etfz[&__ !X]Gv2b^wQû J^hWC҆nE's؈pT>Ûy: f:i,³%Lwܚ#Dct2& |X‹]Sy0 *zPqyoy%~ 1_1W5C^?&A}@L3(ܶW5(w {etXbpK^cr%k[Rݨ;?˓V~IfK_kl"m8޼8> a[')X\,aC5 e]婎z]F5Y-T9dS]ѣ%[;JϨ.GO)KUzy&>Gۄ"6=R[kŅq]ϠMo9s3mؾס'+ Gu&_ڊ_KzD]sy.q% \r7e=8S:Í&(7ܡFUYo$y"E2;bnUֵ־ >^by5>p}hG3,coN-l\ݯ& ~wYzI} 7ٓزm"8+A`Qe"lE"w +4er!Ww>l!\,klAБωCɎ-W&!zAdrI۱Y%sE[bITVu01Ƣ9r}wza"ad r[onK᰹u67zA{k>-ja7_e*'z 6U9P}'Y|e |F ֦D3_klOG@/O,HcRd]`!"Gг+b>CXYO|Q0'%G$蠟9{i֍vk5`o+۩z̐RYy"YݸF/9޲DSiohꍼt&E';Fz ^' \MP#RV)!Rl/.J7+_\{*2wvL;i}]Vǎ.P e̅d,O6E:*km>g"$pc)0FqD= cdl>@ UNd<ƃIiyTޗ=z,Zҩ4p24U0S+j'9,y,acoG8gs _, dx8m?y1<K~,W&PcszĶ%v16~c5kB>}(}&P=OKt^_{~ [vl3pݡ_<3&_5Ƥ!?A(=_VF,i|^gce;If+q-:^b]Hwz*acXQ[5W'`sH=w^YC'3o. (y`S{N)N+:vug$sQųZx^%Dׁ]x  lX p?5; :kS OX@\/:\n?tc&ן>?8@`s sRR":X{NyxmNih%l K1oǷF/?r u~sk4x}F&Cb>sx}[dB7{KB:˱e}HLV'u=)y&sJzZ 6<=gsQ?gLx<ynf߮s$xa2~<;%'hJY6sFcUZLzX?kc7m|T>F:sd9$[q_.Euzix-(*?^^OjO cQhHtQ{|X}t?*zVcTSZ_Q4]_E8q<S*Z[W@ߧk~j=SOgھ sD3֔Ft;pyiM*߸Z~wwn}Rv$x{mk?CvsA^EWv7 rG"^]M"DHXd#0@)t`otAeoۏ\SECJ^M^XlMCt"nI7YtK3{E<=mهiK+Oc]Ao}-پ0wsYx(c_"8emBu?\[ISW; ;FEQk_frsL%j c0̈?;[hq -[>07pE:ayӣy2F#Ɛxwa]VuKj5 6/zncLrWۖoT"{)51%󛛲 V+\I}ٲe8|=p3!q_^t>a?ErDX\hY@.E>g0< REץ |[{O+HqO/\ lU<_ |෮ ob`ןx$}~xme)l+h0WCH7/)6u|wun/!>;<[i~DT, -ネ[7u9mzz0υƦz$g䷌(M=!BuyyۦոIt*N};k`<ߓ%ۺ ~|o{Vz98/яpy!8pe [eӡޚbcv#'664dtX3砽,?߹E4}cEcNajwҹt{EKr|]ݰ.L?m@/&mڮogXO$uR18љgu> jc] YQ8:0`G?y E b |*߀+/t91Gƻ)fU·5u4ɪ\W}p1`,H V\8XɈ,ְ-/ W>#{[:YcF~뗎d~DAXrt^vz=śP7켒Ά|=XY0oHWwx6d{[?[Jdha|p&ߒ%ãW[+z6 X*[v5WY.a7zM4qF6@SFT؛p=c@Qpgx?PemCcb󥧧p *ōl?EQz(}\!z\i~J,W*YDq|=O|Z맖_E,: ;qU\w=(I7Lbd"O$@VwM uo+6 ad'|e$AlF~XGO;0i0b)dBzѤ4bLAXϼoV$֍ԅ7Ƀ0[ Y"rXۨ0 m2D{v{b;߃I 8Lhm2&OC IZh6'¢xޜ}8L|KSy !=o RՉWrUj=*6`e8+m_`gW5oUodtEgAh)ne{Xg|Cg$c!^mZ/}ľј}d|lP/a kvP}aS=cxJM=6^zܕ^zv:#1):>$"^Qc8]]WNMwۆ>K~/P3{3Cmd\-L縹IVuEhBݴa!+<݉' $z]WL͚blfZ^pX_tYI4+;?mn$ŘIA@֏,Ynz!aJ$+euY,G:?4}nNpU6~Ơȟo Rrٮ>N4^UV}m81Oԝ$1[+g|d?Y[M]/^SZ\R 8a m4f]p%_;{gm>A䫜 GMC44Wذ#{!= >NHv?Ko[h=ZĢx>}+">I I.ij:z됽w|vv]7f։2R\.}HnuB_â:@vMDO8z\t1~qǸ >`\@Yf8W2RP{\uVwxoZ+ ntyhn>=r:]O@}`\;T"*xrF \LWwly!kѷeJmafl"R}˿$PU>VY?DQI HOK/&N?uhzy#LGI$=;6NN z=ѓJ¿H3$C#UN8(!lާq6֤lWm._E:4'k2ȿko| cA`-hScrH8XPP"i*pr$vX>wK 1O}`7^&C={.ScY>?L;S\O\tY?`ip~ iٙ>Pk,UU {qGv.|ѫbd\gPϣmssoS0gI͘UPߪH!^LToudWסezuH!OTM- q=s, p"+H=J!]5~[^&_П2ptYVt[sǩ^P߱),'~sQAsr yHa&09k#;T%AQhZA|"Cn%]686oڐjܖJy~0v3_plT,&{4+U۱1a|Vv<uϴr8A b[w,&gWqvb{˘HJ<wGvwj{ z-Ӈt pqbm({#c&{阍_ˬ$ܶ?ladv{*C 2ωh)aH61k.Fڸz?f|[Nv|dάz^H˧uz):36JEX_݇=ծ/vb&^0BqOuu/*oT{!;O8!#h`c2"WΨ y_dj%"1ꝃ@&Xӵ'L_p-e%ߴ)or؇.F99Z@&lm_.%럛7/N`o>Jܔ_BbN5޿:O4[,nr);ǃ$%{3̜:N] ;1RR7zG+ltg;4%Fr~`:@mEʲAYM,Ա&ƺr٭c؅*gz;ً]=E߀!׀fR$9 D^ӟ9Y|^1"m KeJ)x9NU@Рmc: NU$0oZuɺ `k|0iQ;:W# fd.`P'l#ﴭ& yMتW6GJBEsdwmU=+-ǀBu@ G0 yk0hKG ķ*X) 6 @wX댱'6}H{`%$T8tBFVket> hII_X}%3 Weòb{?"X^5`M%lҮ-vr&+[Lx{Z[mu]rLeyc`cw$,/hrXcP$Uz: ݢ]+rP"Ynިufؖ*<,$u5H<O~$rwPG=ztfOW _cf7ޓjlS_fs_]D6O Ɂ8@,:I(ckIܯ:nUU>;l%k۾s~C:4 5)edק~\&S'Cʱh/=R=#,)\뿒عa]6{]>홝{|j#j@Qس1vz+u{9];Z]_naN^QU\GEk$pA>] EIq4V.S^15xwB?z6( d ^ U|Ml _sNtp0PAn3pI BgɆ x0oJ8L\e 6pˆF)=p1/n'Mx VVex>@eY*{{>m^%Cڂɥ@Ĵh|Ⱥ`Ǡw`,]<~-cvJfCגaZ^e|qcZ&;!ʾ>v|fN<ߎ%>8Gf.7e=7pM6 '#W1%3x5;cĆ= q5_ݥw3yJ/? EwDvSܞO şCM3>=؞Ljҥj^_rV?Vy1x._Acm |S;7a( {(Uk{< \ړ hC?YftP+`M_1W`Qq<(tYlV|\+]cmBq3lx|:|Ob9:HWNSNWi _'IUy撓~ {ToÏ=|kJ#'e;"K&'Џn>sk;k:ga],Ǔ5 𯐓k*_pX锾^*&z 9@~k6[w02nC D.}ku1>cfzZp{;)amqs,x*uߌ(,;}rʱX4鸝+??]>$qn}lK[S*.ϧDޞ/ s?M=[I0^7gX빠P\I|j8CDGš$M[ҞԟL:MlE|zgql3;v3\ Α!NUX ʔR VT qM2K cokN7)Z#r*w:2O]i WbH :+ʰYTKZD}e/Jed\I6d 4#>P /h-eK/.+ʔ1[_M7Y*L]hC CkBi%kyKK: YL3M$rr6Nz8Hϟ˨ "߰g ʠl?l]vOl"9axMO6 I9_fMfhM =XJ7Q7i/S0|TQ:ng yBUsOkKAsպѕ}OTqySWo2xe%W>KuBiR*]s~}AO o~SmgR3rGoM/H&3\ipT]uڭcݷo:UJyKc+ :ir $q1H rHtfyQp! 38dk>mGn^VÂ6@D//eQ.3\{G0oA4;_'/`ߌz fs0$S×+s3%gPwfzv*ox',1\ey$ՈOS})?;)? IDATgӳ{A_xm(_J"/ ֖wG.n)-Sɾ_u[Tw**lmE:*ft|&aבGB#CdEb5Nae2"G 愧4Lflwے݌B{ҕO(7|("]<9mfͭ^_ӘC##yS/0JAv( n#|l f@nTWHb%(0كF( 6+lPf3?g4 ; `ظmƊXG6'}ciooceh,k◲,(S봵h=8҇%+e|5 }d wʏ V= vT/rlKpUIvXxLMG Jo">pQƒf~piSg W= ~_rٵ U;&l58>s/8I/Be+)yB=^h *=֞+.ڸo]*np=aE&?_{p9՗檋wRŬaz ;bԶ[O~mN\{^ m1k߳P߹'#V0 @Ӵ=3`ߍLsd=fg_&Wn/KCzhCg-n7]q#W{2X]jb(&$i7ܰcLTX|+nziܳʾvG񒰿urw:C-$_9 _̶f_qڹ쏍n)r;'Ҽzsd#ڸ4u#HlIlAʮ{EHOJ]=^gC(U2"gŦ)pL7vYh ?L}v,X;@ c9%A^l r&Гa0V(x :ٚ l,a ;K|ہmS>ȗdGpBSkyS.[Wj2?K@xqb -K'oO\N_i,Wڴk4XS.~m}Alp}Zhj$8򯵆$1=Ⱦ"謂 vv\v垎1$M]߹ ̰tbCɆ"]cWΟ U|wQUi׺Y ,-Z.E.H+ilwuRDG2Ӓ z~A{AIV/]74z0-٠7YY{A6aڥ@WRq+^aGc È˗AF6p1 8mn} aU (rc^xr+ο{9gp΂ \bRɱKls0`/nLD3lt; T|7,v:> ByE{%j=["1lMB||+6z'\׳Lh>oV} :COa??ψk;}u:NٷX_$#MUܵV_ь1isIZ9 <^huYi_1ݤ",H{EW"<ܵtk \)U?ٵA".0Ь~3E,,8{ճgwQTx GAS钨&?xU9eL:B}ԾӷVAx K]u֌ e|T}C;vk4}_>kM2>>h4yHu$xmUժ@תK.lZ _7?𫡇*GߘuL>~!~szc\U|`Uυ5.S ztNY [PE5 )ۦ:Q9F:Enh_$ ic>g{z)?țz8[[O2۶}}oLDض o'A 6{AxԯLs4u# A.QY㘖ORm6mFQeN,uH[#?+iz( |H/c5zrMPJAXY#_*@$uO:(ֈ%(HZi{BD#LLz__!;&?ֿ=f?<؂&o6o:(f)~9Sx6?P>!8 &;F,b%xrsev7MVWoǠI+>ݞ4k.in?҆[7JKgۧ Խz>ϓ~ dEt ӏ践S\sﵕ.o>QՓ>P!^{0X7o>L-97#xMhtc"2[ !CH:T('Rȼ޷0%|Gş-Zk vo9~/=ǯK73?t`Gfi|^CS0jӲiɜ/^+2+e>#OO8 ^.|-,fLtƂ \\^#WRSAW=(P?\$,( G&U I;L d/J.8pfxE7 P0( iYH]A1˂T^EEǴ)MZ߃H_k9OJy_!;Q^aG>WHw;K`jYs`gcXɢy~`/̽7e׮Ҿ#G{R^ ?:c.lrX,FI]5|qo?I9Mc`vG?ן  Ygl:r۷C˃gHucr95-MحV]_¦z_rOOaj8\:guݾn4ekC#9w HE @$}҆j·'v@jUL0C:3ge|euw(:Zs9O̞O t/&zHE?.%zYɗ'+H1.K$xd(Eju`ԇ K7 ^`}4.]2ˀr 5mLHB|(IeyQS웚pX›5Nzi2+rGD&L6/'h_<ch}2OfS@aLWjrjiٵk#cr?+M c9"4 fj߮-&7̓5?e;TǺnc/} 06>Gr\}7Eh}W{yܟ7zOg}mgwC|>e'E>=K6 A㷮={HzhWQm@;m2eX.r83}+uC F^pD(oi`(7.&tاRT/@@YEv/VcvE_#٭_8=~4le}DP k. ?Ѝ\qps7YJI[,(4یms`\Oޢ?#c1ĝ.+|ۡI4.!LDjb0Z5ߏ_ Ǘ=z/!I 8ٮDCa}ao`]2~ecxs^ɸ\Z;t$}8,P.p(U ` ]^PR9j K{inX;pZ^޿=5r I;UŹwGuF$ڧ0|EV< knz|Zl@hOdn<  ˎ% IOLc3G>4+¸u?gկ"_QCGwU۫j囉 .] LG A5r8LG(?K4 }ʐ-d٫HWyMVھ\w)|^{ `<ЃĵM`4rYԟ_xQt19y y> 9ˠTG WKo+~g:@ 3ݳk8<&W7z2sb6=ϙT̮ixMpk_>^XNi'Ipktr!m~W1/˒x')Ws}7bPW|鼺d U Ƿ!q M=:C@otq#µ_I/gϺBC_>C߅i9{`w`2sM===&~}؎s~`䯾syG;';;?֠guၚmMLm=[̰:%Ce<}ёvATHܭT ҮQ_w['*/@xRRQ' l;-SF@ӂTc&{b-?L\> cx@ ~' f4*rʆcuhiYVB68@۟%i@.Sa>-Y?Zsd7mBS /i9|16+ ǮviR6Á5J~!mCm'6ο fglϱg*{Vq&ʲNcF! AuquErYۑj2uֱ-u~\)w:mQ/(]rj@twi۽g{֟=K]ycXw-k3ꛄiI/GW''N]wEmUB8Y ._>MlE?n5a,% *@<;V}<\,[ո-< mToQxI/IP +ADVg`m1^ (A$QWuH+&M[F~UnVAh+o KBbcQwfߓ!B_%K֗ JY][޵-I@>qe2Su=N(!@ 8Ȯ QQo[o}QPkvӠ/w r^E67?V?sҖH~twv A_oXwqg vAdZ 5-rlN`2dtLQعX߬FYw%1Gxz~ l{/l˳wߴ? -Mfޘ1|=35G;δ{XZ"wz#/Ҽ͑{~Ll[A -,J5U|7]MȠmM$OĮb/kN߄>FOݦg2|ե!0' G[&sOZ>(BNSLu/+h>+t‰C2|3SZ)~D9.'6eP#NhK+p!Eo6W)\L}w7aP#vO_;,Q +;cxxÓ1g!<Ə~AsR~C1^jB>QH@|rJolCYS}lܘl3;:}i㑧?/ۀ;ypЫ7Jfq1fӸGx5pMZ5mgL1Z{ FCRk0 ng`K@3 \ ?,`"{%d=`O̹M2T "!93~lO + ./Ew=B}h3ŋʽdD6&ǼLp/+]J^W΋iE/>b?kIV >Iـ0 op@D^=+9e0m+ExEZk.6>Y% \bW?ZPqTL_+". IDAT$Mpט>Yf?Z6X.nn̿t2a,f?lX LgCw2ZgCiY>k$xf=ǮC; /q66>K=cUqI!avݰ{g/8D"^"4oEq dt4~l#9~G-GF'=׋[Ovk>f<n8rԍ#L>ш*KƁPwj ˔|TfD:+6C,z ,xQacs\:8q-],;ޟ8Hru ߅nZqu] z =+e %Nn4 yV?6鈄l]CɈ/lsL4).nޏiM`̪:1ݟgK\:p1hK ! E83!,! P`U3Q)yS OZ ۴ z^t> -^)!'<+R:8+N 8'm?pI {EȕIk[b*S]TlS:GJWKyq!k>[:O3gI=߰ hZ"g۹[f|mswnrps;h#y&I^`;r)PVW F7Yطgؚc-@㢵uF( (4C2<>IO+%=ֳjݘY|\屌~3mm^__N"9m<{*~Yu_kj`yݰM$U;>?p-. y`>cu/ߍzP}s&c2&RGsA&ܳ4&5+K9ml k+X y <=runHwc?rf\.ptq_<rf2Tq%1BeL?WaO[ٓ,6(TÐley<~u~wzhdވD7v?#p7FE]z#;#ᙍA.*']Z]ǩ/0GYfk9b3,9O؅iQiz|1"<4?Ѹ׷* ;~þFa}vб}netcq?(rM^~sԽ=$/fOW6|; LF`XZk]ͽ! q^6#c|ϟQ ܐwF~^8擓eA AriB>rQ)9h]sVi]۬a}a%p'3 /oo-Ώzzp _=^oMGd%6 5)Pʣ:p*rcuIJP|ඟr9CK> 'Lp[uc7vFXM %s/σzcIy>\6fr^Jz J?)t6Bwj ib[ NvDv9}bھšɆ)/~'6^Q\62f @Ƕ+KOB3Jۀٳ/ǜ (€|fDx_Jp6*d )G}^ۯf&~3r{}Ư3׮pt2c-Yo֝sHISgWN]a=-z>wb0Q/P5U#gڕ={3W[}3bm9R>Y b]Og#xn my d&"X8{)8b-{VrzyŲ! ɒ6* MB T )VgJǤX颋MQ" u:fbnn<r\Zceeu} FBO+]zl iD6Y!o^}1A_ɞu~p4ąِ4o?‡ t2Џ姍vJd;=ݰ-l*k}6 NX4wMCY=<^Gmf$~s1ETa6gF@5 Uddq=\G!qzHbm9R4 lCT~9=ƧSbvnuɬysRDq\LD-ºհ.@E U}N0evœahIp7va]o|"ܷqPXӂ5=^(7~}W WG;pk-U'Ozwq,Ke*j3t,^6gx<}u.;me*ahVr<1يxAѝ6pв1~*7d)_==!?%@a @f[̃8x~`9yu'w3zGٽnU`cQ]C\kt^ ǎ̭d1r y5dxtuJcylۃ TD4nF|%{D#ems>S>Y+> /SSx>vFS o -͜wB]pO{AGu{T{mt1|=xc8>Xo=p%gi+"u& '-QL `@X~^ϤNp3vQN@+@@Z*F6,O ;{"m+T˃R؎tE l<NV{WSO# wt4RTo:C?GSB?uV'p:n~SRv~V8l?sP1w x$CBz^ $f6JFlڅx@KDAӱoJSW#>hsso65+Խ,?w9Ưv54ԕu_uk 揝?V:َ`2|O_X{ 'Y߹'^e1KbI |,gWo6]_5w_S:wuIJ>2~\j|t[Mb` 7|5w>u>? 圽1,dMsmjƼZF9#&7$В!&нs20u\o^ +IB--  9?y,xO7aC?ß^.XV4?@oNW +}qq/ xt8DrX4 &Y0EgB!·v_ݍon3*B*A7rd@ l'<0dӁ'aIlj_+YN鈌ZG-I7GgY)39@i2 ];8w9QAVjskf'J*pF=X9< թVWOw|(*WŹ_Ol&sW'|nz6Tg9}D|ߙc1Ihbp*q&)8[iQ M:51yr[[{+^$NS`f06X?}y mowe=u>YBd4׎ K4צN5پt ; n}"}G=2zzn[|N ڂ˔|W ވ9!{eۏkzm9{Km4R.ӾXCRv\ yQȾj?U'MWrRϒbaE؜Em/W|24<tJDhRL'O|Xno?wWۉkmD &f8A|?& ƌPP:/:!h>H=\2)^~Z|vi hò$U6qac{'HKO9E灍l8?ul [1\~3\qfkDH9~QDan4 ?cڐ>㞋[StZf1+7ֹyVum!I[$.~DU::6_c[2Xd>?/S嵾r{7V04S3!k_ī>uF<"eio>87 ̭{R':|L \`>ХhX\/ ļ=MHy>I>S\][-G0z{NYFOpB@: lv"ۜŅZ+qڷGFo *63AĦ53Id jѿgo\twbz=Ҹ#ӄNrچz%|~%si㑞9vLʝS\TEy5nnвMK+?MNa~k5IF_6vkrұxg|i'@M+?>v *2W< + ն<9c`ľ;8*[|#r,3EAlTE ˟=xdx[E8g+)u~UPݦjyoa }$OPm:㷏XW'\Nż_CGNH3?5si&Q:ˋGn:8lNW m SscO+\`L~FTq:9HR؉ݡ"#tfP|Z$~ r'[ə.3Q>H wEڸaOKYAF&j*_U{[|.+l" LvB1-g/F:ʕm3t3Bo`}6?g,gq٢̈́XK}mXjN{tt }5]RO,O ?CsZ i:X؅gέFvs,\>Sj\~F<$ߤu Þr ν6nVozqvwvyЌs鷎[3'yzz9\Fo/M34ouԴz42<:ALqj2sr#t?T^eU<?_[~SHo7d5׿']}ʧ}y Fcy;٘wU'3+x`qnju-8_^Xy'1ϼa4D<>uP|g 2A!KVF,5IUoOȠ6DZ_zu-nߥ96Ēi1*ڊf#V]t'=/Aɥm|Lfp^|?oktmȋYoO6D!4…ކǮ u `c'A {oϖOt]N]VM`QD :9jԗ|k.x8 cG.Ұgz=]\X.~ko}!9 i?]@}ZHM[a[?q9>tY$Nkly@Ĭ{掩9}Z&o3~njl*}MJ#FԵׯ6$I]'cŧ .D@fcP bCaEșMKC}ԲfBJ߳E0ot @D ukg" uOz[|%W7xnb#BnP+:]]Nb\2Mؠ oogKll@{8.?W7mYJSݸ StR'8R(s'c8?2l3iR0/yW<| }%>0F:M}f^mqٷ# @*i.k:Mkx0x{>xP'U>ꈅ9Y@;bl흻~z|mah IDATlȻ3}W&Qk8T}Q5y 8ӑk~{E#aco,g*aCHMj{AIô>Oy3fxёMGnu?J?68"-h ֠zM^5)F^GXa9d&҆*b6xғ{`\8fo{MwMK|nZxƌ=z-8!k޷>WQB׉ʕ~VO%K?M[j6>/g)fx >` Bp,o?YO[m7)jRz(\J_y~/ȉk>gx!kG׈Hm@zz~L"ߑW^7ETM؏bM"keioiFr4Ú'CFMkzVasmf!ՋGINvO1x:y{wq;7_5'2/=A'kXg@qm(z}N} mI-ɗyrs[_hB#{N. ;^K|.S'dl~K٥{a[T)N>f>=g"jw@[Zq=8O]c&0cˊe`zlG_xsS('Q^¹EI$wt&&oFٟdaXB&pE A Jmdbqvl |vr6Si*ů|syRɥ HugHwJgs KB߁5Wo ۲ЯWop\ԯcb`nx,b)(g91g1/ÒOsE]5G[{%gguLJ/1amE۵ t]Rtygj; /x%kL'~}PE$ܒ78S/=,cq5>ic4M['Cw-)zp,7HHAs|͆ao2䏁M ;m~=COYS1c#5Fe& !ow@քROWωtkQ16O]VW됁uz:XK_,7@?zk]LK6%,뻨Ha-c7l-qw.nb~Mh>SVwʮ]K^~) -zI98wq\ي6̹knc,7Dqc -@ˏ6??%~(/9ǁٶ$}לY,CxX2rAE^ p=~EꄞQv7 &۝j9SW$؈ϜjW~t9Z,Bvcƃk)* DOk 7, s />Sǟx R9ejO-4.Bvq8b|c轍ü7,b/x?0 Dy7G1Ϲ^j{L7os|>h3и7 'Al~ 2[Ӵ.^m @1ԁKÜep{FT;]m!c/v-DQ}طpD?AtQ?gڂdDޢ߯ͩsSH ^p+)Mՠ̜֜ߖIDjB+v=h/*p8"##/RցN~ʧR?OcFdb#N?83Rhr4ئ J =@1R@b)^ &£.\@ǨsbJvl#C":>$EuKh᩶͊Y0]?W~HGz'3Yנv5xO_m. 47{xO*bB1a>xd~txyH%˅>|< j/υl.ƥ7z4WBVy`O<!T7Iࢭ/Ӿ>wo d;z `_0|[㾴}w>f޷pyjfsi6(?w6g>ްW -G[񌐧h3Jv8tS9sus:]a_+ ~w '`z8P_7ȯh9'8pGq0ќ:`s%l]I]ڑwNo犵-{oϫ F_ˆ_ada9.b|#\Yv;`ss|5ݞ1~v,d%+o;m/[_d`z+@?u}TWہ4o29(M6A]н5?qDTӅrhOY&ױχ^nJ>e <ΰ+zob=^N.7qR1um =a7t6OK礼"Fy1ʕq.Kʀ0O3O>3ۄNKNg>y7ғId.rmNd17A$r%2#h_zPNzO3MW;P5 !܌iµ1s'|: ]R<ڈZ{HdpY9>zYŦ~7:`M8d[lM\ϔ|y1ƇSȳӥu5/g >3(gf pEzUx^mʋökj[,-RCz!.#}F~6.kHQ}HVyHY5$o8%bB}F,uYdc13.$Q:++kr] A}73W[|jG/{O|_ټ |`zPgq\*с9l WU8+(?l~m3-˥𑝱?sAS1s]QU>nO|2:uG:?ȫU5x='p<#X[qfƽ;,(e9H>; +AO4#Jf{yV6l|ZڼV9^Ṋ6h<^7r΁^/{`QWp~c#M^yn.|'k!u2P5L"[6+a+x\kצ<y?c4zAo?[wd7 *&Ԍmpfė2-Hj8 '>Yz(| w pC2S4>.A)O~;N`7)DtVV N9^Mx;ZrS3˦_ %z J*yCtex}p2zh.~5t;U_!yCGYK >K힕?c"\6qM\KF;/O zX |Tg[\sOX=6yނ9 }m|9]V-3CKBl_`]&nW5Uk:\1K)zvyvm^#l so b.d& ܖψgCBץ=67^v,duY<1ؒc[- W@ t<~%Pa~n)}+8iu`H@)~IyJN{>;eWi6(PY\}6P(;!rٖ>iOm 5]!9t /}p|)СD/x挤ҧhEd| .TCtT'yGyegQ's$OdPs;<7OK ( l IDAT.x?2%Pk7SJӏzF^j(Qr F3r0S}OAH~~?Z7nC翲`6OV>c"Uk|ph,[4$]ьy8I_u^PP\XWx̵memLH푆vM?$  MR4|JMU7 NJ r~ˌGM>"2_|q-:i9q|rmhkv3 - \xs( f,#u٬a"rzS>;b ˩ u?/xKW42H3EXƊB;Y5l]GO&?#X '4nEsm}fnsGvA `L~$}5S9~bl4\**͜IU쬄$^0틃P;f]pϓ~c7u4"8N"nAq-6 h17%ಫ2+Y}*=7>s?AOlL/BUz"/A fi]gaX%w~L_ M$rn1(s%<;{I>@luCVLQ=C O^)p^[4 YQn96U0oskE+`+gjhx]<_`,8tm5!>>N's߄}r "StT6Z\Jx0C E~)"/N?OY:ްwڈOXmN͗bZw\0#6R1?zyy\nm~6[Ăqh?Agq땄JV>{w2XC~@=oꮽWou|F=sSrB>_pXoߒ:VqU}hZi6xQR/LS Tm yţ>Qy?mg'cg4n:k~1 .xzxl N_'o{>Sch<䡊IzQб0pM\j hSEp'l/º-,j` <ހ|yq"մ~~{>Ɖhti68>t<uN?^oL7aq̇'u.]|@Hyb}JK貃~j [j '}Z/gs-oBG';mohs  9To~? ?o>gݙ~8Xt8rʙky4Yz~dS''LT~kw?ox1خ̰K`M߬0M>oglCߝ&9ZW@3xvEvnPvbu"oJ,33`ю_-雡g#:.`gW_sO T7ۣnO?6s<3|=OƉRsFu \1MO됑i P,*ڢ'"*q6 jm$濠(`+իuMA{sz9}I/bu@3,7ƿxè6fF[2wkGLD8NO)Ox⍷TF) 4K h76 #O8Y>HrqtZ*r2%\tNv;} ;n r8rsyCF*'28@jϷk=gl!1Mw}u_G'Lڛ*\bç,rq톢_[noi{xi:N6C,/G}G?|33 ۰B&78-ioȭlRV՜M1QblB{jT1nnfX}Nsv?`/3'J9}NNV[9>Eͥ/q9ݘޮâ'BLa![.Eܕm(L#-t|W44MHKd`\uSI ~|@/Uvƕ3#,6E+7lGҌ䥐ہ- X1īʥõF> 36n71{/ \[m{NGOIOBFjck~\ cWmDz9wh_vނǬʷ,v %6e 4E'|ʦ$t,(+LUlB8:i͸s}֘ۜm,z8/.?b^Y>m>etlol9e`GX4nÿzޅHֈjԨNNwMFrsoEU"'MA䓀 >}:@4ǁvރSqm9D!7T܉g%o͘Qz!Y۝,O,Q߹Z{Uq¦ Pԯ#!3cTԵtuo؈1q}߀,O/Tа'Ld&J<.~qAo9>mWcK^ғ!Q;758x.e:\cm8H;[,H:#ظ?lu-h:Fr9Spk$"px\dB1N*3oW6NBtu:DqmV#mԶK~vz~;n**u[k,ѬKje}~͖SG{ʁh9;0g鹃C+rw/EfrJ9~' L#<^n&A*30m.K@G\##wk$"QX.H>nh+\)-=@w뻡NNZ]u@Ea E!8swȡ`sn|Dvm rn8ŚCm)oo\zйg%xNg݄ +_fa+PWK|"|/ss;zKۄ,1x6R>/X}Jw5G[/ב,SUptvJ{n N/6hח!g˕qiU\AΎguxsHpƩ=4'7LO:@qƁ]}d4ΎN݆uepGR,`*<.+E~ffBtDEgSUE>,AVcl.̶IyOـFBzQ/^^7Pߤ<%Nnۨp)m`/ѩwo(;~IÌ|+JF㩱 ɹ,# }Ke}*9ݕЅ/w~d{)ݣغNrڑ;|E#OkDd8d^¯/(Ѓ&HPipy"bWv |+Z\AϥogQxGC;/6 ;=hGt5'0|*Zmk޿Oq2@xc먾E \~@ɱ9&@~#|"0N|br}&y#)-#P72B*u47g6ࢹӮx~4NG;ƕ_H~~68FzM͸oɍ=;vjw@q Phtexx?bK _曹{Y{S1kqMdWmU!y#o’](~3~k|x֖TDkvzz2z~C=n9^Ǝg-;~[aku[;΄ {8+]m{qلhg[DGӺO/x JvI|NDz"X`jQ¦+Da p\V@lJ\~opw.N47ОX_~_|f>ؓUSgxlB>雬lG_l룑|Dm\թ1GwA\ }k4-OO*\',93g'n/-|iΛ"1xMso8 [xLӖ`mF˂d ʁ϶ے*uYG<]˗slT hE_ ܓ6;BJN5}UAIվFmqM`}nt hF=bZHm>[1|5k}J'|PH~ǴZ\|,+SH#|J٩>62-k0甜_}v܁W ~va`v{mds] }v<&XSծa>&~6?6Vv^w}$hgt3eyowN+VS70rtMYC&sA*hM{9CGM+y& P! pbjwC}!6(b\P?B&bTt })tT[@FPmBχ-SGom'D6M8\NywGhc4yྎ3?Í+lX>( ¼٤}"^a9%W;2ݜctHhO@cI6YZ"x'\躀lĊ;? 9nppnNy^9/o/bbL8ʁHjͨ4uxC脬Fb;9`k? ;vWT98)F`Kb3߹UP]LmKȢI%}{y{p/j]3J'q+Ū`ޡcL/<`1N^?XP >Q_Sn"JY*J(paV ?fGXtE Dl>E}S[Eh's oWL^ĿB8L. \O}y=2 kt@žmH^g=܍l:fiN3c*mL5~%>CpLY v~(b], R ܤJ@>+0w/_{'nfEqK$5?viwn&x=~1y6('lDqFe'Ku>W֏ jt7/0Γ^KQ/u潾bg>L|؁֍luy >6Wu}y?;UP/l%*ݰ~Wm±x7~^=U[0fά\.QD1= Q^fEX\taC}϶O􉦮>`ڨ>YZ=~I쫭 07tM54\7WFnzJ6?}A74,ApzŹA'M'i4_'.|Lag}EO򾪟؄ $cܢo#+[29e[o>IqۇM:O_?py6fvL=Q>{ٯy yޕ|o21Ίۭ DodfOq9[f Pl;!m h"c]9PZZXK.}a [͜}`]9G? s[#:4$m?>"q i Sx̘^Dǯ}).L%xNxX\Xw]7ՇGl$i!oxۇ,9~ژ M}lK>pCFj ݞi/hE)P WB^Bk,-R>jCe=?nBEirqF~|q0TxBdL>ڐMJ 9Bf;?x=VDh7) ӭco$*{^0 er?ISˤPmZ@|o,>Ax<5fu @7G < 1ی%.asa+Q(7N#]l$I}˫F|,ۂ=qɑnnN:g'̇GCY|nR7;r5 t [`9Vy/Uzb2 " 0mv,U͂yomK <-rsYcZŲFWmcfVZbo6l7'w໷&QɺKmS5vǭC4|CW6n72%|vz[Zdy`|8-mɪWkCwML \a3CEU!S }-ᢛ-@7~:$6*-9) g/\:oF`MWNrW,cNt}2%4z5pbo"ngщߜog- :! 9Cta^exgp+N{'y-[:ŗCA̅ucl0&+0obZ2W9='`E~NᘝOgQ?H`ހ@T?7Ho[c=9>+AwNbGwOu2ڊ˜N)56S}[(ᔷC?fVT\=mgA]vXo':6U]/]'7}ͻ4@>m"ߑrH\@']P~j4/b@zj%a7~3\QpIapI胊G7[=^MPd)$c+M_Q aA/p-3wBݬ#>&N'68g'M:Yy-zǴkOwʼnʵ{uB]sx`lbӼW< J9bd8$J&,} ;>B(3ͧaoʵ͹_RH&:r=[XT ?1a[\!i8[5>jNVב8Zfηf!t.ӏ4%imn_ I[@8,[y]YVS+3a?m0"?rr ;HU]|K#iAElPN04ꋯ&E -f)賅%h {nGx'd|Wi*|0}ŖoK@])j}pnt20M )F71aP:l^x0¹'Φ&;N`ǎ55k̪"m!:IƟO(>d{FlBDcS#Y= i ˛* x92;U\`}g4wcw|9~d&lUM5Q t|"ٍ|si<;#맨MNy؝l5ܻì 5=*."KBC}.ol]6m,|~E]ׁ A>JS]fY5lIT}m\o޵@l9cm{dtvo|J~Uw%J#x HˋѵOdx=HJm'w+AkhJd̹GElvoGOFg8''b/<ٓ,`~[|GNeO(/z=,Zxn, NWH #o~v'8v̛pE&-{BEC~kO؍e>fXxWqLHGUk"Nl#%E]};E[Ǟ#AaoO%yW4m]^YYsvJ,$r_(}Y/KCh߿9a+,k l-489gӌ?88r/ޥgb0-Eb? )޾m/n7O$ zi7{ǒ3:j"v.~B7f4 :Mױp =)~IN|M',V2;^6ZyqGOfAbO#G~G ..{'pSc}˙}! #[m7RGjyr3&_"~\tT%ì}Վu-)ӧ+e>nW&;A }*||9yra~N+8SG'vA񼏩Ʃӕɭ_;B~ߓ:qJ7,fЭL/oj`X:g VS#EFx5~<<z87M1F>7lO ~}`O̫j1p&w |&?? |&?~@rd~6z2Jk6f!6r  rM 71 6K}_f 0D r;+]_sdӾ{{&)cldv췚n$q*p2>Sۙ\peAd)RA6]>[ 72r o:Le6J<},r$Q0W~Aq"`65s۽/۔dOn6SeNƋf1y+PLhk @o[R' ;: ,.WzBx/mC3s@1'CNOI7մ.+{ a =ܝx.u{>9O>GHBr8h0إEŧA3oZ ^/k㯗t3rãb)+zz|#{]lyU_5('Nd!o9N@c7^u<~avyCv>(PuinA~4~MܼKi!=-H<5k094})>s~k߲Gg;:y*}V| `})E#W֏?7<7<~llS]#t dH]6:~o=ђEƋUͱ!wUa@;`RL.x~\Q&L/K& 䮙*>2mk~{^5䬴7RVmIS?ygxq6ۛLݡkOi_ AX?葌`qL~w?^sfzJT|Tě񚗜AxgKS{)bQkeAӔހۘxCܮ3<hPqly־ Lf㵣˔861%o[HS# ?m1mcoKt21lhWG|7w->>vnl{fW:o8J:!e7gPcJpn NJbͿ ^#a=Fm+\', ל'ހ9Cu! 7(\VˮF6gxz44b2yAqzp4rr|4. P=t9Wa^}sR;;mJsQe\x/ "H>( z1V=`[cMʹL W|7:^s1余1qf x̵: YbmAdP࿟ŏ'-N^Iٳ/c-G_M1J։pgBK>>C+ ŧh/ԱNo,lSSg(6»mS>wv>G~3+|틺*/еӓH7v,:ļ~)H 2w~^CtQc@;%scՙ_{Jǐi+cL #RD(`np.Qd>y.w՘rиWr9?#DZ5t Տ\O0])^f߁gUvh:v ]g BQ\_SV9ǻ /9Vmu^WK'mM]ffiF0/Z_c>Ay̵:HnЎP࿏Eؔq4yh]ϻ{+\Fym:"iR#,,>Y@ n^fu-otV2xcy=58\u ~&O:\g}v/Dܨnjop2]y37o:kɽ2_)<|[lǯQ2q4߳1d㟑g$7[Op3l"¦ ~8'O$++$k>^NjŅ y l#e 1#tynXK _>A~n)!b+1폅U WMk&nu^Wl?ikr2?Q^Q.bQuwpLk+rY$| Nq쩁>"K`GO(^.echY :k$@]lE"pFps ,|YMຍ)SP~ <7pQGl4S=vc u\=w..]+"VpBC \Ǐ7r9[FGFc!OL. +W??ěe2@ YOܒN7pN*ř +)oq!$PꠘlAZ.bE33* N~4PFzZ: L38Y1 B7kΕdj?|b\sܚёml遏zwQa콝PGKްy'.jc ֗)! ͉Y*=$'Lg5YFLjv9ȃJ+)L9@ IDAT_[+o96#M[1=ֈsĚfxnyy'iSv 0P_p7ۣ!cx|"x.;4_bǽ)z|0Y)>lK%Wp_}lyxٷeÒi-0!Pk=t-Nft2*]/ }C7,V;aU]:滙pO3񢴧Vf՘hs B%t&Q((gO\'cf0=MU/We6xqFEJ@+=fQu:ˢYKUmvzzǦ9qK=6.hD26j?pm1Uklk.rE'{3T?-5xZ'vsIhBnJ!3?ˍZJLB% }4/KùػmQ^}#JSM3N57l<,3WYWv=轵Ȧ7#>xm E2vV~<~0NŊle}*z؄e#} Y%MϲH*k _ʝБBd|!\?ЏnGlǫMbKy, z\ 걡v}ɔvUNCu?D頟aY$fVWc Eޓ'Je{NxG:D!|68C|V[^^^pEul$ˍQ+g\*9<'egh>K؀E9Jlxz$Aem~20M[ 0#)^yb$[j6i->ֺ//k VL:꾨}5Ԙاeqb5d|'1k?ЇQtpd$>Q}˭CוzKAkWu.`ǿUmxQ ̤;bU\` ?Zga[^{%}AaȎWx0JлbWe{2|ɒnMhG-F%TK%$|Mu5%_ڦޞ\|3c_ؖg\GG~ Iz;un.bj31kɻwqו>O'g1o/`~XԿxroC1x+9{*/Ѻ<~ Af`R/?z: 5|C{?y_LAh/ϕMε ;D:0}0Fw8뭩sU.V`I&nGL#g8R߅dgrׇT_܄MIi`!cX”]8,)ruY|?ЏbN%0)nBqX/@:0=,nɂ&}fG ԩ|Tv;D#?V9y}MkU&=|"Bwxfb $>ൺYY'i%&;79rKHmPz!V`} |_?4ov775y0MXy"]Zy,>g2บ񮳐k2?yX?y Ot`K7P'?\_% '.#cfx6x\[}˨H&y,MP #߅D5D>?Bj;3 hlzDH2 zޣۮty#Z]rU]m|Une`j&ݷ#n4W-及JGJ>zlA^Db2W|Be+K QF_'ˀ-tV&훑ךM<)(;|%1G3k_<ǭHFwp3:9sw_$Q69 e}A,KB;w9KH%/o#uPwТ6~ۆ٦h}QQ8)KLQύwNcr1SQ1\1fa>،KF6uSXlk~8ÇA{ö.؊dd'E㣛/nDfCv*kwj&#_Pqz߹ M}y\;C} m۞CsFuL5Q\[#fIA׶Suy?/iF|ʹ~:&ew򇐵J3z@?S="[26ɟ- ;=J#u9ǿͽLʦQ\'pP$/diB]VMdqy wdj]|5(hEA S]b-hz.WY[V鴚v J7p<>k0h.}Y(.M NSu3ϸ Wb*XKBG?ưrV1zi|>%XȦPw !ZtNKPX#F")cGv-y_k Nz񉿏 luRmGW#P]y:]#Yѣ:ݜp P};/~nOG6'03ϟ!NSe^ZLϪ0H.xJPyRA1EId+˖;E [0#9- q-LV^V.Ԫ&E: xHtcqmS6_ߙOo:A$my- `Wr_N4x, ;='ȠuR2>S:K7cW~Q6e׎U:8h;si?뷦 Y{u|)HwѲ>θ `PG^Qb|Ơl@>ѻFކbͧ$em@I#/vIL3㹣?zl; })TϾ6x{>k>+<ڴ[U>d-Sqƪ;L)H><$3/M ]Ox>uMM1Q͕H~v])q (ulr\[_*6+?WɄS*v4A`d}<ī"i?#)ł` ԮI*[sYzzjPT&Ѣ$o$x!K8ϙo/ŀ%Vo7l.+}ggb!ywF^E'Ƚ†/;iA6 pRq߬ LyWQܻ?hN+d~by'gxԐ/x0x<+ˍ<)2#u=?Aaw@CG'P>[-tp+ صzu ̓pt@y䏇;ZS6 Lc;Ƽfב|ed_kN38[Х?M W4u s0R_hU{{~C>.ĶwǮAȷeM[ z-J랷>s37ܲ<66ymZ*洇V%)_49?@M\{H@"8n8[0 - /fʂX[y8rqoWB!u=[,~Z^e0ɵ0WCr[Hp=˜ rَ_ >. D49Tmneov}PΠj[ˌGwb7$/%|+%ǾG7*%6\C>·.d_XXFs&=YTX [aXmdpXtx?#&^JOWOS&&kNߍcЎBlXbѭ đwW%1 (ȗ+O@$i_cP^/,ڴnᝋ0QWPڊ{ӆ཮w3YUY QEEYϗ\1{ky`y|?e}kd"՟D֯ɣ鿲oo`{ftxt MDd53// kJFHF>9ZיCd_hӛ`W-܁^܏Rl!֡~Q뤠~?D3Q(n6%݋|gGv .ࡄpXX܍Ma;=тok&i̉ )N#ץ]-k1pCCX*ngHg{&~<+@|06qc.ﬞj}'{}uQJNnP0乮7+)&n%) uxXdpLe.{_OɄںlʝ2==} u0=LUzNeS7ӧ1eK.  :׹uȩH=V t/ec1yC4H281>LauYc>aHT Wy,83C0P|,8G,y|@2f-ge{2V@=޳Y%exێRn=^\ɒmmu!c0D"G#Aq~j&yn2{}ye`@ݐzl%wG_66xc <ư:R </?,/- qWi]@@,@ ;c 3 >fYd~@mvª?΅"~sI|#k۩U}%3)&y%} gњo2*hwvLe}Bqlnt.YPxHW ;Gs0q{}mSU:גj4g]Y9Q?7N|~@7w@, FoY¶NwG?+6^ /# 1fV(/׷.NyEX|"$tW$ߗ͇:d){{` }Ytߔ,iݼo}C@cW{rF$䨲)S'6.}}LxpS1if?Peznkl~R1k(exH<5V:n7O|rNqoڮ }) e=,gu ^WoQ ؚ;Xmwj==!dbmU!t2JB/w q@u˸5AQi;GL|٣ˁ?C|>7W& {<l7)ͅ#'=`|+. HPN`ݑqCF6O='xx9~kZ,ְ2!L!C1~I]qVI1h^@oiu_GS6 Bͽ/p"GgQC?2sG|u0 |y;ܴZ=ȇyئ@U]WՄ~V:wA7%=`$A8uq-M=A$iY1:阦3__qYxIs{p:@l~ &-nDI~z% mg_QuEPeWzԳ&im}{-$vb -Ǵҡٻ~m~eY+8&ͬ~cj_o .݄~T=[Qv'7h$^|x,^TwDt~xk/S&eњud}oH*DZ@!9^D 9#QW9 ⺯j"v6Sb~~`tjgZE %hmi؍n|.FGѬeỼ(<DŽqu.X,<^V|?@0R6Y@:?m S/Hz3;Ir9Rtp&JBYq)N'I{#ĭW2?) iT^suui >o6ֲ]Ҷ_k;uŸmJ^GS6m;e[GXB\ n c͘&cpU _Z"]}{I{TǨgy1{Ln3,I,&sEA9?!_6vg5-y;L+?ʅ< ],@v[PG'䨹!9sAAwrt2e#y"kݫwoX91 nH:N. *twB27W]ϧiNs6yaߦ 7y˘8?*`Xd9g֯[u{taFdVTeͷB*j, p Ot!iKqό}HN7ק҆AOɏhHG3I4]r,ft(&uY(rufr FzAUR9Q U\y||ѮUy*lӋ.6@z BNcի5K0ՏdxoifSB!8t; \)ҼM?Fj{sFʻJ}9Tl;?=||4auȜr޾R<<',8&a1Œq Ư2ƮkCNӤjFd1T*ouT$&bd* IDATT,C:3x:8B Wm15lgA6<^qlO/]Nj7.?"YV߭8  0 ,5-b^kWSxkٝ2dƴQY@Q%x륲A=`D٬ bE $;_V&>WXK @ac~UWƾw sQ[o}|ҝº-0 Աϖemd&,[txIvćm F}Q3{"mu}u@.=a=ϯ:?%@MI^s;vYx|C@T<>1a\зqk|m 1_Ó7<=y<y9dltrx怄{ru Ժ\}GQ;d̴̡͔F~|ʝ̈z?iݮqekt*_?K )ۆ#V\:VPR]qI^Vl=P"#WA.P_)6dJ|TOY;^LO%L53PO2]NUSDDϜ_P=]{"*H=}]!ϯ*'^&Q}{>&fװ܅U~UﴜWQoݔOu`zQL8n[7S3 sԴWW}Šr7}I/dX.P` T=]u(jL]W?fHb/T5L3܇}}|xߠr&?YUt 2]:ss#c']Έōw.ZwEdG*M]WOұ tsTm*'թ g:w{oC)ɸѡaM߂iHxHwl>o$q9{e]%>Ɗq?Vu/@rP itWM,RıܴT<$S<[%T_&l^qɶ2/0{)2r7$Ey#j7/[>/Ϸ- ƏOօQP/`io46XFy{~ 1A]_z"Iu2f=/>LtgJ )ŲUe?u$J,4WNzv92,w_rkF5z(; ٲz ?_/AسLR;Lgd)~0? >c`䭠 @+سȲB׮~Cub]c '^BοJfx7+Sњ]* lKЛ]m6MZd};FT0v,P߀h}[g]dU4NFGIYL. xp]_c2zD/9Ѽ~[`> WCI29/^sdȳ+}F&ľv6*fv/=_g\@*{ :K=Ho;\GÞf4vAS]:細1~D(OwSmSjy6.ojWq3K_|rQh#y2@ G=c2b1֭/RnRk)Qຓ Zv;= ej=Ov+*+ K}}d aKVA2iFI#6 lt<ӫ X%|PXܽ*=/2%ڙlC@ÂA̾<;'gjղ3@c dCƸD\dsǙ?,_4'w~1ɔln. (oȡF]l%V_0ief*|o9oxRzhde۱ GL[/+tbsǨMPqn^2\5v6ڙڃe1{p$I>z0")=4N; UO/^!t!pqW@?lmyؘ!AC~<Tz;L2ix}!?&c3(&y=\,]_' ͚,4}!B$1͏$坲=-௻kT|P %= u[Eh:V^32NWcW%g݋8~¯.H^/ h~4o-MwfԶ7*7HLjb}ߔ޼`ceZ@6M0Dž1D=zEh6XGR9+i^#E} dmhڷt\qZȆ:V9C}G5.:̕߹t !tpCt}.ÐȞ֣S?ΟC;p]a~i*Gb֮\>q\ _~YS @D^Q^L8x"`$TR[^M7Zv %%kWS'!Pئ.u&xgʉ\@tO lUƪgv4`Go7OVa?#E!@^N3:Xƶ ?A> ߍHafv ^ZXqOg3qG7!>uw&6i_D1\㴬ع+[Rn16s} z^%.##:{G{\Cϖ}ܖ^~jpr== )1x;e^as?@7 z;gcڤ%~fQSo\9%pAVww|?p'<9e RW}Ldm/([pM VfxB߉p{2x2rP}x;^czp*z|EbK G,'|3tG6rbGN6P>񄏮INao]ھ7F*@a+W\ݞؗ",ǐHfJn?t 3,5ױmnJc|GsaP;0upvR#V?;b)JtO(xu8$xm7Esv tՋ@F?zrqbNcg+aO3:[^O}5ݎ[KPwިiUy/PIO\Ml[L ~;^@ouR~+ǟYK;s-Ua}&zpm<Nv`rzLl$r_8fuH35؂@fךAuhdY/M'G#{I>뒜|_.H BLR"OQ(dG%| f1~^$.ݲ}(~`{eW#Pf[;_WSTTNEt3F$vH$K鑘WY9"#.["Polup͡]T$xQ[ع7S hm۹Ɵݱ;![&ʌQK3:hʷAEt/޵tmހ:~+] NqM:\74;2 >^hl^o^>S,GogVb1MGW^kt$9R<P=^?Oab%m3w}ՀV:xP~,5`JLd/9Y>6/p+dw@rUzBN)Q{C=N}n(-|_]y֏]%|bG66{d<Ѧ@ɺEfdž2uU{SF؞XEc fx{拨^Z]f-~(yj{)͟s ~Gx/m~pbG;q..#sh)9 ʼδx"]zgU(ĻK(ɤu}6:o|7Ml~L~*}P [{9e}7_ze_&= ̡>xDG6lH#dǦ)KxS(H!,a{J$ɜ'um—&Zλ|2 r€:  ޗV3 EnT W'/; W7m u$NG ]^`  ^\" ^py2IV_D4]CΘ?MZ% G`M]iN3lŽ;cb-~sA5sҼ->ja0ߧ@3 1o[O1g1 N]y_|gX? " {oտTR GcoG׹[_{>}x}MF_-qΏH=ΚGhu X1}EۈclES(?:"z}"x?w`$_kWG(ٽK%-{)X+$ͮOL΀/ ;1{I(d7 ضf H C: ב#T w'xۨή^N/#5-=g~n6|4c8&`}ف=NGa8B >O$!@P8)ɳ'ڶ*O3,Yb]PuߊDzvq|Hxd֑nl̃P^&vFV#~̸*71j?Ǭ.:% z|:p0KM3c†4s|4=!$R?b{:~K~^rBQقFPV{.t]r|ctc>Fsk|U ˻+?&g*bI'Rɵ4I&L^$c9L-~GƈJ ܕ `tTaxfٱg4Qo(0fur} iL.ٹuSr͝8uvzv]ҢhKMӽR TX6+$R"::FuA"?uP>SՕ{]b/óӅ|9{X񣷮wT~jG[>Tpqƺ8d7{2w?9$?EtΛA.;6fzM&torU[d}N1Er9 ݏr7Ie^a>O<{h^ ] OL$>;Xb,gl5"U zGnW67|W~LV +V/OzQ؀|X-SHy2XA$@B{`ޣʯ p=l5}oQ2Fޟ$>wDd99ܗYo{pj0VPUBAauvqĽZ'-.yK_C@YM-䦀7mGQ0Fi331όX>Vi}wc|%m64óm=2ZB];(Y=S'Aœ)]} ڽc;P@ WAo%2BtSwY_[/9WZM #gOhNv]UFNѣSd.NbDG'~4K]>WXҟ4n6eY0-棷*IE>ݏ}??]I܏:eL;ZVkq6ɛ}gWJ{uJ,֐K.@l`VN  xk}]&ՙ@ gwD_.m:}NZ CvPn/gv!,HceqkYMm},pA~UflϡGˀoBnŪ~(cSNbTG_Cv]:a[Bc\c Rda2)R2lɋP}JF0Y@k,U&;ZTHs2"_%3-1ƈX#ʖdzQNtMн ;MwO 0]K0}:?*d+?/_|0۴\ʻͲ?ᇕ2sҳc_?gc<;F%4ulu{0%`*F*NnՎ/܄΀Ãj]Tū[EA\<].q=86:U>n_k@'J 2w#uZ۫ՂMnدv~<Ӻ* y=|, c=yte3;,%=MyWDf;Ojƻ.9豈rldgz]egolP|{g{ AjM(Pv9Y'(9 ci`l~1wrhc}]68`kR3D{_@Gc7V~3I遬>g gE>c ӥ$c}@gwY`.^2T?/ hEKȃAߪo4)+ IDATlu+NE;IctC*^ov&vn7rvn A},wsb&ǯd>i-oz}@mm͇|-9#$'F8U#Γd7 _ ௾V҉5W!zpÖTD"ox]@U,|Vp7־w.#m䝨k Q:$w 7vHtQ zdW(>tu)uj:v@41ψ {8xAio9hrB$Pl6m,<)S |RO>Xdul8Ć;c+θ\>EbUh>T+1.} 8 ~sށ`2xԳ=q>x6s"Ǹu_׿p3|Jߩb[qwy͋^CWs+QO{4u70^_wIJ ['szP@SBc^' CDI`I@e">gr6 2|'H@J/OoKm}L]C= e!* jGN)AUy_-{OЭzg.;fJ{k??/lw)>X-f RO%X{Jb=@ Bn` w5oZ|%<]BeI9*Zǰ벖7P$arkaARE scpxvπ3'򊼽Y0(KT׉̎ PCs]!~-l{a (f1%Y;ځB?>t' :TfDx``$#Ae03??/c{RwiS]ʆwB`EWgͷ@GWM(8|8Jh܄NFcU`=䕨O0u3UJGc~ k 'Ǯr{ؿ5Y#wZDq(IvMbvpԼC;As3FUS0Dvlwbt}Ͷ& 3uSbIy=qC;q3!X'=`0?=FwdGb%7۟)?3y#۔-N0P$FfbYh &a=2Jwo,nW|~'Grږs1z+:H~@O'f>$>,{y|Gӧ:y! >0%=Osy+'٫cK V(׫7kBn$vbFeP[@7X H"9fzc@hG*|͈}2FgP6~z>qGRpWu5EG,-ʹlTI{j_xp .fO>5wLg?K_#^X Z7*5Hgu*yP[w/؍ ]@{ov S׽@:eN;ejb_`Y.`2lv6s; pE@XtAz3TpVǒϟ }E:~V/ FFvy* (E'H[rc}Y~wF\̐E<(ޯz=F?FX2_c},('X[Ř.0eO?I)$ .t g{tb(M7y o> ~h净OGŲ`(b/s]/בS~d{$ew? AsF/FJ]ʛ5L'l#B'X)͒l x!pt7**ח1*VnF%}@%wjpygfmxk /ր8 zvj`ܷ Nc~rd < f?X 0Dž8@ wQ?n)AEZp7(Cr֋+T%㺂aBG/Ճc4鬣HrQ&U?8I~3je|Z3co8x:Oׁاn^#r== ?us #:%Ky-q`7u[6;E tN"}2~bcqV~(h;s+M%vNR#̓ƩRQJr㴗#GD{|%u~=GMK9;h5iP/]8ُ͊V|j@y/~[;!Q{[]#&a֘U ڗI~ xYnr=Hmhib ;(BN_&P}pθ뵯##..w#\<:rS2#:G$q}~!nh̾knC` p+$'w{'WЏf g/mZd8Fw˲/.4ӫKa!cxVwM񋛀OCMʘ8$kTW rgҌ:q<)e"uA;NhHz 9Y᫛΀^H/s&q% Фk2` (vj:X]#Xo 0Pjǯ߽?;}M| mtgw]6,FoW`{ĤC8 x{EÓ?o @j1DDI&vJgQym؈E^`v OuD:) r˳\eʮ=Ьucy~IhO%f%!⻚q`e4[wF/^\%xF]&W)DeԎ>V@X#` تe]b>nu}9௹K8y&NT_Nb 86dW[:.űv1+n`׮*6@m89LP cٔ@rt|/H!%+|TSEq' V)&6x2G]2}Ux!vzYqVN92m^&p׵1cGV 'f}m3o1|.氛F\ynĖ+f9+%FTXR}3mk;~x; {tNcZɐ L{D3:KW0G^iEqRRݑ+[J'u-JC f3|1:D]/#8tdFg?YkD4&wc }y{~Up:H9NZa {ro&w:Js\M;p|G ˀ6]Ҍ/`?_BOȔruV@>P_΅N^ԜC v).%ybDB X3Fh-&^F2H'}06gP{; bf$>TA`at'fotGH Wgr)چCip#ҧ5e~-I~9t`a7ycJGs?@՛ޚPߥ{9BٮxvJ]tpP^Қɴ#VKW^y.u惻_C2H. Di0ۋ[ 22lWڪ`}νPl_K 0vҺ>O]<ڗ'e$Q1g,tLh/'hPU9'h[&vxs2 < וs<]Y#O .Et uQt v %gE/M1v"g,0F! Z_$}I\JwEf [6}1Sp=a\RC"17-P\]`~qj^2 =vמOkjz`sC1Vǀb]pxW wnu҃ 2dL#-&ccP];Pg W(fϭVo?:^!Y~]MցBg-lTv(87@p- 'VKCԎ@W>23qzulhl`cOZS(}.ق8TY@-^neAx>6קñ @gg΁NS@pikH9j(YWH8?.$0l6=9Ϧ0['nW,g~ iwL6OԀ"&ݏ7ܔu3,ƃ\CFwrt tcK3l~|I+8[qBw]ٍ7.@񭰳r4QdB*i tYmIS:C8eY_-4fS$\?;f$M4XNIڒ]ϔS2b:X=>x1`+ u%g &FSzإ8Cϧe.l?Yp?Ut+C.Nq|tȔEJ%:8ҥ_ѹ6Ng~GkQ-O?qKѥ}{=>0q01sտ)&o}k\-vu})ji8Ab2J1zybs557"0> —iQ}u2Jv(`T;rW]@WZțc5띖1WÀGf@Y](3:/=uz-Ͷ"nMa6`"mi8h**+X`:x@?i?5rL)svW>H=ІY ov?%H;o`BM/cΊrd,LZz.m,Mo&z&:@Nꋛ] _Nr!vms49GStFJ| IDATKJ>{L뚱? ;qs6>cNpxR~=IkUhLj'ۑ}'? .6 `4όWʃcfu= Рr[D~H{ًV1+[=t2jy:~@4KvY# ,+WcKXS np@\ĞNdc`{׏(bƦ֚>b>^G_9^^&E클?sq tɂ'QY%B\A? hD)UdXT,)~t?9gy?HLV)_}5>E0Z uN@}{V]\t$px> 㽥 ߎW.I\a2t#aS '~Gֹ@hDǧ~{b `ΡW+:d2ƓMM^UNFT_V6(Z-HU흶MJWѿW] ,wXޕ6{{E'%:)߃ضw{Q?'pWvUq}tn?n Ge*p['Ἵ0>%@.Rh. D g͋ܣ\]C _ļ0';Y?59yв@ԗ9;nBπI'mu(;}vo 0|lۦ_2F<=O7C{.M}Mp/ﳓW;p@)h=ohbPv~(9[}/'-ZZʀ洶,DWK3q1ee3p|0Iy!7k22)R u3 Ғ &ʱ2A OJ /" ?RǂX1ˀ:D>;5h9`nZm'y7 z@#@c[ȶE_lUFحe}[7Ŗюz Ń7(\-FSel@uNAPGFc~\ҡ9Ǒ1D2 9=v|(g;e M4#ht) (Wzy)ء;zL "E`z٬ | CA)Hzq/;W? Ɓ&MW۬d2 M|7gpڏ5OӾ23^C<>ˎ5q;u)@tu" -i@+'I=I+_4uOrn` E$c _Notȗ]+|mtw0xNNll2ov|`2h˲g~ 03r2r>}beկ؛yO7HaГ0hMG4Flg6XvDx>EI.kK<_WDH%@֌v)9~yxh~hZh 1d,D n΁rH^EL~~^TO3@A/vZTiv\dgfmv{_jnvHݚ kր͋zg&ڧI.e+tP QVbƄ״񿰭mxJ`PU_k}CWb@?UY;-(^*|!=gg$)::k2snb1x'?/gJ93>"AU?fI@;ogW]a̺`/(;]~^ѝPIa}g #]3`LWO~gȈrC߿ ݤbdUb_,-N&fm-:K\Xgk;jyBp+*)/ L *'X;3w |N'd"{eH;id8wk0ck7;qʠm[6؇=wIa_?r)ܻLo0 M}wb=) i Ig "khGf^1FbDcS3jte$ 3+o@ CV۱IXy.6=a@p3$b4o/}%_۲Ō'gVϥ VE彈mzp~q{/h }UKC[(()MTdagk\ 1b3Fnܗ4 ΁$UۡK|&d@ m VĢ{];xІia{st,Bv;:YټW.0Bߛc3XOmllզ5Uq}%_?d:H刽ЛaSsh%NYm_PJȕP Gcwi}dꃸH<^&?6e_q0rrY؛UtbMb/GOC8/Y| f}8(|aZEj%6hM<Ϝ>9R=Ө-Do= =ts;H>5K$9N݀.g^(%MAXIRmrௐa) j> 1 냿ޱ/бاAvZkWYc-c0XyKdzH_vq8i[2~A]FV vC&ɋ- 4FovJ YU0{ԟ};Êa:ț\gރYPؔ6BESfa&+3y\m!|1=疐ǒ(SS*~^s7_ˎ: El4Y+>S4 HS̻~`_N?fWQa :'By>Q`Xƅ"%5x#gLʧW18| ^FR3x$Su0J'w,;q0]jGmd7}w ==?:~6`11Xݝ+gywJ|u@_}WKDhAԿgG#pb(bP5(HXy4v@ Y쏿zmGuA/\,^ i-շr4/9`G)3 }9A|&$4O}К)ƌV# Dvgӎ1䲯#Oߨq'f 4%( ވh5t^{Fѿ{Ⱥx~4mdZߒ8w{ϕr0>4ٝm~k~h ј>x|V%$QYT>+@4RJF$ gpKZ)L cs3 F?n#;! |Nq`} |@(fۧ>1uw6. NIl:yqa]r-3VVǏ rK֏*Ds'Vy2U@/x|L܆m.#cH\Y& GM.8N& Qn4:T#uc2_;Ӆ:?@%Y/ p|~>;g!^ich‰ay)dV׹ͽurab/LuP7JwrB8J %}"rP+ܵ쪴%tb0^|;ḪTrt*_@NJGs0[nN@~@~0]às쵖b*щ㞵lm+j'DF7o0JqKz;3Nv<<Ԃ$\"[CUO+,18gҺGV׳S鎀.ZpjgԲL U7D%=U{^vz|' wH]]=+C] .A{I0H]JE%3{{3ꋸKM`W[ލuizWsgp|p pPb@ܘ:af [\+ew'*|/fX2]>Y:~ VT@Z$oL(9u,Pob!wG| vrSA5.)dws"J?%#DtB`FiFE<&~_*,$rIs/q Rbp:$dsޟr얬&u~R"YPOfw IL+ÂYchO.xW*_)G^#,UML+@pԷ (NRWEok~3jV']}7ͣ"hZNSC\tl91Yc;/3.xfD!m r=XY\`"W";~/ Uh7s&uOdɾ)xIdإ}޾^ R iP \Ɏf_Wǚ\wϭoUG%Wj_Wlp2oǴtv:F7 u1AG ,[֔{$({5yF";w~(R2 W}^aKhbt3Xcba?JR|@iLx OVGYB4=鯡As'm _Ez̝0z|bK?ܟ2M3 1:b|6`uNI]&}h-7m/Mr(~pA'"@V'~:|཮ֻ_ŧ h[$ج1NZpU@\vF4@(+Љl?;wg6~2Vh k)"V8cUɘx0d5,i (3q1ծm1؆HlJ@#.ധ7e)SV8:.1XI~MSOy7wdCq *9|Jh4VhӞF;;bLo~p+-98dMf$pK"/_7M ['L<KcaM~st{q3:D]g'8O>yef %% =Id 묞y  ku׾> 0l  jn1)Kxm9}5v`bu /,ƚnw21pǮ^-8v5`bX7D210o~(_~ D0=}/K(l-8eު[OtrܶΛ u;snӚ|йC@D$0]@GaY}o%M.tߤRMq*?t>9+Lv? Q}ekhW6Hx\lY'ljx%;z!;N!K-!xGk'^ O_=Qy| OpcG$4eZ_8jzw>cӕuDXzSʼnA-bxl<h߾=GYHw1dch0=ݼ@v0]~9'S~>e~926_G@O۽!04PT(ZF!$2(@߀b3u4@BF "d\ۚA*=8@C1;{dbdվHqȳ;K\WCLJAvro/hɐ*D[G({>R8۔ 8Ȱc '/+d@xLs]E2eXW)ESj :x6l]kLM}UrNHNӛ5t!_tger .juoL.Ov :(M<tO~؇cƬO7m5w^8m~L7Qc j5!$S]@Igut3R~A܍q"v HO~v8~A0S^@ l9whm .f ھli\Af7Gk h " *O(/5K Ri eO%]<P~xa:](_Z\]?CV4ք/hU#^ hp_ACIm}~n甛|f"ÔLn8__voo4k+ΰ%P:)! .k@AP!It,, ]Wlh1&|޿e1P:qT[.x+݃ VBvPݳۍwδ~k>ac[KVt=[])o{]aI'_XeP0F~`zqcE ?N;cSIZEF@)03{i=?}$z^NrH^&9@lLZODT9pu2P Uf/R뀞QѦYk P>kkj|%Ok7V__ƶYzIU}m0 1o}&z H6+eSa pӧ3sEX_䯪apʨXX/H(u_v{AiB_&( "KmߢΐwD&ҏõ񹩊8l=@gm\K)mz `0X~~ɨ t>j0?-ǯ2X;_,t6=G@ߧc<Dn*Y5*jI#J{`瀴Pܱؓ Le{i.=isx#xF_vzƧxkG`wmc7f/=jMŇ/imGko]Ϧ^ҧ:r,+Îv\1 QOOGtw ->GeBQ,D.2q[3WIއIl&쁹THzhZN'biW]:?^]Z Dv[ZL1@^7 ʬƪ@xYG56Ia9Uy3,0, ,h6}`R`r t>X@golu>? >/< a]Uf=niԁ M/2x\:Vc=TRz }G*eeƳ ulK%gI}?ܼa￿tZO>> F"{:Q%LZR݀ nn^Wÿa@r]-; i;~屮[6'/zdtº3J5'$ƽ](pbA]PC \ L9u-o:"(dӺi!Ww //7[W{_{h_-5:*&m ނ#M~>U6x73xUض'y3{_ )z O}Lyb"56#bY~ݑώrۭ#/g2V*~_]Nθb-(w~`F`pMS'vowngSt20|_]J ^jr^Nz `"a6LH?w6qm3<0^|'A OHi^׀Z б}ۂsZfn} z^pr7-Y J͂TXzp"YvVGej=Dy fz+2,~, D@kӀg]W%b@^e*8>o"C*~96H nePtd V= 5}k~d1Dγ}3ʔyGӸb2{"N̜K}qHLNE8y"JiH |;}7K' bhpgT}~bLv+šϑi>mBM{iȃ IHO/[Wn8x(8qʎnLC`} x[t}o_ 2{5`wF?%@/ƶpMDI˶@FOѽW] K'9OzS'ޗ䌏U(~9hwgGYSJعXCxchYG" Nc: Φp3 E$ge{ZD?k/ |'`C` ~_ɯx9tieijG@/JeQ91Vd4+{Б9Q{ >}"ћ<2G2Q{)}o & ]^Sb`bkv ?~ ȅ /S/m|+ƅb(슿!WNGe޷o9q`AP

 ĵ_csI'0n]LqT?r¯z%|32h.i !й+jdDwa1aq z*jRCo|zs_/l1H]:e^=YMto*fJ@A}u#`m׀zsԽnNV/z 1.lꮹUz`KQ#8.2.PϘ8h'dlӅ j۝d1fOMz#|O:yGv.eJawm\c +пm_[ܱ,/?^RaM~0`7;؃}x~ue }YrCjGQj$oZUͮ` Dzɞo, )w[ oXa^,ByEQ&i3\DǫV(ܻ3&Z8&W!^=G\|9GNU]Ѱ!OAVoSk<;5Y*@ǀI6рf]2Ͼ 2;a}4`rZO &,8A3ޯ<oݖxطo7~ I!-nf _e~jG+ZyV͖yW9K`ŤzABTaiK@=om1,Z2Pe߸WO5E?Wc}!ZjY@] J7'S}%"zhhrQdWT)n`k8cr:@pjta++} z /osAw~x=qhþoJj7'7xxsIlJ8a) { GvtV mɮT2y s/Oro/|.즯z"iJ0\:r{Sl_ɌM%*ⳁ1̕]uq dIC#sK;LKfڗytAx6\+5:d .$cva+GVڕo˹!v.}T٠&~ _q%p܆u=&>zN}19cgf}\` هY|/?WO6+\ȁ8qS?X`H \rdC\{.k 0=7&*4Y#Ac:;h@dMtJ3IiWz VPw7U[ IK@cyƫie|r[y ?]^No n~pΌ Bujb||XH XG f0 ^cgƷ8P=\g;4T$t?_RJGb-ԶV=x.(8iP5)?>QkTs94>>W(w69ǛM!M'b{(N!fxU9y{ĩ[RWc 3yKdceػ D8xugù{d둝I7 kA0_  B~ RP&/͍ĕ_q:hbbWۀ%Uygb]`+v/-մ۹WUᣂ2n}AN/V *v !UF{n`Cc>Ǫ>&+a/֯]E.Y̨=+y^)!cR{~W\A}ī*_y|9o7_TYq%e*}e{'gXaS?[^+7%,'X9 vFd* :hJIKjwd {8. \+py$raO`!O>Vgj28+Uপޖ_C` B^x IҎ05K?6z)j Ai6eR{^C5/k(v/'Њ^<`HUoUQ70x1=+-}!?]Qgž.ɻOh f:xNnR?\qs{q+ԗJM:XQqsY ~:Y7 Pc,\]bʜ$NM:mVBw/\^Wߡ֕ ۥː@[+MctnvN5!\`oZuೇl#9>;ZvǍ;5G <^vZmϯK9Nc(PDzYL/zlx&etx~ (*dIѭu?˕%r[0|ߘꔖybNj렝JWvke+kթCY2,uΌqhKlL:ȎeU^_l .~H"JBì>OQ]^ᯊS!m%``] 5v,·Sw g`嫩-UxnzmW9C71WBk=MW6 Ցhѥ}+euXfӾQ=ct,לmq=UQ 16$r (tT F߅GnO9#]o:͊9rt(.:H'3w wGK}&EH?¹-  O]7jeƀR ƂO ^|̕ד('z+! ~Ch]&0"D} 5Lzwf>COȃu6 &zj``:3oZ ^m ]컯_m*$D+zS'9Eh߲.o@EWd,^UA?P۽&*O^+8ߩ7duQ}RaΠuu.mYM)6Q38CC$fx˽qjhB<^LTMl3oo)p-g{45S {3XV^ ~<-˜])}nBnsm8{xZ͟Pq)#YȺh;mN&夽7aTL+(eā^Z/R:֑ Q2BXokț̀gg[6̅· /}+Nm?rR]}?vJ ߥcE'Х! '*KeU܏ B3B V[:rIXF `@Q*:1aYVaws xlis<RU}G;NÜsøw7i`yت@!Gfw˄]~dVNlZx7eXJvѧ?wrs됮8SLq6˶!Y Հ/v;x ب1 (>kv/-˿@_ ]0g-`  ׳|K|yKXm8(ůNNZW2eߴ~~2h& bȌDCq |#<;SN2D`5o_}d VcJCR꛴- q** A+F#8NrQG2 YI\S%nc3{}öFK:zحFru8kEfiBNNW@Nk,u5T twokSP% ǖ&hJW@IVpmX3|g^vWGq ^&oD `AWKXZzUu|Q@wIh qmzr%(QpW++Pt)Ȋ ZK e=w}_s+r"M]@4 _:qK|)~A,2ߊ@Ⱦ1z80%P]|Hޚn4m)uW9T7*wq )>~̟-*; ze?8LNj/0O!p:!lbgW~18,Sg[`𡢯<1_踩, (vU 9ysoS[\e_@wO VWDVc T:qD !VSzoMKv@|R5吔@pmT-r"mgSBM rV"QXkY}S_]^SCU6!8| 'SǏ2 Y]=^c|sM4Hۨ1[p]Iq|vd5m랷VQ 091VϜ96 Y =iiXki\k|Bä xi$>Cn'Y~XOJkI% N `9±DQ֘*i)O1U޼o;<㟝\ߖ[s|R>X:*i'&C]ML"X~tAD䣂eB7髡6i HS7`>Lh [BBo|˖ oc$~6KIQj'A$Ru.;~vy]R9 >qu)?HIꩁ\䡀xo1;a0yT뫍@Ŗ3G}ꗡNJifqމW~@-^aFx8>QOp WL97C=9 '\.LSbN.8smN"||'wL,ݍ_vُOZVjB(,'L+ͫ w,1+N7/B'zʛyv3(~Y _ Qq}V/\77`LJ؋>) χjYK&SWaCu9ո&?䡒2&PF X,kўݯ_Ԝ€ c8S'}hwiRWjd1ͯ6;Q虳Vummrۻ3hIypmŝam ~ƒ8[HׇD^܁vGeSe-=HӁps<]~^ #M":[ƻZo G.n,\;dkeRփz[ vQHl#9pGMV)"P+PZ E OUf$TRU@x5DPX.5Ѣ^ľ껹]:ةЫ۝W-o^~H @h)ic 8fw?_~5}Wp TO 7c%(x4~@=֋ MkpN[f_rQ Ϝ!F:pNpc'g<@p3Nټ3 S`&^UpPn30"tTÏP<1x7]8p, }q=`ʑѮ^e{$@`T-!(~ŎQo^zT=Æ'5Z pQX@9goB);U7ok5EW@H, _ _x~_'%V k/3.ܷG>C$H 1| X9%#OOPY1Jź ~í϶8_&e;zIQAEbG&诩9zqi_SNln4c஽':C3# kϥUfr ,w-f(})ϋgb"ZIq@8 nmҨQ7Gg$n4uPN <7@aκ:LzT)&Ë_}aËYY ./fA#X]f+!"y|G᯿W0 K?wk^ڪ#h/ Ŋcl3y\z7| ի2` OP\ k|mP93|1kjUxoz_boTGkSfq A|%G@o6TU-/ Gu}tty6l p:1AO;I8+4P_ sYJ׳X5 A(m^vƒ~(EHnooyi`?.C ;>aV8qrry˒ &F'.jKB! 3b7'!Ut"F= '3(0o~ s ^7 c_/X=X$l}>E~w{e2y򛆲Qa/D[R]q,b.!vL46`آVw0pXL}lu1`5巯/%gؠX.C}nyMVnAay_ɥ̙BZIɍvٹ{4?Y> A+ddΉVt q{mQXZ=}ۘ>[v`Ȕ/um:jd K^Vǂ|gS—o|2~zgȢճ$*yektUA&W[($SA_A|T6"rÁqx߫Ndžo'n1߫@0w.5b]Y0T*X1o$/UMƇɸ\5:?Pa{P^e;sL0W^\ig e,@xuUR?D^n&.R> .:߷#LO ]Ćf5@yw?c˓g_O[-BBIB}U>?蘔6%~4/M/t6"YmC$*o}45~d]Ҏݮ]Zr8O$NP͌3x=ߠxt).gtyh6|*z$̳k ;J(2wQzyA}] ?W Q)( 6]*O`;!KEL4y{&Jtﲔ6->%  C_=@?zV%UeN ߋY軶WNK8+?,~0:7o_o8E_>a㧎eN8cQ>"^'MaJhn!*8^9.J&gmS>m($΀s#9Lhګ]pAoJ(j.UyLU{{jx4[e>φ|{ y|p[IsJJׄ;!ք? w{褪=R) ~uxL#ZeTo?SKcq'=U. <0d7! O%S/Q仿ȇ2`'ZO]{;4 ~g+uP0-t&y dBoUx셏ճJ""2oN_Y_ꜤǔxEogI}nYXV L3ceod[A5oځ&zR IL_}{v!-IݩPbQƻusR䮪7=wIs-|+jˆ`W2 D~]< Cy)}K"~n)3vˋ8<=ϵHgݟli1Gݍ脵._r!Y -!A"*퇭?m^"+od{^O@Ok rBZ5_}e+u..LxD_ů&̀Cpܱ-+77J7  $hQG1x7n>7au"*Z}|V87doPReV;=Bǩ/ظP+]7HuS@O~"{PrYJ247ow y_XGfɤ8|"ln7Hvܙ WfP\a `&ҥF&M &CsBUWD2Y𗼲V^|Zy oyD\aV"(E+pSv+34|WW ]>UY_1huC-tFo udĬu`=^uC;3R{mGiU.]k z1z aaW)7ƥr҉}v4+TݕStpaP{]Y\w'P)v0En^/(ēMހ]D~)Oy ſOrHQ^mh'I$ٍg4Lk2`Mn .#*ބn /uT?7_yE\QK&A&Z|r/"{V[+T6Evޯ+וB@8tA>0NЃXW28e rϋe?PM6,6lWb֨:TF[}:,NGqJcr黡t u3֗L.|[9ܽφcu,3=.]%\[wCq-MA7l+\큉 4f.kL 0-+/'lp Й]i_N{qҕ{௄Joƾ_'~8-;/}v6 /*xTcȩs|ʆksc;/g_,DĆ`G993O:RQ7-]KmY{%;NcP}ya}kJDD p9-7 _|Qwo!Ē.qzNlC~ޘ IDAT ں7h+lMm>T4[BE<mtwW kN>y: 2 w_ 7 DE*v~hI ;_ws)|T}&B_pE+gj^ԿaxJo4B&fdͱPĢD>`7뵼Aʨqmеx1 9 R\!i;ocXS)u;:Ru5TF>ؓ)un`! r- wSs_y@XXÝtV=[z3$|OZ̬)l&#> u@ʆT_UD/+FV' HcG vu˱4jx2!`*6}F۾pT@_BP5659PqJH_;@S$|EW؟eG n*? ++ƮMA6Td gAԝ6y(f-p$-6:wRhh G^U8>&mq* 4'p'@tAlAꍟN~PZ v:Ew'B\ɩ'B3&󀾲%RK|61plC'΀gƭyBX6c?h*`-amZr`k&'6 ?)B1o۳mPZ~ AXA~~,K!S^IOBO >.ЊaREmӥ)bdYoX M2O_S_G^o,~P>NRd|U8Bـ.IrHЎ3v[lo7 ;3X:`DqOX'?/!il>^-S7/)8] LvM #v^ ?FtJxrinɍs 00\uڋR~+!θVȀSzb|C~ G$bjBwsN *$ߎ #ܻ%44x*m{{W<ʠ ~wd,*Vʥhy= 9ƾ[S+K.a a~}:f9dݑ f1Dp;[:P eJIPh.'(K@.+ А+:QA>yS#Nt5[_ƢM<y=Bjb%<,߲oTk}۹(D=aCy/ WI;CQ`MD[_ޠoL؁2ya eR\zJ^NJ/-C}<"_ #kWNpm-7w N89 d*N T)tEJ;MOɰoC+K҃A V#THϧ`ĨI}` u#NK8q]"@\xL3I`2qj#!A]+v䵢 :>P(KpAanP@C\+}ۨ4IpYko'F%SwSr[-b+ja}WCV ύh Y/No^\+oJA;jj'F _ѶȚ~71^]Izj/2^]q6∁騭f)&zGHxP"+LXz $<xV\/dWRȕem H(|:S+-^_IVK=8U˜ f2-? [--rm_GF#8F:ߠ~Ub#'J.C!+H7#/+[냺<Ѽ^jWq-nWr\,8=pߕA=3=I~-ՓOm' 2!Ș 2vS0ZOG ;Lg>ulb.m\έDxЯ k6i^5Fi+ g,㥄jz@ ؇ ҭII/hƊ G aDžz-6Y㒹ߣy|! " qI77[S,PۃD.R_R#'0UcEw8}gϢY N4EA:7>HU7Τƥc*R>Uud,#cUuJnW+ h?ƙ\Wm롰<B -Z , eK T.TUruopDڼ|sow&hRr[> CNH3̲om}5߼#3~G_N܎0)6/(oh8&"&WG_X%qs**0usU^g(BU'Lfma_"G9d(0(BPV:*lC_q:5\Yߍ&N[?b]S0N}툚A:\#Ě)dAs_% a纨Z0xѫC 7Iu^+ssk:20Ol7z`Yd5HY[uTjN«|Qw>W3YU"6Wώ 2`3|p/+@'n9-@` 6r`6\)BWfkg^_;BEjbfIL A H\I([˒r[h rek2E~N>N9*1Ɵz9P>קrrHI =+I@ ?'7*W@?""h`jua[nv3rF\צc5~Ey'Q9CWm[*9#O|`ٮ5!l[>)à@ژo֦*ЕէYފ2g y`d*@5웼3,_\nO _vk_Ȋr{`yo u2@* ʣh;Gp#j'|;dwF@HGl 8Y`p]g~}z PհeB0<#rś8)[nI76`||뮭!!(痰G^j'=yȾ5nv.tJϮF&_T"򕹉%>*(a+y].([JG@j[U.ݨmlHEq: 7O+2& "r|0ji[xq': 3W#/-]kqzV}UϏ`>;ڭ96}$!;4}mq aeP3|~ l x0 ~p_kof픲SKS'{.oR͏3L>s{y)iA%gɟȭWwmJcaIdvNEW:XK!'R)pH2_ 컿&A]5/Ab}o [>҂ҥ]=s6J?I=> W :l3qө^6aq#@!>̵ \6 'uQu\wO897˪ >x`jvμUtlu?1r |lD1ggA2&w/M. ,O|\t Wq- l˹0Ӽh-G5ɧwcƉ\G?,ջAu \ kU KL# |^"[*C|z,e119$21\ t&YSG0UWz(Uu/ 5@= E%GjП\L0e9}hWod:m\)8)mV=-N6Cbm1]\;und? ~ =9;ԻwU)kJ铟g'$9z4 )d3e|XJ>'Soځ!쭿wZ0wBrճ&0{%l7ūƙ޲gAB\&N.WC kJ A.7~ۊ06 Va+K=/[,o޸&Nje8l:`du_<6_cU\:ΙNRNwGdFλ_0aލz IĻBHl A '_O2~]3J#{ѥcIr]۰먞QuXklC!8V۫UA 0)CzȮMo9gs^r)!_ " b;zVWUMlz6/\lE\2&Kنe)s# _S]B= 3!d+y2?_똄6k>*J@#@8-{sfEAl9Tt5 e}LauorGl ˺l3U;;4_#Wt  ! rq cG2'~&d,Ԅ˞da?pwtN8Ф+5X51HR;AO&b ` = 0 @*SAyԠ@. p. V.FWuy^HUF ~XQͷKeT4(W_c"mWsˠmOgc_Kc:F(8;?>q7-~.egCqk_]*]c]_wO{LkU{>fdiY00r73e77#_&"{kicąvrQyrv=>%v }&6SC땞ao3Tv1^eahw\'ѝ)HW^}*S1~z6^iw5񦡨kߕr(VT R*sgq% Lpա 7;P(/|VKiy "fTF@Ƭ M A: W :{twVFoC}i\,SkʤAiKBny:ǭu2[``pry+a.H 9@%ujzy1[!C6%+'SES5W.CWl[+/}'nބRᯊV[[Qlj8GŅT7CCvz)iPɌ@6 08 n|DL taC3W+kuĜ-(\'u1M94jjZH=:~i7UӘ|P9lOHwys 2^=CMӠoQpZ'm.D! IDAT&()DWǴI,xucUnz|xx Z?ksq3G>Yf.Asr8:1M`㻸]E !( S~ ]Lcob\/GVaܕLA_Ԩu=ۢ'< Q>@ǵYNyؘ-mݠ rٖn@݅Ie/J1XZǼ-(\'+_3c#O)g[Uh$7  W}FiY“Y !2jBN4I_1G'm{X^ZR`c|Rer Dl1[WUlB>U շwKxA`j R_sݑr^-ہc\#X&86~|{a*R}D#v [ -Uy-cUXB? G>Iv%@`s)ed[>_C"j6Nq¬(=4u3y!hZ ëY! "ùa[@@ʶk{,Vo췾Ž_|1! ,*<66?Uzsp. ަ4.>56_{Wn%'Qfaf$yOM/iTw@?%䋨.bSK 1}s%0_?{R?GH'ޞN0+&Tn 8B_0 $:!_?d4D!x[JRA+_i*'̛ z _%ԃz}eX[atWߊT};8:ZWfC;˨eE_i(VX~ %.Ǒ?j5ʑ.Zk`9as#~5Wy;ܱ4+/mOS];XJ/]@ނ13ɹ'ق8?52`W?D`ϓK]T 5)Z`x7\P8.e(mKMv9_K{&kbq޽!(qO÷M'ϖL'c#m33P}BӬ@ l46*ir%q:~a4KaQ_ :.Wn,k\27VӧuI9$,|ҀO=N7N/Zښ-AvAk!DߊMPǞê(I/7%qB [֌jv@VZ`xz#dsM[Rk^Y7Gvywp^{rQA: |B<`M[qtyX t=.Z9 pB7wݰKHqϫo)p*ݿ QEP5ޠn K,‚_TwO @¹)xU4$?{[xD̢1b( 6Yw~P|3na Hf@obzճ)Gd,!*-UBIT^BżWv 67yիմ>R\o*cUg 0J&Nj"`xO Ec SF ʲ q6x*bWk$~quRZuHqە of |Rf v[ᘲb-}G`#ڱ&1KC!Aʝ̢JC;|CXིx!\&~9ȋEԝV k+[+̻q]}'MiқdիV!jh~uoVW-PԚ2_[zVnJj#}ɨ]Lo rA& Hܧ aw9AnE^L@WFx#F8s-cL)ޫ+ jGF stCjv8睏zw&~6_prjL6[bsXCoW/LIyfy2'Ѝ2- tQ:T_8%2w\$Ei!ӾWV5vkλl5>1\cDw |=c*ձtp3)O݆uy>Qp*UFc%z峌˄x Wa3S[+\yWU~q|l2{Ne]ҷ۳J [ܮ:-XHVG*F2"p\p"~V 稺9UvW;O2x-I\L Cɂ0~2ŏe#Q3gL7 .+;P]@x IC `(W~BYi %=R~~Oqs}+3O9zX{}]AdR GF"+hf @Fu#VFԈRrùᲃ'+ɴf}I9a^ž^0Ȃn$v3Yc K, E:+dd=pLȿ+-\Y7=JP^an]}P7_!0,;$ĥ6xxp49u,Tjcoy%9˄%BTOAXvOo m*w__en%@ބWY JҞ>ܦ/`)%tD/8(Ne?:9WN{_Oiۜ? aWq*;9'pT##Ʒ3;Q>NXgعNlk'Bd|ղ`pXLWf5%w"I4 UuRE YhKzOz'!W@:{L ]wBXƥw5wޅv[W/)4S6y\ U.9&%H`#`V_, rA8xՆce7CcQ #%AnK_;xm(_, z4gu W 7 [u p+a܅mndcuo>wS&EWwo5md8:),Hĺ#/XXNc}߁7,sfc`%]!`dhjH(h!+dro,cFk?`Oq{@p9=TV}%2 S3Nu#Ѳ ZrE2@*&T 2?]f>2 %enoE*/-᥆Mc^_6|0J'( Zp,A:@;H'_8gٸ}C鷡972VεU%NNuw;u1%g9vm.Lxu#d*e>(  Yd O`S8}/Vn/ wHoސFKh'3vue#vwe;>[wCkd TI<$2coYV&xlv̛ ZPh ԊPUV/׆6d瀎X)o m`ᯓ}W b1/̇<@G[r2]Rx|H;I Ϛ2jUwgY0/_a@\]p%Yn0Sgni\w_OWȝ`p{Ϟˋdk&a@9g~e,8Uf[ _tE`3ūᑛ|ٻtF] uEVhp{仹 ZNG *0cT2X5s{%<>R վ.ߖ-oB$f:JV TQL5` 욯fҶ\>"W"' Յ$699%U+^1Ƙ$TŠl㪺>t7;1(12[یw6:|޼L^t6uιl]+w+ML$d>,bd>`0nS5( SS x(#%esn  ZXԴpx]5agC] AHK{>7.PCYY յ>SWނW7' p3^lC^^ip*ğ02e`V fXrz]yhOL_:6U!|c#gG> z2 m_j)$Efn.PywEIm?3YXL n|, ,dar:x(ad$yuyꛓZѣ+wUl[ez| a wC8ֵe0d^B-&gCYQ-O LV]_CW79.I+Hj MQ2\~K3kZ5 @3Փn1o]Ƅ#x|߃WZA_iqsՠ~o1饸x_%g9nKo55<܍47+N -;JO@]cq"V%Hyt;r@i.!,tHdoe7]weE+{\=FAnf[c ۞fz!IGp1(]܉@qb^^}|,YV+W_tf>1^o^l1ASX^-#8HtKv0z_F*N홁:<9%np nט-.!pU|V mF. N\Y@XC.(<@ѽeBX}?Qi$A;Tî 9j1{ 2c> oZ0!X˧unEhv:dsyMuX/_J_v rAI =8ɷv6P܎3{E3{` j;O]K'(= ~7Ў@x_Eh1)R*:_仿~K60dp}Ѥ"P)Ӎ< v &zAGLΊaP[B41-urLM da! ˺pc{:'SݾՇhɔ?eOl]T>U oJ[XcW^댐E&8u5x)gX x':2`j_T\6s|hpF[ ߘ-tC0m ~ߪ\Iu8kul%(H+ A1:Ʒe?d>,~!6pkkh$ d6e>h{|q]9fM{NR@;*枕wܪiK34 4NC_*ifuF;/Ndâ\[Oda k&BS[Dtrd5*u^-2">|Һ mjܳc m.S5ji13QOA6IHFeނDK>? 3k*qft 293h, Ld=T0LH¾.#9Ȝד^k룻,+U zmJH]| x(k|ݑQGNZL R+ 0cnC`_rhXkK"Z@ʟ ~nofbhԃ쓯fp\bAؘoSePQ] 8kowuyȕ56W ,㟳' IDATtln7*pzq8Gut<_r{CoO>HF\W뜀vaKith8=KjB{>e w05` iۍ!.(4'aن➇֝ɨ;^ x+J(/aP$AG[N2Y>òreJw-8o˿Ish@+s} ->|bj߾u ]<\CDvp$F6ÀMƒd_QS +Q٧ս_*-oS/h\_ץ8umW '%okn,_Ⱥ+|LtoO&s[rG`ȥ|l&8W]irGnWGsea" MT(Mo,wuKz9L% [>tnM킀GDg o_Pp)=UIN_W:,eXf6+ f ֑66Wg}B9p>c;)c2KIS7:G~%ӱ} B ApO~Zl[S3Y?tNqoaS:rc~L&\|WԱAlύw,QU&'jѮLM8tf,`l҉ X0]?K74 )ӥphVYLȷL*&nc:mx_?cܱ../RG/ Sq]lg`6`e]TV h9~`vE~jlW;N l(+}4ƉlVۍUH7/k;[mc"cu ;lG~8 sTO3 ugxQ/ȅdL^1 ur[1kϗ;%&:~I4G} `dސw`L|y Pɭ ^%$͕}rv۫ߜSn Gu܆?=ĉlԧИ j*Օ)xp5Bwʰ|#hrݲṶ-A~.cp'maqKWzI\!fk 66we|WOl4~1_\]<65~$}.׭xy#A=YFx;} !9  8p3Penϓ$& l?ĭܒOi5LRSrca gSo鐊/59G_뙲6̧+-(qDo7g'ܝvu#͢[ :9vJowXf r5^1mh(/a.ε~Y#U鸏q; q)5r){nH|*_w˚ f<1n3NNcd3?OeCrY ϗFyMv-bn X9̍_8ogG˦MPv=}Bh+xL["3ی/[~'#k9X{wc>W k6 >L&nGQ댟>I5\MMDώ2>ezF`p+ N7™[ZVNI"I鮾 {l<<|1?Lfwt-wzW` ,b:7wFqh_G1#,oKѼS]_DW$v17.1Wtn^fˀeǴ  =_7&Yǁ\@l6i| F>z8 z|; =ӥp_OEr>ʅvs MJ94=M;9tU\3N- >3N !̓@8ۥ%<9< M* C4×F9:*w1 Glփ`~fY[:i|`-6{u ;*A.eBdB @/-H`DZ+6>έd[@N.}O6`R`^H/#{ze4E__wC&WT]QHb>ZczP>q9[ĦTzm’Napxԥh84Iu=@p|l ϓ% >~ ] Cv]&$R7_ 3:7Dk:=jP~?k~!G"!]mLkfU])9R Q)RU/ S^VX ^Be2 M|cS`FaOv}قN(*叶/\ o >>>a5;m9%vGji͆ NL!eI|ʀF"5z 7gtOW؆R=]>YúL;65V߫ -tAr۔K<|u+<ag/h?-Aw֬BFŻ6:6cKʦ*pQjg WٖȪHcv`j=LA~ޯ/~U~v/kWBm *`f|W؞G_!@oI~_ҧ5APGM׻׭s%~&QU랃|'g@:_1 rgnԈ w]V.i>]𷔒 " i?:E>;uVt! ASLgv#Ӭmʩv! } ̂$/UZ_ϗ@Pӭ*9L?û,oBf-|?/K${5XdƂ}ڕo ݆/z_2'hb A0fOQ0r Djbc!r~[t?Eo.SȬ9xQۨcvN0OjP8Au&?_eV c/ ՟'wL¯@‰eAr OM@zo)냻OH7}xJHQS @h]ϫ"{ f{ 7}vg8@ \@Iy㰧>ا UR*9uI}뀫*3PZlaN XE 6+-2V4QVl_}e`)WW߲OP>ǿS)3#1hlCAc^3g'CQGϺ?v^57 xJFdG_9h@G<'t"Wi8^0xx݁qnҽ.())=oS[7x#Vμ7^5w=mg ֈֵ-OUE]'֍Y@!1n| IuH#[aY}< |촾K~ݭ%ڄK$ ?]JeiK˩̹ Q6h+E PNS_k?VEe8fsB?tV☺z[2Rjsz΍@P, {P!0tdjE;K,<^n,.<wR,#\}N/6ːpU+ÿ;c8Lw3=}سkY8AW̖?{坕md;>r:@bR2s >86z5N!(/S۳)ԧ4%'yYPβ~vrW8|5+{߲uY?z%5%ˊx5.R@U,7 !_lqI|Ejp`eMrdLaca+`?]՗gV9+Qܯp=ʣmtܭw@pi{ቒP~F v7JݚJapjM',w<| ? !tAƘcn& fr~<_p|6yEg:vd^\c 8"ҁaD ӳIZyzqj_O݇^qAa  'TA<_+~@A ~m/A/,W6 xI`!|}rVMPKB9oӐoIW2[E>%_rLȘN ie^T/yqvLc L\fcǢ,\u\=PO{7Ixް,~@|Po;%YU$ É~G'nw-**3t ZW`7.4<;,[7T m L z'n U djD`x&G9o&f_"zKyߴͷQSæDGSwI;-eYT}qOhV zH"آASGVLK`{ZNM݂9+{1B TzCy|&Sh-ud&ם# ^=5!' |p@+i\̭^|-aouѮ?̑paq;Zڿ=Z A@$Ccrޮ\UiA?|_!r.D XET< G. a-2lH<~H7{V˭p@Pεwr4of:V5IӰ֓^S`^Ȏ{vnTW5OZ MI؊TmE n>+7ɷ}3[kJJb9ioճjVa!Ckm1IW~1XJG ,mKL}}m|P_,W8e Pot^c]ZKNy*S gzPJg^/^an~1P^qH>7ub EYIq ́^j1uި)i7feS`Jb_' ]Pml\|_",vtudɘr,]ݾ*ߍAweD?StQV^*==;Agbz 9D6>G=d  ejm:/wR Vn~ Z|^Yc @Z~4'd%*%0ذˬ{\%t Yb׳Zc:ﴎֆ U tu9tvL']@ek>ZR}r[ڲ\W$#OdEt)~w51)Pl q}M\BO[oӵ!UR A3) ptёwj.&;8B7 Y= U&[䴎¹Z%NgCY3 pa_*֔+{QӁ7d\"X_X%Cg E[% P@N8S}Â*F>ۍ U;N>:ݞ) Hp\ HuFtg(lđCst~decXI0ؤ֡:Q{{.SL^Ώ>1n_)德comJj/gzs/^c C;t|mx;JSG|lIy wW Q bwrBЙGi/l(Q}:yMcIk#)r+|DzQ_'8d/Ý ;hkvfe<9ۺݳ a|g@MR9~7*k,jF@ < J1n/vDXt)00y 2$Kk+i:bd78ŭҩ z! 5NuOAy_TŶ0%_2Ħ IDATf}G>bX2M?q;Nڇ+JuJVt7B=_;CB]Jƞͻ > >(~t;0Įrܡ3]dи=Sd1wuyD:ɍw>w+9J}.~rIy]ټ4[9 cO]S|MɧB_VI$?U^ $-'ץPI+ z4!!T?t[^T*eYׁTI7۩0قOSzPX;7qE(}1Q+2c/Z!̾k๑S~8!Ґ&[?@Y`r7K =\Q`Tֽc`nӵBB`԰R!Y *ܓpܩ-֕yd:3c9 =CYw%'%sn Ldspd}2;t.} ~^IUw|܎AyAW=13#Q2|(e~o_ ts^ (r%F>Y\AgJGնMp7+Xp]uXfP @VZ?\9y3{er:/_rIbq3:䟀JM3=oKE?4W쏸=4 B7zʮOm\dٍJESms?7jr#*@:rډV !%eyy[^ ^> Nශ0 ϗʴJY=[g'J/6:t,%OHT86m{BMm](+0Y6 qVs z~*P}emFZ@ҝþDTVIj[f0S5i2靲hZG LJ(G~ .(e &'d09g43&4mE=l vZ.(+0*ϒ8 nN 46! o_PP<ϻnr {!t$}A\%Nu[=C8gǎbbBqP(?38m9lXYr"2z#[?5;8xXjG i^>A[8~y6ݺ.T1 UGxo^[3u+mUKa2dnK S83hM!7&W55oVV_P%VKtIG4GpPg\R~%ﮈ?}-8Rf  ~rPRsl6hz) ,2P)Du <^NYݕ l P(?Nnq)(m0Y;Яe霌}žT+gF~+J~vt, jN0^B''5ӣ T[)K:Xq 2p@@=>6\7Ŷ9N?;epCx$>U-%e+Aq d̖uš ɸ@ Pd-oNtq|U &б#`~K#DHE Ū%zDI/G^y |kԹ_Ntld;u19Nb~ňf%`jC^*g]! @xAC,$@Dz&-kfȏz1*n+VP5)WfyㅽB}6T e"6ZzVHpC.ѓkm!p^귔_7UoHlQX,%ӜИk8o_d7//]֗;r1.3a[ ~eŷצ 4rɆ6ӧ^ ?*J~8-N1Q# pྌj>\c`I .{;o4|%x~n~wP==~6iCP8({PW)}o<tAJZm\aja,eTdV}D!gLG]qɌ`{6g]/ gk 3T?M;uT#B]-Eg_oܪƝmh)5]^w̞ubJi뤱9>I%)sRdW4ui۲Jbdd +[G-\K# M- J5x)+_<C9V_*'Ey.=x}H5:?yӱ՚A!ԟfbuv0osDG'x[FmoSfe/Qbno\_F7 U ms×;ͫ058ΦW9+A&_;Ǽ»#+ IC*xгkԜrM3.Kszc#7%!seGo}X;1Il}=Y~1w-)m=7Hc̶.`%CZ)걍ס T 3p9\~+C`}q%e2pWd,JYѻeKcX8d_~ A[7K%Oڹ; q-Կ1:B+f|K 6 gof$~K'-!/$5Nգ%:HM(`&vza EPG9Ws? K5o[O +<'s/;uUܬ [pIvޏG*gԆHuJlpAQ #Ε;`P瞧sdlnLS5?+eX~C0ԣ[%eHÙPտ"T,URO*\k"xJ`A0QU.~O/y=Q9\z9#@,GXe^(LI>Kh؇Io}{w0>sQYguTK|ʴrN1ϒh׽vܯ h6,nBbW HJ>fIIIՑ&X&B>Un؈{+(@4әTYt+ k IJ^qBp(O 40 ܆s9!##( |zΜ5)q S}uq~ؽe |< = Z/)j A< :QraLA.ĸNƒTx!RoL<& w&w_9gj7ͧӸ']O:7u$E>a di}1+:\cTΨ@L*&~#@X&`U`/)pQɶ/¿ `@z=@Nt0>z*[IxrhlYt% <+w>GW9ʨMjK<_VWA)^=V[ϵ۞IvZ8[sǟ GWb8ebhܪ1>7H;r➯VcLl$٭Q_4酮u`@ґc?{*X+HMː}1P%m&hA^ ݧ-!؅2 #\T;8BG1Z-ۀ$=6^v͏vc6=x8աE8ɕ p\ vfAa\+Y.08t\qOۃ`pu< ɒCQq}$tzdz-hկL4keLrD ?&MSՍL+K#ʨGH ZK-8RM4HP%)WZ(AI]\[a",.ejPqV/qec ٵ3XR]倗(Vvs\?/7̯:5-VOWҠp|5_˷iwcڥ>\7 e&"Q@9.wfIvSo%a'OoI=QzlcZPg`TLܓ5I7 |\[D;ng #cKs~, H.2if7yN*лANn&$ߖdd+/^uM>GGf18\Mc$?">@b8Ww@*TIիmKecRV!5I @NCb~uPwW(g+9e:(T循7uME2u̿myI9qJ)3|28v}n9@B}':LguT.u5B{[\eL 5ֈdLo 6*5ͫrD2zlhi+wh9tCъ'\ado ֒,kOh ۹#I6&|;bɏ :S. 5?Z6㚵BmPeN֙O1hդS5aAb?9 &U;"1?8,@ a CȁqgRʢRV){mvݬ y_X O*]ZtWSZ1/?B4Jsķa% >z36~ }y=P11m_dVz&lap]{6ז!6\te2kUIF,D#nI'=jA JZ 3aC1au`()?) oDƧymEKsS ^'̬ 3+̪ 6=Lggh|VXNYRP_0hkjF t@"ǾgRg }2 HB& aSVkx+_ߨ +p!ypbFv<+USXNַgnS]cStS6j#Xv~\Q \_-u~CQ+LŷXB8g4֔ž/i`S~CHCgBJ۷ dN.@*<O0t֙A?6LPxCEe_d\RJ[WiG0 ]+r8Q3qC1}tBJZ~v)_ ݳy;nIzfOԱ*ܰ>4IlL Քi2 RlϫF^V TA*T") Y[ I 7Y$ mVp }krl?~S]I{P[AQF\fLifvP&a\4߂  0ӻ"|ͫW5r'8Nc)f;ʙ\y։o*>wU~DNK='7l YX\qO]S!v.-mZ ]SG&/x+תlt[3 Ax5$ͪRVJ%$(?"OH=Oҝ#LFC IUP}P [FvY|\Z5=0}oRF PrI[v.BkC;O/9Ր×3=e0s8E/nr0T IDATh0"W,UAM{mN-v2CCqu`U6rIq>tkİ4W~B"`a[6nC׌b> u'mG6|wT#`k1G牳 7Qے M} X%L `'f`KtR?E~Fɼ<-ahR`qҭc㽴l5q qrU.:/+X:,w`J}9VRo\U/ҥAP sάO*(/a/^!/`"U `l6;{e##dpgU)AKXt>t1NŖg8ИrɩβngsMN25)qT\n0 lm*teяPgitJlWa  WϽ/(Qj{gm 0-ҁߟ(̟\J$yBHfhu sؗ܏O0UDt᯾Ƥ Y)fQ[K>1[ys 5\WmKG4^=wbgI)2MT~/Lͦbˏ]jD_])dO_/ CNg;a0Subcw65)7 ={%~pJrvURwg O5gL'Y :\>S>A\"D?Dj.4eY z[HJWXŎi՚dz鼥'Mt"eA+h 'nUFr_3\1a@f_\A[n OP?oY@xىו;'IP87y\n0\&̣s!6yvN57ɗϽ7mN P`\a;X!uNNW(Ѿw`Ei%c8%u@0oJciKy cǁ)t'1i ^YRYziz>?RͣV%0; BPOr@ےB|w#Dކ60=K:7ݞ8ۗ(?tȭ\8D.:Aj]+1/'ӻ€Ar"Dq+p +}ilR̴Xߞ@וApP8S.sP'$-sW?k53UVN> jLgr g+̪2>ںо5C,ur;&6c('`6I9 gP|`NikW;Y;mlʧ&5쫃eD$wd!k./6}[$RѵphUFUk0|fp7)E2gq9aPo#{,!ãgHj*Z$(EXwT dV"Mn8oe,\aJf#*$_:&V2o !!G!'bz!9A_j`V|^@[\ "a*ghhx}D EZ @!4  xwlG+pvۼ8 ^4vaunZ%WEߐK\߯rl(nSD,{ƌ慔>o9cW <| hcRg%= Vt3g-lPo-uHNua쏏ܲVٖ9:`h F+V)^`ʶrU`5z&TJy- I{K Sߤm 7pUgTNo`qК` A/e `#?qߗb۵Y~ez8P'}[{@`Xnʕh"9Gx<_9n2qĄa@p W08\ .p{r} ~S..OHӠYeTN}eڭʵ'&aLw`f|wfpFj*,ɥ+7'^bK%-I$c?mkkj]LlA!DBL7{!wģR`wr;MI!sOZG6 鐮ESP=Ծsڳ}jtOY|{kR3\?U][}Xh8qiPW1uq^zAp8cJ߾U+Mn}2/S {%w$ c~]PR oJr0"U*ׂf6+]M)DWV{^@Yq1-JϡIXy K y2jcѯ_ (L/l'o;VH?*Ր曯rM_IkmJmC߱+4%1 85Q.M^sC 3)c'}ᾁr: ;%W1=ZWFor+]aYV, Uc}̜'3$sANeRd nVh:+pb:Gz+Tw3 Sp~fX{OKyh`2YhxVa= ;OMa KxY9lZY-/N 4?GljVdмvP &D|f[g팭!HtY!,\I@$7v" 31aܱq@SϷS]a%83- $W1_*Ho\_k!rx7DUo?%&VVؖ{g6P -˜俱&gf_1d>wT{\r/;s.Jݝp8 oSMTo\6D?.-gb9YXan%9K*䍤%f-K{:]}2J@*uf+nȁ$FjL_A#{WleuP\_A*@32e01Pxw6\ѕ4$. m%c}BfHcX/Pm(&8^p=֮uP |'}{#jK6r1FM"er1hX }A<;0 msz՞+W!XL~?yVˆ—3 ȥӼ %,Oks+ m|n7VKؚ+ghR|ʣRV+Ǫw`m!|1@Voɠ쓤?(ǀz[XSzn oQjڍ-]OZ᯼U4(B[I/VS=4.8ۘ1խ\G`zԀ;7H48Dʚzk۸u/Л*-E 8U!pr[=?/l,@{Ggacv \heKP@\Nrc]y2׏> أo9OsY7nUIۂ+#$< d Qrz2-l'y 𫁱6,^+]7${amW敿vJ%%HV=UQdE00xqwfK+U`:~,9*k=a]6`zN,SopeU`xmb[cL@VM.m5NQG}* 4%W#?]mx9_R&`Гt"X=u)*bW,;+˄ A2%&љG7М}%r -s^~odѭ5D-X~Zב:z- ~=v~DUnQ!pC&~7"{#bt~ 8M+¾Ap4 e}10<@yrʽS>q[ ֈIZu4MIy,gW?iRtö]yv}C;ơ\@!oiTcOK싷.\P(bD~ H` zg ~>[4#iYڋs l AV[AN~ BoQ]Ծ .bK.` Vsռܒy LJq.$C>/xM|*"[ z v̩O.Sb7:ї8\ E7y[Ngnva)bErKs6Q& rXǸ}א%`xh#J9ℿ ߛD3N|Қ=ctr;ߺ|2ӌlڽC!0-Wd1!WM{ҔzfeEЖ%# o /*GN{,c|[`@I<b<~ 9@MdqMn"Uş6؏4*6^j@i{ vlNtoף>n^c\ڦ@Pn/ׄ気xgDW`]Ap[ > J;P& O +U,9; Ȇram0<]KUǙ '֛@^M~dw;``~TkOlӲ?U) \n3R0LD0I@Y:7=L3 d_ipRūw|쨀T5$S@b6v`;ڦk{hd9Vd@VyT~aninmwڕmw#eMNqHoLw'u-c @p50xv>sGLgȕ@x[(Ws'߹s !Ί[zɘ/N տlUbqO{4Emf+ Z*@L1m \߉?Ut*f+$6x?ȣi[3s#:G^I:Sp1G#ePN܂yCe|nC =[mZ E:!ϹF!9sdžذ "Wx-=6ocZ,5CmFItqej}7珗wcT !r$.OX`5ԦzCنM͓pwqtJ'@ㅡʆ<4 riIeamqο`_v$Hǝ0Q2cI;A ~^'_=[:5(xk*,y 5^?lŷCz>o97-~RaEފU,Jڡ훳`/$r}tiFR.@H˔Y+jq;\8_bD _-[nuA Aܟ2lHwz:o}4Ǐ*uxlh sXL ,f@5^&\SLʂ`Iȿ\$7?/27vc(7cvoWh:{kZ)nr)z\sm6 u  <h3ͭ!LpO#>06?ipUq'/W jaR.`R%ֹDy*q=h`x|@]w-SXx$d*z&e"[xqlۨSk @oBXUjR/[$pq^?*2u?{=+.9psA_ -_SZ _#L|01Yw +sb놾bZ!c2(*c 3i>3Xb@dc4]oQ129 <:֗1 WkT IDATPc6AKELvay Ea)vh0V>Jm l \~ 6MN~Q W7KX̶U4x0ӯշม[ʹ`_4lCu94yۅ|"a= C_e·-e u |hr ݦm|5|w( >B @IFa04;8{ֹ)c< -ù(nwnJylmx\W78^XlQr\{U>Fuj[+- xk-*m ;n1du-Q8nfmd`M蟴YD۾lo _J h"w H'6pYK$>1? 9RƓB,LR`W\/J]_[GHsWnI>` 'Ѳ c]8yֈB6f* \fN10>gsw08l ,W ꖝ)Pn\_P7.K#FOA=fbьTǙ+D2  ]J` %bAJJ݊a^0 ۃ''Y%9Sai|UIu=.ܴ=d6_,_xcO8}{Z@To k٧X+ۑ.pE:[ئFY>D1ѭTǻ ~k =mF{ޟX>.Z 8O,+YLPZdxaprSvFn'a:7/waO3w )ONj-\gLhviy~0l f".&fH>h,R}z6T8hO-ѰtfV V-/l != Wi=sa/XMkDV9dG+z]xpZܦV!RE^V2T%ZLD@6`Ⱥ =y-,"hm@4PyC5J%+eC6h7tdT0}4Dx$͆D+[9''brYSTo(7#< 8Or*e`QiA#:flB@`3.gSN_q|FϢ"!kZ,M峜Z7dwlCExE!]-ri6#p rs^EI/.e>\7 ڌ0:î9CcMf*+QTb6Hw`C:>اNwarj1R@?'鈗 @S|ZZ}4ɩ08dSe#n mdim(7%O8Sid0\Ԏ^U-@~ N|@1!AֻP򥸅揷~kd~phtQN}peASkq--QgȶAN'fX I]3`]8p_+T7݈t}tg%~]P|t`?`[1q,}6^Tr0\ 6l6?W.~G#*,I0?<ҿ~V=  TP$ cy:o)as?_ KTj45hUTA\`6!yb'#cdJrl"=B 0#&,AZ>b&I[HG[' eV/+#`3R٠?40L}$%;mfRb7Qi}fmb bUGc۟4/AхS^D<*'qz΀n*02QNc8aeWG U`p@\9vʚh(ֈmCtMz(MCrtcف?={Ԍ*D.apꮀ 5+~N4Xp@VyDF81SR;~)\ 3 J['Qg]#[1 Dc+i>+Ŕ_2VV"Fhe Ձm`-TZ/|o6|M[j=:Zd.5)L8vmo@ӰX15N*Gz<;乻qMwm8!#Ox6ۉf<Ѕ6+waOʏ bCl'C3xr-o-J2 ^ꇾع8^S'Y&wsQ1#Ch:1'&ZWVA=ˎMWn>#LLU! H-50Cs ^K"Ȧz-׏">E }tpE TEu_4#twaa!pD&@FCDȩ^yw*&5K9)/ #T-Ow m, ].W'ݷ%ʆm(>ٿ[ǻggkܽoo#n홄zMw+ɏ˷@;")YNX5]<ҺHQ\'߽9-12|X~W1сt%Vò2q9eVWXi?@ ʨ"!N{ _%Lh^v.鯹] 3$"ۆO /ASÞ.A-hd Xr$ΡR#[}ql(wVАw|t~>%օzPx:PNp޲bH֤Ҿ8ۺY QRX;`M=}|΀aص>j+[ \j5Itg2S1' [Aw!9 @(ٻfi;K{g0;kWfĴ11mb[+^#3=/A(#X[?m-$VFEzZi=iq[,=9vF@p֏ * wKY 8ɷto ~ '4?z*d9r _ ۧ`+M14Dט|p w9mUe*mD}٣W!h!.WL $%k ߠ^JucF_: ~ &78m+m-1p&2EY (/\_=dE7{"H`] 3xML4a2ވ7IƵtU]%]jq a8LWpT5@p ZGi|},DΌUbQ@Tm p/Ǹ0m2<ÕzyS5Ap ҥP`f2A?g_SWg y[x&(,VQ2`xd\.8pl_+"'`-gD{@}!WTL 22in3-mA w[*3]JM+"cS@WQM` $0oŬn ]oi OxPG(3霍@Z6^X6;MR~Z *'N6e9c5]岩q]2tB_zKg$[ğgM[qo8&%XewG`J>a5e߾aqd\-.|C ©>.;fw;; EҞPϤ>&^ 0Ӽ_yMb_d +kEK-v Vn4Ab gBCM/ gPNù08BpgO>wvA?eR#;jwnw ~}P"hNbl0px6e>V1㇄'qϤR ` ,G xp3b#%"& 7z:i%3E1FYrnm%4:M|&P68r(PSz,.#f3=-cW`+7)BGb`mոX[!KAK%Mus]g WˬvrA8vqCȅˑ2~6]4 (+0?3y?/EmTPpsɲ.6'!)o} ڒV۔ $U7wa\bwa/GDv̫MA0 AQϟ&TlيO-(֖n3P=7xO*1;&pBRah$:3Aߴۈ{p - 萎/ҡ^ό2ǵUN1O}U@۷@̤Jn x nh6VO +o( +vCjvgmzZfo9edr@ 攓XLшlCsd>E:Mp Nj-v}clX\l^dVm_6Y5+RiT$Cv@]Q `=Zm,_1ؠ+9s(+oE7D-_bl} վЏE?F0}C)Rw?^zf"8ֈn+h:V>;jK`KaS&L U~tPwi^;CR"Dv'Ȧ tו9Q@_"öfe.̂HϠ| mH IGp']bVǛ,**4bcM.!y, nA(eMĖz@_ZJM>)ӟ'P^)_GZήQQ]A0ԟT?@Kᯱ=4VMXu{wFv*c/N@U#kW a9QnCLl9ݘ@(}>\D1*& 9۵k|y´ugy2Xo[;MN.PltQY 'Oxx&.L?wO?km h$Q [mߨXP zw(]֓VZyM Ol+i.09ŊI 1xf dhfos ~!JP5)rshM 7@ }~~둼-tc^enRú#Ir oEoQ  e^tt^߳s@`X(Z˺ njN@k*UP]wh_ī01}[.A6/.`Ց<@lx셀p0Px]>[ 'Ou'bYL7.>+{@Wq& o?NpSm?!*GQ"(mr/2i![`$zQlV 0;B%Ccam% u~ lZ?i} &%x`+RߣSte7E !ͫc nE`U &u$`@ >˜0|P.ד."»~疢9\B$82 l̕_7/of"WXFܩlTx5P{900(!wDHx?z) O.x5Y$ N:p;2j+ד}󣰼6IjnZGKcbM-@L{^[2-Ū\P |dBթQ.:FLcH/u7Vh d 2 䃵r2v_~:T tcϿ _::_ edM A-wcnMہ:PFv 9 !"UwgIvg\`A?X;8W9S+mk6%P@!Š xSj 2OL<`X7C UY!)Y IDATfչHh =%Łpp[p R57ޢ}vy|.[@709#a„{b@,dIvĨa qYeل:^՘]1@ǏA~V_~Cxx@QaszSt``c-иay%PB].q+)Cc/8 ι C!*m,-]vGo ܘ͸EY "o Μ02J2ׅJ ~1ʙ$=(C[ ;'Ņv5QÏM ]~$n!;OFǷ"-Phj[AV!uS-įØp?Җh@JªS0m =2?jֿ1N\Y^~WgA'% /_tb6ེԎ-ci(0Udu,K˔1PLlIesM 81-"ǀP( C3qfE+<. Mo~yHaCue϶/!4µ:bclNXMxdLxq`͗,j~ UX"$Upg4qIA rX` cϾǏ69 %ǣ' ;pd~c+2{ MC9ꈹIn寍fcuH1dېsg(Ahs|%|?niΛx˻?Ӛr䩏9ؒ}OGp~3\m/_7,*?w .c׮pM.LN{3µ)v6TO=W G]ǣݿ_N\` \oUm͋Wr_8 \=KvciNCFtP 'Vʭ5YB -ėN)EU)"eU\(qi+#U^qYɇºH-IoI3 |N %+XR |+]Ձ}vչ u1+1gN&hPoAkK߹l8^SSOejRRVS۸Azd vʬ):t6Tn+atNE@uZ8нx=>.&KVɷGetc}4NpI|!>=/~e)qѠݽX[e7"ZNR8ɷ̖ѹp+Z _2P55`/fmn{iUC`~#L z8( r:dMAd S逈ˀde";᯴ 6YAPWRZ%ٰO߹Utn*su+Xv/g+ p{zY[{[{vcFWUr V O9ungN8 4<\6UbAW}4KenvJ'SჀo=n;,tod4*=3W \>[j_sZ^;`hqk |0*eHA+=* 3/\Fn_z4|u U/:4˃_ " ck8FsւRVlwAeBgV];8Q1!zfNJnV_ϲ,I3@pf0xf(Bҕ×q^ _ni O Y4Pxee,քUNR*ϗv򷪉&+/h'v7$28M0 rT3ң[3愾"euQ}4_ yrhle~zt_ MC3 b+Dǧm9y+r9b ouqq@dMJwݍ/bE29ժM=G}U׿ x.tc CU ? 8DX Nofr51!1xtP+::2Z\75$wד t4뙲ݖBjSz]Ѻ@Ok+Y1~L*km)ˠxMMO(ވ8GG/I;\_*.Hdž T& @ק1m=ޫ\'L~t0?pOD2h0O xC+i#ց8@̯(a1XF@vh wLkH`m;'TLYzV"}.$L] ΏWrA_*`L,:1ˑr$x^Q.jׇ\ Q\`jf<#w _O.Fx6R,n wJ-U: ";Ugx%5?OK ͞9_֕BnVUf4ex`QWj9v\:jvz@Km/>X%9ˠ-r+o60 /IpV*jS]r_Z >U&LQo#j~bPO7l/ u\ ʝ |>37l\!2 >9 NLπLz0*W J@X:׺('*' /!z7~cLo)]KNI|D'j>R}3װ"mM6 !5+#WbCWRӌ@iMGY>nثѡ/Vƫ[V!bVO&m=8W jaF:$ ezG4X?"SV3/Oipi)@G?\T@QƲu]mU=H8צ:O*((v_:` jf\*8 TUq;fW}/2E$n D6^B6h`ٲ¥Dȝ4Ͽ9_>&6O·w RH 5OĘqCZ6[yd%5&:5.J%*\0p6zH }Q;`: $qp!<+boN[8u@4FR 7HuC$1 9{8uqd_#J]K8^1GI`OΚ@^/ x#:$(@2 xn!ܪϳJ 8zUV08s]_`@pK7Prl0I6n$)sUp!#gzm~e B(?()KM֗[Ag퀺vc wSx@R$}A<$l] }3ѾN{ă1aJXF6eȉ@b _ i'AG^#Ax2&`+iFv$acXh͘4n[o7κ]"BL:\غ`o Nzq$eTfΤlѪkfo{ӻD+"Og´w | *up!0`g{ONHynl(ܭ;&`[޲p7QOɹEߩe'-9-lQ,Z~W2V5C JnԫN an^nB_H}YB6VE}Xc?\W@{W/*Ww V.3)_%9aG 2\_ RDs8X `OG4&KVK]B;Edk_{v-R;]) u Nf?$VG.j A!9%cl6Dsۻ l`,OY*š;‰+Aa^;NaԵwx< ĴO%ym0% HjYPZ*D!no їt󽼰Ako;^{(A4T #.|{a=p.^ (6)\?`[ e2 U7 l5*s>|DoU,q>3=y c~WxWs<c^^ⱃc~58'4;5_9n?Pz|2yn\K J @Х!/wj]K},~SJ):4΀xR'{)cPtgˆ&3W=UohfULc`#GPN`w{ehSmoZ$OR`( jY*4R?ag 4Xh9$@v,?؇@v}VB` NBP[#˃fQ.36d' 6;Eq*'Wcac* S%)^;!'a!sf+{B x{F|OW9:# Њ]n08F08J3?Q*rs?k5<9)]RO< _0-Ȟ(8(.]OH$*1Y bzVgCe: _u#>1 o TPvV/PnnSde]I=# o!?&?k_:$i_b:Q0௾8brbxxljR=O.NQR-~vOA'3\AFw:5/ SNb{d41S208(ϬӤb#ޝ l'a kȆKf\+-l@\-M^6Mڍd<|0we%Nm~PR5cr" j"Mޫ?tU!Mv8! ј~@02(oi ns)}o}1']sϨRzLz0٢H7=EBau PhFA9:L FBk/Vt[8P UA4"/ŲA|W,eLng,AMJ8 rS5Jcw=!sx3<0Q؝W5yV"K^ݒw U!,#R V`s{Ct!b Ӆ<䯿/f1i.F90Vdm6+bW)}\" /' `NjV`_;_|6Ҟ]pEmYEo͏Uu :<*GfIWEӁ9 &?`6`1ǀKJES@UZ/Rڀc^ꒁ7o~\ o]ڹ/ k l UEOn:*xo[ %O%~7s@pO 7߃Ӈ\S>/!08xS, FԊ}eR;s;գiwz$( }l0\x8c Ǟ+#-yp']TźP7Ӣ p«$W`NԤ/آ pHcXN`6-nmTEx2}ژeڪQ8&oS(9Kl뚽}Tyx~5RVm{#oAp@ $/96|N1 ^f* ?'̨7iCA'掜{Vaw-CT ;wK {0l-%_g8x4 ;9e&ه4?"0ޒi(C_"4eyz s{<Bb},!$;_ (<ٙ)b]KuNO]L3*,Ɔ_-oQ`cz =3蝎shJǹZ9_Ghl0:.f;=8wjK:A 2v) G;DJzUa6 A6ĐrAp0x6nyUʝm_WWA?ffnvkwr׾8Lۧ 'zHn0eN-5ۀպvH*t؍` |>6LKŲ2[ifCҬk˪C\ dpDho Q Z$`f)U{Eۚ~`΄R6%l !Iz_ZE =*@v @ے89 ._*Cqoq|alu)znS*NE Uޗ ԅ/ 7fD ,VGheSKq,4=Tyv=U8)r3~ (~3O4"9IR߫L; Ƕ9 $EQb {2= /U[* +|8n Ov{M/qT/m~f'H&h>KUjriEd"j!,4QKwⵁߤ=2Uѓ}/]}-m쀢;sQsKrFm)HCm~֗\\? |ZM`#`DPuAh͑aHWOZ[N/ϋ1=R B3.`qت룣SiP8\'$)UaʢN*Xӯ3/߀1 xm-Xb ZT:vPq6ߘZpȚJĹF ƎulWaRF* wEޗr'ldR?>. iJgsZ1J+I[h'PAtN}cOrQ s䕾O](t) IDATfx@Ĺ%t[!$5d xbGBGzOB_$9kRsx'UPWOg7+$>S-wMwfղpE-[ΒD=w l hcAu HQے/\dC^X? MWn0',X`LoEɀX^hG%*Y~W. _tPN2CRM78=/>4/߲Z[d?T/xp*cc9g&qbj4ݧoǀ^WnAڿr'Y W!Y(:G5YR%'蕪>Tx$_ q~6jTַ)( p 5ɗǕw#9n-%錾$DGqFO.$Cm`ys꩎잼!ouw Ag?[@]nuUj̡Nt>П%N5o}Ѿ լ0+)Hdn: bͻA^uZKH\kabK +Kj9}C6eҵA9֥-_|Uɪ*ģ#iQS5ٶ`#c8\|џ $H,mw[jM 7  0&m8<^rHp~\߿ܔJI7 z1``T<ۦt3b-ֵJ'6C%a}O68Fi_݆ñ}1\ۅ ,~\ 6|?C)`F hկfPn!{~):!cJR&_RVtw+q$ ]\Am'k.{6,`p8=j ?]6uhtF9m֨pjxhv4'k˨ :hl.5'-7jS"0! &I;I{Ao,K_FE~{җ?hzոE]52D"q|̺jԒVSA3pesϕ#/Ѡ*(;!v{u0 7qb԰1̦m!&$DZcXLk7j&p,g!|t*'e~ s ޙ)w<CwPoɀ^7U=I_t0iH1]Rf+ /-"g*6z4ݢئlFd#.-z~:Ar~/MlhaCN + Bt(lrcC~g חEahvo8!cCXƃ䵿?^l~ <^&3=S5SuéB) ܠ _O5[lt>#zUv`ۧzPT>̼q` 4@+8v@WHp|@,xzeB?R0.GZېa};ik C/(|9<$ob{^څ8S)u 8bE=A\u^{O`15JaKX Ap[F6S+C+o5, J>'}#E3Y]4iSm~ /9vLj A[ Nv(ӧwzjl)ҤQnԤy6n Yb<VQH+ Gm 88RA6fd7.5./%uI Ix_EulEd1T 's_*PgAV,SKUPS6 I2yOAQk%{W]Q}[Rvew (8E9 NT.D+wu\N|>².j7(CNZ6S_Ty>~C~x0Fn;W][%G_ *35| 3jC&˪V?q>s>l#q٦;w`Ӧ6d_v3m0#IF£j_*Yp o[31%i00,MLVrb`߱|__o#_{*;u/,Ehi& h i?g`m$`E>:QvpRZ?-g4Y*7S{VB9$7N=1w i$@h8I+S}}'㇋OM_?bǯ0n?oci0'La~{yM,M 01ST5_cˢ-w*)]gy^ZM6 u36[l5rD?vM$I߷,V`H,Nퟐ\FT_1aVLi̹}WhCɝ8_IUA_BZk1.HCG ])'{Ď uq0HǒF~'n7D[&';ҽ>tLʴ:~ ]܎LbU=N[ CONɀ=0x 3 vu2 @v:yD餧7os'2ѠVscC9)vk;`xNsՙ ~{u)'E'  )nMc ԏ}dI9d]V7Hv&/Y T-D=|$\yӊ/fR&s0jT ˭TnSM SBaKe uݒ[Q[7k=W6n(eZ@xv,K1, bӉ\Aq`_T~ZTBW>c]]<ŭDnz;9e,25DϨnp|YՖFfM8CK2vuH7:?;/NlroJ~:QBc4;v!Uǟf[?gɢpy g0TVW-~Xe/`TCSJ DY7AP籎~)?o(/fmSkF(ߙh5:8FAgoۇ C D9 %%[ :ZҘ 0%|G6TLeQ96)+B¼Կ50^#GTFB0Wv}̈́'4~A'w?dݠm@)jCl/pFhC5햎bnXl*$~Wبg\qlOʆ` `QhGwL xn%aU ] HiǓ.eƲpTǫAʶt+$Q mi\e7R '?~#܇_V f&w&k%.0-7e<1 U:1E9B~S@L1*:ɜS{IvGfgR$<;dA8\qE^XyɣZ|+UU~/ I7~3k=ͨ$ r>aQWrPPT]UƗQf8`ul^?/P!m }2[9z?m'oNBoBp ȺLgt`DB6)TvB`W邟9 J׼>8 {-u$MhZkѱ$>N( q. js>R`<_n#gL/aS:a~kõlP Ss۸ݮLT3 J+7>a l䩬Hq/- "ݢL-\n.-sd`Ի\T/% žK{vVUOE ЖQe~{ =[ƪؒJÿ'jd0#N1Z0p.#pYdKbPa4#& ȉPi{mZizn+ !P"daM(xG:T6*&>#~ ;n ~G;f|_x.ov/g[˧eJNR]2q}Wmq\3zRVυ?0TKO%e*Arvf,yry*+. lQzjkepx-˭#Ut¥*/ N }.T/% BҞw|d4/}jIҭyח^b}{c{yT_2D5eHL1Fgc\%@PzsC$i fZ%k$o( N ΊRT3m'*B"g >L VꧦHUoL]^/!gxQҳUt2dj{*G8!В / [6PjiNI l p #yEQpчWd VO`3{NEN'_+EɮX=P@IQ"0@ͤ=u"cOuJ8K7qű J3=.LmmY 10XˇZVLg,#CO\N oN;+/9pwle<כe@@ux'KnD /"d3]GLO-:ÂTzlfaIhp A??mlRʰ:& xt%O0C $m"@)3 IDATK} ǭ֦/jfXkI㶥U%(Eml+ڨsĭDwˑjiإZMRpa`4(*эʼm*-=0feF0I2>9|SQ\]7RN[ P=FIͻ%UTC6 ݆zOmC^I`qCd M NtSG"қnc4\_K6is;8;XM>!Qu`MgkiuK+dFI@d+m ߝFR8CPUb0"mzW!Jt({xWl C$|w(Gpy`ӣcFM:mK]eA! сV|>*`Q` C`20ORD^) &Z~W39s%m8sא +P{{9jP-ڦ01Iᝣ )-DD1q\ˀCI ` ה$!Լla&и,/ 8`B._YPr Q+mE}?Oq6@F@ǗYȬS^[ ! ڀlI:Ϩ`&oBk)=Q_Z6o:Vۼd}Api婿DwZy!qpWNf|{6S_74_{?T lW75~gW{2G{6kfmC-]E7XÓ-zQar(u';Y2GB a_cKq]ӫaYRFGy">`S (PNj}*P (O,ә%qB9c)b,#EqN$PcUADBO<'߷)MJ5~2&mt mjB2M` =EݭhrE.}tU"  Eo jE \}auK3x0g ~tsS*/lP}G&!s@WsfrC-# K[>^/A8/+ ?F`Wo8I= }Cۿx(#EB$ r wKh4~D+U6 Б^L闠{50EE3 8\y̠7KbJ_{`1A@M&1\O^] • h'Qow،@Nab=} -V^nCiQv_6)uI̭ BQZU [unAp#[ 4t'CgxRf>~4 vm[%x_H:?&a}dvR]!RmC-Ik D0suY4}?C)ǥ?='u7+~#lA!WBѾ>3 oazS dv#eXnT>u͞]FTz9 1DoPJ~#6TmXߎ?TK?O9b7;f-7͘r|IUK;l[lw.[c9Ujі.GNqBn㰔u{ dySO__4z3)'.ÂnToĦdu'Bv9JY=)A ]1 {205*>jHB ]? %@ғ0p/~$$ *`B hA\&s (XFG6Fޘ#a?rµA ft#X"?eK{ m]u<ǹ&5aVrmDֽ` R_^f.l ]r( ʨ%]qK z4'e黙O.8/hO=b #ܻ67 n&6"^?W2 n0#nžL;³uNW2+0((`xK,_}幷XVWю쵞{r/~x&S`B<M4u`qp$ UrXuzU$hPIRb>V)&{:+7!jPP!S$>Nqa$=_.WF6+:^q$)i@e+R25p?m#vamϓBJy^e 8_;_zV]3;Ҟ:R7u#,uC5 l&@Mց& Ѝ[FE'Ѽ4\ .4lkЗ_M.DWr@"flT9Z{^&B;ea7*^M,}SGto<30W!ĊP[UZ0BBZW*CGx<#7Өno`u1 _( L^ !ѫ& y𷁌 0C9L%SM6A\.9۸A=oA NQR]D&Ffb k>Ln o @xF]t@m088ϏNMҁ;֐lJ6m0hIer*cp׮W,h7 p\]'7!l:[adO)Woc<_i'B_RڽVh_߆K7~f \Zk,VMmu$F} ݸ]_`qm`v0-=H4uz?4mmYn9lS؞ \ݨs(]4IV*`s Cc? ຦0@Z9lqɆ"3IIݷ5\Nď (EPX0Pj0s1e9kiqM%scm"zB.}5l ١~ᎃԬ7h\)»fMV lIi㔠 鵖KjKvyaJhEX P-zº^ 1Nc|!Wd_|roK|m>"扜B̛ƼAY_?sK#UizR* .CoCQڟ;@p{໺yvGfb3pA( 3K\Evv *æa~*kDtHزtKvfGljdMGp/ژ;;^Z2Z1K T,nV?8~4r!X)c>D'WR@PL6՚&^? XqT$h @! V< lm]T7lSQmQh@0ݸH"^yV[m^p}Ap c-aFڴI@<@Oz^S|vRHhDY 3 Io@(Qn Ɍ@؅~+}L@#C6j \Gҗ'ϔ!0V[&eR'l; OAkL.N :rC$LЀ%_\][AL~ 0o `Rl2FD! >ʶvY$fiB9.R) Ѿi [  g)W=Pc=\c}ЀA1=` m VN5qOՠckS'l&aU3CB`ov[zn :HԀ.4j T_m`P#t_G2a@nw,G6!wIe:G6_ ğj5[U9Y{vKdާ_RFL`p "'{X; PAUˠ0{;ӌ3]TGr]l5uB[9h` qx [ Ut!9~`B:9C]mqUˤc_6R C _cE=x1-8ǚOB$㴡ւnЛ׃];^p P.v6&]ۨ<2RBC3{pD=W<1~L ~JC,ghj># o0Pfo,ucSC>r@@&¼}g|Ռlhj?~y>]LBwYI}U_~.=y%ݸ?!C[A, ;:k/b52^.7??NZc_{MJ; 4W./Sro@M'vn&NGH:G eXlX"zCˎGpW6ξ9n08C#PntI݀LH 1, 2|(V+Sj}n*:P`[7,&@V ` pѷh sKhۍngEF <%:˃1Ϭ0GZ#t0j@f_)t00MXa,DBI!x o'"A߈292nR&W9$!A{FB`V y gWj,f9 E)UIR%ox|I9"E}MLxz 6eCᛥijb#J礖!>|,S_pf&|46/^}P=jr3;cjϗىp6")l%M)I=&S8?*6Al; ؑ_R8+ )T&`e- p6p|FE7# a6' K u5Ա}lf*uyx`}ef c7~4aX޵g6&d dr۬YK%`%}ZЛi ~/\o)!@ZAK\.MpSC\H@_EpUI V]iAdG2UPBP(:DKMBзI@n=(@\!>a\ԣ[ZrV)&_ /~7L%"%0轧 ur:oD7 *7sbVxP$ kJ|I#7a%&w#e2 ?EW& Rh [n\Y үbݫs.uoY|P@K2x%??dU82VP`{; N扱Kwٍʳ2+>RcB;xbHʃ-$tjQQ:փqqœU= Yp:p<k}H(?ċ7 |"KyW.Jت12vܭ@pȰ]lKE`a`*%l`hhV+{OZYcvQT_ W#"I pPx9YQz9*c>-u!o,󀍭ʝv>KP^|)8Iub;_W@#Z裏j TS/~6H혦}^WMTֺN&Ŋd ϸ`+u'{1G51 lsU@pvlk&{Z/{v{Q 9藥{řRóaMud0< ̖ uJΤoNVT5|B%ZIY9 t"!l{(xEn{'ahEH7*aG?_:ZJN~~`BmP􃡏~Ʌ IDAT< m,~sV+MW/73Pwb(X@ ):₾|+cub6}]iթ-M^RktEKTՔg.ū0#F3+3(`^E:Zs7zڪ`#;%^D +>{!W  d9eCl(<\W6N62әݤ:EZLRTS(uJ䷿)*xJ$p W7,XF"S@Oa 1/:1N pR1;I.6*ݫ(صB`;lpȼ;]0vm?zN~.8(˟x\B'}S:0]SuCl?EAp Y}W\/d9lXK\d] Uf{]'+1$c !nVmUSoׇ9˞+[]߇_+DŽqgM(]|\tkq.s\v!Ua_7>RTܳ2QfN!_Ä|P8klJ JIZx$)'CvʾBNI\]a6GƄtU|%~%kc}Ԥ?^j@nbu>yEwe-k:@Ί$V6mU_t̃)qIqVHzwmqzdԬ3PiEg:jj5t`AoqUOpXE[; qqBS@ƽ2f ģ`[f^:UaRr}pfZ/,cϞms zՋF lq>М[:ӯ ׉678} *_'e]*\.NZ T [6XV>m;-_kfvBT08YOǽ #J#3CLtmJ@؍?szLɼSE o,sa({(*s,:WLLz~Vmοiͤ!|8/,:+;>p|q&!UZxhۿYG8o &ҡ(t(ib RL'}R(_2Em>HO1D.mt>;$Gc\Х+NYWy  2 {SO V+h]\QN`<Kl[oW{?Oaӣ5S]M%Y@ oI-I37MJ0={\. Ə u|  }2.,*tl1cMdLP~c^oM(4aDJ*phO;l_tPU%ކx YOPD8vv)qBf*26@_,SW͔G` JW G-!!6ߩo l* YM0m\=:a+[~#:ƫ5ʩBn VPxnat2z{_v>&MSsa({8|7m~/;+LU~(#K'P&!Gn p ]d)a"n)e)qxD#Z`['/z+VUebԪZ QII;A=R mT7偬n hr*q*afPR5W}[@23<(5 :kR3 v[-2%pyq}?=u*čϭGNYJ9R"+S_< ?Ym X]A0QӃB8#m641ް/_eBC)b&׉7aXhPҽ:㝓CJ6|8}Ƀc1Ur4R]H:o"5r (D(нv.f)O<; 5ʫ}i?@>ЍFp\2rjENp3ʬ^ax6%B:*_~BrD^k^l_] e9qv$~X}ce|ĝQWJPDu ݷ=ׁt6lVA0.E] ꢛ` 8ݡ6 ,f@ |U pX,:V6ΐ*'Y?( wmW'_d0ErŸh[G{ɯ2H 8䕞tQ%4+CkL ߂e @]7bN:}[O(ZU}VqT*L WapTe]@2j`%TN嬜@{^bra.!1 ᵯeR49,-$!nW Cw[XWQA=X$H]8Hڔ ON-r+ &pgcS@|  ^6r#g$mP3 k%۷N|pob /QXK`\B%I2 '׏JwIe.Q1Y}q ? @Ph}#v}b&,aY_}B S\2Ej/6_=ut<f&mҪH!O57/3l[.,Uv`2y*sվO Fd Ap U+= W}/w  ta8Q^?^FfԼq{y.Xy0 4Yn!S灐O7 } w/v*UMd+9+US>?< "js2 /՗@^|2j1tE>FT~Zl!D^p"v+\}zX`'y\[p#hηW@c^2Wm+/)!NZx ϧX'%|2@ xUcv%?dg880sj`n)Ϊ>N߸'։q9HH7("ƺagl'W$#?oFZsF?.U$l- ?;/Kg2v[ BYuјv 1¨ռN6jfj )0Dm'W@,Y exr i v?g8 vQvabl/O K 2`d=R#?E /u~>'vSU )I|)=E9M A wN[9'~*xE7 +=8@<#p;+ƺ ~[QX+0Fqm͙ ejrsC }֥5fD`%a9n~.D4Qo' 3/pP8a9ǃ4Ӄ&zu_>q]R_6^O7PwIˁH#yM'f &k@rMQW/]6za 1eR~-;MMߋ׫~ +"\{llEm\Q~? mVb#22`UP5LK7bszZMI@ۧD)y|GyJ9.o蚠o^??BX"IAS-cQg\_"G4AD>3~ɐ6]RxB6F .Z`M_0,;bc^VߵV_fK,WJq+Аiƛ~ q0Y0rkW'.N=3?l(<"cdJ Z9o ?_~ >IOj~+$Ƀ4  >zõzV,L1H;,u>; c/N9ƙ@VGH;wa&@l/r;lJy/< 1WP&NzU%f>Q6 Xd%v>kH ~UpVʶ&2CtMno5v7zf7t>8|;]9bh )O3Ay/Ssy4V= }]M[) ?1 3ۉ ~WUd|WL2 D6Ǭʎpq;-+=ԇ3{LX2 PGj%\",u0%xwm/]Vc_0EJoK,D]gPn#8I><<X](@"FVr8i֟"We)4_-R&gRFMbrz@j/? {V+m9Pm?/L[z9*,;̥Ru9ͻ*ڕYnU%\98xz] ]1ڮI˃A`jLh5_U};V:b eYܜ^]^ƽȖ1\f~%KqhN0`- V߯@Nl@46#j>@h xMV$ lPFu>y9(9^4P<Œmd3J2}MWft,sT0(qP[@~"\s h)սɜ*8Q/T s-V(]Q۬^8}_72lWGA{,@OUi 1ð*+)O距Ӌ"3銛|M-};pRуCd9`'QuǓ@*jD^E}M>#"3rxHeiu,e8 ̉O mG&"u[@g\&M_ @#XRF_$<%H*MuP&Tꁇܫm)v@3UjaI*hIczvu\EYApm_|z۝=\+7fKc,+U`{fk7n$'OO L"jן % ɁcYo?j`ML~ ^qz\ԒM&4W.C}N"+NIdžr}^l?RA+ЎS>x',$fQ% p'CZ )ȹBn \eŁ%֧ PْڅLJQ`\叶~v"B\3?LX@,7GkOLU]L dY([CM"$~g(xP4d_W5 ӗ}fkmj@؍צo6l(e"Qwd2Hz3`/yyI4@PԞqO#yu.PBrW=T0$ъMnw $gP:qdT?3WCSv˾jYͰ~x. T|Է8CBS Ս}屬R+*K\V(Z!lP:_(8̴b wTr3'@fr!\LQ6 .1P⒥OFdڡ"G(Ă&֊Bţ:pbm$qYhh&QRePǙl>uN~}FȲSBfoq&6M@zg6M̏*՛.*YKIז;a7++az6t.sP;C8XZ8 i|~+n\(m P$t>aɅF9HVHU5!7`  Xh| خzangi*MdZ3f&Y% =Bx0pr(@+m &O黝JVn0l y e kqDߧ½RREn9X _)"k,?΂HB[lЮ i0ۻ:MzU`JWJz%ŷl܉-X5J bٗp|"zUܾ p M!p89OiL U`m<v .fFAr[+-&A*fUg{ٝG]~؃KɆ[&Ngvc٠R=R~C 1j\TlV4M];uc\ ,U Io _WW\.ܡbۂ d8/3%/'$1{и(v" =10*Ή=P< n(T 8\jse޻eCG ~U@i%2ؗɫB1 @P[ ۘU$=O!w|cPeЂ~U qKCí~ԏ ?fMɇ!OkMbF!k{%<,ɽu^KHaK")5SeVf$&,jTjWLz SvQ,R6v>fW Q_*4 p0`b~v>eK0oUg9 &q-;To^7Yf|u7d`RhJ˻&+EIPC|?>r(.?_.;-S͕[7j6qU1CROޘ\d#w\WնL,J¸DE^B>4>}SWD[:]֕w]۬-VN.R1LS;ÓP _ٕ fuOgk+&;'04+gҷ;Ml!YbpzVia%JtQv4 9W]qW)O73,y=4+䔔džǤ~B1&wq!Ma(^I1밹4Qk_nfa%}i:G^W$s' peIRm`Zmm@WIoS> u@oMYSLUg_K_J}7y]+ A@pV%""#`hC0DZ!Eǃk^Q[&Q1>Q])j8YVohIKoR( Θ]喱d.m7P ;ę\3sE `i$@bԬ2..|8}}vT[|TzxC4<0o:=Vv1r_\@~8%*'A}_VlT`ënJ=sי%+*We/ӼStB"4v?y]0؍EfPц$ܪ{uyBFq@2KFǶ_T82WL$7q$~egMm?.[ ?!a.Dp=gїs)itPHrVTpjaFW 3MbTI)&4ؽA] \D>>Ǒ w* g!7epTLMfgՕALSrD m~~CTl^.8 }Υ"ZY t(ø6a Y`A@ 9A!,3#h(K>P#+ßFe_J!8]?ꀴ*Fc |Սcw/A.]Cĭ ųz~]ng*pj`qg]4<.QCw7Dw`k:H qaBTq4bcЌ6d4[m <39" uP߾}/Xc|>_;Tf2'5I=Jer]X.5ETYp BAk/p 1SkJܺX\u3K0%r5H| C7BŢL直Apѹ s*fU(o? fл~CYCC^?ӏӈUM}wp1f '݀lQ>9,Ag<=Bج<'\1 nÝ8!IS)zZX'%&8Mt:@NR+jy/Vm aQ9 70: t:. 2 -霊I&+*n)gK롸 X:Ů{N9vkCZC~+#3v/*ư`pf`y0 WWV%8l oM#O黁۹ύy!f-@BHb?5~:5!%֏ܣ0m `98js[}5~=ܷIN?2y̘UV8j\&xOr]߁$A^'\LS$sm@9$ Apu^j:.ٚ$?+ޝ `]mϊ}U2iY@p qV\EakMDLSW^W +{aͲyDBA)j=GPo\-.wtPqP]#(ymܘidDuwCE5ֳ]|nDj7nBG[;ʥ ~_f8u2/gՔ&b'Kt[FaBSdSrhf{@N[㈾!A0j>T~;p0~ pM'y4q~iFqpߦe@z&[Po<9JVk L5siBZ833d@ }PvQҁU@po`T+p% ug\3lPڪ*K? ]\Ff  'Iں"ɫ%4ȍ?=x?BL ~YVn,K#HtBF`ts| GBOwj]u2,>ff_ |U -Eh YLHJ*6ڠi@G7 .㞶n}T9d~6 D}t @rj`sjBApj :_KpsCy7G> fjlPM;.79_>xirY62<&iRx VHZťh׽CzA~*T)=H~hA+4A@X.wDRv`ibpLD']|RI=qaʟ޿zghalO'M҄Z)}\Q%+lH+U!@N.WhD=h~u 0̴|LqVN ('I*G|$~je 0x_T[`.܃nPObJavR˦Ws@oMshք,:2&fIENDB`ukui-greeter/README.md0000644000175000017500000000125614204635151013372 0ustar fengfeng# ukui-greeter ![Check build](https://github.com/ukui/ukui-greeter/workflows/Check%20build/badge.svg?branch=master) Lightdm greeter for UKUI ## Getting started Clone this repository and update all submodule. ``` git clone https://github.com/ukui/ukui-greeter.git git submodule init git submodule update ``` Install necessary dependencies: * `Ubuntu/Debian`: `qttools5-dev-tools`, `pkg-config`, `qtbase5-dev`, `libqt5x11extras5-dev`, `libqt5svg5-dev`, `liblightdm-qt5-3-dev`, `libx11-dev`, `libxtst-dev`, `libxrandr-dev`, `qttools5-dev-tools`, `libimlib2-dev` Use `qmake` generate _Makefile_, then build the project. ``` mkdir -p build && cd build qmake ../ make -j$(nproc) ```